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

Support for query based site searching.

parent 11f741ad
Pipeline #32350490 (#132) failed with stage
in 2 minutes and 43 seconds
Subproject commit 087b72f684edb5b309286c6d979044e8c7b6dd3e Subproject commit 3d04d775e01ab1a437bb42166e92662ffffeedd4
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadItems.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */; }; 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 */; }; 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 */; }; 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 */; }; 93D39E501250468B10790F5F /* UIUtils+MP.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B2EC0C429BBBF90463B /* UIUtils+MP.m */; };
93D39EBD5353FE65B8F5EDF7 /* ViewConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3984BB76CF8DB86D9D2C6 /* ViewConfiguration.m */; }; 93D39EBD5353FE65B8F5EDF7 /* ViewConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3984BB76CF8DB86D9D2C6 /* ViewConfiguration.m */; };
93D39EDADBB0A8211D8CF2A5 /* MPLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D397615F791F856B7814B9 /* MPLoginView.swift */; }; 93D39EDADBB0A8211D8CF2A5 /* MPLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D397615F791F856B7814B9 /* MPLoginView.swift */; };
...@@ -331,6 +332,7 @@ ...@@ -331,6 +332,7 @@
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadItems.h"; sourceTree = "<group>"; };
...@@ -837,6 +839,7 @@ ...@@ -837,6 +839,7 @@
93D39CBCB72BD96FDAF90722 /* UILabel+MPFontSize.m */, 93D39CBCB72BD96FDAF90722 /* UILabel+MPFontSize.m */,
93D3996563C944ADDFC56F61 /* UILabel+MPFontSize.h */, 93D3996563C944ADDFC56F61 /* UILabel+MPFontSize.h */,
93D39D251169E42F0AAC451F /* MPURLUtils.swift */, 93D39D251169E42F0AAC451F /* MPURLUtils.swift */,
93D3915A208E1B01BDD05A3E /* MPQuery.swift */,
); );
path = Util; path = Util;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -2034,6 +2037,7 @@ ...@@ -2034,6 +2037,7 @@
93D398854968B51F6B8DB031 /* MPAlertView.swift in Sources */, 93D398854968B51F6B8DB031 /* MPAlertView.swift in Sources */,
93D39CB424D9186B39E9AD81 /* MPSiteDetailsViewController.swift in Sources */, 93D39CB424D9186B39E9AD81 /* MPSiteDetailsViewController.swift in Sources */,
93D39470EC1F954780E354CB /* MPImageView.swift in Sources */, 93D39470EC1F954780E354CB /* MPImageView.swift in Sources */,
93D39E39D98B1A46DAC48058 /* MPQuery.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import Foundation import Foundation
class MPSite: NSObject { class MPSite: NSObject, Comparable {
var observers = Observers<MPSiteObserver>() var observers = Observers<MPSiteObserver>()
let user: MPUser let user: MPUser
...@@ -99,6 +99,14 @@ class MPSite: NSObject { ...@@ -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 // MARK: - mpw
func result(keyPurpose: MPKeyPurpose = .authentication, keyContext: String? = nil, resultParam: String? = nil) func result(keyPurpose: MPKeyPurpose = .authentication, keyContext: String? = nil, resultParam: String? = nil)
......
...@@ -46,6 +46,11 @@ class MPUser { ...@@ -46,6 +46,11 @@ class MPUser {
self.observers.notify { $0.userDidUpdateSites() } self.observers.notify { $0.userDidUpdateSites() }
} }
} }
var sortedSites : [ MPSite ] {
get {
return self.sites.sorted()
}
}
// MARK: - Life // MARK: - Life
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import UIKit import UIKit
import pop import pop
class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewObserver, MPSitesViewObserver { class MPSitesViewController: UIViewController, UITextFieldDelegate, MPSiteViewObserver, MPSitesViewObserver {
private lazy var topContainer = MPButton( content: self.searchField ) private lazy var topContainer = MPButton( content: self.searchField )
private let searchField = UITextField() private let searchField = UITextField()
private let userButton = UIButton( type: .custom ) private let userButton = UIButton( type: .custom )
...@@ -15,7 +15,7 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb ...@@ -15,7 +15,7 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb
private let siteViewConfiguration = ViewConfiguration() private let siteViewConfiguration = ViewConfiguration()
var user: MPUser? { var user: MPUser? {
didSet { didSet {
self.sitesView.user = self.user self.sitesView.user = self.user
...@@ -51,11 +51,13 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb ...@@ -51,11 +51,13 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb
self.searchField.rightViewMode = .unlessEditing self.searchField.rightViewMode = .unlessEditing
self.searchField.keyboardAppearance = .dark self.searchField.keyboardAppearance = .dark
self.searchField.keyboardType = .URL self.searchField.keyboardType = .URL
self.searchField.autocapitalizationType = .none
self.searchField.autocorrectionType = .no
if #available( iOS 10.0, * ) { if #available( iOS 10.0, * ) {
self.searchField.textContentType = .URL 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.setImage( UIImage( named: "icon_user" ), for: .normal )
self.userButton.sizeToFit() self.userButton.sizeToFit()
...@@ -148,4 +150,10 @@ class MPSitesViewController: UIViewController, UISearchBarDelegate, MPSiteViewOb ...@@ -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 ...@@ -15,14 +15,20 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
} }
didSet { didSet {
self.user?.observers.register( self ) self.user?.observers.register( self )
self.userDidUpdateSites() self.query = nil
} }
} }
let data = NSMutableArray()
var selectedSite: MPSite? { var selectedSite: MPSite? {
didSet { didSet {
self.observers.notify { $0.siteWasSelected( selectedSite: self.selectedSite ) } self.observers.notify { $0.siteWasSelected( selectedSite: self.selectedSite ) }
} }
} }
var query: String? {
didSet {
self.updateSites()
}
}
// MARK: - Life // MARK: - Life
...@@ -41,6 +47,24 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs ...@@ -41,6 +47,24 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
fatalError( "init(coder:) is not supported for this class" ) 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 // MARK: - UITableViewDelegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
...@@ -60,7 +84,7 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs ...@@ -60,7 +84,7 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
} }
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 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 self.isSelecting = false
} }
...@@ -68,13 +92,13 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs ...@@ -68,13 +92,13 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
-> Int { -> Int {
return self.user?.sites.count ?? 0 return self.dataSection( section: section ).count
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell { -> UITableViewCell {
let cell = SiteCell.dequeue( from: tableView, indexPath: indexPath ) 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 return cell
} }
...@@ -82,30 +106,29 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs ...@@ -82,30 +106,29 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
// MARK: - MPUserObserver // MARK: - MPUserObserver
func userDidLogin() { func userDidLogin() {
PearlMainQueue { self.updateSites()
self.reloadData()
}
} }
func userDidLogout() { func userDidLogout() {
PearlMainQueue { self.updateSites()
self.reloadData()
}
} }
func userDidChange() { func userDidChange() {
} }
func userDidUpdateSites() { func userDidUpdateSites() {
PearlMainQueue { self.updateSites()
self.reloadData()
}
} }
// MARK: - Types // MARK: - Types
class SiteCell: UITableViewCell, MPSiteObserver { class SiteCell: UITableViewCell, MPSiteObserver {
var site: MPSite? { var result: MPQuery.Result<MPSite>? {
didSet {
self.site = self.result?.value
}
}
var site: MPSite? {
willSet { willSet {
self.site?.observers.unregister( self ) self.site?.observers.unregister( self )
} }
...@@ -227,7 +250,14 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs ...@@ -227,7 +250,14 @@ class MPSitesView: UITableView, UITableViewDelegate, UITableViewDataSource, MPUs
func siteDidChange() { func siteDidChange() {
PearlMainQueue { 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 ) self.indicatorView.backgroundColor = self.site?.color.withAlphaComponent( 0.85 )
} }
PearlNotMainQueue { 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