From 8b4840717fc347d0492afcf24cb59a6b3c7aa0c6 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Wed, 20 Dec 2023 18:08:05 +0100 Subject: [PATCH 1/3] Update copy for Sync feature flag alerts --- DuckDuckGo/Common/Extensions/NSAlertExtension.swift | 10 ++++++++++ DuckDuckGo/Common/Localizables/UserText.swift | 3 +++ DuckDuckGo/Preferences/Model/PreferencesSection.swift | 4 +++- .../SyncUI/Views/ManagementView/SyncEnabledView.swift | 2 +- .../SyncUI/Views/ManagementView/SyncSetupView.swift | 7 ++----- .../SyncUI/Sources/SyncUI/internal/UserText.swift | 7 ++++--- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 06d86aa2fc..063ed32f36 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -221,6 +221,16 @@ extension NSAlert { return alert } + static func dataSyncingDisabledByFeatureFlag(upgradeRequired: Bool) -> NSAlert { + let alert = NSAlert() + alert.messageText = UserText.syncPausedTitle + alert.informativeText = upgradeRequired ? UserText.syncUnavailableMessageUpgradeRequired : UserText.syncUnavailableMessage + alert.alertStyle = .warning + alert.addButton(withTitle: UserText.ok) + alert.addButton(withTitle: UserText.learnMore) + return alert + } + static func customConfigurationAlert(configurationUrl: String) -> NSAlert { let alert = NSAlert() alert.messageText = "Set custom configuration URL:" diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index d4084d6d47..0eb91fe989 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -503,6 +503,9 @@ struct UserText { static let syncBookmarkPausedAlertDescription = NSLocalizedString("alert.sync-bookmarks-paused-description", value: "You have exceeded the bookmarks sync limit. Try deleting some bookmarks. Until this is resolved your bookmarks will not be backed up.", comment: "Description for alert shown when sync bookmarks paused for too many items") static let syncCredentialsPausedAlertTitle = NSLocalizedString("alert.sync-credentials-paused-title", value: "Passwords Sync is Paused", comment: "Title for alert shown when sync credentials paused for too many items") static let syncCredentialsPausedAlertDescription = NSLocalizedString("alert.sync-credentials-paused-description", value: "You have exceeded the passwords sync limit. Try deleting some passwords. Until this is resolved your passwords will not be backed up.", comment: "Description for alert shown when sync credentials paused for too many items") + static let syncPausedTitle = NSLocalizedString("alert.sync.warning.sync-paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") + static let syncUnavailableMessage = NSLocalizedString("alert.sync.warning.sync-unavailable-message", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") + static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("alert.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 defaultBrowser = NSLocalizedString("preferences.default-browser", value: "Default Browser", comment: "Show default browser preferences") static let appearance = NSLocalizedString("preferences.appearance", value: "Appearance", comment: "Show appearance preferences") static let privacy = NSLocalizedString("preferences.privacy", value: "Privacy", comment: "Show privacy browser preferences") diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index ad061c7ed0..195733ce23 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -92,6 +92,7 @@ enum PreferencePaneIdentifier: String, Equatable, Hashable, Identifiable { self.init(rawValue: path) } + @MainActor var displayName: String { switch self { case .general: @@ -99,7 +100,8 @@ enum PreferencePaneIdentifier: String, Equatable, Hashable, Identifiable { case .sync: let isSyncBookmarksPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) let isSyncCredentialsPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) - if isSyncBookmarksPaused || isSyncCredentialsPaused { + let isDataSyncingDisabled = NSApp.delegateTyped.syncService?.featureFlags.contains(.dataSyncing) == true + if isSyncBookmarksPaused || isSyncCredentialsPaused || isDataSyncingDisabled { return UserText.sync + " ⚠️" } return UserText.sync diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift index 3878a7d693..53ffb0513e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift @@ -154,7 +154,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { if model.isDataSyncingAvailable { EmptyView() } else { - SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + SyncWarningMessage(title: UserText.syncPausedTitle, message: UserText.syncUnavailableMessage) } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift index 1ea3bf81a7..133f1b4b2a 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift @@ -63,11 +63,8 @@ struct SyncSetupView: View where ViewModel: ManagementViewModel { @ViewBuilder fileprivate func syncUnavailableView() -> some View { - if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable { - SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) - .padding(.top, 16) - } else if !model.isAccountCreationAvailable { - SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningAccountCreationDisabled) + if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable || !model.isAccountCreationAvailable { + SyncWarningMessage(title: UserText.syncUnavailableTitle, message: UserText.syncUnavailableMessage) .padding(.top, 16) } else { EmptyView() diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index 2b7d2a1059..57d66f925e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -159,7 +159,8 @@ enum UserText { static let keepFaviconsUpdated = NSLocalizedString("prefrences.sync.keep-favicons-updated", value: "Keep Bookmarks Icons Updated", comment: "Title of the confirmation button for favicons fetching") // 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.sync-unavailable-message", 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") } From 043a058ac30a043033e25b97ffb43cebb552c176 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Wed, 20 Dec 2023 19:18:49 +0100 Subject: [PATCH 2/3] Display alert whenever data syncing gets disabled for a logged in user --- DuckDuckGo/Application/AppDelegate.swift | 28 +++++++++++++++++++ .../Common/Extensions/NSAlertExtension.swift | 2 +- .../Utilities/UserDefaultsWrapper.swift | 1 + .../Model/PreferencesSection.swift | 3 +- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index bf83796dcf..75d6901896 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -371,6 +371,34 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } subscribeSyncQueueToScreenLockedNotifications() + subscribeToSyncFeatureFlags(syncService) + } + + @UserDefaultsWrapper(key: .syncDidShowSyncPausedByFeatureFlagAlert, defaultValue: false) + private var syncDidShowSyncPausedByFeatureFlagAlert: Bool + + private func subscribeToSyncFeatureFlags(_ syncService: DDGSync) { + syncFeatureFlagsCancellable = syncService.featureFlagsPublisher + .dropFirst() + .map { $0.contains(.dataSyncing) } + .receive(on: DispatchQueue.main) + .sink { [weak self, weak syncService] isDataSyncingAvailable in + if isDataSyncingAvailable { + self?.syncDidShowSyncPausedByFeatureFlagAlert = false + } else if syncService?.authState == .active, self?.syncDidShowSyncPausedByFeatureFlagAlert == false { + let alert = NSAlert.dataSyncingDisabledByFeatureFlag() + let response = alert.runModal() + self?.syncDidShowSyncPausedByFeatureFlagAlert = true + + switch response { + case .alertSecondButtonReturn: + alert.window.sheetParent?.endSheet(alert.window) + WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .sync) + default: + break + } + } + } } private func subscribeSyncQueueToScreenLockedNotifications() { diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 063ed32f36..9568eec5cc 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -221,7 +221,7 @@ extension NSAlert { return alert } - static func dataSyncingDisabledByFeatureFlag(upgradeRequired: Bool) -> NSAlert { + static func dataSyncingDisabledByFeatureFlag(upgradeRequired: Bool = false) -> NSAlert { let alert = NSAlert() alert.messageText = UserText.syncPausedTitle alert.informativeText = upgradeRequired ? UserText.syncUnavailableMessageUpgradeRequired : UserText.syncUnavailableMessage diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 7bcf40d4b8..9ce0d13a89 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -176,6 +176,7 @@ public struct UserDefaultsWrapper { case syncIsEligibleForFaviconsFetcherOnboarding = "sync.is-eligible-for-favicons-fetcher-onboarding" case syncDidPresentFaviconsFetcherOnboarding = "sync.did-present-favicons-fetcher-onboarding" case syncDidMigrateToImprovedListsHandling = "sync.did-migrate-to-improved-lists-handling" + case syncDidShowSyncPausedByFeatureFlagAlert = "sync.did-show-sync-paused-by-feature-flag-alert" } enum RemovedKeys: String, CaseIterable { diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 195733ce23..4e32a5af5f 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -100,7 +100,8 @@ enum PreferencePaneIdentifier: String, Equatable, Hashable, Identifiable { case .sync: let isSyncBookmarksPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) let isSyncCredentialsPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) - let isDataSyncingDisabled = NSApp.delegateTyped.syncService?.featureFlags.contains(.dataSyncing) == true + let syncService = NSApp.delegateTyped.syncService + let isDataSyncingDisabled = syncService?.featureFlags.contains(.dataSyncing) == false && syncService?.authState == .active if isSyncBookmarksPaused || isSyncCredentialsPaused || isDataSyncingDisabled { return UserText.sync + " ⚠️" } From 0a9ecca1ac5d476586295f364a4c9047d0e4bd10 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Thu, 21 Dec 2023 13:28:41 +0100 Subject: [PATCH 3/3] Hide 'Learn More' button in the Sync paused alert when Sync UI is not visible --- DuckDuckGo/Application/AppDelegate.swift | 3 ++- DuckDuckGo/Common/Extensions/NSAlertExtension.swift | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 75d6901896..307d5f5ada 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -386,7 +386,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel if isDataSyncingAvailable { self?.syncDidShowSyncPausedByFeatureFlagAlert = false } else if syncService?.authState == .active, self?.syncDidShowSyncPausedByFeatureFlagAlert == false { - let alert = NSAlert.dataSyncingDisabledByFeatureFlag() + let isSyncUIVisible = syncService?.featureFlags.contains(.userInterface) == true + let alert = NSAlert.dataSyncingDisabledByFeatureFlag(showLearnMore: isSyncUIVisible) let response = alert.runModal() self?.syncDidShowSyncPausedByFeatureFlagAlert = true diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 9568eec5cc..628ddeddfc 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -221,13 +221,15 @@ extension NSAlert { return alert } - static func dataSyncingDisabledByFeatureFlag(upgradeRequired: Bool = false) -> NSAlert { + static func dataSyncingDisabledByFeatureFlag(showLearnMore: Bool, upgradeRequired: Bool = false) -> NSAlert { let alert = NSAlert() alert.messageText = UserText.syncPausedTitle alert.informativeText = upgradeRequired ? UserText.syncUnavailableMessageUpgradeRequired : UserText.syncUnavailableMessage alert.alertStyle = .warning alert.addButton(withTitle: UserText.ok) - alert.addButton(withTitle: UserText.learnMore) + if showLearnMore { + alert.addButton(withTitle: UserText.learnMore) + } return alert }