diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index e5316c4e73..ae2dfa9071 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -106,6 +106,7 @@ public struct UserDefaultsWrapper { case syncIsEligibleForFaviconsFetcherOnboarding = "com.duckduckgo.ios.sync-is-eligible-for-favicons-fetcher-onboarding" case syncDidPresentFaviconsFetcherOnboarding = "com.duckduckgo.ios.sync-did-present-favicons-fetcher-onboarding" case syncDidMigrateToImprovedListsHandling = "com.duckduckgo.ios.sync-did-migrate-to-improved-lists-handling" + case syncDidShowSyncPausedByFeatureFlagAlert = "com.duckduckgo.ios.sync-did-show-sync-paused-by-feature-flag-alert" case networkProtectionDebugOptionAlwaysOnDisabled = "com.duckduckgo.network-protection.always-on.disabled" case networkProtectionWaitlistTermsAndConditionsAccepted = "com.duckduckgo.ios.vpn.terms-and-conditions-accepted" diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 5aaf4871fe..a3b7a7f914 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -89,8 +89,13 @@ class MainViewController: UIViewController { private var favoritesViewModel: FavoritesListInteracting let syncService: DDGSyncing let syncDataProviders: SyncDataProviders + + @UserDefaultsWrapper(key: .syncDidShowSyncPausedByFeatureFlagAlert, defaultValue: false) + private var syncDidShowSyncPausedByFeatureFlagAlert: Bool + private var localUpdatesCancellable: AnyCancellable? private var syncUpdatesCancellable: AnyCancellable? + private var syncFeatureFlagsCancellable: AnyCancellable? private var favoritesDisplayModeCancellable: AnyCancellable? private var emailCancellables = Set() @@ -349,6 +354,21 @@ class MainViewController: UIViewController { selector: #selector(showSyncPausedError), name: SyncCredentialsAdapter.credentialsSyncLimitReached, object: nil) + syncFeatureFlagsCancellable = syncService.featureFlagsPublisher + .dropFirst() + .map { $0.contains(.dataSyncing) } + .receive(on: DispatchQueue.main) + .sink { [weak self] isDataSyncingAvailable in + guard let self else { + return + } + if isDataSyncingAvailable { + self.syncDidShowSyncPausedByFeatureFlagAlert = false + } else if self.syncService.authState == .active, !self.syncDidShowSyncPausedByFeatureFlagAlert { + self.showSyncPausedByFeatureFlagAlert() + self.syncDidShowSyncPausedByFeatureFlagAlert = true + } + } } @objc private func showSyncPausedError(_ notification: Notification) { @@ -378,6 +398,26 @@ class MainViewController: UIViewController { } } + private func showSyncPausedByFeatureFlagAlert(upgradeRequired: Bool = false) { + let title = UserText.syncPausedTitle + let description = upgradeRequired ? UserText.syncUnavailableMessageUpgradeRequired : UserText.syncUnavailableMessage + if self.presentedViewController is SyncSettingsViewController { + return + } + self.presentedViewController?.dismiss(animated: true) + let alert = UIAlertController(title: title, + message: description, + preferredStyle: .alert) + if syncService.featureFlags.contains(.userInterface) { + let learnMoreAction = UIAlertAction(title: UserText.syncPausedAlertLearnMoreButton, style: .default) { _ in + self.segueToSettingsSync() + } + alert.addAction(learnMoreAction) + } + alert.addAction(UIAlertAction(title: UserText.syncPausedAlertOkButton, style: .cancel)) + self.present(alert, animated: true) + } + func registerForSettingsChangeNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(onAddressBarPositionChanged), diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 81b82048a0..be726a9d4b 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -113,7 +113,7 @@ class SettingsViewController: UITableViewController { } private var shouldShowSyncCell: Bool { - return syncService.featureFlags.contains(.userInterface) || internalUserDecider.isInternalUser + return syncService.featureFlags.contains(.userInterface) } private var shouldShowTextSizeCell: Bool { @@ -260,7 +260,8 @@ class SettingsViewController: UITableViewController { private func configureSyncCell() { syncCell.textLabel?.text = "Sync & Backup" - if SyncBookmarksAdapter.isSyncBookmarksPaused || SyncCredentialsAdapter.isSyncCredentialsPaused { + let isDataSyncingDisabled = !syncService.featureFlags.contains(.dataSyncing) && syncService.authState == .active + if SyncBookmarksAdapter.isSyncBookmarksPaused || SyncCredentialsAdapter.isSyncCredentialsPaused || isDataSyncingDisabled { syncCell.textLabel?.text = "⚠️ " + "Sync & Backup" } syncCell.isHidden = !shouldShowSyncCell diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 8cb02086b2..88f5f98999 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -867,6 +867,9 @@ But if you *do* want a peek under the hood, you can find more information about public static let unableToTurnSyncOffDescription = NSLocalizedString("alert.unable-to-turn-sync-off-description", value: "Unable to turn sync off.", comment: "Description for unable to turn sync off error") public static let unableToDeleteDataDescription = NSLocalizedString("alert.unable-to-delete-data-description", value: "Unable to delete data on the server.", comment: "Description for unable to delete data error") public static let unableToRemoveDeviceDescription = NSLocalizedString("alert.unable-to-remove-device-description", value: "Unable to remove the specified device from the synchronized devices.", comment: "Description for unable to remove device error") + static let syncPausedTitle = NSLocalizedString("sync.warning.sync.paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") + static let syncUnavailableMessage = NSLocalizedString("sync.warning.data.syncing.disabled", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") + static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("sync.warning.data.syncing.disabled.upgrade.required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message") static let preemptiveCrashTitle = NSLocalizedString("error.preemptive-crash.title", value: "App issue detected", comment: "Alert title") static let preemptiveCrashBody = NSLocalizedString("error.preemptive-crash.body", value: "Looks like there's an issue with the app and it needs to close. Please reopen to continue.", comment: "Alert message") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 12865874a9..692c00ba01 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1771,6 +1771,15 @@ But if you *do* want a peek under the hood, you can find more information about /* No comment provided by engineer. */ "sync.remove-device.message" = "\"%@\" will no longer be able to access your synced data."; +/* Data syncing unavailable warning message */ +"sync.warning.data.syncing.disabled" = "Sorry, but Sync & Backup is currently unavailable. Please try again later."; + +/* Data syncing unavailable warning message */ +"sync.warning.data.syncing.disabled.upgrade.required" = "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue."; + +/* Title of the warning message */ +"sync.warning.sync.paused" = "Sync & Backup is Paused"; + /* Accessibility label on remove button */ "tab.close.home" = "Close home tab"; diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift index 591a324fdd..9c6ffcd586 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift @@ -160,9 +160,10 @@ struct UserText { static let fetchFaviconsOnboardingButtonTitle = NSLocalizedString("fetch.favicons.onboarding.button.title", value: "Keep Bookmarks Icons Updated", comment: "Fetch Favicons Onboarding - Button Title") // Sync Feature Flags - static let serviceUnavailable = NSLocalizedString("sync.warning.service.unavailable", value: "Service Unavailable", comment: "Title of the warning message") - static let warningSyncDisabled = NSLocalizedString("sync.warning.sync.disabled", value: "We apologize, but the service is currently unavailable. Please try again later.", comment: "Sync unavailable warning message") - static let warningAccountCreationDisabled = NSLocalizedString("sync.warning.account.creation.disabled", value: "We apologize, but new account creation is currently unavailable for this service. Please try again later.", comment: "Sync unavailable warning message") + static let syncUnavailableTitle = NSLocalizedString("sync.warning.sync.unavailable", value: "Sync & Backup is Unavailable", comment: "Title of the warning message") + static let syncPausedTitle = NSLocalizedString("sync.warning.sync.paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") + static let syncUnavailableMessage = NSLocalizedString("sync.warning.data.syncing.disabled", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") + static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("sync.warning.data.syncing.disabled.upgrade.required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message") // swiftlint:enable line_length } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift index d796e39d20..c941ca8b0f 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift @@ -100,10 +100,8 @@ extension SyncSettingsView { @ViewBuilder fileprivate func syncUnavailableViewWhileLoggedOut() -> some View { - if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable { - SyncWarningMessageView(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) - } else if !model.isAccountCreationAvailable { - SyncWarningMessageView(title: UserText.serviceUnavailable, message: UserText.warningAccountCreationDisabled) + if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable || !model.isAccountCreationAvailable { + SyncWarningMessageView(title: UserText.syncUnavailableTitle, message: UserText.syncUnavailableMessage) } else { EmptyView() } @@ -182,7 +180,7 @@ extension SyncSettingsView { if model.isDataSyncingAvailable { EmptyView() } else { - SyncWarningMessageView(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + SyncWarningMessageView(title: UserText.syncPausedTitle, message: UserText.syncUnavailableMessage) } }