Commit 30e64acc authored by Maarten Billemont's avatar Maarten Billemont

Support for query based site searching.

parent 11f741ad
Pipeline #32350490 failed with stage
in 2 minutes and 43 seconds
Subproject commit 087b72f684edb5b309286c6d979044e8c7b6dd3e
Subproject commit 3d04d775e01ab1a437bb42166e92662ffffeedd4
......@@ -63,6 +63,7 @@
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadItems.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */; };
93D39E39D98B1A46DAC48058 /* MPQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D3915A208E1B01BDD05A3E /* MPQuery.swift */; };
93D39E501250468B10790F5F /* UIUtils+MP.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B2EC0C429BBBF90463B /* UIUtils+MP.m */; };
93D39EBD5353FE65B8F5EDF7 /* ViewConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3984BB76CF8DB86D9D2C6 /* ViewConfiguration.m */; };
93D39EDADBB0A8211D8CF2A5 /* MPLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D397615F791F856B7814B9 /* MPLoginView.swift */; };
......@@ -331,6 +332,7 @@
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; };
93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlAdjustInsets.m"; sourceTree = "<group>"; };
93D39156E806BB78E04F78B9 /* PearlSizedTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlSizedTextView.m; sourceTree = "<group>"; };
93D3915A208E1B01BDD05A3E /* MPQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPQuery.swift; sourceTree = "<group>"; };
93D39169E1BADEFA0517D4D2 /* MPUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPUser.swift; sourceTree = "<group>"; };
93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadItems.h"; sourceTree = "<group>"; };
......@@ -837,6 +839,7 @@
93D39CBCB72BD96FDAF90722 /* UILabel+MPFontSize.m */,
93D3996563C944ADDFC56F61 /* UILabel+MPFontSize.h */,
93D39D251169E42F0AAC451F /* MPURLUtils.swift */,
93D3915A208E1B01BDD05A3E /* MPQuery.swift */,
);
path = Util;
sourceTree = "<group>";
......@@ -2034,6 +2037,7 @@
93D398854968B51F6B8DB031 /* MPAlertView.swift in Sources */,
93D39CB424D9186B39E9AD81 /* MPSiteDetailsViewController.swift in Sources */,
93D39470EC1F954780E354CB /* MPImageView.swift in Sources */,
93D39E39D98B1A46DAC48058 /* MPQuery.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
......@@ -5,7 +5,7 @@
import Foundation
class MPSite: NSObject {
class MPSite: NSObject, Comparable {
var observers = Observers<MPSiteObserver>()
let user: MPUser
......@@ -99,6 +99,14 @@ class MPSite: NSObject {
} )
}
static func <(lhs: MPSite, rhs: MPSite) -> Bool {
if lhs.lastUsed != rhs.lastUsed {
return lhs.lastUsed < rhs.lastUsed
}
return lhs.siteName < rhs.siteName
}
// MARK: - mpw
func result(keyPurpose: MPKeyPurpose = .authentication, keyContext: String? = nil, resultParam: String? = nil)
......
......@@ -46,6 +46,11 @@ class MPUser {
self.observers.notify { $0.userDidUpdateSites() }
}
}
var sortedSites : [ MPSite ] {
get {
return self.sites.sorted()
}
}
// MARK: - Life
......
......@@ -6,7 +6,7 @@
import UIKit
import pop
class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewObserver, MPSitesViewObserver {
class MPSitesViewController: UIViewController, UITextFieldDelegate, MPSiteViewObserver, MPSitesViewObserver {
private lazy var topContainer = MPButton( content: self.searchField )
private let searchField = UITextField()
private let userButton = UIButton( type: .custom )
......@@ -15,7 +15,7 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb
private let siteViewConfiguration = ViewConfiguration()
var user: MPUser? {
var user: MPUser? {
didSet {
self.sitesView.user = self.user
......@@ -51,11 +51,13 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb
self.searchField.rightViewMode = .unlessEditing
self.searchField.keyboardAppearance = .dark
self.searchField.keyboardType = .URL
self.searchField.autocapitalizationType = .none
self.searchField.autocorrectionType = .no
if #available( iOS 10.0, * ) {
self.searchField.textContentType = .URL
}
self.searchField.autocapitalizationType = .none
self.searchField.autocorrectionType = .no
self.searchField.delegate = self
self.searchField.addTarget( self, action: #selector( textFieldEditingChanged ), for: .editingChanged )
self.userButton.setImage( UIImage( named: "icon_user" ), for: .normal )
self.userButton.sizeToFit()
......@@ -148,4 +150,10 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb
} )
}
}
// MARK: - UITextFieldDelegate
@objc
func textFieldEditingChanged(_ textField: UITextField) {
self.sitesView.query = self.searchField.text
}
}
//
// Created by Maarten Billemont on 2018-10-08.
// Copyright (c) 2018 Lyndir. All rights reserved.
//
import Foundation
class MPQuery {
public let query: String
init(_ query: String) {
self.query = query
}
func matches<V>(_ value: V, key: String)
-> Result<V>? {
let result = Result( value: value, key: key )
guard self.query.count > 0
else {
return result
}
guard key.count > 0
else {
return nil
}
// Consume query and key characters until one of them runs out, recording any matches against the result's key.
var q = self.query.startIndex, k = key.startIndex
while ((q < self.query.endIndex) && (k < key.endIndex)) {
if self.query[q] == key[k] {
result.keyMatched( at: k )
q = self.query.index( after: q )
}
k = key.index( after: k )
}
// If the match against the query broke before the end of the query, it failed.
return (q < self.query.endIndex) ? nil: result
}
func find<V>(_ values: [V], valueToKey: (V) -> String) -> [Result<V>] {
var results = [ Result<V> ]()
for value in values {
if let result = self.matches( value, key: valueToKey( value ) ) {
results.append( result )
}
}
return results
}
class Result<V> : NSObject where V: Hashable {
let value: V
let key: String
var keyMatched = Set<String.Index>()
init(value: V, key: String) {
self.value = value
self.key = key
}
func keyMatched(at k: String.Index) {
self.keyMatched.insert( k )
}
override func isEqual(_ object: Any?) -> Bool {
if let object = object as? Result<V> {
return self.value == object.value && self.key == object.key && self.keyMatched == object.keyMatched
} else {
return false
}
}
override var hash: Int {
return self.value.hashValue
}
func debugDescription() -> String {
return "{Result: \(self.key)}"
}
}
}
......@@ -15,14 +15,20 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
}
didSet {
self.user?.observers.register( self )
self.userDidUpdateSites()
self.query = nil
}
}
let data = NSMutableArray()
var selectedSite: MPSite? {
didSet {
self.observers.notify { $0.siteWasSelected( selectedSite: self.selectedSite ) }
}
}
var query: String? {
didSet {
self.updateSites()
}
}
// MARK: - Life
......@@ -41,6 +47,24 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
fatalError( "init(coder:) is not supported for this class" )
}
// MARK: - Internal
func dataSection(section: Int) -> NSArray {
return self.data.object( at: section ) as! NSArray
}
func dataRow(section: Int, row: Int) -> MPQuery.Result<MPSite> {
return self.dataSection( section: section ).object( at: row ) as! MPQuery.Result<MPSite>
}
func updateSites() {
let newSites = [ MPQuery( self.query ?? "" ).find( self.user?.sortedSites ?? [] ) { $0.siteName } ]
PearlMainQueue {
self.updateDataSource( self.data, toSections: newSites as NSArray, reloadItems: self.data, with: .automatic )
}
}
// MARK: - UITableViewDelegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
......@@ -60,7 +84,7 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedSite = self.user?.sites[indexPath.row]
self.selectedSite = self.dataRow( section: indexPath.section, row: indexPath.row ).value
self.isSelecting = false
}
......@@ -68,13 +92,13 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
-> Int {
return self.user?.sites.count ?? 0
return self.dataSection( section: section ).count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let cell = SiteCell.dequeue( from: tableView, indexPath: indexPath )
cell.site = self.user?.sites[indexPath.row]
cell.result = self.dataRow( section: indexPath.section, row: indexPath.row )
return cell
}
......@@ -82,30 +106,29 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
// MARK: - MPUserObserver
func userDidLogin() {
PearlMainQueue {
self.reloadData()
}
self.updateSites()
}
func userDidLogout() {
PearlMainQueue {
self.reloadData()
}
self.updateSites()
}
func userDidChange() {
}
func userDidUpdateSites() {
PearlMainQueue {
self.reloadData()
}
self.updateSites()
}
// MARK: - Types
class SiteCell: UITableViewCell, MPSiteObserver {
var site: MPSite? {
var result: MPQuery.Result<MPSite>? {
didSet {
self.site = self.result?.value
}
}
var site: MPSite? {
willSet {
self.site?.observers.unregister( self )
}
......@@ -227,7 +250,14 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
func siteDidChange() {
PearlMainQueue {
self.nameLabel.text = self.site?.siteName
var name: NSAttributedString = NSAttributedString( string: self.site?.siteName ?? "" )
if let result = self.result {
for match in result.keyMatched {
name = strra( name, NSRange( location: match.encodedOffset, length: 1 ),
[ NSAttributedStringKey.backgroundColor: UIColor.red ] )
}
}
self.nameLabel.attributedText = name
self.indicatorView.backgroundColor = self.site?.color.withAlphaComponent( 0.85 )
}
PearlNotMainQueue {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment