From f2a0c4ccf1c638dcc5065da471c415490672925f Mon Sep 17 00:00:00 2001 From: Dominik Johs Date: Fri, 10 Mar 2023 06:33:19 +0100 Subject: [PATCH] do not dismiss views when application is resumed (#605) * do not dismiss views when application is resumed * prevents the PasswordNavigationViewController and PasswordDetailTableViewController from being dismissed when the app is put to the background and then brought back to the foreground * Instead, the PasswordEntities are re-fetched from the context by their path to handle the re-creation of the entities during an update process that could have run in the background * update SwiftLint to version 0.50.* * update SwiftFormat to 0.51.* --------- Co-authored-by: Mingshen Sun --- .swiftformat | 1 - .../AdvancedSettingsTableViewController.swift | 8 ++--- .../PasswordDetailTableViewController.swift | 30 +++++++++++++++- .../PasswordEditorTableViewController.swift | 6 ++-- .../PasswordNavigationViewController.swift | 34 ++++++++++++++++--- .../CredentialProviderViewController.swift | 10 +++--- .../Controllers/ExtensionViewController.swift | 2 +- .../PasscodeLockViewController.swift | 4 +-- passKit/Models/GitCredential.swift | 6 ++-- scripts/swiftformat.sh | 2 +- 10 files changed, 77 insertions(+), 26 deletions(-) diff --git a/.swiftformat b/.swiftformat index 8d02852..88c8815 100644 --- a/.swiftformat +++ b/.swiftformat @@ -44,7 +44,6 @@ numberFormatting, \ # opaqueGenericParameters, \ # organizeDeclarations, \ - preferDouble, \ preferKeyPath, \ redundantBackticks, \ redundantBreak, \ diff --git a/pass/Controllers/AdvancedSettingsTableViewController.swift b/pass/Controllers/AdvancedSettingsTableViewController.swift index 2877d9d..dca815f 100644 --- a/pass/Controllers/AdvancedSettingsTableViewController.swift +++ b/pass/Controllers/AdvancedSettingsTableViewController.swift @@ -53,8 +53,8 @@ class AdvancedSettingsTableViewController: UITableViewController { alert.addAction( UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in SVProgressHUD.show(withStatus: "Erasing...".localize()) - self.passwordStore.erase() - self.navigationController!.popViewController(animated: true) + passwordStore.erase() + navigationController!.popViewController(animated: true) SVProgressHUD.showSuccess(withStatus: "Done".localize()) SVProgressHUD.dismiss(withDelay: 1) } @@ -67,8 +67,8 @@ class AdvancedSettingsTableViewController: UITableViewController { UIAlertAction(title: "DiscardAllLocalChanges".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in SVProgressHUD.show(withStatus: "Resetting...".localize()) do { - let numberDiscarded = try self.passwordStore.reset() - self.navigationController!.popViewController(animated: true) + let numberDiscarded = try passwordStore.reset() + navigationController!.popViewController(animated: true) SVProgressHUD.showSuccess(withStatus: "DiscardedCommits(%d)".localize(numberDiscarded)) SVProgressHUD.dismiss(withDelay: 1) } catch { diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index 4a462d3..0d115bb 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -15,13 +15,21 @@ import UIKit import YubiKit class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate, AlertPresenting { - var passwordEntity: PasswordEntity? + var passwordEntity: PasswordEntity? { + didSet { + passwordPath = passwordEntity?.path + } + } + private var password: Password? private var passwordImage: UIImage? private var oneTimePasswordIndexPath: IndexPath? private var shouldPopCurrentView = false private let passwordStore = PasswordStore.shared + // preserve path so it can be reloaded even if the passwordEntity is deleted during the update process + private var passwordPath: String? + private lazy var editUIBarButtonItem: UIBarButtonItem = { let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit)) return uiBarButtonItem @@ -74,6 +82,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni // reset the data table if the disaply settings have been changed NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPasswordSelector), name: .passwordDetailDisplaySettingChanged, object: nil) + + // 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(actOnPossiblePasswordStoreUpdate), name: UIApplication.willEnterForegroundNotification, object: nil) } override func viewDidAppear(_ animated: Bool) { @@ -526,6 +537,23 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } extension PasswordDetailTableViewController { + @objc + func actOnPossiblePasswordStoreUpdate() { + DispatchQueue.main.async { + if let path = self.passwordPath { + // reload PasswordEntity because all PasswordEntities are re-created on PasswordStore update + self.passwordEntity = PasswordStore.shared.fetchPasswordEntity(with: path) + + // dismiss if the PasswordEntity does not exist anymore + if self.passwordEntity == nil { + self.navigationController?.popToRootViewController(animated: true) + } else { + self.decryptThenShowPassword() + } + } + } + } + private func requestYubiKeyPIN(completion: @escaping (String) -> Void, cancellation: @escaping () -> Void) { let alert = UIAlertController(title: "YubiKey PIN", message: "Verify YubiKey OpenPGP PIN.", preferredStyle: .alert) alert.addAction( diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift index 2b7cf65..375271c 100644 --- a/pass/Controllers/PasswordEditorTableViewController.swift +++ b/pass/Controllers/PasswordEditorTableViewController.swift @@ -248,7 +248,7 @@ class PasswordEditorTableViewController: UITableViewController { let alert = UIAlertController(title: "DeletePassword?".localize(), message: nil, preferredStyle: UIAlertController.Style.alert) alert.addAction( UIAlertAction(title: "Delete".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in - self.performSegue(withIdentifier: "deletePasswordSegue", sender: self) + performSegue(withIdentifier: "deletePasswordSegue", sender: self) } ) alert.addAction(UIAlertAction.cancel()) @@ -442,9 +442,9 @@ extension PasswordEditorTableViewController: SFSafariViewControllerDelegate { alert.addAction( UIAlertAction(title: "Yes", style: UIAlertAction.Style.default) { [unowned self] _ in // update tableData so to make sure reloadData() works correctly - self.tableData[self.passwordSection][0][PasswordEditorCellKey.content] = generatedPassword + tableData[passwordSection][0][PasswordEditorCellKey.content] = generatedPassword // update cell manually, no need to call reloadData() - self.fillPasswordCell?.setContent(content: generatedPassword) + fillPasswordCell?.setContent(content: generatedPassword) } ) alert.addAction(UIAlertAction.cancel()) diff --git a/pass/Controllers/PasswordNavigationViewController.swift b/pass/Controllers/PasswordNavigationViewController.swift index 2030e19..af81bfa 100644 --- a/pass/Controllers/PasswordNavigationViewController.swift +++ b/pass/Controllers/PasswordNavigationViewController.swift @@ -21,7 +21,14 @@ class PasswordNavigationViewController: UIViewController { @IBOutlet var tableView: UITableView! var dataSource: PasswordNavigationDataSource? - var parentPasswordEntity: PasswordEntity? + var parentPasswordEntity: PasswordEntity? { + didSet { + parentPath = parentPasswordEntity?.path + } + } + + // preserve parent path so it can be reloaded even if the parentPasswordEntity is deleted during the update process + private var parentPath: String? var viewingUnsyncedPasswords = false var tapTabBarTime: TimeInterval = 0 @@ -181,13 +188,13 @@ class PasswordNavigationViewController: UIViewController { private func configureNotification() { let notificationCenter = NotificationCenter.default // Reset the data table if some password (maybe another one) has been updated. - notificationCenter.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordStoreUpdated, object: nil) + notificationCenter.addObserver(self, selector: #selector(actOnPossiblePasswordStoreUpdate), name: .passwordStoreUpdated, object: nil) // Reset the data table if the disaply settings have been changed. notificationCenter.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordDisplaySettingChanged, object: nil) // Search entrypoint for home screen quick action. notificationCenter.addObserver(self, selector: #selector(actOnSearchNotification), name: .passwordSearch, object: nil) // A Siri shortcut can change the state of the app in the background. Hence, reload when opening the app. - notificationCenter.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: UIApplication.willEnterForegroundNotification, object: nil) + notificationCenter.addObserver(self, selector: #selector(actOnPossiblePasswordStoreUpdate), name: UIApplication.willEnterForegroundNotification, object: nil) } @objc @@ -352,6 +359,23 @@ extension PasswordNavigationViewController { } } + @objc + func actOnPossiblePasswordStoreUpdate() { + DispatchQueue.main.async { + if let path = self.parentPath { + // reload parent because all PasswordEntities are re-created on PasswordStore update + self.parentPasswordEntity = PasswordStore.shared.fetchPasswordEntity(with: path) + + // pop to the root controller if the parent does not exist anymore + if self.parentPasswordEntity == nil { + self.navigationController?.popToRootViewController(animated: true) + } + } + + self.resetViews() + } + } + func resetViews() { configureTableView(in: parentPasswordEntity) tableView.reloadData() @@ -447,14 +471,14 @@ extension PasswordNavigationViewController: PasswordAlertPresenter { } DispatchQueue.global(qos: .userInitiated).async { [unowned self] in do { - let pullOptions = gitCredential.getCredentialOptions(passwordProvider: self.present) + let pullOptions = gitCredential.getCredentialOptions(passwordProvider: present) try PasswordStore.shared.pullRepository(options: pullOptions) { git_transfer_progress, _ in DispatchQueue.main.async { SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects) / Float(git_transfer_progress.pointee.total_objects), status: "PullingFromRemoteRepository".localize()) } } if PasswordStore.shared.numberOfLocalCommits > 0 { - let pushOptions = gitCredential.getCredentialOptions(passwordProvider: self.present) + let pushOptions = gitCredential.getCredentialOptions(passwordProvider: present) try PasswordStore.shared.pushRepository(options: pushOptions) { current, total, _, _ in DispatchQueue.main.async { SVProgressHUD.showProgress(Float(current) / Float(total), status: "PushingToRemoteRepository".localize()) diff --git a/passAutoFillExtension/Controllers/CredentialProviderViewController.swift b/passAutoFillExtension/Controllers/CredentialProviderViewController.swift index 1b3ea85..3022f5e 100644 --- a/passAutoFillExtension/Controllers/CredentialProviderViewController.swift +++ b/passAutoFillExtension/Controllers/CredentialProviderViewController.swift @@ -38,13 +38,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController { passcodelock.presentPasscodeLockIfNeeded(self) { self.view.isHidden = true } after: { [unowned self] in - self.view.isHidden = false - self.credentialProvider.identifier = serviceIdentifiers.first + view.isHidden = false + credentialProvider.identifier = serviceIdentifiers.first let url = serviceIdentifiers.first .map(\.identifier) .flatMap(URL.init) - self.passwordsViewController.navigationItem.prompt = url?.host - self.passwordsViewController.showPasswordsWithSuggestion(matching: url?.host ?? "") + passwordsViewController.navigationItem.prompt = url?.host + passwordsViewController.showPasswordsWithSuggestion(matching: url?.host ?? "") } } @@ -61,7 +61,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController { passcodelock.presentPasscodeLockIfNeeded(self) { self.view.isHidden = true } after: { [unowned self] in - self.credentialProvider.credentials(for: credentialIdentity) + credentialProvider.credentials(for: credentialIdentity) } } diff --git a/passExtension/Controllers/ExtensionViewController.swift b/passExtension/Controllers/ExtensionViewController.swift index bd18ecd..7877651 100644 --- a/passExtension/Controllers/ExtensionViewController.swift +++ b/passExtension/Controllers/ExtensionViewController.swift @@ -46,7 +46,7 @@ class ExtensionViewController: UIViewController { super.viewWillAppear(animated) prepareCredentialList() passcodelock.presentPasscodeLockIfNeeded(self, after: { [unowned self] in - self.view.isHidden = false + view.isHidden = false }) } diff --git a/passKit/Controllers/PasscodeLockViewController.swift b/passKit/Controllers/PasscodeLockViewController.swift index b641d63..1e4d20e 100644 --- a/passKit/Controllers/PasscodeLockViewController.swift +++ b/passKit/Controllers/PasscodeLockViewController.swift @@ -191,8 +191,8 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate { let myContext = LAContext() // If the device passcode is not set, reset the app. guard myContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) else { - self.passwordStore.erase() - self.passcodeLockDidSucceed() + passwordStore.erase() + passcodeLockDidSucceed() return } // If the device passcode is set, authentication is required. diff --git a/passKit/Models/GitCredential.swift b/passKit/Models/GitCredential.swift index 7ab9fcc..9534d18 100644 --- a/passKit/Models/GitCredential.swift +++ b/passKit/Models/GitCredential.swift @@ -77,14 +77,14 @@ public struct GitCredential { private func createCredentialProvider(_ passwordProvider: @escaping PasswordProvider) -> GTCredentialProvider { var attempts = 1 return GTCredentialProvider { _, _, _ -> GTCredential? in - if attempts > self.credentialType.allowedAttempts { + if attempts > credentialType.allowedAttempts { return nil } - guard let password = self.getPassword(attempts: attempts, passwordProvider: passwordProvider) else { + guard let password = getPassword(attempts: attempts, passwordProvider: passwordProvider) else { return nil } attempts += 1 - return try? self.credentialType.createGTCredential(password: password) + return try? credentialType.createGTCredential(password: password) } } diff --git a/scripts/swiftformat.sh b/scripts/swiftformat.sh index 021720b..01bb970 100755 --- a/scripts/swiftformat.sh +++ b/scripts/swiftformat.sh @@ -1,5 +1,5 @@ export PATH="/opt/homebrew/bin:/opt/homebrew/sbin${PATH+:$PATH}" -SWIFTFORMAT_VERSION="0.50.*" +SWIFTFORMAT_VERSION="0.51.*" if [[ "${CI}" == "true" ]]; then echo "Running in a Continuous Integration environment. Formatting is skipped."