Better search results
This commit is contained in:
parent
0eb4b01fb7
commit
6bf4716366
6 changed files with 160 additions and 136 deletions
|
|
@ -105,6 +105,8 @@
|
||||||
A267002A1EEC466A00176B8A /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A26700281EEC466A00176B8A /* MainInterface.storyboard */; };
|
A267002A1EEC466A00176B8A /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A26700281EEC466A00176B8A /* MainInterface.storyboard */; };
|
||||||
A267002E1EEC466A00176B8A /* passExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = A26700241EEC466A00176B8A /* passExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
A267002E1EEC466A00176B8A /* passExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = A26700241EEC466A00176B8A /* passExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
A26700371EEC475600176B8A /* passProcessor.js in Resources */ = {isa = PBXBuildFile; fileRef = A26700351EEC475600176B8A /* passProcessor.js */; };
|
A26700371EEC475600176B8A /* passProcessor.js in Resources */ = {isa = PBXBuildFile; fileRef = A26700351EEC475600176B8A /* passProcessor.js */; };
|
||||||
|
A2699ACD2402631400F36323 /* PasswordTableEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2699ACC2402631400F36323 /* PasswordTableEntry.swift */; };
|
||||||
|
A2699ACF24027D9500F36323 /* PasswordTableEntryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2699ACE24027D9500F36323 /* PasswordTableEntryTest.swift */; };
|
||||||
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
|
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
|
||||||
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2802BF81E70813A00879216 /* SliderTableViewCell.xib */; };
|
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2802BF81E70813A00879216 /* SliderTableViewCell.xib */; };
|
||||||
A2A61C131EEF90CB00CFE063 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; };
|
A2A61C131EEF90CB00CFE063 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; };
|
||||||
|
|
@ -327,6 +329,8 @@
|
||||||
A26700321EEC46C400176B8A /* pass.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pass.entitlements; sourceTree = "<group>"; };
|
A26700321EEC46C400176B8A /* pass.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pass.entitlements; sourceTree = "<group>"; };
|
||||||
A26700331EEC46C900176B8A /* passExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passExtension.entitlements; sourceTree = "<group>"; };
|
A26700331EEC46C900176B8A /* passExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passExtension.entitlements; sourceTree = "<group>"; };
|
||||||
A26700351EEC475600176B8A /* passProcessor.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = passProcessor.js; sourceTree = "<group>"; };
|
A26700351EEC475600176B8A /* passProcessor.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = passProcessor.js; sourceTree = "<group>"; };
|
||||||
|
A2699ACC2402631400F36323 /* PasswordTableEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordTableEntry.swift; sourceTree = "<group>"; };
|
||||||
|
A2699ACE24027D9500F36323 /* PasswordTableEntryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordTableEntryTest.swift; sourceTree = "<group>"; };
|
||||||
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; };
|
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderTableViewCell.xib; sourceTree = "<group>"; };
|
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
A2A61C1F1EEFABAD00CFE063 /* UtilsExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilsExtension.swift; sourceTree = "<group>"; };
|
A2A61C1F1EEFABAD00CFE063 /* UtilsExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilsExtension.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -545,6 +549,7 @@
|
||||||
30C015A7214ED378005BB6DF /* Models */ = {
|
30C015A7214ED378005BB6DF /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A2699ACE24027D9500F36323 /* PasswordTableEntryTest.swift */,
|
||||||
30B0485F209A5141001013CA /* PasswordTest.swift */,
|
30B0485F209A5141001013CA /* PasswordTest.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
|
|
@ -661,6 +666,7 @@
|
||||||
30697C4021F63CAB0064FCAC /* Password.swift */,
|
30697C4021F63CAB0064FCAC /* Password.swift */,
|
||||||
30697C3F21F63CAA0064FCAC /* PasswordEntity.swift */,
|
30697C3F21F63CAA0064FCAC /* PasswordEntity.swift */,
|
||||||
30697C4321F63CAB0064FCAC /* PasswordStore.swift */,
|
30697C4321F63CAB0064FCAC /* PasswordStore.swift */,
|
||||||
|
A2699ACC2402631400F36323 /* PasswordTableEntry.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1323,6 +1329,7 @@
|
||||||
30697C2A21F63C5A0064FCAC /* NotificationNames.swift in Sources */,
|
30697C2A21F63C5A0064FCAC /* NotificationNames.swift in Sources */,
|
||||||
30CCA91623258C380048CA51 /* PgpInterface.swift in Sources */,
|
30CCA91623258C380048CA51 /* PgpInterface.swift in Sources */,
|
||||||
30697C4721F63CAB0064FCAC /* PasscodeLock.swift in Sources */,
|
30697C4721F63CAB0064FCAC /* PasscodeLock.swift in Sources */,
|
||||||
|
A2699ACD2402631400F36323 /* PasswordTableEntry.swift in Sources */,
|
||||||
30697C3421F63C8B0064FCAC /* PasscodeLockViewController.swift in Sources */,
|
30697C3421F63C8B0064FCAC /* PasscodeLockViewController.swift in Sources */,
|
||||||
3087574F2343E42A00B971A2 /* Colors.swift in Sources */,
|
3087574F2343E42A00B971A2 /* Colors.swift in Sources */,
|
||||||
30697C2C21F63C5A0064FCAC /* FileManagerExtension.swift in Sources */,
|
30697C2C21F63C5A0064FCAC /* FileManagerExtension.swift in Sources */,
|
||||||
|
|
@ -1345,6 +1352,7 @@
|
||||||
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */,
|
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */,
|
||||||
301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */,
|
301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */,
|
||||||
30BAC8CB22E3BB6C00438475 /* DictBasedKeychain.swift in Sources */,
|
30BAC8CB22E3BB6C00438475 /* DictBasedKeychain.swift in Sources */,
|
||||||
|
A2699ACF24027D9500F36323 /* PasswordTableEntryTest.swift in Sources */,
|
||||||
30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */,
|
30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */,
|
||||||
A2AA934622DE3A8000D79A00 /* PGPAgentTest.swift in Sources */,
|
A2AA934622DE3A8000D79A00 /* PGPAgentTest.swift in Sources */,
|
||||||
30BAC8C622E3BAAF00438475 /* TestBase.swift in Sources */,
|
30BAC8C622E3BAAF00438475 /* TestBase.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -10,23 +10,11 @@ import UIKit
|
||||||
import SVProgressHUD
|
import SVProgressHUD
|
||||||
import passKit
|
import passKit
|
||||||
|
|
||||||
fileprivate class PasswordsTableEntry : NSObject {
|
fileprivate let hideSectionHeaderThreshold = 6 // hide section header if passwords count is less than the threshold
|
||||||
@objc var title: String
|
|
||||||
var isDir: Bool
|
|
||||||
var passwordEntity: PasswordEntity?
|
|
||||||
init(title: String, isDir: Bool, passwordEntity: PasswordEntity?) {
|
|
||||||
self.title = title
|
|
||||||
self.isDir = isDir
|
|
||||||
self.passwordEntity = passwordEntity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate let hideSectionHeaderTreshold = 6 // hide section header if passwords count is less than the threshold
|
|
||||||
|
|
||||||
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate {
|
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate {
|
||||||
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
private var passwordsTableEntries: [PasswordTableEntry] = []
|
||||||
private var passwordsTableAllEntries: [PasswordsTableEntry] = []
|
private var passwordsTableAllEntries: [PasswordTableEntry] = []
|
||||||
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
|
||||||
private var parentPasswordEntity: PasswordEntity? = nil
|
private var parentPasswordEntity: PasswordEntity? = nil
|
||||||
private let passwordStore = PasswordStore.shared
|
private let passwordStore = PasswordStore.shared
|
||||||
private let keychain = AppKeychain.shared
|
private let keychain = AppKeychain.shared
|
||||||
|
|
@ -34,9 +22,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
private var tapTabBarTime: TimeInterval = 0
|
private var tapTabBarTime: TimeInterval = 0
|
||||||
private var tapNavigationBarGestureRecognizer: UITapGestureRecognizer!
|
private var tapNavigationBarGestureRecognizer: UITapGestureRecognizer!
|
||||||
|
|
||||||
private var sections = [(title: String, entries: [PasswordsTableEntry])]()
|
private var sections = [(title: String, entries: [PasswordTableEntry])]()
|
||||||
|
|
||||||
private var searchActive : Bool = false
|
|
||||||
private enum PasswordLabel {
|
private enum PasswordLabel {
|
||||||
case all
|
case all
|
||||||
case unsynced
|
case unsynced
|
||||||
|
|
@ -130,23 +117,18 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
||||||
private func initPasswordsTableEntries(parent: PasswordEntity?) {
|
private func initPasswordsTableEntries(parent: PasswordEntity?) {
|
||||||
passwordsTableEntries.removeAll()
|
let passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||||
passwordsTableAllEntries.removeAll()
|
passwordsTableAllEntries = passwordAllEntities.compactMap {
|
||||||
filteredPasswordsTableEntries.removeAll()
|
PasswordTableEntry($0)
|
||||||
var passwordEntities = [PasswordEntity]()
|
|
||||||
var passwordAllEntities = [PasswordEntity]()
|
|
||||||
if Defaults.isShowFolderOn {
|
|
||||||
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(parent: parent)
|
|
||||||
} else {
|
|
||||||
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
|
||||||
}
|
}
|
||||||
passwordsTableEntries = passwordEntities.map {
|
|
||||||
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
|
let passwordEntities = Defaults.isShowFolderOn ?
|
||||||
}
|
self.passwordStore.fetchPasswordEntityCoreData(parent: parent) :
|
||||||
passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
passwordAllEntities
|
||||||
passwordsTableAllEntries = passwordAllEntities.map {
|
passwordsTableEntries = passwordEntities.compactMap {
|
||||||
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
|
PasswordTableEntry($0)
|
||||||
}
|
}
|
||||||
|
|
||||||
parentPasswordEntity = parent
|
parentPasswordEntity = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +168,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
SVProgressHUD.setDefaultStyle(.light)
|
SVProgressHUD.setDefaultStyle(.light)
|
||||||
SVProgressHUD.show(withStatus: "SyncingPasswordStore".localize())
|
SVProgressHUD.show(withStatus: "SyncingPasswordStore".localize())
|
||||||
|
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||||
do {
|
do {
|
||||||
try self.passwordStore.pullRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, progressBlock: {(git_transfer_progress, stop) in
|
try self.passwordStore.pullRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, progressBlock: {(git_transfer_progress, stop) in
|
||||||
|
|
@ -286,10 +267,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
self.reloadTableView(parent: nil, label: .all)
|
self.reloadTableView(parent: nil, label: .all)
|
||||||
}
|
}
|
||||||
let unsyncedAction = UIAlertAction(title: "Unsynced Passwords", style: .default) { _ in
|
let unsyncedAction = UIAlertAction(title: "Unsynced Passwords", style: .default) { _ in
|
||||||
self.filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in
|
let filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in
|
||||||
return !entry.passwordEntity!.synced
|
return !entry.synced
|
||||||
}
|
}
|
||||||
self.reloadTableView(data: self.filteredPasswordsTableEntries, label: .unsynced)
|
self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced)
|
||||||
}
|
}
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
|
||||||
|
|
||||||
|
|
@ -343,7 +324,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
return recognizer
|
return recognizer
|
||||||
}()
|
}()
|
||||||
let entry = getPasswordEntry(by: indexPath)
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
let passwordEntity = entry.passwordEntity!
|
let passwordEntity = entry.passwordEntity
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||||
|
|
||||||
cell.textLabel?.text = passwordEntity.synced ? entry.title : "↻ \(entry.title)"
|
cell.textLabel?.text = passwordEntity.synced ? entry.title : "↻ \(entry.title)"
|
||||||
|
|
@ -367,7 +348,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
|
||||||
return sections[indexPath.section].entries[indexPath.row]
|
return sections[indexPath.section].entries[indexPath.row]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,7 +401,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
|
|
||||||
private func hideSectionHeader() -> Bool {
|
private func hideSectionHeader() -> Bool {
|
||||||
if passwordsTableEntries.count < hideSectionHeaderTreshold || self.searchController.isActive {
|
if passwordsTableEntries.count < hideSectionHeaderThreshold || self.searchController.isActive {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -475,7 +456,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
|
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity!
|
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
SVProgressHUD.dismiss()
|
SVProgressHUD.dismiss()
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
|
|
@ -497,27 +478,27 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateSections(item: [PasswordsTableEntry]) {
|
private func generateSections(item: [PasswordTableEntry]) {
|
||||||
let collation = UILocalizedIndexedCollation.current()
|
let collation = UILocalizedIndexedCollation.current()
|
||||||
let sectionTitles = collation.sectionIndexTitles
|
let sectionTitles = collation.sectionIndexTitles
|
||||||
var newSections = [(title: String, entries: [PasswordsTableEntry])]()
|
var newSections = [(title: String, entries: [PasswordTableEntry])]()
|
||||||
|
|
||||||
// initialize all sections
|
// initialize all sections
|
||||||
for i in 0..<sectionTitles.count {
|
for i in 0..<sectionTitles.count {
|
||||||
newSections.append((title: sectionTitles[i], entries: [PasswordsTableEntry]()))
|
newSections.append((title: sectionTitles[i], entries: [PasswordTableEntry]()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// put entries into sections
|
// put entries into sections
|
||||||
for entry in item {
|
for entry in item {
|
||||||
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
|
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordTableEntry.title))
|
||||||
newSections[sectionNumber].entries.append(entry)
|
newSections[sectionNumber].entries.append(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort each list and set sectionTitles
|
// sort each list and set sectionTitles
|
||||||
for i in 0..<sectionTitles.count {
|
for i in 0..<sectionTitles.count {
|
||||||
let entriesToSort = newSections[i].entries
|
let entriesToSort = newSections[i].entries
|
||||||
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
|
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordTableEntry.title))
|
||||||
newSections[i].entries = sortedEntries as! [PasswordsTableEntry]
|
newSections[i].entries = sortedEntries as! [PasswordTableEntry]
|
||||||
}
|
}
|
||||||
|
|
||||||
// only keep non-empty sections
|
// only keep non-empty sections
|
||||||
|
|
@ -552,7 +533,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
if segue.identifier == "showPasswordDetail" {
|
if segue.identifier == "showPasswordDetail" {
|
||||||
if let viewController = segue.destination as? PasswordDetailTableViewController {
|
if let viewController = segue.destination as? PasswordDetailTableViewController {
|
||||||
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
|
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
|
||||||
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity!
|
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity
|
||||||
viewController.passwordEntity = passwordEntity
|
viewController.passwordEntity = passwordEntity
|
||||||
}
|
}
|
||||||
} else if segue.identifier == "addPasswordSegue" {
|
} else if segue.identifier == "addPasswordSegue" {
|
||||||
|
|
@ -567,32 +548,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterContentForSearchText(searchText: String, scope: SearchBarScope = .all) {
|
func filterContentForSearchText(searchText: String, scope: SearchBarScope = .all) {
|
||||||
switch scope {
|
var entries: [PasswordTableEntry] = scope == .all ? passwordsTableAllEntries : passwordsTableEntries
|
||||||
case .all:
|
if searchController.isActive && searchController.searchBar.text != "" {
|
||||||
filteredPasswordsTableEntries = passwordsTableAllEntries.filter { entry in
|
entries = entries.filter {$0.match(searchText)}
|
||||||
let name = entry.passwordEntity?.nameWithCategory ?? entry.title
|
|
||||||
return name.localizedCaseInsensitiveContains(searchText)
|
|
||||||
}
|
|
||||||
if searchController.isActive && searchController.searchBar.text != "" {
|
|
||||||
reloadTableView(data: filteredPasswordsTableEntries)
|
|
||||||
} else {
|
|
||||||
reloadTableView(data: passwordsTableAllEntries)
|
|
||||||
}
|
|
||||||
case .current:
|
|
||||||
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
|
||||||
return entry.title.lowercased().contains(searchText.lowercased())
|
|
||||||
}
|
|
||||||
if searchController.isActive && searchController.searchBar.text != "" {
|
|
||||||
reloadTableView(data: filteredPasswordsTableEntries)
|
|
||||||
} else {
|
|
||||||
reloadTableView(data: passwordsTableEntries)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
reloadTableView(data: entries)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadTableView(data: [PasswordsTableEntry], label: PasswordLabel = .all, anim: CAAnimation? = nil) {
|
private func reloadTableView(data: [PasswordTableEntry], label: PasswordLabel = .all, anim: CAAnimation? = nil) {
|
||||||
// set navigation item
|
// set navigation item
|
||||||
if passwordStore.numberOfLocalCommits != 0 {
|
if passwordStore.numberOfLocalCommits != 0 {
|
||||||
navigationController?.tabBarItem.badgeValue = "\(passwordStore.numberOfLocalCommits)"
|
navigationController?.tabBarItem.badgeValue = "\(passwordStore.numberOfLocalCommits)"
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,6 @@
|
||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
import passKit
|
import passKit
|
||||||
|
|
||||||
fileprivate class PasswordsTableEntry : NSObject {
|
|
||||||
var title: String
|
|
||||||
var categoryText: String
|
|
||||||
var categoryArray: [String]
|
|
||||||
var passwordEntity: PasswordEntity?
|
|
||||||
init(_ entity: PasswordEntity) {
|
|
||||||
self.title = entity.name!
|
|
||||||
self.categoryText = entity.getCategoryText()
|
|
||||||
self.categoryArray = entity.getCategoryArray()
|
|
||||||
self.passwordEntity = entity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
|
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
|
||||||
@IBOutlet weak var searchBar: UISearchBar!
|
@IBOutlet weak var searchBar: UISearchBar!
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
@ -30,8 +17,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
private let keychain = AppKeychain.shared
|
private let keychain = AppKeychain.shared
|
||||||
|
|
||||||
private var searchActive = false
|
private var searchActive = false
|
||||||
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
private var passwordsTableEntries: [PasswordTableEntry] = []
|
||||||
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
private var filteredPasswordsTableEntries: [PasswordTableEntry] = []
|
||||||
|
|
||||||
private lazy var passcodelock: PasscodeExtensionDisplay = {
|
private lazy var passcodelock: PasscodeExtensionDisplay = {
|
||||||
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
|
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
|
||||||
|
|
@ -118,12 +105,11 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
}
|
}
|
||||||
|
|
||||||
private func initPasswordsTableEntries() {
|
private func initPasswordsTableEntries() {
|
||||||
passwordsTableEntries.removeAll()
|
|
||||||
filteredPasswordsTableEntries.removeAll()
|
filteredPasswordsTableEntries.removeAll()
|
||||||
var passwordEntities = [PasswordEntity]()
|
|
||||||
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||||
passwordsTableEntries = passwordEntities.map {
|
passwordsTableEntries = passwordEntities.compactMap {
|
||||||
PasswordsTableEntry($0)
|
PasswordTableEntry($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,14 +117,14 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||||
let entry = getPasswordEntry(by: indexPath)
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
if entry.passwordEntity!.synced {
|
if entry.passwordEntity.synced {
|
||||||
cell.textLabel?.text = entry.title
|
cell.textLabel?.text = entry.title
|
||||||
} else {
|
} else {
|
||||||
cell.textLabel?.text = "↻ \(entry.title)"
|
cell.textLabel?.text = "↻ \(entry.title)"
|
||||||
}
|
}
|
||||||
cell.accessoryType = .none
|
cell.accessoryType = .none
|
||||||
cell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
|
cell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||||
cell.detailTextLabel?.text = entry.passwordEntity?.getCategoryText()
|
cell.detailTextLabel?.text = entry.categoryText
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,7 +137,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let passwordEntity = entry.passwordEntity!
|
let passwordEntity = entry.passwordEntity
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
var decryptedPassword: Password?
|
var decryptedPassword: Password?
|
||||||
|
|
@ -212,16 +198,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
|
|
||||||
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||||
if let searchText = searchBar.text, searchText.isEmpty == false {
|
if let searchText = searchBar.text, searchText.isEmpty == false {
|
||||||
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
filteredPasswordsTableEntries = passwordsTableEntries.filter {$0.match(searchText)}
|
||||||
var matched = false
|
|
||||||
matched = matched || entry.title.range(of: searchText, options: .caseInsensitive) != nil
|
|
||||||
matched = matched || searchText.range(of: entry.title, options: .caseInsensitive) != nil
|
|
||||||
entry.categoryArray.forEach({ (category) in
|
|
||||||
matched = matched || category.range(of: searchText, options: .caseInsensitive) != nil
|
|
||||||
matched = matched || searchText.range(of: category, options: .caseInsensitive) != nil
|
|
||||||
})
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
searchActive = true
|
searchActive = true
|
||||||
} else {
|
} else {
|
||||||
searchActive = false
|
searchActive = false
|
||||||
|
|
@ -233,7 +210,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
searchBarSearchButtonClicked(searchBar)
|
searchBarSearchButtonClicked(searchBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
|
||||||
if searchActive {
|
if searchActive {
|
||||||
return filteredPasswordsTableEntries[indexPath.row]
|
return filteredPasswordsTableEntries[indexPath.row]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,6 @@ import Foundation
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import passKit
|
import passKit
|
||||||
|
|
||||||
fileprivate class PasswordsTableEntry : NSObject {
|
|
||||||
var title: String
|
|
||||||
var categoryText: String
|
|
||||||
var categoryArray: [String]
|
|
||||||
var passwordEntity: PasswordEntity?
|
|
||||||
init(_ entity: PasswordEntity) {
|
|
||||||
self.title = entity.name!
|
|
||||||
self.categoryText = entity.getCategoryText()
|
|
||||||
self.categoryArray = entity.getCategoryArray()
|
|
||||||
self.passwordEntity = entity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
|
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
|
||||||
@IBOutlet weak var searchBar: UISearchBar!
|
@IBOutlet weak var searchBar: UISearchBar!
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
@ -31,8 +18,8 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
private let keychain = AppKeychain.shared
|
private let keychain = AppKeychain.shared
|
||||||
|
|
||||||
private var searchActive = false
|
private var searchActive = false
|
||||||
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
private var passwordsTableEntries: [PasswordTableEntry] = []
|
||||||
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
private var filteredPasswordsTableEntries: [PasswordTableEntry] = []
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
case findLogin, fillBrowser, unknown
|
case findLogin, fillBrowser, unknown
|
||||||
|
|
@ -46,12 +33,11 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private func initPasswordsTableEntries() {
|
private func initPasswordsTableEntries() {
|
||||||
passwordsTableEntries.removeAll()
|
|
||||||
filteredPasswordsTableEntries.removeAll()
|
filteredPasswordsTableEntries.removeAll()
|
||||||
var passwordEntities = [PasswordEntity]()
|
|
||||||
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||||
passwordsTableEntries = passwordEntities.map {
|
passwordsTableEntries = passwordEntities.map {
|
||||||
PasswordsTableEntry($0)
|
PasswordTableEntry($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +125,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||||
let entry = getPasswordEntry(by: indexPath)
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
if entry.passwordEntity!.synced {
|
if entry.synced {
|
||||||
cell.textLabel?.text = entry.title
|
cell.textLabel?.text = entry.title
|
||||||
} else {
|
} else {
|
||||||
cell.textLabel?.text = "↻ \(entry.title)"
|
cell.textLabel?.text = "↻ \(entry.title)"
|
||||||
|
|
@ -159,7 +145,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let passwordEntity = entry.passwordEntity!
|
let passwordEntity = entry.passwordEntity
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
var decryptedPassword: Password?
|
var decryptedPassword: Password?
|
||||||
|
|
@ -243,16 +229,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
|
|
||||||
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||||
if let searchText = searchBar.text, searchText.isEmpty == false {
|
if let searchText = searchBar.text, searchText.isEmpty == false {
|
||||||
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
filteredPasswordsTableEntries = passwordsTableEntries.filter {$0.match(searchText)}
|
||||||
var matched = false
|
|
||||||
matched = matched || entry.title.range(of: searchText, options: .caseInsensitive) != nil
|
|
||||||
matched = matched || searchText.range(of: entry.title, options: .caseInsensitive) != nil
|
|
||||||
entry.categoryArray.forEach({ (category) in
|
|
||||||
matched = matched || category.range(of: searchText, options: .caseInsensitive) != nil
|
|
||||||
matched = matched || searchText.range(of: category, options: .caseInsensitive) != nil
|
|
||||||
})
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
searchActive = true
|
searchActive = true
|
||||||
} else {
|
} else {
|
||||||
searchActive = false
|
searchActive = false
|
||||||
|
|
@ -264,7 +241,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
searchBarSearchButtonClicked(searchBar)
|
searchBarSearchButtonClicked(searchBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
|
||||||
if searchActive {
|
if searchActive {
|
||||||
return filteredPasswordsTableEntries[indexPath.row]
|
return filteredPasswordsTableEntries[indexPath.row]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
44
passKit/Models/PasswordTableEntry.swift
Normal file
44
passKit/Models/PasswordTableEntry.swift
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// PasswordTableEntry.swift
|
||||||
|
// passKit
|
||||||
|
//
|
||||||
|
// Created by Yishi Lin on 2020/2/23.
|
||||||
|
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class PasswordTableEntry: NSObject {
|
||||||
|
public let passwordEntity: PasswordEntity
|
||||||
|
@objc public let title: String
|
||||||
|
public let isDir: Bool
|
||||||
|
public let synced: Bool
|
||||||
|
public let categoryText: String
|
||||||
|
|
||||||
|
public init(_ entity: PasswordEntity) {
|
||||||
|
self.passwordEntity = entity
|
||||||
|
self.title = entity.name!
|
||||||
|
self.isDir = entity.isDir
|
||||||
|
self.synced = entity.synced
|
||||||
|
self.categoryText = entity.getCategoryText()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func match(_ searchText: String) -> Bool {
|
||||||
|
return PasswordTableEntry.match(nameWithCategory: passwordEntity.nameWithCategory, searchText: searchText)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func match(nameWithCategory: String, searchText: String) -> Bool {
|
||||||
|
let titleSplit = nameWithCategory.split{ !($0.isLetter || $0.isNumber || $0 == ".") }
|
||||||
|
for str in titleSplit {
|
||||||
|
if (str.localizedCaseInsensitiveContains(searchText)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (searchText.localizedCaseInsensitiveContains(str)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
55
passKitTests/Models/PasswordTableEntryTest.swift
Normal file
55
passKitTests/Models/PasswordTableEntryTest.swift
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
//
|
||||||
|
// PasswordTableEntryTest.swift
|
||||||
|
// passKitTests
|
||||||
|
//
|
||||||
|
// Created by Yishi Lin on 2020/2/23.
|
||||||
|
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import passKit
|
||||||
|
|
||||||
|
class PasswordTableEntryTest: XCTestCase {
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
let nameWithCategoryList = [
|
||||||
|
"github",
|
||||||
|
"github.com",
|
||||||
|
"www.github.com",
|
||||||
|
"personal/github",
|
||||||
|
"personal/github.com",
|
||||||
|
"personal/www.github.com",
|
||||||
|
"github/personal",
|
||||||
|
"github.com/personal",
|
||||||
|
"www.github.com/personal",
|
||||||
|
"github (personal)",
|
||||||
|
]
|
||||||
|
let searchTextList1 = [
|
||||||
|
"github.com",
|
||||||
|
"www.github.com"
|
||||||
|
]
|
||||||
|
let searchTextList2 = [
|
||||||
|
"xx.com",
|
||||||
|
"www.xx.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
for nameWithCategory in nameWithCategoryList {
|
||||||
|
for searchText in searchTextList1 {
|
||||||
|
XCTAssertTrue(PasswordTableEntry.match(nameWithCategory: nameWithCategory, searchText: searchText))
|
||||||
|
}
|
||||||
|
for searchText in searchTextList2 {
|
||||||
|
XCTAssertFalse(PasswordTableEntry.match(nameWithCategory: nameWithCategory, searchText: searchText))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue