diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 96ee5fdd1b..2bdc678cb5 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -616,6 +616,66 @@ } } }, + "autofill.excluded-sites" : { + "comment" : "Autofill settings section title", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Excluded Sites" + } + } + } + }, + "autofill.excluded-sites.explanation" : { + "comment" : "Subtitle providing additional information about the excluded sites section", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Websites you selected to never ask to save your password." + } + } + } + }, + "autofill.excluded-sites.reset" : { + "comment" : "Button title allowing users to reset their list of excluded sites", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reset" + } + } + } + }, + "autofill.excluded-sites.reset.action.message" : { + "comment" : "Alert title", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "If you reset excluded sites, you will be prompted to save your password next time you sign in to any of these sites." + } + } + } + }, + "autofill.excluded-sites.reset.action.title" : { + "comment" : "Alert title", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reset Excluded Sites?" + } + } + } + }, "autofill.hide-password" : { "comment" : "Tooltip for the Autofill panel's Hide Password button", "extractionState" : "extracted_with_value", @@ -5094,6 +5154,18 @@ } } }, + "never.for.this.site" : { + "comment" : "Never ask to save login credentials for this site button", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Never Ask for This Site" + } + } + } + }, "New Tab" : { "comment" : "Main Menu File item" }, diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index e1f62308ba..efdb7eed5c 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -211,8 +211,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) } } } @@ -361,8 +360,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) } } } @@ -375,8 +373,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { managementDialogModel.endFlow() mapDevices(devices) } catch { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableToUpdateDeviceName, description: error.localizedDescription) + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToUpdateDeviceName, description: error.localizedDescription) } } } @@ -416,8 +413,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { Pixel.fire(.syncSignupDirect) presentDialog(for: .saveRecoveryCode(recoveryCode ?? "")) } catch { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableToSync, description: error.localizedDescription) + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToSyncToServer, description: error.localizedDescription) } } } @@ -442,8 +438,17 @@ extension SyncPreferences: ManagementDialogModelDelegate { } } catch { if syncService.account == nil { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableToSync, description: error.localizedDescription) + if isRecovery { + managementDialogModel.syncErrorMessage = SyncErrorMessage( + type: .unableToSyncToServer, + description: error.localizedDescription + ) + } else { + managementDialogModel.syncErrorMessage = SyncErrorMessage( + type: .unableToSyncToOtherDevice, + description: error.localizedDescription + ) + } } } } @@ -456,17 +461,19 @@ extension SyncPreferences: ManagementDialogModelDelegate { func recoverDevice(recoveryCode: String, fromRecoveryScreen: Bool) { Task { @MainActor in - do { - guard let syncCode = try? SyncCode.decodeBase64String(recoveryCode) else { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .invalidCode, description: "") - return - } - presentDialog(for: .prepareToSync) - if let recoveryKey = syncCode.recovery { - // This will error if the account already exists, we don't have good UI for this just now + guard let syncCode = try? SyncCode.decodeBase64String(recoveryCode) else { + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .invalidCode, description: "") + return + } + presentDialog(for: .prepareToSync) + if let recoveryKey = syncCode.recovery { + do { try await loginAndShowPresentedDialog(recoveryKey, isRecovery: fromRecoveryScreen) - } else if let connectKey = syncCode.connect { + } catch { + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToMergeTwoAccounts, description: "") + } + } else if let connectKey = syncCode.connect { + do { if syncService.account == nil { let device = deviceInfo() try await syncService.createAccount(deviceName: device.name, deviceType: device.type) @@ -483,16 +490,16 @@ extension SyncPreferences: ManagementDialogModelDelegate { guard let self else { return } self.presentDialog(for: .saveRecoveryCode(recoveryCode)) }.store(in: &cancellables) - // The UI will update when the devices list changes. - } else { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .invalidCode, description: "") - return + } catch { + managementDialogModel.syncErrorMessage = SyncErrorMessage( + type: .unableToSyncToOtherDevice, + description: error.localizedDescription + ) } - } catch { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableToSync, description: error.localizedDescription) + } else { + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .invalidCode, description: "") + return } } } @@ -522,8 +529,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { do { try data.writeFileWithProgress(to: location) } catch { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableCreateRecoveryPDF, description: error.localizedDescription) + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableCreateRecoveryPDF, description: error.localizedDescription) } } @@ -537,8 +543,8 @@ extension SyncPreferences: ManagementDialogModelDelegate { refreshDevices() managementDialogModel.endFlow() } catch { - managementDialogModel.syncErrorMessage - = SyncErrorMessage(type: .unableToRemoveDevice, description: error.localizedDescription) } + managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToRemoveDevice, description: error.localizedDescription) + } } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift index 4e1d1de021..e640e57d79 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift @@ -55,8 +55,9 @@ public protocol ManagementViewModel: ObservableObject { } public enum SyncErrorType { - case unableToSync - case unableToGetDevices + case unableToSyncToServer + case unableToSyncToOtherDevice + case unableToMergeTwoAccounts case unableToUpdateDeviceName case unableToTurnSyncOff case unableToDeleteData @@ -70,10 +71,12 @@ public enum SyncErrorType { var description: String { switch self { - case .unableToSync: - return UserText.unableToSyncDescription - case .unableToGetDevices: - return UserText.unableToGetDevicesDescription + case .unableToSyncToServer: + return UserText.unableToSyncToServerDescription + case .unableToSyncToOtherDevice: + return UserText.unableToSyncWithAnotherDeviceDescription + case .unableToMergeTwoAccounts: + return UserText.unableToMergeTwoAccountsDescription case .unableToUpdateDeviceName: return UserText.unableToUpdateDeviceNameDescription case .unableToTurnSyncOff: diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index 57d66f925e..20c4d6d236 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -144,15 +144,16 @@ enum UserText { static let credentialsLimitExceededDescription = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-description", value: "Logins limit exceeded. Delete some to resume syncing.", comment: "Description for sync credentials limits exceeded warning") static let bookmarksLimitExceededAction = NSLocalizedString("prefrences.sync.bookmarks-limit-exceeded-action", value: "Manage Bookmarks", comment: "Button title for sync bookmarks limits exceeded warning to manage bookmarks") static let credentialsLimitExceededAction = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-action", value: "Manage passwords...", comment: "Button title for sync credentials limits exceeded warning to manage logins") - static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", value: "Sync Error", comment: "Title for sync error alert") - static let unableToSyncDescription = NSLocalizedString("alert.unable-to-sync-description", value: "Unable to sync.", comment: "Description for unable to sync error") - static let unableToGetDevicesDescription = NSLocalizedString("alert.unable-to-get-devices-description", value: "Unable to retrieve the list of connected devices.", comment: "Description for unable to get devices error") - static let unableToUpdateDeviceNameDescription = NSLocalizedString("alert.unable-to-update-device-name-description", value: "Unable to update the name of the device.", comment: "Description for unable to update device name error") - 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") + static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", value: "Sync & Backup Error", comment: "Title for sync error alert") + static let unableToSyncToServerDescription = NSLocalizedString("alert.unable-to-sync-to-server-description", value: "Unable to connect to the server.", comment: "Description for unable to sync to server error") + static let unableToSyncWithAnotherDeviceDescription = NSLocalizedString("alert.unable-to-sync-with-another-device-description", value: "Unable to Sync with another device.", comment: "Description for unable to sync with another device error") + static let unableToMergeTwoAccountsDescription = NSLocalizedString("alert.unable-to-merge-two-accounts-description", value: "To pair these devices, turn off Sync & Backup on one device then tap \"Sync with Another Device\" on the other device.", comment: "Description for unable to merge two accounts error") + static let unableToUpdateDeviceNameDescription = NSLocalizedString("alert.unable-to-update-device-name-description", value: "Unable to update the device name.", comment: "Description for unable to update device name error") + static let unableToTurnSyncOffDescription = NSLocalizedString("alert.unable-to-turn-sync-off-description", value: "Unable to turn Sync & Backup off.", comment: "Description for unable to turn sync off error") 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") - 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 invalidCodeDescription = NSLocalizedString("alert.invalid-code-description", value: "The code used is invalid.", comment: "Description for invalid code error") - static let unableCreateRecoveryPdfDescription = NSLocalizedString("alert.unable-to-create-recovery-pdf-description", value: "There was a problem creating the recovery PDF.", comment: "Description for unable to create recovery pdf error") + static let unableToRemoveDeviceDescription = NSLocalizedString("alert.unable-to-remove-device-description", value: "Unable to remove this device from Sync & Backup.", comment: "Description for unable to remove device error") + static let invalidCodeDescription = NSLocalizedString("alert.invalid-code-description", value: "Sorry, this code is invalid. Please make sure it was entered correctly.", comment: "Description for invalid code error") + static let unableCreateRecoveryPdfDescription = NSLocalizedString("alert.unable-to-create-recovery-pdf-description", value: "Unable to create the recovery PDF.", comment: "Description for unable to create recovery pdf error") static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog") static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog")