diff --git a/CHANGES.md b/CHANGES.md index 39ef0be2f3..d5664364a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +## Changes in 1.11.38 (2026-05-27) + +🐛 Bugfixes + +- Hook up the Reset flow to Verification when there is no recovery or other devices. ([#8024](https://github.com/element-hq/element-ios/issues/8024)) + ## Changes in 1.11.37 (2026-04-01) 🙌 Improvements diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 532a3ad409..dcf8b4546d 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.11.37 -CURRENT_PROJECT_VERSION = 1.11.37 +MARKETING_VERSION = 1.11.38 +CURRENT_PROJECT_VERSION = 1.11.38 diff --git a/Podfile.lock b/Podfile.lock index 7c48ede275..d9e5472be9 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -44,13 +44,13 @@ PODS: - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - - MatrixSDKCrypto (= 0.11.1) + - MatrixSDKCrypto (= 0.17.0) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - MatrixSDK/JingleCallStack (0.27.18): - JitsiMeetSDKLite (= 12.0.0-lite) - MatrixSDK/Core - - MatrixSDKCrypto (0.11.1) + - MatrixSDKCrypto (0.17.0) - ReadMoreTextView (3.0.1) - Realm (10.27.0): - Realm/Headers (= 10.27.0) @@ -176,8 +176,8 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatrixSDK: c2ee6ae269e0fe57b06d824530481fcb879265f7 - MatrixSDKCrypto: e44608012cae9befc52f13cd8e56c6f51ac83702 + MatrixSDK: fdccd88405ec9e9f8e9978091bc0ac67cf396542 + MatrixSDKCrypto: aa38e272e573c7798aa3cd761b2b61ec5c7731b8 ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: 9ca328bd7e700cc19703799785e37f77d1a130f2 Reusable: 6bae6a5e8aa793c9c441db0213c863a64bce9136 diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index ff635dc4ce..76d21a48cd 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -963,7 +963,7 @@ "user_verification_sessions_list_user_trust_level_trusted_title" = "Vertraut"; "user_verification_sessions_list_user_trust_level_warning_title" = "Warnung"; "user_verification_sessions_list_user_trust_level_unknown_title" = "Unbekannt"; -"user_verification_sessions_list_information" = "Nachrichten mit diesem Gegenüber sind Ende-zu-Ende verschlüsselt und können nicht von Dritten gelesen werden."; +"user_verification_sessions_list_information" = "Nachrichten mit diesem Gegenüber in diesem Raum sind Ende-zu-Ende verschlüsselt und können nicht von Dritten gelesen werden."; "user_verification_sessions_list_table_title" = "Sitzungen"; "user_verification_sessions_list_session_trusted" = "Vertraut"; "user_verification_sessions_list_session_untrusted" = "Nicht vertraut"; @@ -2795,3 +2795,9 @@ "call_unsupported_matrix_rtc_call" = "Nicht unterstützter Anruf. Die neue Element X App wird benötigt, um an diesem Anruf teilzunehmen."; "wysiwyg_composer_action_minimise_action" = "Editor verkleinern"; "wysiwyg_composer_action_maximise_action" = "Editor erweitern"; + +// Verification Required Banner +"verification_required_banner_title" = "Verifiziere dieses Gerät"; +"verification_required_banner_description" = "Ab Oktober 2026 können unverifizierte Geräte keine Nachrichten mehr senden und empfangen."; +"verification_required_banner_learn_more" = "Mehr erfahren"; +"verification_required_banner_verify_button" = "Jetzt verifizieren"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 5e213d526b..e3b8362f37 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2617,7 +2617,7 @@ To enable access, tap Settings> Location and select Always"; // MARK: - Verification Required Banner "verification_required_banner_title" = "Verify this device"; -"verification_required_banner_description" = "As of April 2026 unverified devices will not be able to send and receive messages"; +"verification_required_banner_description" = "As of October 2026 unverified devices will not be able to send and receive messages"; "verification_required_banner_learn_more" = "Learn more"; "verification_required_banner_verify_button" = "Verify now"; diff --git a/Riot/Assets/fi.lproj/Localizable.strings b/Riot/Assets/fi.lproj/Localizable.strings index 3e339a60d7..a133278c12 100644 --- a/Riot/Assets/fi.lproj/Localizable.strings +++ b/Riot/Assets/fi.lproj/Localizable.strings @@ -1,2 +1,9 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Viesti käyttäjältä %@"; +"MSG_FROM_USER" = "%@ lähetti viestin"; +"LOCATION_FROM_USER" = "%@ jakoi sijaintinsa"; +"REPLY_FROM_USER_TITLE" = "%@ vastasi"; +"PICTURE_FROM_USER" = "%@ lähetti kuvan"; +"VIDEO_FROM_USER" = "%@ lähetti videon"; +"VOICE_MESSAGE_FROM_USER" = "%@ lähetti ääniviestin"; +"FILE_FROM_USER" = "%@ lähetti tiedoston %@"; +"Notification" = "Ilmoitus"; diff --git a/Riot/Assets/fi.lproj/Vector.strings b/Riot/Assets/fi.lproj/Vector.strings index e66ee0801f..c19fc3311c 100644 --- a/Riot/Assets/fi.lproj/Vector.strings +++ b/Riot/Assets/fi.lproj/Vector.strings @@ -216,3 +216,18 @@ "active_call" = "Aktiivinen puhelu"; "enable" = "Ota käyttöön"; "store_promotional_text" = "Yksityisyyttä varjeleva chat ja yhteistyösovellus, avoimessa verkossa. Hajautettu hallintaa varten. Ei datalouhintaa, takaovia tai kolmannen osapuolen pääsyä."; +"people_invites_section" = "KUTSUT"; +"people_conversation_section" = "KESKUSTELUT"; +"home_context_menu_unfavourite" = "Poista suosikeista"; +"room_recents_no_conversation" = "Ei huoneita"; +"people_no_conversation" = "Ei keskusteluja"; +"group_invite_section" = "KUTSUT"; +"all_chats_edit_layout_unreads" = "Lukemattomat"; +"people_empty_view_title" = "Ihmiset"; +"room_recents_favourites_section" = "SUOSIKIT"; +"room_recents_people_section" = "IHMISET"; +"room_recents_suggested_rooms_section" = "EHDOTETUT HUONEET"; +"spaces_creation_email_invites_email_title" = "Sähköposti"; +"room_recents_invites_section" = "KUTSUT"; +"rooms_empty_view_title" = "Huoneet"; +"room_recents_conversations_section" = "HUONEET"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index ee14358945..955d25baac 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -2733,3 +2733,9 @@ // MARK: Password policy errors "password_policy_too_short_pwd_error" = "Mot de passe trop court"; "accessibility_selected" = "sélectionné"; + +// Verification Required Banner +"verification_required_banner_title" = "Vérifier cet appareil"; +"verification_required_banner_description" = "À partir d’octobre 2026, les appareils non vérifiés ne pourront plus envoyer ni recevoir de messages."; +"verification_required_banner_learn_more" = "En savoir plus"; +"verification_required_banner_verify_button" = "Vérifier maintenant"; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index f0ddd9d106..55afcd7240 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -2800,3 +2800,9 @@ "manage_session_sign_out_other_sessions" = "Bij alle andere sessies afmelden"; "user_sessions_hide_location_info" = "Verberg IP-adres"; "user_sessions_show_location_info" = "Toon IP-adres"; + +// Verification Required Banner +"verification_required_banner_title" = "Dit apparaat verifiëren"; +"verification_required_banner_description" = "Vanaf oktober 2026 kunnen niet-geverifieerde apparaten geen berichten meer verzenden en ontvangen."; +"verification_required_banner_learn_more" = "Meer informatie"; +"verification_required_banner_verify_button" = "Nu verifiëren"; diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings index 900783a861..e7c1f5e30d 100644 --- a/Riot/Assets/pl.lproj/Vector.strings +++ b/Riot/Assets/pl.lproj/Vector.strings @@ -2868,3 +2868,10 @@ "wysiwyg_composer_action_maximise_action" = "Powiększ edytora treści"; "room_action_report_prompt_reason" = "Powód zgłoszenia tego pokoju"; "room_action_report" = "Zgłoś pokój"; + +// Verification Required Banner +"verification_required_banner_title" = "Zweryfikuj to urządzenie"; +"verification_required_banner_description" = "Od października 2026 r. niezweryfikowane urządzenia nie będą mogły wysyłać ani odbierać wiadomości."; +"verification_required_banner_learn_more" = "Dowiedz się więcej"; +"verification_required_banner_verify_button" = "Zweryfikuj teraz"; + diff --git a/Riot/Assets/sl.lproj/InfoPlist.strings b/Riot/Assets/sl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Riot/Assets/sl.lproj/InfoPlist.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/tr.lproj/Vector.strings b/Riot/Assets/tr.lproj/Vector.strings new file mode 100644 index 0000000000..a3cd68f95b --- /dev/null +++ b/Riot/Assets/tr.lproj/Vector.strings @@ -0,0 +1,117 @@ +"store_short_description" = "Güvenli sohbet/VoIP"; +"title_home" = "Ana Sayfa"; +"title_favourites" = "Favoriler"; +"title_people" = "Kişiler"; +"title_rooms" = "Odalar"; +"title_groups" = "Topluluklar"; +"warning" = "Uyarı"; +"next" = "Sonraki"; +"back" = "Geri"; +"continue" = "Devam"; +"create" = "Oluştur"; +"start" = "Başla"; +"leave" = "Ayrıl"; +"remove" = "Çıkart"; +"on" = "Aç"; +"enable" = "Aktif"; +"cancel" = "İptal"; +"save" = "Kaydet"; +"join" = "Üye ol"; +"decline" = "Reddet"; +"accept" = "Kabul et"; +"preview" = "Ön izleme"; +"camera" = "Kamera"; +"voice" = "Ses"; +"video" = "Video"; +"active_call" = "Aktif Çağrı"; +"active_call_details" = "(%@) Aktif Çağrı"; +"later" = "Sonra"; +"collapse" = "sorun oluştu"; +"send_to" = "%@ gönder"; +"skip" = "Geç"; +"joined" = "Katıldı"; +"switch" = "Değiştir"; +"more" = "Daha çok"; +"less" = "Az"; +"open" = "Açık"; +"done" = "Tamamla"; +"private" = "Özel"; +"public" = "Genel"; +"stop" = "Durdur"; +"new_word" = "Yeni"; +"existing" = "Mevcut"; +"add" = "Ekle"; +"ok" = "Tamam"; +"error" = "Hata"; +"suggest" = "Öneri"; +"edit" = "Düzenle"; +"invite_to" = "%@ davet"; +"loading" = "Yükleniyor"; +"sending" = "Gönderiliyor"; +"saving" = "Kaydediliyor"; +"callbar_active_and_single_paused" = "1 aktif arama (%@) · 1 duraklatılmış arama"; +"callbar_active_and_multiple_paused" = "1 aktif arama (%@) · %@ duraklatılmış aramalar"; +"callbar_only_single_paused" = "Arama duraklatıldı"; +"callbar_only_multiple_paused" = "%@ duraklatılmış aramalar"; +"accessibility_checkbox_label" = "onay kutusu"; +"accessibility_button_label" = "düğme"; +"accessibility_selected" = "seçildi"; +"sunset_delegated_oidc_registration_not_supported_message" = "Hesabınız için %2$@'yi kullanmak üzere %1$@'yi indirin veya farklı bir sunucusu seçin."; +"store_promotional_text" = "Gizliliği koruyan sohbet ve iş birliği uygulaması, açık bir ağ üzerinde çalışır. Kontrolü size veren **dağıtık yapıya sahip**tir. Veri madenciliği yoktur, arka kapı bulunmaz ve üçüncü taraf erişimi söz konusu değildir."; +"view" = "Görünüm"; +"off" = "Kapat"; +"confirm" = "Onayla"; +"invite" = "Davet et"; +"close" = "Kapalı"; +"callbar_only_single_active" = "Aramaya geri dönmek için dokunun (%@)"; +"retry" = "Tekrar"; +"rename" = "Yeni ad"; +"joining" = "Katılma"; +"callbar_return" = "Geri dönüş"; +"callbar_only_single_active_group" = "(%@) Grup görüşmesine katılmak için dokunun"; +"sunset_delegated_oidc_registration_not_supported_title" = "Bu uygulama üzerinden artık %1$@ ile hesap oluşturamazsınız"; +"authentication_verify_email_text_field_placeholder" = "E-posta"; +"authentication_choose_password_input_message" = "En az 8 karakter olduğundan emin olun"; +"authentication_choose_password_text_field_placeholder" = "Yeni Parola"; +"authentication_verify_email_waiting_title" = "E-postanızı doğrulayın."; +"authentication_verify_email_waiting_message" = "%@ adresine gönderilen talimatları izleyin"; +"authentication_verify_email_waiting_hint" = "E-posta almadınız mı?"; +"authentication_verify_email_waiting_button" = "E-postayı yeniden gönder"; +"authentication_forgot_password_input_title" = "E-posta adresinizi giriniz"; +"authentication_forgot_password_text_field_placeholder" = "E-posta"; +"authentication_forgot_password_waiting_title" = "E-postanızı kontrol edin."; +"authentication_forgot_password_waiting_message" = "%@ adresine gönderilen talimatları izleyin"; +"authentication_forgot_password_waiting_button" = "E-postayı yeniden gönder"; +"authentication_choose_password_submit_button" = "Parolayı Sıfırla"; +"authentication_choose_password_not_verified_title" = "E-posta doğrulanmadı"; +"authentication_choose_password_not_verified_message" = "Gelen kutunuzu kontrol edin"; +"authentication_verify_msisdn_input_title" = "Telefon numaranızı girin"; +"authentication_verify_msisdn_text_field_placeholder" = "Telefon Numarası"; +"authentication_verify_msisdn_otp_text_field_placeholder" = "Onay Kodu"; +"authentication_verify_msisdn_waiting_title" = "Telefon numaranızı doğrulayın"; +"authentication_verify_msisdn_waiting_button" = "Kodu yeniden gönder"; +"authentication_verify_msisdn_invalid_phone_number" = "Geçersiz telefon numarası"; +"authentication_terms_title" = "Gizlilik politikaları"; +"authentication_terms_policy_url_error" = "Seçilen politika bulunamadı. Lütfen daha sonra tekrar deneyin."; +"authentication_recaptcha_title" = "İnsan olduğunu doğrular mısın?"; +"authentication_qr_login_start_title" = "QR kodu okutun"; +"authentication_qr_login_start_subtitle" = "Bu cihazdaki kamerayı kullanarak diğer cihazınızda gösterilen QR kodunu tarayın:"; +"authentication_qr_login_start_step3" = "‘Bir cihaz bağla’ seçeneğini seçin"; +"authentication_qr_login_start_step4" = "‘Bu cihazda QR kodunu göster’ seçeneğini işaretleyin"; +"authentication_qr_login_start_need_alternative" = "Alternatif bir yönteme mi ihtiyacınız var?"; +"authentication_qr_login_start_display_qr" = "Bu cihazda QR kodunu göster"; +"authentication_qr_login_display_title" = "Bir cihaz bağlayın"; +"authentication_qr_login_display_step1" = "Element'i diğer cihazınızda açın"; +"authentication_qr_login_display_step2" = "‘QR koduyla giriş yap’ seçeneğini seçin"; +"authentication_qr_login_scan_title" = "QR kodunu tarayın"; +"authentication_qr_login_scan_subtitle" = "QR kodunu aşağıdaki kareye yerleştirin"; +"authentication_qr_login_confirm_title" = "Güvenli bağlantı kuruldu"; +"authentication_choose_password_input_title" = "Yeni bir parola belirleyin"; +"authentication_terms_message" = "Lütfen %@'nin şartlarını ve politikalarını okuyun"; +"authentication_qr_login_start_step1" = "Element'i diğer cihazınızda açın"; +"authentication_choose_password_signout_all_devices" = "Tüm cihazlardan çıkış yapın"; +"authentication_verify_msisdn_input_message" = "%@ hesabınızı doğrulamalıdır"; +"authentication_qr_login_start_step2" = "Ayarlar -> Güvenlik ve Gizlilik bölümüne gidin"; +"authentication_forgot_password_input_message" = "%@ size bir doğrulama bağlantısı gönderecektir"; +"authentication_verify_msisdn_waiting_message" = "%@ adresine bir kod gönderildi"; +"authentication_qr_login_display_subtitle" = "Oturumunuz kapalı olan cihazınızla aşağıdaki QR kodunu tarayın."; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 13c0dc4e73..858d0ccb3d 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -9367,7 +9367,7 @@ public class VectorL10n: NSObject { public static func userVerificationStartWaitingPartner(_ p1: String) -> String { return VectorL10n.tr("Vector", "user_verification_start_waiting_partner", p1) } - /// As of April 2026 unverified devices will not be able to send and receive messages + /// As of October 2026 unverified devices will not be able to send and receive messages public static var verificationRequiredBannerDescription: String { return VectorL10n.tr("Vector", "verification_required_banner_description") } diff --git a/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift b/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift index 74b7f967ea..a9a99c2ada 100644 --- a/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift +++ b/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift @@ -54,7 +54,7 @@ final class CrossSigningSetupCoordinator: CrossSigningSetupCoordinatorType { // MARK: - Private methods - private func setupCrossSigning(with authenticationParameters: [String: Any] = [:]) { + private func setupCrossSigning(with authenticationParameters: [String: Any] = [:], hasAuthenticated: Bool = false) { guard let crossSigning = parameters.session.crypto?.crossSigning else { return } crossSigning.setup(withAuthParams: authenticationParameters) { [weak self] in @@ -64,7 +64,8 @@ final class CrossSigningSetupCoordinator: CrossSigningSetupCoordinatorType { guard let self else { return } if let responseData = (error as NSError).userInfo[MXHTTPClientErrorResponseDataKey] as? [AnyHashable: Any], - let authenticationSession = MXAuthenticationSession(fromJSON: responseData) { + let authenticationSession = MXAuthenticationSession(fromJSON: responseData), + !hasAuthenticated { // Don't re-presenting authentication if the user closes the web view without finishing. showReauthentication(authenticationSession: authenticationSession) } else { delegate?.crossSigningSetupCoordinator(self, didFailWithError: error) @@ -93,7 +94,7 @@ final class CrossSigningSetupCoordinator: CrossSigningSetupCoordinatorType { extension CrossSigningSetupCoordinator: ReauthenticationCoordinatorDelegate { func reauthenticationCoordinatorDidComplete(_ coordinator: ReauthenticationCoordinatorType, withAuthenticationParameters authenticationParameters: [String: Any]?) { - self.setupCrossSigning(with: authenticationParameters ?? [:]) + self.setupCrossSigning(with: authenticationParameters ?? [:], hasAuthenticated: true) } func reauthenticationCoordinatorDidCancel(_ coordinator: ReauthenticationCoordinatorType) { diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift index 71220203d2..038e9f5475 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift @@ -189,6 +189,17 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { return coordinator } + private func showSecretsReset() { + let coordinator = SecretsResetCoordinator(session: self.session, isCancellable: false) + coordinator.delegate = self + coordinator.start() + + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator.toPresentable(), animated: true, popCompletion: { [weak self] in + self?.remove(childCoordinator: coordinator) + }) + } + private func showSecretsRecovery(with recoveryMode: SecretsRecoveryMode) { let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: recoveryMode, recoveryGoal: .verifyDevice, navigationRouter: self.navigationRouter, cancellable: self.cancellable) coordinator.delegate = self @@ -436,9 +447,13 @@ extension KeyVerificationCoordinator: KeyVerificationSelfVerifyWaitCoordinatorDe self.didCancel() } - func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, wantsToRecoverSecretsWith secretsRecoveryMode: SecretsRecoveryMode) { + func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, wantsToRecoverSecretsWith secretsRecoveryMode: SecretsRecoveryMode) { self.showSecretsRecovery(with: secretsRecoveryMode) } + + func keyVerificationSelfVerifyWaitCoordinatorWantsToResetSecrets(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType) { + self.showSecretsReset() + } } // MARK: - KeyVerificationScanConfirmationCoordinatorDelegate @@ -466,3 +481,15 @@ extension KeyVerificationCoordinator: SecretsRecoveryCoordinatorDelegate { self.didCancel() } } + +// MARK: - SecretsResetCoordinatorDelegate +extension KeyVerificationCoordinator: SecretsResetCoordinatorDelegate { + + func secretsResetCoordinatorDidResetSecrets(_ coordinator: SecretsResetCoordinatorType) { + self.showVerified(animated: true) + } + + func secretsResetCoordinatorDidCancel(_ coordinator: SecretsResetCoordinatorType) { + // Not used, the cancel button is hidden. + } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift index 68dcab40e7..cc005d0f32 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift @@ -76,4 +76,8 @@ extension KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyWai func keyVerificationSelfVerifyWaitViewModel(_ coordinator: KeyVerificationSelfVerifyWaitViewModelType, wantsToRecoverSecretsWith secretsRecoveryMode: SecretsRecoveryMode) { self.delegate?.keyVerificationSelfVerifyWaitCoordinator(self, wantsToRecoverSecretsWith: secretsRecoveryMode) } + + func keyVerificationSelfVerifyWaitViewModelWantsToResetSecrets(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType) { + self.delegate?.keyVerificationSelfVerifyWaitCoordinatorWantsToResetSecrets(self) + } } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift index 8724493ea2..1a27be1d2a 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift @@ -23,6 +23,7 @@ protocol KeyVerificationSelfVerifyWaitCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptIncomingSASTransaction incomingSASTransaction: MXSASTransaction) func keyVerificationSelfVerifyWaitCoordinatorDidCancel(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType) func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, wantsToRecoverSecretsWith secretsRecoveryMode: SecretsRecoveryMode) + func keyVerificationSelfVerifyWaitCoordinatorWantsToResetSecrets(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType) } /// `KeyVerificationSelfVerifyWaitCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewAction.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewAction.swift index 77d80ab707..522d8294ab 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewAction.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewAction.swift @@ -23,4 +23,5 @@ enum KeyVerificationSelfVerifyWaitViewAction { case loadData case cancel case recoverSecrets + case resetSecrets } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard index 0dcf70679f..a031902b69 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -17,13 +17,13 @@ - + - + - + diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift index 91d3920ba5..c14df526ed 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift @@ -43,6 +43,9 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { @IBOutlet private weak var recoverSecretsButton: RoundedButton! @IBOutlet private weak var recoverSecretsAdditionalInformationLabel: UILabel! + @IBOutlet private weak var resetSecretsContainerView: UIView! + @IBOutlet private weak var resetSecretsButton: UIButton! + // MARK: Private private var viewModel: KeyVerificationSelfVerifyWaitViewModelType! @@ -101,6 +104,13 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { self.mobileClientImageView.tintColor = theme.tintColor self.recoverSecretsAvailabilityLoadingLabel.textColor = theme.textSecondaryColor self.recoverSecretsAvailabilityActivityIndicatorView.color = theme.tintColor + + // Reset secrets button + let resetSecretsAttributedString = NSMutableAttributedString(string: VectorL10n.secretsRecoveryResetActionPart1, + attributes: [.foregroundColor: self.theme.textPrimaryColor]) + resetSecretsAttributedString.append(NSAttributedString(string: VectorL10n.secretsRecoveryResetActionPart2, + attributes: [.foregroundColor: self.theme.warningColor])) + self.resetSecretsButton.setAttributedTitle(resetSecretsAttributedString, for: .normal) } private func registerThemeServiceDidChangeThemeNotification() { @@ -121,6 +131,9 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { self.navigationItem.rightBarButtonItem = cancelBarButtonItem self.cancelBarButtonItem = cancelBarButtonItem + + self.resetSecretsButton.vc_enableMultiLinesTitle() + self.resetSecretsButton.isHidden = !RiotSettings.shared.secretsRecoveryAllowReset } self.titleLabel.text = VectorL10n.deviceVerificationSelfVerifyOpenOnOtherDeviceTitle(AppInfo.current.displayName) @@ -158,22 +171,26 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { self.recoverSecretsAvailabilityActivityIndicatorView.startAnimating() self.recoverSecretsAvailabilityLoadingContainerView.isHidden = false self.recoverSecretsContainerView.isHidden = true + self.resetSecretsContainerView.isHidden = true } private func renderLoaded(viewData: KeyVerificationSelfVerifyWaitViewData) { self.activityPresenter.removeCurrentActivityIndicator(animated: true) self.cancelBarButtonItem?.title = viewData.isNewSignIn ? VectorL10n.skip : VectorL10n.cancel - + let hideRecoverSecrets: Bool + let hideResetSecrets: Bool let recoverSecretsButtonTitle: String? switch viewData.secretsRecoveryAvailability { case .notAvailable: hideRecoverSecrets = true + hideResetSecrets = false recoverSecretsButtonTitle = nil case .available(let secretsRecoveryMode): hideRecoverSecrets = false + hideResetSecrets = true switch secretsRecoveryMode { case .passphraseOrKey: @@ -187,6 +204,8 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { self.recoverSecretsAvailabilityActivityIndicatorView.stopAnimating() self.recoverSecretsContainerView.isHidden = hideRecoverSecrets self.recoverSecretsButton.setTitle(recoverSecretsButtonTitle, for: .normal) + + self.resetSecretsContainerView.isHidden = hideResetSecrets } private func renderCancelled(reason: MXTransactionCancelCode) { @@ -223,6 +242,10 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { @IBAction private func recoverSecretsButtonAction(_ sender: Any) { self.viewModel.process(viewAction: .recoverSecrets) } + + @IBAction private func resetSecretsButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .resetSecrets) + } } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift index 2f1b8a2ce0..a09529cb6b 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift @@ -80,6 +80,8 @@ final class KeyVerificationSelfVerifyWaitViewModel: KeyVerificationSelfVerifyWai case .available(let secretsRecoveryMode): self.coordinatorDelegate?.keyVerificationSelfVerifyWaitViewModel(self, wantsToRecoverSecretsWith: secretsRecoveryMode) } + case .resetSecrets: + self.coordinatorDelegate?.keyVerificationSelfVerifyWaitViewModelWantsToResetSecrets(self) } } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift index 66027c9e87..11b63f24ac 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift @@ -27,6 +27,7 @@ protocol KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptIncomingSASTransaction incomingSASTransaction: MXSASTransaction) func keyVerificationSelfVerifyWaitViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType) func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, wantsToRecoverSecretsWith secretsRecoveryMode: SecretsRecoveryMode) + func keyVerificationSelfVerifyWaitViewModelWantsToResetSecrets(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType) } /// Protocol describing the view model used by `KeyVerificationSelfVerifyWaitViewController` diff --git a/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift b/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift index 14c7887c30..35c9676b51 100644 --- a/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift +++ b/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift @@ -38,6 +38,8 @@ final class ReauthenticationCoordinator: ReauthenticationCoordinatorType { private var authenticationSession: SSOAuthentificationSessionProtocol? + private weak var presentedNavigationController: UINavigationController? + private var presentingViewController: UIViewController { return self.parameters.presenter.toPresentable() } @@ -58,7 +60,7 @@ final class ReauthenticationCoordinator: ReauthenticationCoordinatorType { self.userInteractiveAuthenticationService = UserInteractiveAuthenticationService(session: parameters.session) self.authenticationParametersBuilder = AuthenticationParametersBuilder() self.uiaViewControllerFactory = UserInteractiveAuthenticationViewControllerFactory() - } + } // MARK: - Public methods @@ -143,28 +145,34 @@ final class ReauthenticationCoordinator: ReauthenticationCoordinatorType { reauthFallbackViewController.title = self.parameters.title reauthFallbackViewController.didCancel = { [weak self] in - guard let self = self else { - return - } + guard let self = self else { return } + + self.dismissFallbackAuthentication() self.delegate?.reauthenticationCoordinatorDidCancel(self) } reauthFallbackViewController.didValidate = { [weak self] in - guard let self = self else { - return - } + guard let self = self else { return } guard let sessionId = authenticationSession.session else { + self.dismissFallbackAuthentication() self.delegate?.reauthenticationCoordinator(self, didFailWithError: ReauthenticationCoordinatorError.failToBuildPasswordParameters) return } let authenticationParameters = self.authenticationParametersBuilder.buildOAuthParameters(with: sessionId) + self.dismissFallbackAuthentication() self.delegate?.reauthenticationCoordinatorDidComplete(self, withAuthenticationParameters: authenticationParameters) } let navigationController = RiotNavigationController(rootViewController: reauthFallbackViewController) + self.presentedNavigationController = navigationController self.presentingViewController.present(navigationController, animated: true) } + + private func dismissFallbackAuthentication() { + presentedNavigationController?.dismiss(animated: true) + presentedNavigationController = nil + } } diff --git a/Riot/Modules/Reauthentication/ReauthenticationCoordinatorBridgePresenter.swift b/Riot/Modules/Reauthentication/ReauthenticationCoordinatorBridgePresenter.swift index 79bd562dc6..32497d019f 100644 --- a/Riot/Modules/Reauthentication/ReauthenticationCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Reauthentication/ReauthenticationCoordinatorBridgePresenter.swift @@ -37,6 +37,9 @@ final class ReauthenticationCoordinatorBridgePresenter: NSObject { // MARK: Private private var coordinator: ReauthenticationCoordinator? + /// Re-authentication dismisses itself automatically, so make sure that the dismiss function + /// only takes effect when dismissing an ongoing re-authentication rather than a completed one. + private var hasDismissed: Bool = false // MARK: Public @@ -55,6 +58,7 @@ final class ReauthenticationCoordinatorBridgePresenter: NSObject { self.didComplete = success self.didCancel = cancel self.didFail = failure + self.hasDismissed = false let coordinator = ReauthenticationCoordinator(parameters: parameters) coordinator.delegate = self @@ -69,12 +73,16 @@ final class ReauthenticationCoordinatorBridgePresenter: NSObject { guard let coordinator = self.coordinator else { return } + + guard !hasDismissed else { + self.coordinator = nil + completion?() + return + } + coordinator.toPresentable().dismiss(animated: animated) { self.coordinator = nil - - if let completion = completion { - completion() - } + completion?() } } @@ -90,14 +98,17 @@ final class ReauthenticationCoordinatorBridgePresenter: NSObject { // MARK: - ReauthenticationCoordinatorDelegate extension ReauthenticationCoordinatorBridgePresenter: ReauthenticationCoordinatorDelegate { func reauthenticationCoordinatorDidComplete(_ coordinator: ReauthenticationCoordinatorType, withAuthenticationParameters authenticationParameters: [String: Any]?) { + self.hasDismissed = true self.didComplete?(authenticationParameters) } func reauthenticationCoordinatorDidCancel(_ coordinator: ReauthenticationCoordinatorType) { + self.hasDismissed = true self.didCancel?() } func reauthenticationCoordinator(_ coordinator: ReauthenticationCoordinatorType, didFailWithError error: Error) { + self.hasDismissed = true self.didFail?(error) } } diff --git a/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift b/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift index bd9740ad59..213b010299 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift @@ -38,11 +38,11 @@ final class SecretsResetCoordinator: SecretsResetCoordinatorType { // MARK: - Setup - init(session: MXSession) { + init(session: MXSession, isCancellable: Bool = true) { self.session = session let secretsResetViewModel = SecretsResetViewModel(session: self.session) - let secretsResetViewController = SecretsResetViewController.instantiate(with: secretsResetViewModel) + let secretsResetViewController = SecretsResetViewController.instantiate(with: secretsResetViewModel, isCancellable: isCancellable) self.secretsResetViewModel = secretsResetViewModel self.secretsResetViewController = secretsResetViewController } @@ -59,13 +59,13 @@ final class SecretsResetCoordinator: SecretsResetCoordinatorType { // MARK: - Private - private func showAuthentication(with request: AuthenticatedEndpointRequest) { + private func showReauthentication(for session: MXAuthenticationSession) { let reauthenticationCoordinatorParameters = ReauthenticationCoordinatorParameters(session: self.session, presenter: self.toPresentable(), title: nil, message: VectorL10n.secretsResetAuthenticationMessage, - authenticatedEndpointRequest: request) + authenticationSession: session) let coordinator = ReauthenticationCoordinator(parameters: reauthenticationCoordinatorParameters) coordinator.delegate = self @@ -77,8 +77,8 @@ final class SecretsResetCoordinator: SecretsResetCoordinatorType { // MARK: - SecretsResetViewModelCoordinatorDelegate extension SecretsResetCoordinator: SecretsResetViewModelCoordinatorDelegate { - func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, needsToAuthenticateWith request: AuthenticatedEndpointRequest) { - self.showAuthentication(with: request) + func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, needsToAuthenticateFor session: MXAuthenticationSession) { + self.showReauthentication(for: session) } func secretsResetViewModelDidResetSecrets(_ viewModel: SecretsResetViewModelType) { diff --git a/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift b/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift index fccbfb6e35..2d961d0581 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift @@ -40,15 +40,17 @@ final class SecretsResetViewController: UIViewController { // MARK: Private private var viewModel: SecretsResetViewModelType! + private var isCancellable = true private var theme: Theme! private var errorPresenter: MXKErrorPresentation! private var activityPresenter: ActivityIndicatorPresenter! // MARK: - Setup - class func instantiate(with viewModel: SecretsResetViewModelType) -> SecretsResetViewController { + class func instantiate(with viewModel: SecretsResetViewModelType, isCancellable: Bool = true) -> SecretsResetViewController { let viewController = StoryboardScene.SecretsResetViewController.initialScene.instantiate() viewController.viewModel = viewModel + viewController.isCancellable = isCancellable viewController.theme = ThemeService.shared().theme return viewController } @@ -108,12 +110,13 @@ final class SecretsResetViewController: UIViewController { } private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in - self?.cancelButtonAction() + if isCancellable { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem } - self.navigationItem.rightBarButtonItem = cancelBarButtonItem - self.title = VectorL10n.secretsResetTitle self.scrollView.keyboardDismissMode = .interactive diff --git a/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift b/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift index 62b0c686fb..b26763bddc 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift @@ -48,11 +48,11 @@ final class SecretsResetViewModel: SecretsResetViewModelType { case .loadData: break case .reset: - self.askAuthentication() + self.resetSecrets() case .authenticationCancelled: self.authenticationCancelled() case .authenticationInfoEntered(let authParameters): - self.resetSecrets(with: authParameters) + self.resetSecrets(with: authParameters, hasAuthenticated: true) case .cancel: self.coordinatorDelegate?.secretsResetViewModelDidCancel(self) } @@ -64,43 +64,42 @@ final class SecretsResetViewModel: SecretsResetViewModelType { self.viewDelegate?.secretsResetViewModel(self, didUpdateViewState: viewState) } - private func resetSecrets(with authParameters: [String: Any]) { - guard let crossSigning = self.session.crypto?.crossSigning else { - return - } + private func resetSecrets(with authParameters: [String: Any] = [:], hasAuthenticated: Bool = false) { + guard let crossSigning = self.session.crypto?.crossSigning else { return } + MXLog.debug("[SecretsResetViewModel] resetSecrets") + self.update(viewState: .resetting) - crossSigning.setup(withAuthParams: authParameters, success: { [weak self] in - guard let self = self else { - return - } - self.recoveryService.deleteRecovery(withDeleteServicesBackups: true, success: { [weak self] in - guard let self = self else { - return - } + crossSigning.setup(withAuthParams: authParameters) { [weak self] in + guard let self else { return } + + self.recoveryService.deleteRecovery(withDeleteServicesBackups: true) { [weak self] in + guard let self else { return } + self.update(viewState: .resetDone) self.coordinatorDelegate?.secretsResetViewModelDidResetSecrets(self) - }, failure: { [weak self] error in - guard let self = self else { - return - } + } failure: { [weak self] error in + guard let self else { return } self.update(viewState: .error(error)) - }) + } - }, failure: { [weak self] error in - guard let self = self else { - return + } failure: { [weak self] error in + guard let self else { return } + + if let responseData = (error as NSError).userInfo[MXHTTPClientErrorResponseDataKey] as? [AnyHashable: Any], + let authenticationSession = MXAuthenticationSession(fromJSON: responseData), + !hasAuthenticated { // Don't re-presenting authentication if the user closes the web view without finishing. + askAuthentication(session: authenticationSession) + } else { + self.update(viewState: .error(error)) } - self.update(viewState: .error(error)) - }) + } } - private func askAuthentication() { - self.update(viewState: .resetting) - + private func askAuthentication(session: MXAuthenticationSession) { let setupCrossSigningRequest = self.crossSigningService.setupCrossSigningRequest() - self.coordinatorDelegate?.secretsResetViewModel(self, needsToAuthenticateWith: setupCrossSigningRequest) + self.coordinatorDelegate?.secretsResetViewModel(self, needsToAuthenticateFor: session) } private func authenticationCancelled() { diff --git a/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift b/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift index 5a5c880775..8ae7d36474 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift @@ -23,7 +23,7 @@ protocol SecretsResetViewModelViewDelegate: AnyObject { } protocol SecretsResetViewModelCoordinatorDelegate: AnyObject { - func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, needsToAuthenticateWith request: AuthenticatedEndpointRequest) + func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, needsToAuthenticateFor session: MXAuthenticationSession) func secretsResetViewModelDidResetSecrets(_ viewModel: SecretsResetViewModelType) func secretsResetViewModelDidCancel(_ viewModel: SecretsResetViewModelType) } diff --git a/matrix-ios-sdk b/matrix-ios-sdk index 942bddbfdf..c8fdfa32e5 160000 --- a/matrix-ios-sdk +++ b/matrix-ios-sdk @@ -1 +1 @@ -Subproject commit 942bddbfdf39cb2f94b6106b2da7c0b6940da065 +Subproject commit c8fdfa32e51a42eccbf695ce522ee9a3e1985678