diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 8e6f1c3beb..5ba31be300 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -682,6 +682,13 @@ extension Pixel { case syncSecureStorageDecodingError case syncAccountRemoved(reason: String) + case syncAskUserToSwitchAccount + case syncUserAcceptedSwitchingAccount + case syncUserCancelledSwitchingAccount + case syncUserSwitchedAccount + case syncUserSwitchedLogoutError + case syncUserSwitchedLoginError + case syncGetOtherDevices case syncGetOtherDevicesCopy case syncGetOtherDevicesShare @@ -1620,6 +1627,13 @@ extension Pixel.Event { case .syncSecureStorageDecodingError: return "sync_secure_storage_decoding_error" case .syncAccountRemoved(let reason): return "sync_account_removed_reason_\(reason)" + case .syncAskUserToSwitchAccount: return "sync_ask_user_to_switch_account" + case .syncUserAcceptedSwitchingAccount: return "sync_user_accepted_switching_account" + case .syncUserCancelledSwitchingAccount: return "sync_user_cancelled_switching_account" + case .syncUserSwitchedAccount: return "sync_user_switched_account" + case .syncUserSwitchedLogoutError: return "sync_user_switched_logout_error" + case .syncUserSwitchedLoginError: return "sync_user_switched_login_error" + case .syncGetOtherDevices: return "sync_get_other_devices" case .syncGetOtherDevicesCopy: return "sync_get_other_devices_copy" case .syncGetOtherDevicesShare: return "sync_get_other_devices_share" diff --git a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift index 23bb29a134..6c6343bfe2 100644 --- a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift +++ b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift @@ -182,6 +182,45 @@ extension SyncSettingsViewController: SyncManagementViewModelDelegate { } } + @MainActor + func promptToSwitchAccounts(recoveryKey: SyncCode.RecoveryKey) { + let alertController = UIAlertController( + title: UserText.syncAlertSwitchAccountTitle, + message: UserText.syncAlertSwitchAccountMessage, + preferredStyle: .alert) + alertController.addAction(title: UserText.syncAlertSwitchAccountButton, style: .default) { [weak self] in + Task { + Pixel.fire(pixel: .syncUserAcceptedSwitchingAccount) + await self?.switchAccounts(recoveryKey: recoveryKey) + } + } + alertController.addAction(title: UserText.actionCancel, style: .cancel) { + Pixel.fire(pixel: .syncUserCancelledSwitchingAccount) + } + // Gives time to the is syncing view to appear + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + self?.dismissPresentedViewController { [weak self] in + self?.present(alertController, animated: true, completion: nil) + Pixel.fire(pixel: .syncAskUserToSwitchAccount) + } + } + } + + func switchAccounts(recoveryKey: SyncCode.RecoveryKey) async { + do { + try await syncService.disconnect() + } catch { + Pixel.fire(pixel: .syncUserSwitchedLogoutError) + } + + do { + try await loginAndShowDeviceConnected(recoveryKey: recoveryKey) + } catch { + Pixel.fire(pixel: .syncUserSwitchedLoginError) + } + Pixel.fire(pixel: .syncUserSwitchedAccount) + } + private func getErrorType(from errorString: String?) -> AsyncErrorType? { guard let errorString = errorString else { return nil diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index 925b291897..6927fd10d8 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -361,7 +361,7 @@ extension SyncSettingsViewController: ScanOrPasteCodeViewModelDelegate { return true } catch { if self.rootView.model.isSyncEnabled { - handleError(.unableToMergeTwoAccounts, error: error, event: .syncLoginExistingAccountError) + await handleTwoSyncAccountsFoundDuringRecovery(recoveryKey) } else { handleError(.unableToSyncToServer, error: error, event: .syncLoginError) } @@ -402,6 +402,14 @@ extension SyncSettingsViewController: ScanOrPasteCodeViewModelDelegate { return false } + private func handleTwoSyncAccountsFoundDuringRecovery(_ recoveryKey: SyncCode.RecoveryKey) async { + if rootView.model.devices.count > 1 { + promptToSwitchAccounts(recoveryKey: recoveryKey) + } else { + await switchAccounts(recoveryKey: recoveryKey) + } + } + func dismissVCAndShowRecoveryPDF() { self.navigationController?.topViewController?.dismiss(animated: true, completion: self.showRecoveryPDF) } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 86a2337975..9e19eef81c 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -961,6 +961,9 @@ But if you *do* want a peek under the hood, you can find more information about static let unknownErrorTryAgainMessage = NSLocalizedString("error.unknown.try.again", value: "An unknown error has occurred", comment: "Generic error message on a dialog for when the cause is not known.") public static let syncPausedAlertOkButton = NSLocalizedString("alert.sync-paused-alert-ok-button", value: "OK", comment: "Confirmation button in alert") public static let syncPausedAlertLearnMoreButton = NSLocalizedString("alert.sync-paused-alert-learn-more-button", value: "Learn More", comment: "Learn more button in alert") + public static let syncAlertSwitchAccountTitle = NSLocalizedString("alert.sync-switch-account-title", value: "Switch to a different Sync?", comment: "Switch account title in alert") + public static let syncAlertSwitchAccountMessage = NSLocalizedString("alert.sync-switch-account-message", value: "This device is already synced, are you sure you want to sync it with a different backup or device? Switching won't remove any data already synced to this device.", comment: "Description for switching sync accounts when there's two") + public static let syncAlertSwitchAccountButton = NSLocalizedString("alert.sync-switch-account-button", value: "Switch Sync", comment: "Switch account button in alert") public static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", value: "Sync & Backup Error", comment: "Title for sync error alert") public 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") public static let unableToSyncWithOtherDeviceDescription = NSLocalizedString("alert.unable-to-sync-with-other-device-description", value: "Unable to Sync with another device.", comment: "Description for unable to sync with another device error") diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index cd11e8d970..3fc146e9d8 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Синхронизирането е на пауза"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Превключване на синхронизирането"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Това устройство вече е синхронизирано, сигурни ли сте, че искате да го синхронизирате с друго резервно копие или устройство? Превключването няма да изтрие данните, които вече са синхронизирани с това устройство."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Превключване към друго синхронизиране?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup временно не е налична."; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 7676ec73c4..171dcadf69 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synchronizace je pozastavená"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Přepnout synchronizaci"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Tohle zařízení už je synchronizované. Opravdu ho chceš synchronizovat s jinou zálohou nebo zařízením? Přepnutím se nesmažou žádná data, která už s tímto zařízením byla synchronizována."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Přepnout synchronizaci?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Funkce Sync & Backup je dočasně nedostupná."; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 20065306df..630cfd0826 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synkronisering er sat på pause"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Skift synkronisering"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Denne enhed er allerede synkroniseret. Er du sikker på, at du vil synkronisere den med en anden sikkerhedskopi eller enhed? Et skifte fjerner ikke data, der allerede er synkroniseret til denne enhed."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Skift til en anden synkronisering?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup er midlertidigt utilgængelig."; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 6d6b3fe1b7..1b4fa26626 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synchronisierung angehalten"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Synchronisierung wechseln"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Dieses Gerät ist bereits synchronisiert. Bist du sicher, dass du es mit einem anderen Back-up oder Gerät synchronisieren möchtest? Beim Wechsel werden keine bereits mit diesem Gerät synchronisierten Daten entfernt."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Zu einer anderen Synchronisierung wechseln?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup ist vorübergehend nicht verfügbar."; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index ab107a549f..42af0ad06a 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Ο συγχρονισμός έχει τεθεί σε παύση"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Αλλαγή συγχρονισμού"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Η συσκευή αυτή είναι ήδη συγχρονισμένη. Θέλετε σίγουρα να τη συγχρονίσετε με ένα διαφορετικό αντίγραφο ασφαλείας ή συσκευή; Η αλλαγή δεν θα αφαιρέσει δεδομένα που έχουν ήδη συγχρονιστεί σε αυτήν τη συσκευή."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Αλλαγή σε διαφορετικό συγχρονισμό;"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Το Sync & Backup είναι προσωρινά μη διαθέσιμο."; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index d1bf6015ea..1296b3253b 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -200,6 +200,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sync is Paused"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Switch Sync"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "This device is already synced, are you sure you want to sync it with a different backup or device? Switching won't remove any data already synced to this device."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Switch to a different Sync?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup is temporarily unavailable."; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index 832c808345..a6708c70ae 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "La sincronización está en pausa"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Cambiar sincronización"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Este dispositivo ya está sincronizado. ¿Seguro que deseas sincronizarlo con una copia de seguridad o con un dispositivo diferente? Cambiar no eliminará ningún dato ya sincronizado en este dispositivo."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "¿Cambiar a una sincronización diferente?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "La sincronización y la copia de seguridad no están disponibles temporalmente."; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index debc2d270a..7ec238ad1d 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sünkroonimine on peatatud"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Synci lülitamine"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "See seade on juba sünkroonitud, kas soovid kindlasti seda sünkroonida teise varukoopia või seadmega? Vahetamine ei eemalda selle seadmega juba sünkroonitud andmeid."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Kas lülituda teisele Sync'ile?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup pole ajutiselt saadaval."; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index 8b910b5bff..e39357759b 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synkronointi on keskeytetty"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Vaihda synkronointi"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Tämä laite on jo synkronoitu. Haluatko varmasti synkronoida sen toisen varmuuskopion tai laitteen kanssa? Vaihtaminen ei poista tähän laitteeseen jo synkronoituja tietoja."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Vaihdetaanko toiseen synkronointiin?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Synkronointi ja varmuuskopiointi on tilapäisesti poissa käytöstä."; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 37dc6fbcdf..16c9ec6bc2 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "La synchronisation est suspendue"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Changer de synchronisation"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Cet appareil est déjà synchronisé. Voulez-vous vraiment le synchroniser avec une autre sauvegarde ou un autre appareil ? Ce changement ne supprimera aucune donnée déjà synchronisée sur cet appareil."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Passer à une autre synchronisation ?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup est temporairement indisponible."; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 5c79140a66..e883cb987d 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sinkronizacija je pauzirana"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Prebaci sinkronizaciju"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Ovaj je uređaj već sinkroniziran. Jesi li siguran da ga želiš sinkronizirati s drugom sigurnosnom kopijom ili uređajem? Prebacivanjem se neće ukloniti nijedan podatak koji je već sinkroniziran s ovim uređajem."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Prijeđi na drugu sinkronizaciju?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sinkronizacija i sigurnosno kopiranje Sync & Backup privremeno su nedostupni."; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index fa1d7a213b..764607c998 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "A szinkronizálás szünetel"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Váltás másik szinkronizálási lehetőségre"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Ez az eszköz már szinkronizálva van. Biztosan szinkronizálni szeretnéd egy másik biztonsági mentéssel vagy eszközzel? A váltás nem távolítja el ezzel az eszközzel már szinkronizált adatokat."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Átváltasz egy másik szinkronizációs lehetőségre?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "A szinkronizálás és biztonsági mentés átmenetileg nem érhető el."; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index 546e285a6f..9e17f2ac86 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sync è in pausa"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Cambia sincronizzazione"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Questo dispositivo è già sincronizzato. Vuoi davvero sincronizzarlo con un backup o un dispositivo diverso? L'operazione non rimuoverà alcun dato già sincronizzato su questo dispositivo."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Vuoi passare a una sincronizzazione diversa?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup non è temporaneamente disponibile."; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index ec407015d1..36fed6c8ee 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sinchronizavimas pristabdytas"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Perjungti sinchronizavimą"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Šis įrenginys jau sinchronizuotas, ar tikrai norite jį sinchronizuoti su kita atsargine kopija ar įrenginiu? Perjungiant nebus pašalinti jokie duomenys, jau sinchronizuoti su šiuo įrenginiu."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Perjungti į kitą sinchronizaciją?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sinchronizavimas ir atsarginės kopijos kūrimas laikinai nepasiekiami."; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index 636cfb7053..563258b4df 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sinhronizācija ir apturēta"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Pārslēgt sinhronizāciju"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Šī ierīce jau ir sinhronizēta, vai tiešām vēlies to sinhronizēt ar citu dublēšanas sistēmu vai ierīci? Pārslēdzot netiks dzēsti dati, kas jau ir sinhronizēti ar šo ierīci."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Vai pārslēgt uz citu sinhronizāciju?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup uz laiku nav pieejams."; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index ccd9ca0330..54807b5739 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synkronisering er satt på pause"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Bytt synkronisering"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Denne enheten er allerede synkronisert. Er du sikker på at du vil synkronisere den med en annen sikkerhetskopi eller enhet? Data som allerede er synkronisert med denne enheten, fjernes ikke hvis du bytter."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Vil du bytte til en annen synkronisering?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup er midlertidig utilgjengelig."; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 08efcd53a0..4d14feec38 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synchronisatie is gepauzeerd"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Schakelen tussen synchronisatie"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Dit apparaat is al gesynchroniseerd, weet je zeker dat je het met een andere back-up of een ander apparaat wilt synchroniseren? Als je overschakelt, worden er geen gegevens verwijderd die al met dit apparaat zijn gesynchroniseerd."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Overschakelen naar een andere synchronisatie?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "'Synchronisatie en back-up' is tijdelijk niet beschikbaar."; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index c91457fda0..3d70694854 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synchronizacja jest wstrzymana"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Przełącz synchronizację"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "To urządzenie jest już synchronizowane, czy na pewno chcesz je synchronizować z inną kopią zapasową lub urządzeniem? Przełączenie nie usunie żadnych danych już zsynchronizowanych z tym urządzeniem."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Przełączyć na inną synchronizację?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Funkcja Sync & Backup jest tymczasowo niedostępna."; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 5cf8042b4a..d7fd5575ae 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "A sincronização está em pausa"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Mudar sincronização"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Este dispositivo já está sincronizado, tens a certeza de que queres sincronizá-lo com uma cópia de segurança ou um dispositivo diferente? A mudança não vai remover nenhum dado já sincronizado com este dispositivo."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Mudar para uma sincronização diferente?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "O Sync & Backup está temporariamente indisponível."; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index 9bfa277d90..ada6250d0c 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sincronizarea este întreruptă"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Comută sincronizarea"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Acest dispozitiv este deja sincronizat, sigur dorești să-l sincronizezi cu o altă copie de rezervă sau alt dispozitiv? Schimbarea nu va șterge datele deja sincronizate cu acest dispozitiv."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Treci la o altă sincronizare?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup sunt temporar indisponibile."; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index 660e9b547e..54545dafb0 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Синхронизация приостановлена"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Переключить синхронизацию"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Это устройство уже синхронизировано. Действительно синхронизировать его с другой резервной копией или устройством? Переключение не приведет к удалению данных, синхронизированных с этим устройством ранее."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Переключиться на другую синхронизацию?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Функция «Синхронизация и резервное копирование» временно недоступна."; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index e8cd577412..635635fd28 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synchronizácia bola pozastavená."; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Prepnutie synchronizácie"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Toto zariadenie je už synchronizované. Naozaj ho že chceš synchronizovať s inou zálohou alebo zariadením? Prepínanie neodstráni žiadne údaje, ktoré už boli synchronizované s týmto zariadením."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Prepnúť na inú synchronizáciu?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Služba Sync & Backup je dočasne nedostupná."; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index eb3046fb6b..04dd067fad 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Sinhronizacija je začasno zaustavljena"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Preklop sinhronizacije"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Ta naprava je že sinhronizirana, ste prepričani, da jo želite sinhronizirati z drugo varnostno kopijo ali napravo? Preklop ne bo odstranil nobenih podatkov, ki so že sinhronizirani s to napravo."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Želite preklopiti na drugo sinhronizacijo?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sinhronizacija in varnostno kopiranje sta začasno nedostopna."; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 77e833357c..59b3ea8dab 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Synkroniseringen är pausad"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Byt synkronisering"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Den här enheten är redan synkroniserad. Är du säker på att du vill synkronisera den med en annan säkerhetskopia eller enhet? Att byta tar inte bort några data som redan har synkroniserats med den här enheten."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Byt till en annan synkronisering?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Sync & Backup är inte tillgängligt just nu."; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index d787a57ece..2ff2ddd704 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -181,6 +181,15 @@ /* Title for alert shown when sync paused for an error */ "alert.sync-paused-title" = "Senkronizasyon Duraklatıldı"; +/* Switch account button in alert */ +"alert.sync-switch-account-button" = "Senkronizasyonu Değiştir"; + +/* Description for switching sync accounts when there's two */ +"alert.sync-switch-account-message" = "Bu cihaz zaten senkronize edilmiş. Farklı bir yedekleme veya cihazla senkronize etmek istediğinden emin misin? Senkronizasyonu değiştirmek, bu cihazla önceden senkronize edilmiş verileri kaldırmayacaktır."; + +/* Switch account title in alert */ +"alert.sync-switch-account-title" = "Farklı bir Senkronizasyona mı geçmek istiyorsunuz?"; + /* Description for alert shown when sync error occurs because of too many requests */ "alert.sync-too-many-requests-error-description" = "Senkronizasyon ve Yedekleme geçici olarak kullanılamıyor."; diff --git a/DuckDuckGoTests/MockDDGSyncing.swift b/DuckDuckGoTests/MockDDGSyncing.swift index 3c7e9e4a69..b1bcd046f8 100644 --- a/DuckDuckGoTests/MockDDGSyncing.swift +++ b/DuckDuckGoTests/MockDDGSyncing.swift @@ -71,8 +71,13 @@ final class MockDDGSyncing: DDGSyncing { func createAccount(deviceName: String, deviceType: String) async throws { } + var stubLogin: [RegisteredDevice] = [] + lazy var spyLogin: (SyncCode.RecoveryKey, String, String) throws -> [RegisteredDevice] = { _, _, _ in + return self.stubLogin + } + func login(_ recoveryKey: SyncCode.RecoveryKey, deviceName: String, deviceType: String) async throws -> [RegisteredDevice] { - return [] + return try spyLogin(recoveryKey, deviceName, deviceType) } func remoteConnect() throws -> RemoteConnecting { diff --git a/DuckDuckGoTests/SyncSettingsViewControllerErrorTests.swift b/DuckDuckGoTests/SyncSettingsViewControllerErrorTests.swift index 0233f92598..cab863e20d 100644 --- a/DuckDuckGoTests/SyncSettingsViewControllerErrorTests.swift +++ b/DuckDuckGoTests/SyncSettingsViewControllerErrorTests.swift @@ -21,15 +21,18 @@ import XCTest @testable import DuckDuckGo import Core import Combine -import DDGSync +@testable import DDGSync import Persistence import Common +import SyncUI final class SyncSettingsViewControllerErrorTests: XCTestCase { var cancellables: Set! var vc: SyncSettingsViewController! var errorHandler: CapturingSyncPausedStateManager! + var ddgSyncing: MockDDGSyncing! + var testRecoveryCode = "eyJyZWNvdmVyeSI6eyJ1c2VyX2lkIjoiMDZGODhFNzEtNDFBRS00RTUxLUE2UkRtRkEwOTcwMDE5QkYwIiwicHJpbWFyeV9rZXkiOiI1QTk3U3dsQVI5RjhZakJaU09FVXBzTktnSnJEYnE3aWxtUmxDZVBWazgwPSJ9fQ==" @MainActor override func setUpWithError() throws { @@ -46,7 +49,7 @@ final class SyncSettingsViewControllerErrorTests: XCTestCase { model: model, readOnly: true, options: [:]) - let ddgSyncing = MockDDGSyncing(authState: .active, isSyncInProgress: false) + ddgSyncing = MockDDGSyncing(authState: .active, isSyncInProgress: false) let bookmarksAdapter = SyncBookmarksAdapter( database: database, favoritesDisplayModeStorage: MockFavoritesDisplayModeStoring(), @@ -162,6 +165,93 @@ final class SyncSettingsViewControllerErrorTests: XCTestCase { await fulfillment(of: [expectation], timeout: 5.0) XCTAssertTrue(errorHandler.syncDidTurnOffCalled) } + + func test_syncCodeEntered_accountAlreadyExists_oneDevice_disconnectsThenLogsInAgain() async { + await setUpWithSingleDevice(id: "1") + + var secondLoginCalled = false + + ddgSyncing.spyLogin = { [weak self] _, _, _ in + guard let self else { return [] } + ddgSyncing.spyLogin = { [weak self] _, _, _ in + secondLoginCalled = true + guard let self else { return [] } + // Assert disconnect was called first + XCTAssert(ddgSyncing.disconnectCalled) + return [RegisteredDevice(id: "1", name: "iPhone", type: "iPhone"), RegisteredDevice(id: "2", name: "Macbook Pro", type: "Macbook Pro")] + } + throw SyncError.accountAlreadyExists + } + + _ = await vc.syncCodeEntered(code: testRecoveryCode) + + XCTAssert(secondLoginCalled) + } + + func test_syncCodeEntered_accountAlreadyExists_oneDevice_updatesDevicesWithReturnedDevices() async throws { + await setUpWithSingleDevice(id: "1") + + ddgSyncing.spyLogin = { [weak self] _, _, _ in + self?.ddgSyncing.spyLogin = { _, _, _ in + return [RegisteredDevice(id: "1", name: "iPhone", type: "iPhone"), RegisteredDevice(id: "2", name: "Macbook Pro", type: "Macbook Pro")] + } + throw SyncError.accountAlreadyExists + } + + _ = await vc.syncCodeEntered(code: testRecoveryCode) + + let deviceIDs = await vc.viewModel?.devices.flatMap(\.id) + XCTAssertEqual(deviceIDs, ["1", "2"]) + } + + func test_switchAccounts_disconnectsThenLogsInAgain() async throws { + var loginCalled = false + + ddgSyncing.spyLogin = { [weak self] _, _, _ in + guard let self else { return [] } + // Assert disconnect before returning from login to ensure correct order + XCTAssert(ddgSyncing.disconnectCalled) + loginCalled = true + return [RegisteredDevice(id: "1", name: "iPhone", type: "iPhone"), RegisteredDevice(id: "2", name: "Macbook Pro", type: "Macbook Pro")] + } + + guard let syncCode = try? SyncCode.decodeBase64String(testRecoveryCode), + let recoveryKey = syncCode.recovery else { + XCTFail("Could not create RecoveryKey from code") + return + } + + await vc.switchAccounts(recoveryKey: recoveryKey) + + XCTAssert(loginCalled) + } + + func test_switchAccounts_updatesDevicesWithReturnedDevices() async throws { + ddgSyncing.spyLogin = { [weak self] _, _, _ in + guard let self else { return [] } + // Assert disconnect before returning from login to ensure correct order + XCTAssert(ddgSyncing.disconnectCalled) + return [RegisteredDevice(id: "1", name: "iPhone", type: "iPhone"), RegisteredDevice(id: "2", name: "Macbook Pro", type: "Macbook Pro")] + } + + guard let syncCode = try? SyncCode.decodeBase64String(testRecoveryCode), + let recoveryKey = syncCode.recovery else { + XCTFail("Could not create RecoveryKey from code") + return + } + + await vc.switchAccounts(recoveryKey: recoveryKey) + + let deviceIDs = await vc.viewModel?.devices.flatMap(\.id) + XCTAssertEqual(deviceIDs, ["1", "2"]) + } + + @MainActor + private func setUpWithSingleDevice(id: String) { + ddgSyncing.account = SyncAccount(deviceId: id, deviceName: "iPhone", deviceType: "iPhone", userId: "", primaryKey: Data(), secretKey: Data(), token: nil, state: .active) + ddgSyncing.registeredDevices = [RegisteredDevice(id: id, name: "iPhone", type: "iPhone")] + vc.viewModel?.devices = [SyncSettingsViewModel.Device(id: id, name: "iPhone", type: "iPhone", isThisDevice: true)] + } } class MockFavoritesDisplayModeStoring: MockFavoriteDisplayModeStorage {}