diff --git a/TwoFAS/Common/Sources/Extensions/Notifications+.swift b/TwoFAS/Common/Sources/Extensions/Notifications+.swift index a0cd0092..75d537ea 100644 --- a/TwoFAS/Common/Sources/Extensions/Notifications+.swift +++ b/TwoFAS/Common/Sources/Extensions/Notifications+.swift @@ -22,6 +22,8 @@ import Foundation public extension Notification.Name { static let servicesWereUpdated = Notification.Name("servicesWereUpdatedNotification") static let sectionsWereUpdated = Notification.Name("sectionsWereUpdatedNotification") + static let syncCompletedSuccessfuly = Notification.Name("syncCompletedSuccessfuly") + static let clearSyncCompletedSuccessfuly = Notification.Name("clearSyncCompletedSuccessfuly") } public extension Notification { diff --git a/TwoFAS/Data/Interactors/CloudBackupStateInteractor.swift b/TwoFAS/Data/Interactors/CloudBackupStateInteractor.swift index e961c5aa..b87b6689 100644 --- a/TwoFAS/Data/Interactors/CloudBackupStateInteractor.swift +++ b/TwoFAS/Data/Interactors/CloudBackupStateInteractor.swift @@ -42,6 +42,10 @@ public protocol CloudBackupStateInteracting: AnyObject { func clearBackup() func synchronizeBackup() + + var successSyncDate: Date? { get } + func saveSuccessSyncDate() + func clearSavesuccessSync() } /// Use one instance per use case @@ -80,6 +84,10 @@ extension CloudBackupStateInteractor: CloudBackupStateInteracting { var isBackupEnabled: Bool { isEnabled } var isBackupAvailable: Bool { isAvailable } + var successSyncDate: Date? { + mainRepository.successSyncDate + } + func startMonitoring() { Log("CloudBackupStateInteractor - start monitoring, listenerID: \(listenerID)", module: .interactor) saveStates() @@ -138,6 +146,16 @@ extension CloudBackupStateInteractor: CloudBackupStateInteracting { Log("CloudBackupStateInteractor - synchronizeBackup", module: .interactor) mainRepository.synchronizeBackup() } + + func saveSuccessSyncDate() { + Log("CloudBackupStateInteractor - saveSuccessSync", module: .interactor) + mainRepository.saveSuccessSyncDate(Date()) + } + + func clearSavesuccessSync() { + Log("CloudBackupStateInteractor - clearSavesuccessSync", module: .interactor) + mainRepository.saveSuccessSyncDate(nil) + } } private extension CloudBackupStateInteractor { diff --git a/TwoFAS/Data/MainRepository/MainRepository.swift b/TwoFAS/Data/MainRepository/MainRepository.swift index 2dcbc23c..f4519986 100644 --- a/TwoFAS/Data/MainRepository/MainRepository.swift +++ b/TwoFAS/Data/MainRepository/MainRepository.swift @@ -122,6 +122,7 @@ protocol MainRepository: AnyObject { // MARK: - Cloud var secretSyncError: ((String) -> Void)? { get set } var isCloudBackupConnected: Bool { get } + var successSyncDate: Date? { get } var cloudCurrentState: CloudState { get } func registerForCloudStateChanges(_ listener: @escaping CloudStateListener, id: CloudStateListenerID) func unregisterForCloudStageChanges(with id: CloudStateListenerID) @@ -133,6 +134,7 @@ protocol MainRepository: AnyObject { userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void ) + func saveSuccessSyncDate(_ date: Date?) // MARK: - Import var fileURL: URL? { get set } diff --git a/TwoFAS/Data/MainRepository/MainRepositoryImpl+Cloud.swift b/TwoFAS/Data/MainRepository/MainRepositoryImpl+Cloud.swift index 458bf648..dec37178 100644 --- a/TwoFAS/Data/MainRepository/MainRepositoryImpl+Cloud.swift +++ b/TwoFAS/Data/MainRepository/MainRepositoryImpl+Cloud.swift @@ -44,6 +44,10 @@ public enum CloudState: Equatable { } extension MainRepositoryImpl { + var successSyncDate: Date? { + userDefaultsRepository.successSyncDate + } + var secretSyncError: ((String) -> Void)? { get { cloudHandler.secretSyncError @@ -89,6 +93,10 @@ extension MainRepositoryImpl { ) { SyncInstance.didReceiveRemoteNotification(userInfo: userInfo, fetchCompletionHandler: completionHandler) } + + func saveSuccessSyncDate(_ date: Date?) { + userDefaultsRepository.saveSuccessSyncDate(date) + } } private extension MainRepositoryImpl { diff --git a/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepository.swift b/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepository.swift index 73aa5093..fa0c73aa 100644 --- a/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepository.swift +++ b/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepository.swift @@ -97,4 +97,7 @@ protocol UserDefaultsRepository: AnyObject { var exchangeToken: String? { get } func setExchangeToken(_ key: String) func clearExchangeToken() + + var successSyncDate: Date? { get } + func saveSuccessSyncDate(_ date: Date?) } diff --git a/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepositoryImpl.swift b/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepositoryImpl.swift index 620e671c..b22b38a5 100644 --- a/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepositoryImpl.swift +++ b/TwoFAS/Data/MainRepository/UserDefaults/UserDefaultsRepositoryImpl.swift @@ -51,6 +51,7 @@ final class UserDefaultsRepositoryImpl: UserDefaultsRepository { case mainMenuPortraitCollapsed case mainMenuLandscapeCollapsed case dateOfFirstRun + case syncSuccessDate } private let userDefaults = UserDefaults() private let sharedDefaults = UserDefaults(suiteName: Config.suiteName)! @@ -279,6 +280,16 @@ final class UserDefaultsRepositoryImpl: UserDefaultsRepository { userDefaults.bool(forKey: Keys.introductionWasShown.rawValue) } + // MARK: - Sync success + + var successSyncDate: Date? { + userDefaults.object(forKey: Keys.syncSuccessDate.rawValue) as? Date + } + func saveSuccessSyncDate(_ date: Date?) { + userDefaults.set(date, forKey: Keys.syncSuccessDate.rawValue) + userDefaults.synchronize() + } + // MARK: - View Path func clearViewPath() { diff --git a/TwoFAS/Sync/CloudHandler.swift b/TwoFAS/Sync/CloudHandler.swift index 4a82fac9..811a6eae 100644 --- a/TwoFAS/Sync/CloudHandler.swift +++ b/TwoFAS/Sync/CloudHandler.swift @@ -85,6 +85,8 @@ final class CloudHandler: CloudHandlerType { private let itemHandlerMigrationProxy: ItemHandlerMigrationProxy private let cloudKit: CloudKit + private let notificationCenter = NotificationCenter.default + private var isClearing = false private var listeners: [String: CloudHandlerStateListener] = [:] @@ -313,6 +315,7 @@ final class CloudHandler: CloudHandlerType { private func setDisabled() { Log("Cloud Handler - Set Disabled", module: .cloudSync) ConstStorage.cloudEnabled = false + notificationCenter.post(name: .clearSyncCompletedSuccessfuly, object: nil) } private var isEnabled: Bool { ConstStorage.cloudEnabled } @@ -344,6 +347,8 @@ final class CloudHandler: CloudHandlerType { if isClearing { clearBackup() + } else { + notificationCenter.post(name: .syncCompletedSuccessfuly, object: nil) } } diff --git a/TwoFAS/TwoFAS/Root/Modules/Main/Interactor/MainModuleInteractor.swift b/TwoFAS/TwoFAS/Root/Modules/Main/Interactor/MainModuleInteractor.swift index abbea9b6..d5a3f581 100644 --- a/TwoFAS/TwoFAS/Root/Modules/Main/Interactor/MainModuleInteractor.swift +++ b/TwoFAS/TwoFAS/Root/Modules/Main/Interactor/MainModuleInteractor.swift @@ -32,6 +32,8 @@ protocol MainModuleInteracting: AnyObject { func clearImportedFileURL() func savePIN(_ PIN: String, ofType pinType: PINType) + func saveSuccessSync() + func clearSavesuccessSync() // MARK: - New app version func checkForNewAppVersion(completion: @escaping (URL?) -> Void) @@ -111,6 +113,14 @@ extension MainModuleInteractor: MainModuleInteracting { func savePIN(_ PIN: String, ofType pinType: PINType) { protectionInteractor.savePIN(PIN, typeOfPIN: pinType) } + + func saveSuccessSync() { + cloudBackupStateInteractor.saveSuccessSyncDate() + } + + func clearSavesuccessSync() { + cloudBackupStateInteractor.clearSavesuccessSync() + } // MARK: - New app version diff --git a/TwoFAS/TwoFAS/Root/Modules/Main/Presenter/MainPresenter.swift b/TwoFAS/TwoFAS/Root/Modules/Main/Presenter/MainPresenter.swift index 17c77c43..6b22a58a 100644 --- a/TwoFAS/TwoFAS/Root/Modules/Main/Presenter/MainPresenter.swift +++ b/TwoFAS/TwoFAS/Root/Modules/Main/Presenter/MainPresenter.swift @@ -84,6 +84,14 @@ final class MainPresenter { viewIsVisible() } + func handleSyncCompletedSuccessfuly() { + interactor.saveSuccessSync() + } + + func handleClearSyncCompletedSuccessfuly() { + interactor.clearSavesuccessSync() + } + func handleSavePIN(_ PIN: String, pinType: PINType) { interactor.savePIN(PIN, ofType: pinType) } diff --git a/TwoFAS/TwoFAS/Root/Modules/Main/View/MainViewController.swift b/TwoFAS/TwoFAS/Root/Modules/Main/View/MainViewController.swift index 4aa08151..70ad9915 100644 --- a/TwoFAS/TwoFAS/Root/Modules/Main/View/MainViewController.swift +++ b/TwoFAS/TwoFAS/Root/Modules/Main/View/MainViewController.swift @@ -30,6 +30,7 @@ final class MainViewController: UIViewController { var presenter: MainPresenter! private let settingsEventController = SettingsEventController() + private let notificationCenter = NotificationCenter.default var splitView: MainSplitViewController? @@ -48,66 +49,78 @@ final class MainViewController: UIViewController { } deinit { - NotificationCenter.default.removeObserver(self) + notificationCenter.removeObserver(self) } } extension MainViewController { private func setupEvents() { - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(refreshAuthList), name: .pushNotificationRefreshAuthList, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(refreshAuthList), name: UIApplication.didBecomeActiveNotification, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(clearAuthList), name: UIApplication.willResignActiveNotification, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(authorizeFromApp), name: .pushNotificationAuthorizeFromApp, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(switchToSetupPIN), name: .switchToSetupPIN, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(switchToBrowserExtension), name: .switchToBrowserExtension, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(fileAwaitsOpening), name: .fileAwaitsOpening, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(tokensVisible), name: .tokensScreenIsVisible, object: nil ) - NotificationCenter.default.addObserver( + notificationCenter.addObserver( self, selector: #selector(tokensVisible), name: .userLoggedIn, object: nil ) + notificationCenter.addObserver( + self, + selector: #selector(syncCompletedSuccessfuly), + name: .syncCompletedSuccessfuly, + object: nil + ) + notificationCenter.addObserver( + self, + selector: #selector(clearSyncCompletedSuccessfuly), + name: .clearSyncCompletedSuccessfuly, + object: nil + ) } @objc @@ -151,6 +164,16 @@ extension MainViewController { private func tokensVisible() { presenter.handleViewIsVisible() } + + @objc + private func syncCompletedSuccessfuly() { + presenter.handleSyncCompletedSuccessfuly() + } + + @objc + private func clearSyncCompletedSuccessfuly() { + presenter.handleClearSyncCompletedSuccessfuly() + } } extension MainViewController: MainViewControlling { diff --git a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Interactor/BackupMenuModuleInteractor.swift b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Interactor/BackupMenuModuleInteractor.swift index 82da2d16..78f187f2 100644 --- a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Interactor/BackupMenuModuleInteractor.swift +++ b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Interactor/BackupMenuModuleInteractor.swift @@ -35,6 +35,8 @@ protocol BackupMenuModuleInteracting: AnyObject { func toggleBackup() func clearBackup() + + var syncSuccessDate: Date? { get } } final class BackupMenuModuleInteractor { @@ -102,6 +104,10 @@ extension BackupMenuModuleInteractor: BackupMenuModuleInteracting { func clearBackup() { cloudBackup.clearBackup() } + + var syncSuccessDate: Date? { + cloudBackup.successSyncDate + } } private extension BackupMenuModuleInteractor { diff --git a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter+Menu.swift b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter+Menu.swift index e0ee3ee0..173c3b6a 100644 --- a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter+Menu.swift +++ b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter+Menu.swift @@ -21,6 +21,15 @@ import Foundation extension BackupMenuPresenter { func buildMenu() -> [BackupMenuSection] { + var footer = T.Backup.sectionDescription + let dateStr: String = { + if let date = interactor.syncSuccessDate { + return dateFormatter.string(from: date) + } + return "-" + }() + footer.append("\n\n\(T.backupSettingsSyncTitle): \(dateStr)") + let cloudBackup = BackupMenuSection( title: T.Backup.cloudBackup, cells: [ @@ -34,7 +43,7 @@ extension BackupMenuPresenter { ) ) ], - footer: T.Backup.sectionDescription + footer: footer ) let exportEnabled = interactor.exportEnabled && interactor.isBackupAllowed diff --git a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter.swift b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter.swift index 206dca6b..099ab36d 100644 --- a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter.swift +++ b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/Presenter/BackupMenuPresenter.swift @@ -23,6 +23,12 @@ final class BackupMenuPresenter { weak var view: BackupMenuViewControlling? private let flowController: BackupMenuFlowControlling + var dateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .short + dateFormatter.timeStyle = .short + return dateFormatter + }() let interactor: BackupMenuModuleInteracting init(flowController: BackupMenuFlowControlling, interactor: BackupMenuModuleInteracting) { @@ -30,7 +36,7 @@ final class BackupMenuPresenter { self.interactor = interactor interactor.reload = { [weak self] in self?.reload() } } - + func viewWillAppear() { interactor.startMonitoring() reload() @@ -74,6 +80,10 @@ final class BackupMenuPresenter { func handleBecomeActive() { reload() } + + func handleSyncSuccessDateUpdate() { + reload() + } } private extension BackupMenuPresenter { diff --git a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/View/BackupMenuViewController.swift b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/View/BackupMenuViewController.swift index 9aebf64d..bce93338 100644 --- a/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/View/BackupMenuViewController.swift +++ b/TwoFAS/TwoFAS/Root/Modules/Settings/Backup/BackupMenu/View/BackupMenuViewController.swift @@ -98,6 +98,12 @@ final class BackupMenuViewController: UIViewController { name: .refreshTabContent, object: nil ) + NotificationCenter.default.addObserver( + self, + selector: #selector(updateSyncSuccDate), + name: .syncCompletedSuccessfuly, + object: nil + ) presenter.viewWillAppear() } @@ -113,6 +119,11 @@ final class BackupMenuViewController: UIViewController { presenter.handleBecomeActive() } + @objc + private func updateSyncSuccDate() { + presenter.handleSyncSuccessDateUpdate() + } + private func setupConstraints() { switch traitCollection.horizontalSizeClass { case .regular: