2017-01-19 21:15:47 +08:00
|
|
|
//
|
2017-02-03 13:01:41 +08:00
|
|
|
// PasswordsViewController.swift
|
2017-01-19 21:15:47 +08:00
|
|
|
// pass
|
|
|
|
|
//
|
2017-02-03 13:01:41 +08:00
|
|
|
// Created by Mingshen Sun on 3/2/2017.
|
2017-01-19 21:15:47 +08:00
|
|
|
// Copyright © 2017 Bob Sun. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
|
import SVProgressHUD
|
2017-06-13 11:42:49 +08:00
|
|
|
import passKit
|
2017-01-19 21:15:47 +08:00
|
|
|
|
2020-02-23 18:05:10 +08:00
|
|
|
fileprivate let hideSectionHeaderThreshold = 6 // hide section header if passwords count is less than the threshold
|
2019-11-17 16:49:14 -08:00
|
|
|
|
2017-04-04 22:48:39 -07:00
|
|
|
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate {
|
2020-02-23 18:05:10 +08:00
|
|
|
private var passwordsTableEntries: [PasswordTableEntry] = []
|
|
|
|
|
private var passwordsTableAllEntries: [PasswordTableEntry] = []
|
2017-03-02 14:51:40 +08:00
|
|
|
private var parentPasswordEntity: PasswordEntity? = nil
|
2017-03-24 21:53:07 +08:00
|
|
|
private let passwordStore = PasswordStore.shared
|
2019-09-08 23:00:46 +02:00
|
|
|
private let keychain = AppKeychain.shared
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-02 22:55:41 +08:00
|
|
|
private var tapTabBarTime: TimeInterval = 0
|
2019-11-17 23:25:30 -08:00
|
|
|
private var tapNavigationBarGestureRecognizer: UITapGestureRecognizer!
|
2017-03-02 14:51:40 +08:00
|
|
|
|
2020-02-23 18:05:10 +08:00
|
|
|
private var sections = [(title: String, entries: [PasswordTableEntry])]()
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-11-17 23:25:30 -08:00
|
|
|
private enum PasswordLabel {
|
|
|
|
|
case all
|
|
|
|
|
case unsynced
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-11-30 15:11:28 -08:00
|
|
|
private var gitCredential: GitCredential {
|
|
|
|
|
get {
|
2020-01-02 00:48:00 +01:00
|
|
|
switch Defaults.gitAuthenticationMethod {
|
2019-11-30 15:11:28 -08:00
|
|
|
case .password:
|
2020-01-02 00:48:00 +01:00
|
|
|
return GitCredential(credential: .http(userName: Defaults.gitUsername))
|
2019-11-30 15:11:28 -08:00
|
|
|
case .key:
|
|
|
|
|
let privateKey: String = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) ?? ""
|
2020-01-02 00:48:00 +01:00
|
|
|
return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey))
|
2019-11-30 15:11:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
private lazy var searchController: UISearchController = {
|
2017-03-02 16:46:23 +08:00
|
|
|
let uiSearchController = UISearchController(searchResultsController: nil)
|
|
|
|
|
uiSearchController.searchResultsUpdater = self
|
|
|
|
|
uiSearchController.dimsBackgroundDuringPresentation = false
|
|
|
|
|
uiSearchController.searchBar.isTranslucent = false
|
|
|
|
|
uiSearchController.searchBar.sizeToFit()
|
|
|
|
|
return uiSearchController
|
|
|
|
|
}()
|
2017-03-24 21:53:07 +08:00
|
|
|
private lazy var syncControl: UIRefreshControl = {
|
|
|
|
|
let syncControl = UIRefreshControl()
|
2019-05-01 17:49:27 +02:00
|
|
|
syncControl.addTarget(self, action: #selector(handleRefresh(_:)), for: UIControl.Event.valueChanged)
|
2017-03-24 21:53:07 +08:00
|
|
|
return syncControl
|
2017-02-04 11:30:57 +08:00
|
|
|
}()
|
2017-10-07 00:24:30 -07:00
|
|
|
private lazy var searchBarView: UIView? = {
|
|
|
|
|
guard #available(iOS 11, *) else {
|
2018-01-14 22:35:21 -08:00
|
|
|
let uiView = UIView(frame: CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 44))
|
2017-10-07 00:24:30 -07:00
|
|
|
uiView.addSubview(self.searchController.searchBar)
|
|
|
|
|
return uiView
|
|
|
|
|
}
|
|
|
|
|
return nil
|
2017-03-02 16:46:23 +08:00
|
|
|
}()
|
2017-03-24 21:53:07 +08:00
|
|
|
private lazy var backUIBarButtonItem: UIBarButtonItem = {
|
2019-09-29 01:00:29 -07:00
|
|
|
let backUIButton = UIButton(type: .system)
|
|
|
|
|
if #available(iOS 13.0, *) {
|
|
|
|
|
let leftImage = UIImage(systemName: "chevron.left", withConfiguration: UIImage.SymbolConfiguration(weight: .bold))
|
|
|
|
|
backUIButton.setImage(leftImage, for: .normal)
|
|
|
|
|
backUIButton.setTitle("Back".localize(), for: .normal)
|
|
|
|
|
let padding = CGFloat(integerLiteral: 3)
|
|
|
|
|
backUIButton.contentEdgeInsets.right += padding
|
|
|
|
|
backUIButton.titleEdgeInsets.left = padding
|
|
|
|
|
backUIButton.titleEdgeInsets.right = -padding
|
|
|
|
|
} else {
|
|
|
|
|
backUIButton.setTitle("Back".localize(), for: .normal)
|
|
|
|
|
}
|
|
|
|
|
backUIButton.addTarget(self, action: #selector(self.backAction(_:)), for: .touchDown)
|
|
|
|
|
let backUIBarButtonItem = UIBarButtonItem(customView: backUIButton)
|
2017-03-02 14:51:40 +08:00
|
|
|
return backUIBarButtonItem
|
|
|
|
|
}()
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-09-29 01:00:29 -07:00
|
|
|
private lazy var addPasswordUIBarButtonItem: UIBarButtonItem = {
|
|
|
|
|
var addPasswordUIBarButtonItem = UIBarButtonItem()
|
|
|
|
|
if #available(iOS 13.0, *) {
|
|
|
|
|
let addPasswordButton = UIButton(type: .system)
|
|
|
|
|
let plusImage = UIImage(systemName: "plus.circle", withConfiguration: UIImage.SymbolConfiguration(weight: .regular))
|
|
|
|
|
addPasswordButton.setImage(plusImage, for: .normal)
|
|
|
|
|
addPasswordButton.addTarget(self, action: #selector(self.addPasswordAction(_:)), for: .touchDown)
|
|
|
|
|
addPasswordUIBarButtonItem.customView = addPasswordButton
|
|
|
|
|
} else {
|
|
|
|
|
addPasswordUIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.addPasswordAction(_:)))
|
|
|
|
|
}
|
|
|
|
|
return addPasswordUIBarButtonItem
|
|
|
|
|
}()
|
|
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
private lazy var transitionFromRight: CATransition = {
|
2017-03-23 22:46:50 -07:00
|
|
|
let transition = CATransition()
|
2019-05-01 17:49:27 +02:00
|
|
|
transition.type = CATransitionType.push
|
|
|
|
|
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
|
|
|
|
transition.fillMode = CAMediaTimingFillMode.forwards
|
2017-03-23 22:46:50 -07:00
|
|
|
transition.duration = 0.25
|
2019-05-01 17:49:27 +02:00
|
|
|
transition.subtype = CATransitionSubtype.fromRight
|
2020-03-21 16:58:11 +01:00
|
|
|
transition.delegate = self
|
2017-03-23 22:46:50 -07:00
|
|
|
return transition
|
|
|
|
|
}()
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
private lazy var transitionFromLeft: CATransition = {
|
2017-03-23 22:46:50 -07:00
|
|
|
let transition = CATransition()
|
2019-05-01 17:49:27 +02:00
|
|
|
transition.type = CATransitionType.push
|
|
|
|
|
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
|
|
|
|
transition.fillMode = CAMediaTimingFillMode.forwards
|
2017-03-23 22:46:50 -07:00
|
|
|
transition.duration = 0.25
|
2019-05-01 17:49:27 +02:00
|
|
|
transition.subtype = CATransitionSubtype.fromLeft
|
2020-03-21 16:58:11 +01:00
|
|
|
transition.delegate = self
|
2017-03-23 22:46:50 -07:00
|
|
|
return transition
|
|
|
|
|
}()
|
2017-02-03 13:01:41 +08:00
|
|
|
|
|
|
|
|
@IBOutlet weak var tableView: UITableView!
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-02 21:39:48 +08:00
|
|
|
private func initPasswordsTableEntries(parent: PasswordEntity?) {
|
2020-02-23 18:05:10 +08:00
|
|
|
let passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
|
|
|
|
passwordsTableAllEntries = passwordAllEntities.compactMap {
|
|
|
|
|
PasswordTableEntry($0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let passwordEntities = Defaults.isShowFolderOn ?
|
|
|
|
|
self.passwordStore.fetchPasswordEntityCoreData(parent: parent) :
|
|
|
|
|
passwordAllEntities
|
|
|
|
|
passwordsTableEntries = passwordEntities.compactMap {
|
|
|
|
|
PasswordTableEntry($0)
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-02 21:39:48 +08:00
|
|
|
parentPasswordEntity = parent
|
2017-03-02 16:46:23 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-02-10 22:15:01 +08:00
|
|
|
@IBAction func cancelAddPassword(segue: UIStoryboardSegue) {
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-02-10 22:15:01 +08:00
|
|
|
}
|
|
|
|
|
@IBAction func saveAddPassword(segue: UIStoryboardSegue) {
|
|
|
|
|
if let controller = segue.source as? AddPasswordTableViewController {
|
2020-04-17 23:56:14 -07:00
|
|
|
addPassword(password: controller.password!)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func addPassword(password: Password, keyID: String? = nil) {
|
|
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
|
|
|
SVProgressHUD.setDefaultStyle(.light)
|
|
|
|
|
SVProgressHUD.show(withStatus: "Saving".localize())
|
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
|
|
|
do {
|
|
|
|
|
let _ = try self.passwordStore.add(password: password, keyID: keyID)
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
// will trigger reloadTableView() by a notification
|
|
|
|
|
SVProgressHUD.showSuccess(withStatus: "Done".localize())
|
|
|
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
|
|
|
}
|
|
|
|
|
} catch AppError.PgpPublicKeyNotFound(let key) {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
// alert: cancel or select keys
|
|
|
|
|
SVProgressHUD.dismiss()
|
|
|
|
|
let alert = UIAlertController(title: "Cannot Encrypt Password", message: AppError.PgpPublicKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
|
|
|
|
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
|
|
|
|
let selectKey = UIAlertAction.selectKey(controller: self) { action in
|
|
|
|
|
self.addPassword(password: password, keyID: action.title)
|
2017-02-11 19:48:47 +08:00
|
|
|
}
|
2020-04-17 23:56:14 -07:00
|
|
|
alert.addAction(selectKey)
|
|
|
|
|
|
|
|
|
|
self.present(alert, animated: true, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
} catch {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
2017-02-11 19:48:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
2017-02-10 22:15:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
private func syncPasswords() {
|
2020-03-04 20:27:23 +01:00
|
|
|
guard passwordStore.repositoryExists() else {
|
2017-04-28 20:33:41 -07:00
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
|
2019-01-14 20:57:45 +01:00
|
|
|
Utils.alert(title: "Error".localize(), message: "NoPasswordStore.".localize(), controller: self, completion: nil)
|
2017-04-28 20:33:41 -07:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-02-08 01:29:00 +08:00
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
|
|
|
SVProgressHUD.setDefaultStyle(.light)
|
2019-01-14 20:57:45 +01:00
|
|
|
SVProgressHUD.show(withStatus: "SyncingPasswordStore".localize())
|
2019-11-30 15:11:28 -08:00
|
|
|
|
2017-02-04 11:35:28 +08:00
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
2017-02-04 14:24:59 +08:00
|
|
|
do {
|
2019-11-30 22:39:21 -08:00
|
|
|
try self.passwordStore.pullRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, progressBlock: {(git_transfer_progress, stop) in
|
2017-02-04 14:24:59 +08:00
|
|
|
DispatchQueue.main.async {
|
2019-09-26 18:00:58 +02:00
|
|
|
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "PullingFromRemoteRepository".localize())
|
2017-02-04 14:24:59 +08:00
|
|
|
}
|
|
|
|
|
})
|
2019-11-30 15:11:28 -08:00
|
|
|
if self.passwordStore.numberOfLocalCommits > 0 {
|
2019-11-30 22:39:21 -08:00
|
|
|
try self.passwordStore.pushRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, transferProgressBlock: {(current, total, bytes, stop) in
|
2017-02-12 13:31:29 +08:00
|
|
|
DispatchQueue.main.async {
|
2019-01-14 20:57:45 +01:00
|
|
|
SVProgressHUD.showProgress(Float(current)/Float(total), status: "PushingToRemoteRepository".localize())
|
2017-02-12 13:31:29 +08:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-01-23 17:36:10 +08:00
|
|
|
DispatchQueue.main.async {
|
2017-03-24 21:53:07 +08:00
|
|
|
self.reloadTableView(parent: nil)
|
2019-01-14 20:57:45 +01:00
|
|
|
SVProgressHUD.showSuccess(withStatus: "Done".localize())
|
2017-01-23 17:36:10 +08:00
|
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
2017-09-24 22:55:24 -07:00
|
|
|
self.syncControl.endRefreshing()
|
2017-01-23 17:36:10 +08:00
|
|
|
}
|
2017-02-04 14:24:59 +08:00
|
|
|
} catch {
|
|
|
|
|
DispatchQueue.main.async {
|
2017-04-05 20:06:40 -07:00
|
|
|
SVProgressHUD.dismiss()
|
2017-04-05 19:21:50 -07:00
|
|
|
self.syncControl.endRefreshing()
|
2017-10-08 21:37:58 +08:00
|
|
|
let error = error as NSError
|
|
|
|
|
var message = error.localizedDescription
|
|
|
|
|
if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
|
2019-01-20 12:15:54 +01:00
|
|
|
message = message | "UnderlyingError".localize(underlyingError.localizedDescription)
|
2019-01-14 20:57:45 +01:00
|
|
|
if underlyingError.localizedDescription.contains("WrongPassphrase".localize()) {
|
2019-02-13 21:31:57 +01:00
|
|
|
message = message | "RecoverySuggestion.".localize()
|
2017-10-08 21:37:58 +08:00
|
|
|
}
|
|
|
|
|
}
|
2017-04-05 20:06:40 -07:00
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
|
2019-01-14 20:57:45 +01:00
|
|
|
Utils.alert(title: "Error".localize(), message: message, controller: self, completion: nil)
|
2017-04-05 20:06:40 -07:00
|
|
|
}
|
2017-02-04 14:24:59 +08:00
|
|
|
}
|
2017-01-23 16:29:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-04-04 22:48:39 -07:00
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
|
|
|
super.viewDidAppear(animated)
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2020-01-02 00:48:00 +01:00
|
|
|
if Defaults.isShowFolderOn {
|
2019-03-05 22:31:03 +01:00
|
|
|
searchController.searchBar.scopeButtonTitles = SearchBarScope.allCases.map { $0.localizedName }
|
2017-04-04 22:48:39 -07:00
|
|
|
} else {
|
|
|
|
|
searchController.searchBar.scopeButtonTitles = nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-01-19 21:15:47 +08:00
|
|
|
override func viewDidLoad() {
|
|
|
|
|
super.viewDidLoad()
|
2017-04-04 22:48:39 -07:00
|
|
|
searchController.searchBar.delegate = self
|
2017-02-03 13:01:41 +08:00
|
|
|
tableView.delegate = self
|
|
|
|
|
tableView.dataSource = self
|
2017-01-23 12:48:20 +08:00
|
|
|
definesPresentationContext = true
|
2017-10-07 00:24:30 -07:00
|
|
|
if #available(iOS 11.0, *) {
|
|
|
|
|
navigationItem.searchController = searchController
|
|
|
|
|
navigationController?.navigationBar.prefersLargeTitles = true
|
|
|
|
|
navigationItem.largeTitleDisplayMode = .automatic
|
2017-10-07 23:14:00 -07:00
|
|
|
navigationItem.hidesSearchBarWhenScrolling = false
|
2017-10-07 00:24:30 -07:00
|
|
|
} else {
|
|
|
|
|
// Fallback on earlier versions
|
2019-05-01 17:49:27 +02:00
|
|
|
tableView.contentInset = UIEdgeInsets.init(top: 44, left: 0, bottom: 0, right: 0)
|
2017-10-07 00:24:30 -07:00
|
|
|
view.addSubview(searchBarView!)
|
|
|
|
|
}
|
2019-09-29 15:59:11 -07:00
|
|
|
navigationItem.title = "PasswordStore".localize()
|
2019-11-17 23:25:30 -08:00
|
|
|
tapNavigationBarGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapNavigationBar))
|
|
|
|
|
|
2017-09-24 22:53:00 -07:00
|
|
|
tableView.refreshControl = syncControl
|
2017-02-06 10:55:24 +08:00
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
2017-03-10 23:07:56 -08:00
|
|
|
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
// initialize the password table
|
|
|
|
|
reloadTableView(parent: nil)
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
// reset the data table if some password (maybe another one) has been updated
|
2017-03-29 00:26:41 +08:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordStoreUpdated, object: nil)
|
2017-03-24 21:53:07 +08:00
|
|
|
// reset the data table if the disaply settings have been changed
|
2017-03-29 00:26:41 +08:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordDisplaySettingChanged, object: nil)
|
2017-03-24 21:53:07 +08:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(actOnSearchNotification), name: .passwordSearch, object: nil)
|
2020-03-16 22:24:56 +01:00
|
|
|
// A Siri shortcut can change the state of the app in the background. Hence, reload when opening the app.
|
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2018-04-12 01:07:19 +08:00
|
|
|
// listen to the swipe back guesture
|
|
|
|
|
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
|
2019-05-01 17:49:27 +02:00
|
|
|
swipeRight.direction = UISwipeGestureRecognizer.Direction.right
|
2018-04-12 01:07:19 +08:00
|
|
|
self.view.addGestureRecognizer(swipeRight)
|
2017-02-02 15:03:34 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-11-17 23:25:30 -08:00
|
|
|
@objc func didTapNavigationBar(_ sender: UITapGestureRecognizer) {
|
|
|
|
|
let location = sender.location(in: self.navigationController?.navigationBar)
|
|
|
|
|
let hitView = self.navigationController?.navigationBar.hitTest(location, with: nil)
|
|
|
|
|
guard !(hitView is UIControl) else { return }
|
|
|
|
|
guard (passwordStore.numberOfLocalCommits != 0) else { return }
|
|
|
|
|
|
|
|
|
|
let ac = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
|
|
|
let allAction = UIAlertAction(title: "All Passwords", style: .default) { _ in
|
|
|
|
|
self.reloadTableView(parent: nil, label: .all)
|
|
|
|
|
}
|
|
|
|
|
let unsyncedAction = UIAlertAction(title: "Unsynced Passwords", style: .default) { _ in
|
2020-02-23 18:05:10 +08:00
|
|
|
let filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in
|
|
|
|
|
return !entry.synced
|
2019-11-17 23:25:30 -08:00
|
|
|
}
|
2020-02-23 18:05:10 +08:00
|
|
|
self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced)
|
2019-11-17 23:25:30 -08:00
|
|
|
}
|
|
|
|
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
|
|
|
|
|
|
|
|
|
|
ac.addAction(allAction)
|
|
|
|
|
ac.addAction(unsyncedAction)
|
|
|
|
|
ac.addAction(cancelAction)
|
|
|
|
|
|
|
|
|
|
self.present(ac, animated: true, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-03 14:20:52 +08:00
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
|
|
|
super.viewWillAppear(animated)
|
2018-01-16 21:54:00 -08:00
|
|
|
tabBarController!.delegate = self
|
2017-02-03 14:20:52 +08:00
|
|
|
if let path = tableView.indexPathForSelectedRow {
|
|
|
|
|
tableView.deselectRow(at: path, animated: false)
|
|
|
|
|
}
|
2019-11-17 23:25:30 -08:00
|
|
|
|
|
|
|
|
// Add gesture recognizer to the navigation bar when the view is about to appear
|
|
|
|
|
self.navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
|
|
|
|
|
|
|
|
|
|
// This allows controlls in the navigation bar to continue receiving touches
|
|
|
|
|
tapNavigationBarGestureRecognizer.cancelsTouchesInView = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
|
|
|
// Remove gesture recognizer from navigation bar when view is about to disappear
|
|
|
|
|
self.navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
|
2017-02-03 14:20:52 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-09-24 23:49:16 -07:00
|
|
|
override func viewWillLayoutSubviews() {
|
|
|
|
|
super.viewWillLayoutSubviews()
|
2017-10-07 00:24:30 -07:00
|
|
|
guard #available(iOS 11, *) else {
|
2018-01-14 22:35:21 -08:00
|
|
|
searchBarView?.frame = CGRect(x: 0, y: navigationController!.navigationBar.bounds.size.height + UIApplication.shared.statusBarFrame.height, width: UIScreen.main.bounds.width, height: 44)
|
2017-10-07 00:24:30 -07:00
|
|
|
searchController.searchBar.sizeToFit()
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-02-07 13:23:18 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
2017-02-02 15:03:34 +08:00
|
|
|
return sections.count
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
2017-04-03 22:40:17 +08:00
|
|
|
return sections[section].entries.count
|
2017-02-03 13:01:41 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
2019-09-29 23:34:11 -07:00
|
|
|
let longPressGestureRecognizer: UILongPressGestureRecognizer = {
|
|
|
|
|
let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction))
|
|
|
|
|
recognizer.minimumPressDuration = 0.6
|
|
|
|
|
return recognizer
|
|
|
|
|
}()
|
|
|
|
|
let entry = getPasswordEntry(by: indexPath)
|
2020-02-23 18:05:10 +08:00
|
|
|
let passwordEntity = entry.passwordEntity
|
2019-09-29 23:34:11 -07:00
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-09-29 23:34:11 -07:00
|
|
|
cell.textLabel?.text = passwordEntity.synced ? entry.title : "↻ \(entry.title)"
|
|
|
|
|
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
|
|
|
|
cell.textLabel?.adjustsFontForContentSizeCategory = true
|
|
|
|
|
cell.accessoryType = .none
|
|
|
|
|
cell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
|
|
|
|
|
cell.detailTextLabel?.adjustsFontForContentSizeCategory = true
|
|
|
|
|
cell.addGestureRecognizer(longPressGestureRecognizer)
|
|
|
|
|
|
|
|
|
|
if entry.isDir {
|
|
|
|
|
cell.accessoryType = .disclosureIndicator
|
2019-11-17 10:56:23 -08:00
|
|
|
cell.textLabel?.font = UIFont.systemFont(ofSize: UIFont.preferredFont(forTextStyle: .body).pointSize, weight: .medium)
|
2019-09-29 23:34:11 -07:00
|
|
|
cell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
|
|
|
|
cell.detailTextLabel?.text = "\(passwordEntity.children?.count ?? 0)"
|
|
|
|
|
cell.removeGestureRecognizer(longPressGestureRecognizer)
|
2017-02-12 01:59:40 +08:00
|
|
|
} else {
|
2019-09-29 23:34:11 -07:00
|
|
|
cell.detailTextLabel?.text = passwordEntity.getCategoryText()
|
2017-02-12 01:59:40 +08:00
|
|
|
}
|
2017-03-10 23:07:56 -08:00
|
|
|
|
2019-09-29 23:34:11 -07:00
|
|
|
return cell
|
2017-02-03 13:01:41 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2020-02-23 18:05:10 +08:00
|
|
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
|
2017-04-03 22:40:17 +08:00
|
|
|
return sections[indexPath.section].entries[indexPath.row]
|
2017-03-02 14:51:40 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-02 14:51:40 +08:00
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
|
|
|
let entry = getPasswordEntry(by: indexPath)
|
|
|
|
|
if !entry.isDir {
|
2017-03-11 21:09:24 -08:00
|
|
|
let segueIdentifier = "showPasswordDetail"
|
|
|
|
|
let sender = tableView.cellForRow(at: indexPath)
|
|
|
|
|
if shouldPerformSegue(withIdentifier: segueIdentifier, sender: sender) {
|
|
|
|
|
performSegue(withIdentifier: segueIdentifier, sender: sender)
|
|
|
|
|
}
|
2017-03-02 14:51:40 +08:00
|
|
|
} else {
|
|
|
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
2017-03-02 16:45:52 +08:00
|
|
|
searchController.isActive = false
|
2017-03-24 21:53:07 +08:00
|
|
|
reloadTableView(parent: entry.passwordEntity, anim: transitionFromRight)
|
2017-03-02 14:51:40 +08:00
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2018-04-12 01:07:19 +08:00
|
|
|
@objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
|
|
|
|
|
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
|
|
|
|
|
// swipe right -> swipe back
|
|
|
|
|
if swipeGesture.direction == .right && parentPasswordEntity != nil {
|
|
|
|
|
self.backAction(nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-09-23 16:29:03 +08:00
|
|
|
@objc func backAction(_ sender: Any?) {
|
2020-01-02 00:48:00 +01:00
|
|
|
guard Defaults.isShowFolderOn else { return }
|
2017-04-05 20:20:26 -07:00
|
|
|
var anim: CATransition? = transitionFromLeft
|
|
|
|
|
if parentPasswordEntity == nil {
|
|
|
|
|
anim = nil
|
|
|
|
|
}
|
|
|
|
|
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
|
2017-03-02 14:51:40 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-09-29 01:00:29 -07:00
|
|
|
@objc func addPasswordAction(_ sender: Any?) {
|
|
|
|
|
if shouldPerformSegue(withIdentifier: "addPasswordSegue", sender: self) {
|
|
|
|
|
performSegue(withIdentifier: "addPasswordSegue", sender: self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-23 16:29:03 +08:00
|
|
|
@objc func longPressAction(_ gesture: UILongPressGestureRecognizer) {
|
2019-05-01 17:49:27 +02:00
|
|
|
if gesture.state == UIGestureRecognizer.State.began {
|
2017-02-08 01:29:00 +08:00
|
|
|
let touchPoint = gesture.location(in: tableView)
|
|
|
|
|
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
|
2017-07-27 22:38:07 +08:00
|
|
|
decryptThenCopyPassword(from: indexPath)
|
2017-02-08 01:29:00 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-11-17 16:49:14 -08:00
|
|
|
private func hideSectionHeader() -> Bool {
|
2020-02-23 18:05:10 +08:00
|
|
|
if passwordsTableEntries.count < hideSectionHeaderThreshold || self.searchController.isActive {
|
2019-11-17 16:49:14 -08:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
2019-11-17 16:49:14 -08:00
|
|
|
if hideSectionHeader() {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2017-02-03 13:01:41 +08:00
|
|
|
return sections[section].title
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
|
2019-11-17 16:49:14 -08:00
|
|
|
if hideSectionHeader() {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2017-02-03 13:01:41 +08:00
|
|
|
return sections.map { $0.title }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
|
|
|
|
return index
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-02-04 11:30:57 +08:00
|
|
|
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
2017-07-27 22:38:07 +08:00
|
|
|
decryptThenCopyPassword(from: indexPath)
|
2017-02-08 01:29:00 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-07-27 22:38:07 +08:00
|
|
|
private func decryptThenCopyPassword(from indexPath: IndexPath) {
|
2019-09-08 23:00:46 +02:00
|
|
|
guard PGPAgent.shared.isPrepared else {
|
2019-10-20 11:47:54 +02:00
|
|
|
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
|
2017-07-27 22:38:07 +08:00
|
|
|
return
|
|
|
|
|
}
|
2020-02-23 18:05:10 +08:00
|
|
|
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity
|
2017-07-27 22:38:07 +08:00
|
|
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
2019-09-30 02:05:01 +08:00
|
|
|
SVProgressHUD.dismiss()
|
2017-02-08 19:18:10 +08:00
|
|
|
DispatchQueue.global(qos: .userInteractive).async {
|
|
|
|
|
var decryptedPassword: Password?
|
|
|
|
|
do {
|
2020-04-13 15:16:03 -07:00
|
|
|
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
|
|
|
|
decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
2017-02-15 11:37:19 +08:00
|
|
|
DispatchQueue.main.async {
|
2017-07-27 23:56:24 +08:00
|
|
|
SecurePasteboard.shared.copy(textToCopy: decryptedPassword?.password)
|
2019-09-30 02:05:01 +08:00
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
|
|
|
SVProgressHUD.setDefaultStyle(.dark)
|
2019-01-14 20:57:45 +01:00
|
|
|
SVProgressHUD.showSuccess(withStatus: "PasswordCopiedToPasteboard.".localize())
|
2017-02-15 11:37:19 +08:00
|
|
|
SVProgressHUD.dismiss(withDelay: 0.6)
|
|
|
|
|
}
|
2017-02-08 19:18:10 +08:00
|
|
|
} catch {
|
2017-02-15 11:37:19 +08:00
|
|
|
DispatchQueue.main.async {
|
2019-01-14 20:57:45 +01:00
|
|
|
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
2017-02-15 11:37:19 +08:00
|
|
|
}
|
2017-02-08 19:18:10 +08:00
|
|
|
}
|
2017-02-06 14:28:57 +08:00
|
|
|
}
|
2017-02-04 11:30:57 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2020-02-23 18:05:10 +08:00
|
|
|
private func generateSections(item: [PasswordTableEntry]) {
|
2017-04-03 22:40:17 +08:00
|
|
|
let collation = UILocalizedIndexedCollation.current()
|
|
|
|
|
let sectionTitles = collation.sectionIndexTitles
|
2020-02-23 18:05:10 +08:00
|
|
|
var newSections = [(title: String, entries: [PasswordTableEntry])]()
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-04-03 22:40:17 +08:00
|
|
|
// initialize all sections
|
|
|
|
|
for i in 0..<sectionTitles.count {
|
2020-02-23 18:05:10 +08:00
|
|
|
newSections.append((title: sectionTitles[i], entries: [PasswordTableEntry]()))
|
2017-02-02 15:03:34 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-04-03 22:40:17 +08:00
|
|
|
// put entries into sections
|
|
|
|
|
for entry in item {
|
2020-02-23 18:05:10 +08:00
|
|
|
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordTableEntry.title))
|
2017-04-03 22:40:17 +08:00
|
|
|
newSections[sectionNumber].entries.append(entry)
|
2017-02-02 15:03:34 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-04-03 22:40:17 +08:00
|
|
|
// sort each list and set sectionTitles
|
|
|
|
|
for i in 0..<sectionTitles.count {
|
|
|
|
|
let entriesToSort = newSections[i].entries
|
2020-02-23 18:05:10 +08:00
|
|
|
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordTableEntry.title))
|
|
|
|
|
newSections[i].entries = sortedEntries as! [PasswordTableEntry]
|
2017-04-03 22:40:17 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-04-03 22:40:17 +08:00
|
|
|
// only keep non-empty sections
|
|
|
|
|
sections = newSections.filter {$0.entries.count > 0}
|
2017-01-23 12:48:20 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-09-23 16:29:03 +08:00
|
|
|
@objc func actOnSearchNotification() {
|
2017-02-08 12:47:05 +08:00
|
|
|
searchController.searchBar.becomeFirstResponder()
|
|
|
|
|
}
|
2017-02-07 16:45:14 +08:00
|
|
|
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-02-06 10:55:24 +08:00
|
|
|
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
|
|
|
|
if identifier == "showPasswordDetail" {
|
2019-09-08 23:00:46 +02:00
|
|
|
guard PGPAgent.shared.isPrepared else {
|
2019-10-20 11:47:54 +02:00
|
|
|
Utils.alert(title: "CannotShowPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
|
2017-02-06 10:55:24 +08:00
|
|
|
if let s = sender as? UITableViewCell {
|
|
|
|
|
let selectedIndexPath = tableView.indexPath(for: s)!
|
|
|
|
|
tableView.deselectRow(at: selectedIndexPath, animated: true)
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2017-04-05 19:33:06 -07:00
|
|
|
} else if identifier == "addPasswordSegue" {
|
2019-09-08 23:00:46 +02:00
|
|
|
guard PGPAgent.shared.isPrepared && self.passwordStore.storeRepository != nil else {
|
2019-01-14 20:57:45 +01:00
|
|
|
Utils.alert(title: "CannotAddPassword".localize(), message: "MakeSurePgpAndGitProperlySet.".localize(), controller: self, completion: nil)
|
2017-04-05 19:33:06 -07:00
|
|
|
return false
|
|
|
|
|
}
|
2017-02-06 10:55:24 +08:00
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
2017-02-03 13:01:41 +08:00
|
|
|
|
2017-01-22 01:42:36 +08:00
|
|
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
|
|
|
|
if segue.identifier == "showPasswordDetail" {
|
2017-02-02 21:04:31 +08:00
|
|
|
if let viewController = segue.destination as? PasswordDetailTableViewController {
|
2017-02-02 15:03:34 +08:00
|
|
|
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
|
2020-02-23 18:05:10 +08:00
|
|
|
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity
|
2017-02-06 14:28:57 +08:00
|
|
|
viewController.passwordEntity = passwordEntity
|
2017-01-22 01:42:36 +08:00
|
|
|
}
|
2017-10-15 16:49:33 +08:00
|
|
|
} else if segue.identifier == "addPasswordSegue" {
|
|
|
|
|
if let navController = segue.destination as? UINavigationController {
|
|
|
|
|
if let viewController = navController.topViewController as? AddPasswordTableViewController {
|
2018-11-10 22:38:12 -08:00
|
|
|
if let path = parentPasswordEntity?.getPath() {
|
2017-10-15 16:49:33 +08:00
|
|
|
viewController.defaultDirPrefix = "\(path)/"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-22 01:42:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-03-05 22:31:03 +01:00
|
|
|
func filterContentForSearchText(searchText: String, scope: SearchBarScope = .all) {
|
2020-02-23 18:05:10 +08:00
|
|
|
var entries: [PasswordTableEntry] = scope == .all ? passwordsTableAllEntries : passwordsTableEntries
|
|
|
|
|
if searchController.isActive && searchController.searchBar.text != "" {
|
|
|
|
|
entries = entries.filter {$0.match(searchText)}
|
2017-02-03 13:01:41 +08:00
|
|
|
}
|
2020-02-23 18:05:10 +08:00
|
|
|
reloadTableView(data: entries)
|
2017-02-04 11:30:57 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2020-02-23 18:05:10 +08:00
|
|
|
private func reloadTableView(data: [PasswordTableEntry], label: PasswordLabel = .all, anim: CAAnimation? = nil) {
|
2017-03-24 21:53:07 +08:00
|
|
|
// set navigation item
|
2019-11-30 15:11:28 -08:00
|
|
|
if passwordStore.numberOfLocalCommits != 0 {
|
|
|
|
|
navigationController?.tabBarItem.badgeValue = "\(passwordStore.numberOfLocalCommits)"
|
2019-01-07 20:21:04 +01:00
|
|
|
} else {
|
|
|
|
|
navigationController?.tabBarItem.badgeValue = nil
|
2017-03-24 21:53:07 +08:00
|
|
|
}
|
2017-03-02 14:51:40 +08:00
|
|
|
if parentPasswordEntity != nil {
|
|
|
|
|
navigationItem.leftBarButtonItem = backUIBarButtonItem
|
2019-09-29 15:59:11 -07:00
|
|
|
navigationItem.title = parentPasswordEntity?.getName()
|
|
|
|
|
if #available(iOS 11, *) {
|
|
|
|
|
navigationController?.navigationBar.prefersLargeTitles = false
|
|
|
|
|
}
|
2019-11-17 23:25:30 -08:00
|
|
|
self.navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
|
2017-03-02 14:51:40 +08:00
|
|
|
} else {
|
|
|
|
|
navigationItem.leftBarButtonItem = nil
|
2019-11-17 23:25:30 -08:00
|
|
|
switch label {
|
|
|
|
|
case .all:
|
|
|
|
|
navigationItem.title = "PasswordStore".localize()
|
|
|
|
|
case .unsynced:
|
|
|
|
|
navigationItem.title = "Unsynced"
|
|
|
|
|
}
|
2019-09-29 15:59:11 -07:00
|
|
|
if #available(iOS 11, *) {
|
|
|
|
|
navigationController?.navigationBar.prefersLargeTitles = true
|
|
|
|
|
}
|
2019-11-17 23:25:30 -08:00
|
|
|
self.navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
|
2017-03-02 14:51:40 +08:00
|
|
|
}
|
2019-09-29 01:00:29 -07:00
|
|
|
navigationItem.rightBarButtonItem = addPasswordUIBarButtonItem
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
// set the password table
|
2017-02-04 11:30:57 +08:00
|
|
|
generateSections(item: data)
|
2017-03-23 22:46:50 -07:00
|
|
|
if anim != nil {
|
|
|
|
|
self.tableView.layer.add(anim!, forKey: "UITableViewReloadDataAnimationKey")
|
|
|
|
|
}
|
2017-02-03 13:01:41 +08:00
|
|
|
tableView.reloadData()
|
2017-03-23 22:46:50 -07:00
|
|
|
self.tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-24 21:53:07 +08:00
|
|
|
// set the sync control title
|
2019-01-14 20:57:45 +01:00
|
|
|
let atribbutedTitle = "LastSynced".localize() + ": \(lastSyncedTimeString())"
|
2017-03-24 21:53:07 +08:00
|
|
|
syncControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
|
|
|
|
|
}
|
2018-11-16 22:28:19 -08:00
|
|
|
|
|
|
|
|
private func lastSyncedTimeString() -> String {
|
|
|
|
|
guard let date = self.passwordStore.lastSyncedTime else {
|
2019-01-14 20:57:45 +01:00
|
|
|
return "SyncAgain?".localize()
|
2018-11-16 22:28:19 -08:00
|
|
|
}
|
|
|
|
|
let formatter = DateFormatter()
|
|
|
|
|
formatter.dateStyle = .medium
|
|
|
|
|
formatter.timeStyle = .short
|
|
|
|
|
return formatter.string(from: date)
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-11-17 23:25:30 -08:00
|
|
|
private func reloadTableView(parent: PasswordEntity?, label: PasswordLabel = .all, anim: CAAnimation? = nil) {
|
2017-03-24 21:53:07 +08:00
|
|
|
initPasswordsTableEntries(parent: parent)
|
2019-11-17 23:25:30 -08:00
|
|
|
reloadTableView(data: passwordsTableEntries, label: label, anim: anim)
|
2017-03-24 21:53:07 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-09-23 16:29:03 +08:00
|
|
|
@objc func actOnReloadTableViewRelatedNotification() {
|
2017-03-29 00:26:41 +08:00
|
|
|
DispatchQueue.main.async { [weak weakSelf = self] in
|
|
|
|
|
guard let strongSelf = weakSelf else { return }
|
2020-04-13 15:16:03 -07:00
|
|
|
// Reset selectedScopeButtonIndex to make sure the correct reloadTableView
|
|
|
|
|
strongSelf.searchController.searchBar.selectedScopeButtonIndex = 0
|
2017-04-23 10:39:28 -07:00
|
|
|
strongSelf.initPasswordsTableEntries(parent: nil)
|
2017-03-29 00:26:41 +08:00
|
|
|
strongSelf.reloadTableView(data: strongSelf.passwordsTableEntries)
|
|
|
|
|
}
|
2017-02-03 13:01:41 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-09-23 16:29:03 +08:00
|
|
|
@objc func handleRefresh(_ syncControl: UIRefreshControl) {
|
2017-02-04 11:47:57 +08:00
|
|
|
syncPasswords()
|
2017-02-04 11:30:57 +08:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-03-02 22:55:41 +08:00
|
|
|
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
|
|
|
|
if viewController == self.navigationController {
|
|
|
|
|
let currentTime = Date().timeIntervalSince1970
|
|
|
|
|
let duration = currentTime - self.tapTabBarTime
|
|
|
|
|
self.tapTabBarTime = currentTime
|
|
|
|
|
if duration < 0.35 {
|
|
|
|
|
let topIndexPath = IndexPath(row: 0, section: 0)
|
2018-01-16 20:04:16 -08:00
|
|
|
if tableView.numberOfSections > 0 {
|
|
|
|
|
tableView.scrollToRow(at: topIndexPath, at: .bottom, animated: true)
|
|
|
|
|
}
|
2017-03-02 22:55:41 +08:00
|
|
|
self.tapTabBarTime = 0
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
backAction(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2017-04-04 22:48:39 -07:00
|
|
|
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
2018-08-24 02:02:57 +01:00
|
|
|
// update the default search scope
|
2020-01-02 00:48:00 +01:00
|
|
|
Defaults.searchDefault = SearchBarScope(rawValue: selectedScope)
|
2017-04-04 22:48:39 -07:00
|
|
|
updateSearchResults(for: searchController)
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2018-08-24 02:02:57 +01:00
|
|
|
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
|
|
|
|
|
// set the default search scope to "all"
|
2020-01-02 00:48:00 +01:00
|
|
|
if Defaults.isShowFolderOn && Defaults.searchDefault == .all {
|
2019-03-05 22:31:03 +01:00
|
|
|
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.all.rawValue
|
2018-08-24 02:02:57 +01:00
|
|
|
} else {
|
2019-03-05 22:31:03 +01:00
|
|
|
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue
|
2018-08-24 02:02:57 +01:00
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2018-08-24 02:02:57 +01:00
|
|
|
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
|
|
|
|
|
// set the default search scope to "current"
|
2019-03-05 22:31:03 +01:00
|
|
|
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue
|
2017-04-04 22:48:39 -07:00
|
|
|
updateSearchResults(for: searchController)
|
2018-08-24 02:02:57 +01:00
|
|
|
return true
|
2017-04-04 22:48:39 -07:00
|
|
|
}
|
2018-12-09 16:59:07 -08:00
|
|
|
|
2019-11-30 22:39:21 -08:00
|
|
|
private func requestCredentialPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
|
2019-11-30 23:05:30 -08:00
|
|
|
return requestGitCredentialPassword(credential: credential, lastPassword: lastPassword, controller: self)
|
2017-06-13 13:19:18 +08:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 21:15:47 +08:00
|
|
|
}
|
2017-01-23 16:29:36 +08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
extension PasswordsViewController: UISearchResultsUpdating {
|
2020-03-21 16:58:11 +01:00
|
|
|
|
2017-01-23 16:29:36 +08:00
|
|
|
func updateSearchResults(for searchController: UISearchController) {
|
2019-03-05 22:31:03 +01:00
|
|
|
let scope = SearchBarScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) ?? .all
|
2017-04-04 22:48:39 -07:00
|
|
|
filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope)
|
2017-01-23 16:29:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
2020-03-21 16:58:11 +01:00
|
|
|
|
|
|
|
|
extension PasswordsViewController: CAAnimationDelegate {
|
|
|
|
|
|
|
|
|
|
func animationDidStart(_ anim: CAAnimation) {
|
|
|
|
|
view.window?.backgroundColor = Colors.systemBackground
|
|
|
|
|
view.layer.backgroundColor = Colors.systemBackground.cgColor
|
|
|
|
|
}
|
|
|
|
|
}
|