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 <bob@mssun.me>
This commit is contained in:
Dominik Johs 2023-03-10 06:33:19 +01:00 committed by GitHub
parent 83c6ae33dc
commit f2a0c4ccf1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 77 additions and 26 deletions

View file

@ -44,7 +44,6 @@
numberFormatting, \ numberFormatting, \
# opaqueGenericParameters, \ # opaqueGenericParameters, \
# organizeDeclarations, \ # organizeDeclarations, \
preferDouble, \
preferKeyPath, \ preferKeyPath, \
redundantBackticks, \ redundantBackticks, \
redundantBreak, \ redundantBreak, \

View file

@ -53,8 +53,8 @@ class AdvancedSettingsTableViewController: UITableViewController {
alert.addAction( alert.addAction(
UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in
SVProgressHUD.show(withStatus: "Erasing...".localize()) SVProgressHUD.show(withStatus: "Erasing...".localize())
self.passwordStore.erase() passwordStore.erase()
self.navigationController!.popViewController(animated: true) navigationController!.popViewController(animated: true)
SVProgressHUD.showSuccess(withStatus: "Done".localize()) SVProgressHUD.showSuccess(withStatus: "Done".localize())
SVProgressHUD.dismiss(withDelay: 1) SVProgressHUD.dismiss(withDelay: 1)
} }
@ -67,8 +67,8 @@ class AdvancedSettingsTableViewController: UITableViewController {
UIAlertAction(title: "DiscardAllLocalChanges".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in UIAlertAction(title: "DiscardAllLocalChanges".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in
SVProgressHUD.show(withStatus: "Resetting...".localize()) SVProgressHUD.show(withStatus: "Resetting...".localize())
do { do {
let numberDiscarded = try self.passwordStore.reset() let numberDiscarded = try passwordStore.reset()
self.navigationController!.popViewController(animated: true) navigationController!.popViewController(animated: true)
SVProgressHUD.showSuccess(withStatus: "DiscardedCommits(%d)".localize(numberDiscarded)) SVProgressHUD.showSuccess(withStatus: "DiscardedCommits(%d)".localize(numberDiscarded))
SVProgressHUD.dismiss(withDelay: 1) SVProgressHUD.dismiss(withDelay: 1)
} catch { } catch {

View file

@ -15,13 +15,21 @@ import UIKit
import YubiKit import YubiKit
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate, AlertPresenting { class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate, AlertPresenting {
var passwordEntity: PasswordEntity? var passwordEntity: PasswordEntity? {
didSet {
passwordPath = passwordEntity?.path
}
}
private var password: Password? private var password: Password?
private var passwordImage: UIImage? private var passwordImage: UIImage?
private var oneTimePasswordIndexPath: IndexPath? private var oneTimePasswordIndexPath: IndexPath?
private var shouldPopCurrentView = false private var shouldPopCurrentView = false
private let passwordStore = PasswordStore.shared 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 = { private lazy var editUIBarButtonItem: UIBarButtonItem = {
let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit)) let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit))
return uiBarButtonItem return uiBarButtonItem
@ -74,6 +82,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
// reset the data table if the disaply settings have been changed // reset the data table if the disaply settings have been changed
NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPasswordSelector), name: .passwordDetailDisplaySettingChanged, object: nil) 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) { override func viewDidAppear(_ animated: Bool) {
@ -526,6 +537,23 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
extension PasswordDetailTableViewController { 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) { private func requestYubiKeyPIN(completion: @escaping (String) -> Void, cancellation: @escaping () -> Void) {
let alert = UIAlertController(title: "YubiKey PIN", message: "Verify YubiKey OpenPGP PIN.", preferredStyle: .alert) let alert = UIAlertController(title: "YubiKey PIN", message: "Verify YubiKey OpenPGP PIN.", preferredStyle: .alert)
alert.addAction( alert.addAction(

View file

@ -248,7 +248,7 @@ class PasswordEditorTableViewController: UITableViewController {
let alert = UIAlertController(title: "DeletePassword?".localize(), message: nil, preferredStyle: UIAlertController.Style.alert) let alert = UIAlertController(title: "DeletePassword?".localize(), message: nil, preferredStyle: UIAlertController.Style.alert)
alert.addAction( alert.addAction(
UIAlertAction(title: "Delete".localize(), style: UIAlertAction.Style.destructive) { [unowned self] _ in 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()) alert.addAction(UIAlertAction.cancel())
@ -442,9 +442,9 @@ extension PasswordEditorTableViewController: SFSafariViewControllerDelegate {
alert.addAction( alert.addAction(
UIAlertAction(title: "Yes", style: UIAlertAction.Style.default) { [unowned self] _ in UIAlertAction(title: "Yes", style: UIAlertAction.Style.default) { [unowned self] _ in
// update tableData so to make sure reloadData() works correctly // 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() // update cell manually, no need to call reloadData()
self.fillPasswordCell?.setContent(content: generatedPassword) fillPasswordCell?.setContent(content: generatedPassword)
} }
) )
alert.addAction(UIAlertAction.cancel()) alert.addAction(UIAlertAction.cancel())

View file

@ -21,7 +21,14 @@ class PasswordNavigationViewController: UIViewController {
@IBOutlet var tableView: UITableView! @IBOutlet var tableView: UITableView!
var dataSource: PasswordNavigationDataSource? 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 viewingUnsyncedPasswords = false
var tapTabBarTime: TimeInterval = 0 var tapTabBarTime: TimeInterval = 0
@ -181,13 +188,13 @@ class PasswordNavigationViewController: UIViewController {
private func configureNotification() { private func configureNotification() {
let notificationCenter = NotificationCenter.default let notificationCenter = NotificationCenter.default
// Reset the data table if some password (maybe another one) has been updated. // 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. // Reset the data table if the disaply settings have been changed.
notificationCenter.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordDisplaySettingChanged, object: nil) notificationCenter.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordDisplaySettingChanged, object: nil)
// Search entrypoint for home screen quick action. // Search entrypoint for home screen quick action.
notificationCenter.addObserver(self, selector: #selector(actOnSearchNotification), name: .passwordSearch, object: nil) 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. // 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 @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() { func resetViews() {
configureTableView(in: parentPasswordEntity) configureTableView(in: parentPasswordEntity)
tableView.reloadData() tableView.reloadData()
@ -447,14 +471,14 @@ extension PasswordNavigationViewController: PasswordAlertPresenter {
} }
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do { do {
let pullOptions = gitCredential.getCredentialOptions(passwordProvider: self.present) let pullOptions = gitCredential.getCredentialOptions(passwordProvider: present)
try PasswordStore.shared.pullRepository(options: pullOptions) { git_transfer_progress, _ in try PasswordStore.shared.pullRepository(options: pullOptions) { git_transfer_progress, _ in
DispatchQueue.main.async { DispatchQueue.main.async {
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects) / Float(git_transfer_progress.pointee.total_objects), status: "PullingFromRemoteRepository".localize()) SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects) / Float(git_transfer_progress.pointee.total_objects), status: "PullingFromRemoteRepository".localize())
} }
} }
if PasswordStore.shared.numberOfLocalCommits > 0 { 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 try PasswordStore.shared.pushRepository(options: pushOptions) { current, total, _, _ in
DispatchQueue.main.async { DispatchQueue.main.async {
SVProgressHUD.showProgress(Float(current) / Float(total), status: "PushingToRemoteRepository".localize()) SVProgressHUD.showProgress(Float(current) / Float(total), status: "PushingToRemoteRepository".localize())

View file

@ -38,13 +38,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
passcodelock.presentPasscodeLockIfNeeded(self) { passcodelock.presentPasscodeLockIfNeeded(self) {
self.view.isHidden = true self.view.isHidden = true
} after: { [unowned self] in } after: { [unowned self] in
self.view.isHidden = false view.isHidden = false
self.credentialProvider.identifier = serviceIdentifiers.first credentialProvider.identifier = serviceIdentifiers.first
let url = serviceIdentifiers.first let url = serviceIdentifiers.first
.map(\.identifier) .map(\.identifier)
.flatMap(URL.init) .flatMap(URL.init)
self.passwordsViewController.navigationItem.prompt = url?.host passwordsViewController.navigationItem.prompt = url?.host
self.passwordsViewController.showPasswordsWithSuggestion(matching: url?.host ?? "") passwordsViewController.showPasswordsWithSuggestion(matching: url?.host ?? "")
} }
} }
@ -61,7 +61,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
passcodelock.presentPasscodeLockIfNeeded(self) { passcodelock.presentPasscodeLockIfNeeded(self) {
self.view.isHidden = true self.view.isHidden = true
} after: { [unowned self] in } after: { [unowned self] in
self.credentialProvider.credentials(for: credentialIdentity) credentialProvider.credentials(for: credentialIdentity)
} }
} }

View file

@ -46,7 +46,7 @@ class ExtensionViewController: UIViewController {
super.viewWillAppear(animated) super.viewWillAppear(animated)
prepareCredentialList() prepareCredentialList()
passcodelock.presentPasscodeLockIfNeeded(self, after: { [unowned self] in passcodelock.presentPasscodeLockIfNeeded(self, after: { [unowned self] in
self.view.isHidden = false view.isHidden = false
}) })
} }

View file

@ -191,8 +191,8 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
let myContext = LAContext() let myContext = LAContext()
// If the device passcode is not set, reset the app. // If the device passcode is not set, reset the app.
guard myContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) else { guard myContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) else {
self.passwordStore.erase() passwordStore.erase()
self.passcodeLockDidSucceed() passcodeLockDidSucceed()
return return
} }
// If the device passcode is set, authentication is required. // If the device passcode is set, authentication is required.

View file

@ -77,14 +77,14 @@ public struct GitCredential {
private func createCredentialProvider(_ passwordProvider: @escaping PasswordProvider) -> GTCredentialProvider { private func createCredentialProvider(_ passwordProvider: @escaping PasswordProvider) -> GTCredentialProvider {
var attempts = 1 var attempts = 1
return GTCredentialProvider { _, _, _ -> GTCredential? in return GTCredentialProvider { _, _, _ -> GTCredential? in
if attempts > self.credentialType.allowedAttempts { if attempts > credentialType.allowedAttempts {
return nil return nil
} }
guard let password = self.getPassword(attempts: attempts, passwordProvider: passwordProvider) else { guard let password = getPassword(attempts: attempts, passwordProvider: passwordProvider) else {
return nil return nil
} }
attempts += 1 attempts += 1
return try? self.credentialType.createGTCredential(password: password) return try? credentialType.createGTCredential(password: password)
} }
} }

View file

@ -1,5 +1,5 @@
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin${PATH+:$PATH}" export PATH="/opt/homebrew/bin:/opt/homebrew/sbin${PATH+:$PATH}"
SWIFTFORMAT_VERSION="0.50.*" SWIFTFORMAT_VERSION="0.51.*"
if [[ "${CI}" == "true" ]]; then if [[ "${CI}" == "true" ]]; then
echo "Running in a Continuous Integration environment. Formatting is skipped." echo "Running in a Continuous Integration environment. Formatting is skipped."