From 5419e07eda3f129e868f9cbcc1cae793596ddf34 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 3 Dec 2025 11:37:29 +0000 Subject: [PATCH 1/4] Add a feature flag for the new QR code flows. --- .../Resources/Localizations/en.lproj/Untranslated.strings | 5 +++++ ElementX/Sources/Application/Settings/AppSettings.swift | 4 ++++ .../FlowCoordinators/SettingsFlowCoordinator.swift | 2 ++ ElementX/Sources/Generated/Strings+Untranslated.swift | 2 ++ .../DeveloperOptionsScreenModels.swift | 1 + .../View/DeveloperOptionsScreen.swift | 3 +++ .../SettingsScreen/SettingsScreenCoordinator.swift | 3 +++ .../Settings/SettingsScreen/SettingsScreenModels.swift | 3 +++ .../Settings/SettingsScreen/SettingsScreenViewModel.swift | 7 +++++++ .../Settings/SettingsScreen/View/SettingsScreen.swift | 8 ++++++++ 10 files changed, 38 insertions(+) diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 2b31da559c..f19b520363 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -14,3 +14,8 @@ "soft_logout_clear_data_submit" = "Clear all data"; "soft_logout_clear_data_dialog_title" = "Clear data"; "soft_logout_clear_data_dialog_content" = "Clear all data currently stored on this device?\nSign in again to access your account data and messages."; + +// MARK: - QR Code + +// TODO: Add to Localazy. +"common_link_new_device" = "Link new device"; diff --git a/ElementX/Sources/Application/Settings/AppSettings.swift b/ElementX/Sources/Application/Settings/AppSettings.swift index df454f02db..6f371e3d4f 100644 --- a/ElementX/Sources/Application/Settings/AppSettings.swift +++ b/ElementX/Sources/Application/Settings/AppSettings.swift @@ -66,6 +66,7 @@ final class AppSettings { case linkPreviewsEnabled case spaceSettingsEnabled case focusEventOnNotificationTap + case newQRCodeLoginFlowsEnabled // Doug's tweaks 🔧 case hideUnreadMessagesBadge @@ -399,6 +400,9 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.linkPreviewsEnabled, defaultValue: false, storageType: .userDefaults(store)) var linkPreviewsEnabled + @UserPreference(key: UserDefaultsKeys.newQRCodeLoginFlowsEnabled, defaultValue: false, storageType: .userDefaults(store)) + var newQRCodeLoginFlowsEnabled + @UserPreference(key: UserDefaultsKeys.developerOptionsEnabled, defaultValue: isDevelopmentBuild, storageType: .userDefaults(store)) var developerOptionsEnabled } diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 5362870352..4cd72f7761 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -83,6 +83,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { startEncryptionSettingsFlow(animated: true) case .userDetails: presentUserDetailsEditScreen() + case .linkNewDevice: + break case let .manageAccount(url): presentAccountManagementURL(url) case .analytics: diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index 55cfabb3ea..3b3e22019d 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -10,6 +10,8 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces internal enum UntranslatedL10n { + /// Link new device + internal static var commonLinkNewDevice: String { return UntranslatedL10n.tr("Untranslated", "common_link_new_device") } /// Clear all data currently stored on this device? /// Sign in again to access your account data and messages. internal static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 94f26097f6..122acc0d0c 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -57,6 +57,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var linkPreviewsEnabled: Bool { get set } var spaceSettingsEnabled: Bool { get set } + var newQRCodeLoginFlowsEnabled: Bool { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 8324c42b84..13c6321510 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -34,6 +34,9 @@ struct DeveloperOptionsScreen: View { } Section("General") { + Toggle(isOn: $context.newQRCodeLoginFlowsEnabled) { + Text("New QR code login flows") + } Toggle(isOn: $context.spaceSettingsEnabled) { Text("Space settings") } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift index c99b4e7682..42ece16448 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift @@ -25,6 +25,7 @@ enum SettingsScreenCoordinatorAction { case bugReport case about case blockedUsers + case linkNewDevice case manageAccount(url: URL) case notifications case advancedSettings @@ -59,6 +60,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.dismiss) case .userDetails: actionsSubject.send(.userDetails) + case .linkNewDevice: + actionsSubject.send(.linkNewDevice) case let .manageAccount(url): actionsSubject.send(.manageAccount(url: url)) case .analytics: diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift index dbac252db4..a02b0acc7d 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift @@ -12,6 +12,7 @@ import UIKit enum SettingsScreenViewModelAction: Equatable { case close case userDetails + case linkNewDevice case manageAccount(url: URL) case analytics case appLock @@ -35,6 +36,7 @@ enum SettingsScreenSecuritySectionMode { struct SettingsScreenViewState: BindableState { var deviceID: String? var userID: String + var showLinkNewDeviceButton: Bool var accountProfileURL: URL? var accountSessionsListURL: URL? var showAccountDeactivation: Bool @@ -66,6 +68,7 @@ enum SettingsScreenViewAction { case about case blockedUsers case secureBackup + case linkNewDevice case manageAccount(url: URL) case notifications case enableDeveloperOptions diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index 2ce120dcd7..08b9543cee 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -25,6 +25,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID, userID: userSession.clientProxy.userID, + showLinkNewDeviceButton: appSettings.newQRCodeLoginFlowsEnabled, showAccountDeactivation: userSession.clientProxy.canDeactivateAccount, showDeveloperOptions: appSettings.developerOptionsEnabled, showAnalyticsSettings: appSettings.canPromptForAnalytics, @@ -35,6 +36,10 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo .weakAssign(to: \.state.showDeveloperOptions, on: self) .store(in: &cancellables) + appSettings.$newQRCodeLoginFlowsEnabled + .weakAssign(to: \.state.showLinkNewDeviceButton, on: self) + .store(in: &cancellables) + userSession.clientProxy.userAvatarURLPublisher .receive(on: DispatchQueue.main) .weakAssign(to: \.state.userAvatarURL, on: self) @@ -93,6 +98,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo actionsSubject.send(.close) case .userDetails: actionsSubject.send(.userDetails) + case .linkNewDevice: + actionsSubject.send(.linkNewDevice) case let .manageAccount(url): actionsSubject.send(.manageAccount(url: url)) case .analytics: diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index 9ef6068b31..359c53c2b1 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -108,6 +108,14 @@ struct SettingsScreen: View { private var manageAccountSection: some View { Section { + if context.viewState.showLinkNewDeviceButton { + ListRow(label: .default(title: UntranslatedL10n.commonLinkNewDevice, + icon: \.devices), + kind: .navigationLink { + context.send(viewAction: .linkNewDevice) + }) + } + if let url = context.viewState.accountProfileURL { ListRow(label: .default(title: L10n.actionManageAccount, icon: \.userProfile), From 94e4df5df0039729692e38787440c529719fd4b6 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 3 Dec 2025 11:57:01 +0000 Subject: [PATCH 2/4] Add a LinkNewDeviceScreen. --- .../Sources/GeneratedAccessibilityTests.swift | 4 + ElementX.xcodeproj/project.pbxproj | 40 +++++ .../en-US.lproj/Localizable.strings | 34 +++- .../en.lproj/Localizable.strings | 34 +++- .../en.lproj/Untranslated.strings | 5 - .../SettingsFlowCoordinator.swift | 25 ++- .../Generated/Strings+Untranslated.swift | 2 - ElementX/Sources/Generated/Strings.swift | 72 +++++++- ElementX/Sources/Mocks/ClientProxyMock.swift | 1 + .../Mocks/Generated/GeneratedMocks.swift | 17 ++ .../TestablePreviewsDictionary.swift | 1 + .../LinkNewDeviceScreenCoordinator.swift | 55 ++++++ .../LinkNewDeviceScreenModels.swift | 25 +++ .../LinkNewDeviceScreenViewModel.swift | 61 +++++++ ...LinkNewDeviceScreenViewModelProtocol.swift | 14 ++ .../View/LinkNewDeviceScreen.swift | 163 ++++++++++++++++++ .../SettingsScreen/View/SettingsScreen.swift | 2 +- .../Sources/Services/Client/ClientProxy.swift | 11 ++ .../Services/Client/ClientProxyProtocol.swift | 2 + .../Sources/GeneratedPreviewTests.swift | 6 + ...kNewDeviceScreen.Generating-iPad-en-GB.png | 3 + ...NewDeviceScreen.Generating-iPad-pseudo.png | 3 + ...ewDeviceScreen.Generating-iPhone-en-GB.png | 3 + ...wDeviceScreen.Generating-iPhone-pseudo.png | 3 + ...linkNewDeviceScreen.Loading-iPad-en-GB.png | 3 + ...inkNewDeviceScreen.Loading-iPad-pseudo.png | 3 + ...nkNewDeviceScreen.Loading-iPhone-en-GB.png | 3 + ...kNewDeviceScreen.Loading-iPhone-pseudo.png | 3 + .../linkNewDeviceScreen.Ready-iPad-en-GB.png | 3 + .../linkNewDeviceScreen.Ready-iPad-pseudo.png | 3 + ...linkNewDeviceScreen.Ready-iPhone-en-GB.png | 3 + ...inkNewDeviceScreen.Ready-iPhone-pseudo.png | 3 + ...NewDeviceScreen.Unsupported-iPad-en-GB.png | 3 + ...ewDeviceScreen.Unsupported-iPad-pseudo.png | 3 + ...wDeviceScreen.Unsupported-iPhone-en-GB.png | 3 + ...DeviceScreen.Unsupported-iPhone-pseudo.png | 3 + .../LinkNewDeviceScreenViewModelTests.swift | 13 ++ 37 files changed, 611 insertions(+), 24 deletions(-) create mode 100644 ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenModels.swift create mode 100644 ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/LinkNewDeviceScreen/View/LinkNewDeviceScreen.swift create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-pseudo.png create mode 100644 UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift index ced163874a..b8d0c27146 100644 --- a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -279,6 +279,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "LegalInformationScreen_Previews") } + func testLinkNewDeviceScreen() async throws { + try await performAccessibilityAudit(named: "LinkNewDeviceScreen_Previews") + } + func testLoadableImage() async throws { try await performAccessibilityAudit(named: "LoadableImage_Previews") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 9b5e1dde51..f64aa5c8d5 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -289,6 +289,7 @@ 31DE6B7FD15E1C6E43885717 /* RoomRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 578AF9CE60816069536C0953 /* RoomRole.swift */; }; 32B7891D937377A59606EDFC /* UserFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */; }; 32F47002A331817F0E6BD7EB /* RoomMembershipDetailsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1434D5169F0EE319E226DA7F /* RoomMembershipDetailsProxyProtocol.swift */; }; + 33544885F5DB8016736CC610 /* LinkNewDeviceScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */; }; 339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; }; 33BA0964A308D2286B39976D /* LabsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C5AA3EF7EC67C01C75CEDD /* LabsScreen.swift */; }; 33CAC1226DFB8B5D8447D286 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; }; @@ -597,6 +598,7 @@ 695BE6A2337A634F48B5DBC8 /* RoomMembersFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */; }; 69A9B430397C15075D86193F /* UserPropertiesExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AFD800AF033D8B0D11191A /* UserPropertiesExt.swift */; }; 69B3C6010B42010F591FC3CB /* RoomRolesAndPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */; }; + 69B9CC733A880E1BB097C113 /* LinkNewDeviceScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */; }; 69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */; }; 69DE29C3E3180BB17D840690 /* ProgressCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97C8E13A1FBA717B0C277ECC /* ProgressCursorModifier.swift */; }; 6A38D0A2BC3943A92D82576E /* EditRoomAddressScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B18089ED50324583BB2FB7 /* EditRoomAddressScreenViewModelProtocol.swift */; }; @@ -834,6 +836,7 @@ 9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */; }; 9295F1F5E04484E10780BCE8 /* CharacterSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8C01DEEA83903D45069BBD /* CharacterSet.swift */; }; 92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */; }; + 92FE657CDFAFE3031576EB43 /* LinkNewDeviceScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED096460D7F26F10168FA33B /* LinkNewDeviceScreenViewModelProtocol.swift */; }; 9312F5A17AE59A9E910C51D6 /* NotificationItemProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840182D7A61402D5947DE094 /* NotificationItemProxyMock.swift */; }; 9322949BFCA6278921085862 /* DeactivateAccountScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664D3C710B4A12CE4E623645 /* DeactivateAccountScreenModels.swift */; }; 934051B17A884AB0635DF81B /* BlockedUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */; }; @@ -1019,6 +1022,7 @@ B3D8AA9988F8A000B162DCB5 /* TombstonedAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D353E10A64172D863769BF /* TombstonedAvatarImage.swift */; }; B402708F8728DD0DB7C324E2 /* StartChatScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */; }; B444F9C184A377C1B481F07F /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E992D7B8BE54B2AB454613AF /* XCUIElement.swift */; }; + B466827F3766FF8E0CD0D34F /* LinkNewDeviceScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA49F233E193590274FD19F /* LinkNewDeviceScreenCoordinator.swift */; }; B47213F07A67CE3A8D49CEC9 /* TimelineControllerFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC1E3FE9B59EA094867863E /* TimelineControllerFactoryProtocol.swift */; }; B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */; }; B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; }; @@ -1088,6 +1092,7 @@ C11D4A49DC29D89CE2BB31B8 /* MediaEventsTimelineScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976ED77B772F50C4BAD757E7 /* MediaEventsTimelineScreenViewModel.swift */; }; C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */; }; C1429699A6A5BB09A25775C1 /* AudioPlayerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */; }; + C16E25C41B858BF27E0C4FC6 /* LinkNewDeviceScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D3DA6959C041E212A718DD3 /* LinkNewDeviceScreenViewModel.swift */; }; C1910A16BDF131FECA77BE22 /* EmojiPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */; }; C1A5C386319835FB0C77736B /* ReportContentScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */; }; C1D0AB8222D7BAFC9AF9C8C0 /* MapLibreMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622D09D4ECE759189009AEAF /* MapLibreMapView.swift */; }; @@ -1237,6 +1242,7 @@ DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; }; DDE7B4771452300C103B1EB8 /* RoomDirectoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */; }; DDFBDEE1DC32BDD5488F898C /* ClientProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */; }; + DE3BF0ED68E56BF625591D49 /* LinkNewDeviceScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72C4A2D279386A811BDC6DAE /* LinkNewDeviceScreen.swift */; }; DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; }; DEDBD3E9CFCC9F20CAC79881 /* JoinRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */; }; DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */; }; @@ -1844,6 +1850,7 @@ 3C3ADF21BE301D0DA48F2A7E /* ShareExtensionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionView.swift; sourceTree = ""; }; 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenModels.swift; sourceTree = ""; }; 3C8A63F1458D97D832C127DD /* app.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = app.yml; sourceTree = ""; }; + 3CA49F233E193590274FD19F /* LinkNewDeviceScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenCoordinator.swift; sourceTree = ""; }; 3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenModels.swift; sourceTree = ""; }; 3CFD5EB0B0EEA4549FB49784 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = ""; }; 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextField.swift; sourceTree = ""; }; @@ -2121,6 +2128,7 @@ 723B055A57857BFF0F18D9CB /* test_rotated_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = test_rotated_image.jpg; sourceTree = ""; }; 72614BFF35B8394C6E13F55A /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.swift; sourceTree = ""; }; 726E901DF76393E335FD7E8E /* ManageAuthorizedSpacesScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageAuthorizedSpacesScreenViewModel.swift; sourceTree = ""; }; + 72C4A2D279386A811BDC6DAE /* LinkNewDeviceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreen.swift; sourceTree = ""; }; 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = ""; }; 7310D8DFE01AF45F0689C3AA /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportFlowCoordinator.swift; sourceTree = ""; }; @@ -2352,6 +2360,7 @@ 9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = ""; }; 9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachment.swift; sourceTree = ""; }; + 9D3DA6959C041E212A718DD3 /* LinkNewDeviceScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModel.swift; sourceTree = ""; }; 9E3B41C36800DD4558D7BDA7 /* VoiceMessageRoomPlaybackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomPlaybackView.swift; sourceTree = ""; }; 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = ""; }; 9E8F4D7D61B80EBD5CB92F8A /* KnockedRoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockedRoomProxyMock.swift; sourceTree = ""; }; @@ -2388,6 +2397,7 @@ A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreen.swift; sourceTree = ""; }; A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = ""; }; A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; + A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenModels.swift; sourceTree = ""; }; A4D9DF4F2DF3507F99B5B97B /* LabsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModel.swift; sourceTree = ""; }; A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineFlowCoordinator.swift; sourceTree = ""; }; A6B19D10B102956066AF117B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = ""; }; @@ -2711,6 +2721,7 @@ E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = ""; }; E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = ""; }; E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = ""; }; + E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = ""; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = ""; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = ""; }; @@ -2778,6 +2789,7 @@ ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; + ED096460D7F26F10168FA33B /* LinkNewDeviceScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelProtocol.swift; sourceTree = ""; }; ED0AD0C652385F69FA90FAF5 /* TimelineMediaPreviewDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewDataSourceTests.swift; sourceTree = ""; }; ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineView.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; @@ -4426,6 +4438,18 @@ path = View; sourceTree = ""; }; + 5E5152617DFA7CCA3F4FA683 /* LinkNewDeviceScreen */ = { + isa = PBXGroup; + children = ( + 3CA49F233E193590274FD19F /* LinkNewDeviceScreenCoordinator.swift */, + A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */, + 9D3DA6959C041E212A718DD3 /* LinkNewDeviceScreenViewModel.swift */, + ED096460D7F26F10168FA33B /* LinkNewDeviceScreenViewModelProtocol.swift */, + BB11CF5847CA0B3BC3A7F8DA /* View */, + ); + path = LinkNewDeviceScreen; + sourceTree = ""; + }; 5EC4A8482DA110602FE6DF42 /* View */ = { isa = PBXGroup; children = ( @@ -4683,6 +4707,7 @@ FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */, C9AC2CC94FA06F728883B694 /* KnockRequestsListScreenViewModelTests.swift */, 6E5725BC6C63604CB769145B /* LegalInformationScreenViewModelTests.swift */, + E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */, C070FD43DC6BF4E50217965A /* LocalizationTests.swift */, 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */, 5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */, @@ -5796,6 +5821,14 @@ path = ServerConfirmationScreen; sourceTree = ""; }; + BB11CF5847CA0B3BC3A7F8DA /* View */ = { + isa = PBXGroup; + children = ( + 72C4A2D279386A811BDC6DAE /* LinkNewDeviceScreen.swift */, + ); + path = View; + sourceTree = ""; + }; BC8F2F197AFDABE63B2E2CA7 /* View */ = { isa = PBXGroup; children = ( @@ -6303,6 +6336,7 @@ FFD7C58CA6A7D6BBC2F584B5 /* JoinRoomScreen */, BF0415BE807CA2BCFC210008 /* KnockRequestsListScreen */, 4D963F50D7AA8FE302CA8ACF /* LabsScreen */, + 5E5152617DFA7CCA3F4FA683 /* LinkNewDeviceScreen */, 948DD12A5533BE1BC260E437 /* LocationSharing */, 73E032ADD008D63812791D97 /* LogViewerScreen */, 24FBB8F76D6435033CD07747 /* ManageAuthorizedSpacesScreen */, @@ -7446,6 +7480,7 @@ BA48D6AFF6421D199148C0A1 /* KnockRequestsListScreenViewModelTests.swift in Sources */, CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */, 8AC256AF0EC54658321C9241 /* LegalInformationScreenViewModelTests.swift in Sources */, + 33544885F5DB8016736CC610 /* LinkNewDeviceScreenViewModelTests.swift in Sources */, 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */, 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, 7434A7F02D587A920B376A9A /* LoginScreenViewModelTests.swift in Sources */, @@ -7990,6 +8025,11 @@ A9D349478F7D4A2B1E40CEF9 /* LegalInformationScreenViewModelProtocol.swift in Sources */, 5E597B9959BDAE7A67DBD5B2 /* LinkMetadataProvider.swift in Sources */, 112C6F1C493B3F26AB22716A /* LinkMetadataProviderProtocol.swift in Sources */, + DE3BF0ED68E56BF625591D49 /* LinkNewDeviceScreen.swift in Sources */, + B466827F3766FF8E0CD0D34F /* LinkNewDeviceScreenCoordinator.swift in Sources */, + 69B9CC733A880E1BB097C113 /* LinkNewDeviceScreenModels.swift in Sources */, + C16E25C41B858BF27E0C4FC6 /* LinkNewDeviceScreenViewModel.swift in Sources */, + 92FE657CDFAFE3031576EB43 /* LinkNewDeviceScreenViewModelProtocol.swift in Sources */, 866FA35E7A2339EF8B6D91CA /* LinkPreviewView.swift in Sources */, 6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */, D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */, diff --git a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings index 299ef2c319..6271de551a 100644 --- a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings @@ -1,7 +1,7 @@ "Notification" = "Notification"; "a11y_add_reaction" = "Add reaction: %1$@"; "a11y_avatar" = "Avatar"; -"a11y_collapse_message_text_field" = "Minimise message text field"; +"a11y_collapse_message_text_field" = "Minimize message text field"; "a11y_delete" = "Delete"; "a11y_edit_avatar" = "Edit avatar"; "a11y_edit_room_address_hint" = "The full address will be %1$@"; @@ -148,6 +148,7 @@ "action_skip" = "Skip"; "action_start" = "Start"; "action_start_chat" = "Start chat"; +"action_start_over" = "Start over"; "action_start_verification" = "Start verification"; "action_static_map_load" = "Tap to load map"; "action_take_photo" = "Take photo"; @@ -579,7 +580,7 @@ "screen_knock_requests_list_initial_loading_title" = "Loading requests to join…"; "screen_labs_enable_threads" = "Enable thread replies"; "screen_labs_enable_threads_description" = "The app will restart to apply this change."; -"screen_labs_header_description" = "Try out our latest ideas in development. These features are not finalised; they may be unstable, may change."; +"screen_labs_header_description" = "Try out our latest ideas in development. These features are not finalized; they may be unstable, may change."; "screen_labs_header_title" = "Feeling experimental?"; "screen_labs_title" = "Labs"; "screen_leave_space_last_admin_info" = "%1$@ (Admin)"; @@ -588,9 +589,32 @@ "screen_leave_space_subtitle_only_last_admin" = "You will not be removed from the following room(s) because you're the only administrator:"; "screen_leave_space_title" = "Leave %1$@?"; "screen_leave_space_title_last_admin" = "You are the only admin for %1$@"; -"screen_link_new_device_desktop_computer" = "Desktop computer"; -"screen_link_new_device_mobile_device" = "Mobile device"; -"screen_link_new_device_title" = "What type of device do you want to link?"; +"screen_link_new_device_desktop_scanning_title" = "Scan the QR code"; +"screen_link_new_device_desktop_step1" = "Open %1$@ on a laptop or desktop computer"; +"screen_link_new_device_desktop_step3" = "Scan the QR code with this device"; +"screen_link_new_device_desktop_submit" = "Ready to scan"; +"screen_link_new_device_desktop_title" = "Open %1$@ on a desktop computer to get the QR code"; +"screen_link_new_device_enter_number_error_numbers_do_not_match" = "The numbers don’t match"; +"screen_link_new_device_enter_number_notice" = "Enter 2-digit code"; +"screen_link_new_device_enter_number_subtitle" = "This will verify that the connection to your other device is secure."; +"screen_link_new_device_enter_number_title" = "Enter the number shown on your other device"; +"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@."; +"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported"; +"screen_link_new_device_error_not_supported_subtitle" = "Your account provider doesn’t support signing into a new device with a QR code."; +"screen_link_new_device_error_not_supported_title" = "QR code not supported"; +"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device."; +"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled"; +"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again."; +"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time"; +"screen_link_new_device_mobile_step1" = "Open %1$@ on the other device"; +"screen_link_new_device_mobile_step2" = "Select %1$@"; +"screen_link_new_device_mobile_step2_action" = "“Sign in with QR code”"; +"screen_link_new_device_mobile_step3" = "Scan the QR code shown here with the other device"; +"screen_link_new_device_mobile_title" = "Open %1$@ on the other device"; +"screen_link_new_device_root_desktop_computer" = "Desktop computer"; +"screen_link_new_device_root_loading_qr_code" = "Loading QR code…"; +"screen_link_new_device_root_mobile_device" = "Mobile device"; +"screen_link_new_device_root_title" = "What type of device do you want to link?"; "screen_manage_authorized_spaces_header" = "Spaces where members can join the room without an invitation."; "screen_manage_authorized_spaces_title" = "Manage spaces"; "screen_manage_authorized_spaces_unknown_space" = "(Unknown space)"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 054923fad6..115a530a33 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -148,6 +148,7 @@ "action_skip" = "Skip"; "action_start" = "Start"; "action_start_chat" = "Start chat"; +"action_start_over" = "Start over"; "action_start_verification" = "Start verification"; "action_static_map_load" = "Tap to load map"; "action_take_photo" = "Take photo"; @@ -588,9 +589,32 @@ "screen_leave_space_subtitle_only_last_admin" = "You will not be removed from the following room(s) because you're the only administrator:"; "screen_leave_space_title" = "Leave %1$@?"; "screen_leave_space_title_last_admin" = "You are the only admin for %1$@"; -"screen_link_new_device_desktop_computer" = "Desktop computer"; -"screen_link_new_device_mobile_device" = "Mobile device"; -"screen_link_new_device_title" = "What type of device do you want to link?"; +"screen_link_new_device_desktop_scanning_title" = "Scan the QR code"; +"screen_link_new_device_desktop_step1" = "Open %1$@ on a laptop or desktop computer"; +"screen_link_new_device_desktop_step3" = "Scan the QR code with this device"; +"screen_link_new_device_desktop_submit" = "Ready to scan"; +"screen_link_new_device_desktop_title" = "Open %1$@ on a desktop computer to get the QR code"; +"screen_link_new_device_enter_number_error_numbers_do_not_match" = "The numbers don’t match"; +"screen_link_new_device_enter_number_notice" = "Enter 2-digit code"; +"screen_link_new_device_enter_number_subtitle" = "This will verify that the connection to your other device is secure."; +"screen_link_new_device_enter_number_title" = "Enter the number shown on your other device"; +"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@."; +"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported"; +"screen_link_new_device_error_not_supported_subtitle" = "Your account provider doesn’t support signing into a new device with a QR code."; +"screen_link_new_device_error_not_supported_title" = "QR code not supported"; +"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device."; +"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled"; +"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again."; +"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time"; +"screen_link_new_device_mobile_step1" = "Open %1$@ on the other device"; +"screen_link_new_device_mobile_step2" = "Select %1$@"; +"screen_link_new_device_mobile_step2_action" = "“Sign in with QR code”"; +"screen_link_new_device_mobile_step3" = "Scan the QR code shown here with the other device"; +"screen_link_new_device_mobile_title" = "Open %1$@ on the other device"; +"screen_link_new_device_root_desktop_computer" = "Desktop computer"; +"screen_link_new_device_root_loading_qr_code" = "Loading QR code…"; +"screen_link_new_device_root_mobile_device" = "Mobile device"; +"screen_link_new_device_root_title" = "What type of device do you want to link?"; "screen_manage_authorized_spaces_header" = "Spaces where members can join the room without an invitation."; "screen_manage_authorized_spaces_title" = "Manage spaces"; "screen_manage_authorized_spaces_unknown_space" = "(Unknown space)"; @@ -657,7 +681,7 @@ "screen_roomlist_clear_filters" = "Clear filters"; "screen_roomlist_tombstoned_room_description" = "This room has been upgraded"; "screen_security_and_privacy_add_room_address_action" = "Add address"; -"screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description" = "Anyone in authorized spaces can join, but everyone else must request access."; +"screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description" = "Anyone in authorised spaces can join, but everyone else must request access."; "screen_security_and_privacy_ask_to_join_option_description" = "Everyone must request access."; "screen_security_and_privacy_ask_to_join_single_space_members_option_description" = "Anyone in %1$@ can join, but everyone else must request access."; "screen_security_and_privacy_enable_encryption_alert_confirm_button_title" = "Yes, enable encryption"; @@ -670,7 +694,7 @@ "screen_security_and_privacy_room_access_invite_only_option_description" = "Only invited people can join."; "screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only"; "screen_security_and_privacy_room_access_section_header" = "Access"; -"screen_security_and_privacy_room_access_space_members_option_multiple_parents_description" = "Anyone in authorized spaces can join."; +"screen_security_and_privacy_room_access_space_members_option_multiple_parents_description" = "Anyone in authorised spaces can join."; "screen_security_and_privacy_room_access_space_members_option_single_parent_description" = "Anyone in %1$@ can join."; "screen_security_and_privacy_room_access_space_members_option_title" = "Space members"; "screen_security_and_privacy_room_access_space_members_option_unavailable_description" = "Spaces are not currently supported"; diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index f19b520363..2b31da559c 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -14,8 +14,3 @@ "soft_logout_clear_data_submit" = "Clear all data"; "soft_logout_clear_data_dialog_title" = "Clear data"; "soft_logout_clear_data_dialog_content" = "Clear all data currently stored on this device?\nSign in again to access your account data and messages."; - -// MARK: - QR Code - -// TODO: Add to Localazy. -"common_link_new_device" = "Link new device"; diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 4cd72f7761..3fa2f4c658 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -84,7 +84,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { case .userDetails: presentUserDetailsEditScreen() case .linkNewDevice: - break + presentLinkNewDeviceScreen() case let .manageAccount(url): presentAccountManagementURL(url) case .analytics: @@ -271,4 +271,27 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { appSettings: flowParameters.appSettings) accountSettingsPresenter?.start() } + + // MARK: - Link New Device + + private func presentLinkNewDeviceScreen() { + let parameters = LinkNewDeviceScreenCoordinatorParameters(clientProxy: flowParameters.userSession.clientProxy) + let coordinator = LinkNewDeviceScreenCoordinator(parameters: parameters) + coordinator.actionsPublisher + .sink { [weak self] action in + guard let self else { return } + + switch action { + case .linkMobileDevice: + break + case .linkDesktopComputer: + break + case .dismiss: + navigationStackCoordinator.pop() + } + } + .store(in: &cancellables) + + navigationStackCoordinator.push(coordinator) + } } diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index 3b3e22019d..55cfabb3ea 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -10,8 +10,6 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces internal enum UntranslatedL10n { - /// Link new device - internal static var commonLinkNewDevice: String { return UntranslatedL10n.tr("Untranslated", "common_link_new_device") } /// Clear all data currently stored on this device? /// Sign in again to access your account data and messages. internal static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") } diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index a4efcd82f9..edfdc009d9 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -334,6 +334,8 @@ internal enum L10n { internal static var actionStart: String { return L10n.tr("Localizable", "action_start") } /// Start chat internal static var actionStartChat: String { return L10n.tr("Localizable", "action_start_chat") } + /// Start over + internal static var actionStartOver: String { return L10n.tr("Localizable", "action_start_over") } /// Start verification internal static var actionStartVerification: String { return L10n.tr("Localizable", "action_start_verification") } /// Tap to load map @@ -1888,12 +1890,72 @@ internal enum L10n { internal static func screenLeaveSpaceTitleLastAdmin(_ p1: Any) -> String { return L10n.tr("Localizable", "screen_leave_space_title_last_admin", String(describing: p1)) } + /// Scan the QR code + internal static var screenLinkNewDeviceDesktopScanningTitle: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_scanning_title") } + /// Open %1$@ on a laptop or desktop computer + internal static func screenLinkNewDeviceDesktopStep1(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_link_new_device_desktop_step1", String(describing: p1)) + } + /// Scan the QR code with this device + internal static var screenLinkNewDeviceDesktopStep3: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_step3") } + /// Ready to scan + internal static var screenLinkNewDeviceDesktopSubmit: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_submit") } + /// Open %1$@ on a desktop computer to get the QR code + internal static func screenLinkNewDeviceDesktopTitle(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_link_new_device_desktop_title", String(describing: p1)) + } + /// The numbers don’t match + internal static var screenLinkNewDeviceEnterNumberErrorNumbersDoNotMatch: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_error_numbers_do_not_match") } + /// Enter 2-digit code + internal static var screenLinkNewDeviceEnterNumberNotice: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_notice") } + /// This will verify that the connection to your other device is secure. + internal static var screenLinkNewDeviceEnterNumberSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_subtitle") } + /// Enter the number shown on your other device + internal static var screenLinkNewDeviceEnterNumberTitle: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_title") } + /// Your account provider does not support %1$@. + internal static func screenLinkNewDeviceErrorAppNotSupportedSubtitle(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_link_new_device_error_app_not_supported_subtitle", String(describing: p1)) + } + /// %1$@ not supported + internal static func screenLinkNewDeviceErrorAppNotSupportedTitle(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_link_new_device_error_app_not_supported_title", String(describing: p1)) + } + /// Your account provider doesn’t support signing into a new device with a QR code. + internal static var screenLinkNewDeviceErrorNotSupportedSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_not_supported_subtitle") } + /// QR code not supported + internal static var screenLinkNewDeviceErrorNotSupportedTitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_not_supported_title") } + /// The sign in was cancelled on the other device. + internal static var screenLinkNewDeviceErrorRequestCancelledSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_cancelled_subtitle") } + /// Sign in request cancelled + internal static var screenLinkNewDeviceErrorRequestCancelledTitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_cancelled_title") } + /// Sign in expired. Please try again. + internal static var screenLinkNewDeviceErrorRequestTimeoutSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_timeout_subtitle") } + /// The sign in was not completed in time + internal static var screenLinkNewDeviceErrorRequestTimeoutTitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_timeout_title") } + /// Open %1$@ on the other device + internal static func screenLinkNewDeviceMobileStep1(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_link_new_device_mobile_step1", String(describing: p1)) + } + /// Select %1$@ + internal static func screenLinkNewDeviceMobileStep2(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_link_new_device_mobile_step2", String(describing: p1)) + } + /// “Sign in with QR code” + internal static var screenLinkNewDeviceMobileStep2Action: String { return L10n.tr("Localizable", "screen_link_new_device_mobile_step2_action") } + /// Scan the QR code shown here with the other device + internal static var screenLinkNewDeviceMobileStep3: String { return L10n.tr("Localizable", "screen_link_new_device_mobile_step3") } + /// Open %1$@ on the other device + internal static func screenLinkNewDeviceMobileTitle(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_link_new_device_mobile_title", String(describing: p1)) + } /// Desktop computer - internal static var screenLinkNewDeviceDesktopComputer: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_computer") } + internal static var screenLinkNewDeviceRootDesktopComputer: String { return L10n.tr("Localizable", "screen_link_new_device_root_desktop_computer") } + /// Loading QR code… + internal static var screenLinkNewDeviceRootLoadingQrCode: String { return L10n.tr("Localizable", "screen_link_new_device_root_loading_qr_code") } /// Mobile device - internal static var screenLinkNewDeviceMobileDevice: String { return L10n.tr("Localizable", "screen_link_new_device_mobile_device") } + internal static var screenLinkNewDeviceRootMobileDevice: String { return L10n.tr("Localizable", "screen_link_new_device_root_mobile_device") } /// What type of device do you want to link? - internal static var screenLinkNewDeviceTitle: String { return L10n.tr("Localizable", "screen_link_new_device_title") } + internal static var screenLinkNewDeviceRootTitle: String { return L10n.tr("Localizable", "screen_link_new_device_root_title") } /// This account has been deactivated. internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") } /// Incorrect username and/or password @@ -2782,7 +2844,7 @@ internal enum L10n { internal static var screenRoomlistTombstonedRoomDescription: String { return L10n.tr("Localizable", "screen_roomlist_tombstoned_room_description") } /// Add address internal static var screenSecurityAndPrivacyAddRoomAddressAction: String { return L10n.tr("Localizable", "screen_security_and_privacy_add_room_address_action") } - /// Anyone in authorized spaces can join, but everyone else must request access. + /// Anyone in authorised spaces can join, but everyone else must request access. internal static var screenSecurityAndPrivacyAskToJoinMultipleSpacesMembersOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description") } /// Everyone must request access. internal static var screenSecurityAndPrivacyAskToJoinOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_ask_to_join_option_description") } @@ -2822,7 +2884,7 @@ internal enum L10n { internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_title") } /// Access internal static var screenSecurityAndPrivacyRoomAccessSectionHeader: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_section_header") } - /// Anyone in authorized spaces can join. + /// Anyone in authorised spaces can join. internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionMultipleParentsDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_multiple_parents_description") } /// Anyone in %1$@ can join. internal static func screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(_ p1: Any) -> String { diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index 63d8113478..635df96d3a 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -144,6 +144,7 @@ extension ClientProxyMock { underlyingIsReportRoomSupported = true underlyingIsLiveKitRTCSupported = true + underlyingIsLoginWithQRCodeSupported = true underlyingTimelineMediaVisibilityPublisher = CurrentValueSubject(configuration.timelineMediaVisibility).asCurrentValuePublisher() underlyingHideInviteAvatarsPublisher = CurrentValueSubject(configuration.hideInviteAvatars).asCurrentValuePublisher() diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 88b7d6045e..8769ae547d 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2332,6 +2332,23 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { } var underlyingIsLiveKitRTCSupported: Bool! var isLiveKitRTCSupportedClosure: (() async -> Bool)? + var isLoginWithQRCodeSupportedCallsCount = 0 + var isLoginWithQRCodeSupportedCalled: Bool { + return isLoginWithQRCodeSupportedCallsCount > 0 + } + + var isLoginWithQRCodeSupported: Bool { + get async { + isLoginWithQRCodeSupportedCallsCount += 1 + if let isLoginWithQRCodeSupportedClosure = isLoginWithQRCodeSupportedClosure { + return await isLoginWithQRCodeSupportedClosure() + } else { + return underlyingIsLoginWithQRCodeSupported + } + } + } + var underlyingIsLoginWithQRCodeSupported: Bool! + var isLoginWithQRCodeSupportedClosure: (() async -> Bool)? var maxMediaUploadSizeCallsCount = 0 var maxMediaUploadSizeCalled: Bool { return maxMediaUploadSizeCallsCount > 0 diff --git a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift index 12ff1eeca8..5927600494 100644 --- a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift +++ b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift @@ -77,6 +77,7 @@ enum TestablePreviewsDictionary { "LeaveSpaceRoomDetailsCell_Previews" : LeaveSpaceRoomDetailsCell_Previews.self, "LeaveSpaceView_Previews" : LeaveSpaceView_Previews.self, "LegalInformationScreen_Previews" : LegalInformationScreen_Previews.self, + "LinkNewDeviceScreen_Previews" : LinkNewDeviceScreen_Previews.self, "LoadableImage_Previews" : LoadableImage_Previews.self, "LocationMarkerView_Previews" : LocationMarkerView_Previews.self, "LocationRoomTimelineView_Previews" : LocationRoomTimelineView_Previews.self, diff --git a/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenCoordinator.swift b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenCoordinator.swift new file mode 100644 index 0000000000..0b188daef0 --- /dev/null +++ b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenCoordinator.swift @@ -0,0 +1,55 @@ +// +// Copyright 2025 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import SwiftUI + +enum LinkNewDeviceScreenCoordinatorAction { + case linkMobileDevice + case linkDesktopComputer + case dismiss +} + +struct LinkNewDeviceScreenCoordinatorParameters { + let clientProxy: ClientProxyProtocol +} + +final class LinkNewDeviceScreenCoordinator: CoordinatorProtocol { + private let viewModel: LinkNewDeviceScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: LinkNewDeviceScreenCoordinatorParameters) { + viewModel = LinkNewDeviceScreenViewModel(clientProxy: parameters.clientProxy) + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + MXLog.info("Coordinator: received view model action: \(action)") + + guard let self else { return } + switch action { + case .linkMobileDevice: + actionsSubject.send(.linkMobileDevice) + case .linkDesktopComputer: + actionsSubject.send(.linkDesktopComputer) + case .dismiss: + actionsSubject.send(.dismiss) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(LinkNewDeviceScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenModels.swift b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenModels.swift new file mode 100644 index 0000000000..e2804f2efb --- /dev/null +++ b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenModels.swift @@ -0,0 +1,25 @@ +// +// Copyright 2025 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Foundation + +enum LinkNewDeviceScreenViewModelAction { + case linkMobileDevice + case linkDesktopComputer + case dismiss +} + +struct LinkNewDeviceScreenViewState: BindableState { + enum Mode: Equatable { case loading, readyToLink(isGeneratingCode: Bool), notSupported } + var mode: Mode = .loading +} + +enum LinkNewDeviceScreenViewAction { + case linkMobileDevice + case linkDesktopComputer + case dismiss +} diff --git a/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModel.swift b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModel.swift new file mode 100644 index 0000000000..7b65cd189b --- /dev/null +++ b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModel.swift @@ -0,0 +1,61 @@ +// +// Copyright 2025 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import SwiftUI + +typealias LinkNewDeviceScreenViewModelType = StateStoreViewModelV2 + +class LinkNewDeviceScreenViewModel: LinkNewDeviceScreenViewModelType, LinkNewDeviceScreenViewModelProtocol { + private let clientProxy: ClientProxyProtocol + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(clientProxy: ClientProxyProtocol) { + self.clientProxy = clientProxy + + super.init(initialViewState: LinkNewDeviceScreenViewState()) + + Task { await checkQRCodeLoginSupport() } + } + + // MARK: - Public + + override func process(viewAction: LinkNewDeviceScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .linkMobileDevice: + linkMobileDevice() + case .linkDesktopComputer: + actionsSubject.send(.linkDesktopComputer) + case .dismiss: + actionsSubject.send(.dismiss) + } + } + + // MARK: - Private + + private func checkQRCodeLoginSupport() async { + if await clientProxy.isLoginWithQRCodeSupported { + state.mode = .readyToLink(isGeneratingCode: false) + } else { + state.mode = .notSupported + } + } + + private func linkMobileDevice() { + state.mode = .readyToLink(isGeneratingCode: true) + + // TODO: Generate a QR code. + + actionsSubject.send(.linkMobileDevice) + } +} diff --git a/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModelProtocol.swift b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModelProtocol.swift new file mode 100644 index 0000000000..4f2123c0c3 --- /dev/null +++ b/ElementX/Sources/Screens/LinkNewDeviceScreen/LinkNewDeviceScreenViewModelProtocol.swift @@ -0,0 +1,14 @@ +// +// Copyright 2025 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Combine + +@MainActor +protocol LinkNewDeviceScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: LinkNewDeviceScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/LinkNewDeviceScreen/View/LinkNewDeviceScreen.swift b/ElementX/Sources/Screens/LinkNewDeviceScreen/View/LinkNewDeviceScreen.swift new file mode 100644 index 0000000000..152dd7ac03 --- /dev/null +++ b/ElementX/Sources/Screens/LinkNewDeviceScreen/View/LinkNewDeviceScreen.swift @@ -0,0 +1,163 @@ +// +// Copyright 2025 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Compound +import SwiftUI + +struct LinkNewDeviceScreen: View { + @Bindable var context: LinkNewDeviceScreenViewModel.Context + + var body: some View { + FullscreenDialog(topPadding: 24) { + mainContent + } bottomContent: { + buttons + } + .background() + .backgroundStyle(.compound.bgSubtleSecondary) + .navigationTitle(L10n.commonLinkNewDevice) + .navigationBarTitleDisplayMode(.inline) + } + + @ViewBuilder + var mainContent: some View { + switch context.viewState.mode { + case .loading, .readyToLink: + VStack(spacing: 16) { + BigIcon(icon: \.computer, style: .default) + + Text(L10n.screenLinkNewDeviceRootTitle) + .font(.compound.headingMDBold) + .foregroundColor(.compound.textPrimary) + .multilineTextAlignment(.center) + } + .padding(.horizontal, 8) + case .notSupported: + VStack(spacing: 16) { + BigIcon(icon: \.errorSolid, style: .alertSolid) + + VStack(spacing: 8) { + Text(L10n.screenLinkNewDeviceErrorNotSupportedTitle) + .font(.compound.headingMDBold) + .foregroundColor(.compound.textPrimary) + .multilineTextAlignment(.center) + + Text(L10n.screenLinkNewDeviceErrorNotSupportedSubtitle) + .font(.compound.bodyMD) + .foregroundColor(.compound.textSecondary) + .multilineTextAlignment(.center) + } + } + .padding(.horizontal, 24) + .padding(.bottom, 40) + } + } + + @ViewBuilder + var buttons: some View { + switch context.viewState.mode { + case .loading: + Button { } label: { + Label { + Text(L10n.commonLoading) + } icon: { + ProgressView() + .tint(.compound.iconOnSolidPrimary) + } + } + .buttonStyle(.compound(.primary)) + .disabled(true) + case .readyToLink(let isGeneratingCode): + VStack(spacing: 16) { + Button { context.send(viewAction: .linkMobileDevice) } label: { + Label { + Text(isGeneratingCode ? L10n.screenLinkNewDeviceRootLoadingQrCode : L10n.screenLinkNewDeviceRootMobileDevice) + } icon: { + if isGeneratingCode { + ProgressView() + .tint(.compound.iconOnSolidPrimary) + } else { + CompoundIcon(\.mobile) + } + } + } + .buttonStyle(.compound(.primary)) + + Button { context.send(viewAction: .linkDesktopComputer) } label: { + Label(L10n.screenLinkNewDeviceRootDesktopComputer, icon: \.computer) + } + .buttonStyle(.compound(.primary)) + } + .disabled(isGeneratingCode) + case .notSupported: + Button(L10n.actionDismiss) { + context.send(viewAction: .dismiss) + } + .buttonStyle(.compound(.primary)) + } + } +} + +// MARK: - Previews + +struct LinkNewDeviceScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = makeViewModel(mode: .readyToLink(isGeneratingCode: false)) + static let generatingViewModel = makeViewModel(mode: .readyToLink(isGeneratingCode: true)) + static let loadingViewModel = makeViewModel(mode: .loading) + static let unsupportedViewModel = makeViewModel(mode: .notSupported) + + static var previews: some View { + NavigationStack { + LinkNewDeviceScreen(context: viewModel.context) + } + .previewDisplayName("Ready") + .snapshotPreferences(expect: viewModel.context.observe(\.viewState.mode).map { $0 == .readyToLink(isGeneratingCode: false) }) + + NavigationStack { + LinkNewDeviceScreen(context: generatingViewModel.context) + } + .previewDisplayName("Generating") + .snapshotPreferences(expect: generatingViewModel.context.observe(\.viewState.mode).map { $0 == .readyToLink(isGeneratingCode: true) }) + + NavigationStack { + LinkNewDeviceScreen(context: loadingViewModel.context) + } + .previewDisplayName("Loading") + + NavigationStack { + LinkNewDeviceScreen(context: unsupportedViewModel.context) + } + .previewDisplayName("Unsupported") + .snapshotPreferences(expect: unsupportedViewModel.context.observe(\.viewState.mode).map { $0 == .notSupported }) + } + + static func makeViewModel(mode: LinkNewDeviceScreenViewState.Mode) -> LinkNewDeviceScreenViewModel { + let clientProxy = ClientProxyMock(.init()) + clientProxy.isLoginWithQRCodeSupportedClosure = { + switch mode { + case .loading: + try? await Task.sleep(for: .seconds(20)) + return false + case .readyToLink: + return true + case .notSupported: + return false + } + } + + let viewModel = LinkNewDeviceScreenViewModel(clientProxy: clientProxy) + + Task { + try? await Task.sleep(for: .milliseconds(100)) + if case .readyToLink(isGeneratingCode: true) = mode { + viewModel.context.send(viewAction: .linkMobileDevice) + } + } + + return viewModel + } +} diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index 359c53c2b1..bce4d5613b 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -109,7 +109,7 @@ struct SettingsScreen: View { private var manageAccountSection: some View { Section { if context.viewState.showLinkNewDeviceButton { - ListRow(label: .default(title: UntranslatedL10n.commonLinkNewDevice, + ListRow(label: .default(title: L10n.commonLinkNewDevice, icon: \.devices), kind: .navigationLink { context.send(viewAction: .linkNewDevice) diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index ccd64a0d5e..481ec89b23 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -313,6 +313,17 @@ class ClientProxy: ClientProxyProtocol { } } + var isLoginWithQRCodeSupported: Bool { + get async { + do { + return try await client.isLoginWithQrCodeSupported() + } catch { + MXLog.error("Failed checking QR code support with error: \(error)") + return false + } + } + } + var maxMediaUploadSize: Result { get async { do { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 1f478cbfe2..a03091fb25 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -132,6 +132,8 @@ protocol ClientProxyProtocol: AnyObject { var isLiveKitRTCSupported: Bool { get async } + var isLoginWithQRCodeSupported: Bool { get async } + var maxMediaUploadSize: Result { get async } func isOnlyDeviceLeft() async -> Result diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 3c67019e30..deccfc09f9 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -419,6 +419,12 @@ extension PreviewTests { } } + func testLinkNewDeviceScreen() async throws { + for (index, preview) in LinkNewDeviceScreen_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + func testLoadableImage() async throws { for (index, preview) in LoadableImage_Previews._allPreviews.enumerated() { try await assertSnapshots(matching: preview, step: index) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-en-GB.png new file mode 100644 index 0000000000..5b1d0df28d --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad18b20c43c8f8127c485c8b434064de0b237d25ee177c98a989eb65113248e4 +size 109374 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-pseudo.png new file mode 100644 index 0000000000..5f5caa9e61 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5645b5827fdf244280e80f6fc16b421a7dc0635b16b6f80b2b50a1907844b4cf +size 123395 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-en-GB.png new file mode 100644 index 0000000000..9baac765a2 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc447ed7e8194814387d698437d3adaeb4b7b6f687842598fb84240d58d4e0fe +size 61504 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-pseudo.png new file mode 100644 index 0000000000..6e601d3e07 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Generating-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8510dfa5fe61fc0fe236ccf4bf69d3fec03a3cd5fb14182c93da27fb93b94356 +size 82818 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-en-GB.png new file mode 100644 index 0000000000..850e6a7483 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5a9223f99dfb8209fc21042a2656ca4a050a4f8f1a1d7723ef613bcf54706b5 +size 98823 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-pseudo.png new file mode 100644 index 0000000000..b58187b63c --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79aba2f30b36dc42149b05844746297c3959cce54108fb6e9043d22a1e441b55 +size 109776 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-en-GB.png new file mode 100644 index 0000000000..3717121b2d --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54ce632ef35c3ac9c60ee051649cb18ab26aec79c12fd764ea729c81d7897dbf +size 52282 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-pseudo.png new file mode 100644 index 0000000000..19cc921c8e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Loading-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c49ca4038efc3d6059b8f8cd715b67741c4c45e4b817ff61aa5569ea3d76ed38 +size 65128 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-en-GB.png new file mode 100644 index 0000000000..6f47f38c39 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f94dff4155601b73d57df0294c051463ff330fe431a2c46a5a5b198cdc372ea +size 109664 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-pseudo.png new file mode 100644 index 0000000000..08beba709f --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a049866c334facc710309e02999e4869a46839cf0448b68660d21e0cb6842a43 +size 123411 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-en-GB.png new file mode 100644 index 0000000000..aa3fb89463 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0d7ce68e28cf45d36e04d4f6b9e62722ed67e7fc5be42287b43e1bc68924a67 +size 61909 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-pseudo.png new file mode 100644 index 0000000000..228bc85a55 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Ready-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c637c11e208a467b4e4d77a2fb9f1ea0fe6602dcc0788934659328074ed4f3bc +size 81263 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-en-GB.png new file mode 100644 index 0000000000..b42205d259 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1959686b35a78a6c49ab16e9eb96f5ca879151ecf95969e13acf31d8c61f461 +size 106107 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-pseudo.png new file mode 100644 index 0000000000..974cf1f4ad --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f15c62942085467d9e450c48bd5233ba51a3ae3b2c7f7c4cce2428ef66675b87 +size 120137 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-en-GB.png new file mode 100644 index 0000000000..00d3249f10 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d85de510b52b116591c0e24b0ba647e3aa404a77b5e645f512a3cbf47d5291dd +size 60430 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-pseudo.png new file mode 100644 index 0000000000..fa1dd7b7ed --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/linkNewDeviceScreen.Unsupported-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a504b62f6c259b91a9a0a6d8a7b1cf18b513e86a69f5f5f9399aedf69053f65d +size 80204 diff --git a/UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift b/UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift new file mode 100644 index 0000000000..042498d131 --- /dev/null +++ b/UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift @@ -0,0 +1,13 @@ +// +// Copyright 2025 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import XCTest + +@testable import ElementX + +@MainActor +class LinkNewDeviceScreenViewModelTests: XCTestCase { } From 38d85eabd497ee934c1879fc0bcb6d3ae559b58e Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 10 Dec 2025 18:28:15 +0000 Subject: [PATCH 3/4] Fix some snapshots. --- .../__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png | 4 ++-- .../__Snapshots__/PreviewTests/labsScreen.iPhone-pseudo-0.png | 4 ++-- ...k-to-join-with-multiple-spaces-members-room-iPad-en-GB.png | 4 ++-- ...to-join-with-multiple-spaces-members-room-iPhone-en-GB.png | 4 ++-- ...ityAndPrivacyScreen.Multiple-Spaces-members-iPad-en-GB.png | 4 ++-- ...yAndPrivacyScreen.Multiple-Spaces-members-iPhone-en-GB.png | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png index f17dded3f6..87f26c624e 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffbf93956ead19e2ad6a5c1a6bb31f00b35d18861632cb00072ba43f3b3638fe -size 130184 +oid sha256:d7fb0c5b2f541ded6f34cff7d7e45865f34527bd1b64fb8c22943e7161de3ea6 +size 130468 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-pseudo-0.png index 36628d6577..90d200b32c 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec67b85d818eaf157b5627be843a1556b32500e440bae1c53f525fbcb27b874c -size 100439 +oid sha256:e88af4b4312e444dbba909dc9da21a5c024d58d8244a54875311955e50e3663a +size 100603 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPad-en-GB.png index 86dfa2a8e8..01ae3a02b4 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:beb3917bac09663e50dc7759681e4c74f737789cc52edc08087a41db0e103a1a -size 211544 +oid sha256:734d18a6d289e4b726746bdc4a6447d2d44c07b68b5aa3619e41d20743b2be43 +size 211278 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPhone-en-GB.png index 89b6c2df0a..04d6db8f86 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Ask-to-join-with-multiple-spaces-members-room-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c20f5acdf0bea460a75100551817860ab7f3db7d6049b179088bf695dc99c64 -size 147525 +oid sha256:7b8c656ed05bb2a51e925ae50156fc786c291451669942fec45ff0877c8d97b6 +size 147337 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPad-en-GB.png index 8051961542..8acccbf505 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbe3080f170b7d36524405eb26f14d180bfa543130f55b3265166638b5eb0c28 -size 169930 +oid sha256:b3bcd80b001708d7b146832168487813d1523f421cb30e17c8c540017b72db92 +size 169810 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPhone-en-GB.png index d250ff1fd6..d47e2cb91e 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/securityAndPrivacyScreen.Multiple-Spaces-members-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2feb5b0aeceb20b979b5930456074fd1a0b4cec6acb9705bdda0d86ac0778fc -size 116785 +oid sha256:3973e132104bbca48b78481b1b625d1cbc322d2346792538ec6f7f7abcc5642c +size 116706 From ceb0cd68bdffd3f7acfd81a7ce01c01cacba64b1 Mon Sep 17 00:00:00 2001 From: Doug Date: Thu, 11 Dec 2025 10:24:34 +0000 Subject: [PATCH 4/4] PR comments. --- ElementX.xcodeproj/project.pbxproj | 4 ---- .../Sources/Application/Settings/AppSettings.swift | 6 +++--- .../DeveloperOptionsScreenModels.swift | 2 +- .../View/DeveloperOptionsScreen.swift | 4 ++-- .../SettingsScreen/SettingsScreenViewModel.swift | 4 ++-- .../Sources/LinkNewDeviceScreenViewModelTests.swift | 13 ------------- 6 files changed, 8 insertions(+), 25 deletions(-) delete mode 100644 UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index f64aa5c8d5..78ce0ba746 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -289,7 +289,6 @@ 31DE6B7FD15E1C6E43885717 /* RoomRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 578AF9CE60816069536C0953 /* RoomRole.swift */; }; 32B7891D937377A59606EDFC /* UserFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */; }; 32F47002A331817F0E6BD7EB /* RoomMembershipDetailsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1434D5169F0EE319E226DA7F /* RoomMembershipDetailsProxyProtocol.swift */; }; - 33544885F5DB8016736CC610 /* LinkNewDeviceScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */; }; 339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; }; 33BA0964A308D2286B39976D /* LabsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C5AA3EF7EC67C01C75CEDD /* LabsScreen.swift */; }; 33CAC1226DFB8B5D8447D286 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; }; @@ -2721,7 +2720,6 @@ E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = ""; }; E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = ""; }; E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = ""; }; - E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = ""; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = ""; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = ""; }; @@ -4707,7 +4705,6 @@ FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */, C9AC2CC94FA06F728883B694 /* KnockRequestsListScreenViewModelTests.swift */, 6E5725BC6C63604CB769145B /* LegalInformationScreenViewModelTests.swift */, - E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */, C070FD43DC6BF4E50217965A /* LocalizationTests.swift */, 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */, 5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */, @@ -7480,7 +7477,6 @@ BA48D6AFF6421D199148C0A1 /* KnockRequestsListScreenViewModelTests.swift in Sources */, CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */, 8AC256AF0EC54658321C9241 /* LegalInformationScreenViewModelTests.swift in Sources */, - 33544885F5DB8016736CC610 /* LinkNewDeviceScreenViewModelTests.swift in Sources */, 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */, 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, 7434A7F02D587A920B376A9A /* LoginScreenViewModelTests.swift in Sources */, diff --git a/ElementX/Sources/Application/Settings/AppSettings.swift b/ElementX/Sources/Application/Settings/AppSettings.swift index 6f371e3d4f..51288d59bf 100644 --- a/ElementX/Sources/Application/Settings/AppSettings.swift +++ b/ElementX/Sources/Application/Settings/AppSettings.swift @@ -66,7 +66,7 @@ final class AppSettings { case linkPreviewsEnabled case spaceSettingsEnabled case focusEventOnNotificationTap - case newQRCodeLoginFlowsEnabled + case linkNewDeviceEnabled // Doug's tweaks 🔧 case hideUnreadMessagesBadge @@ -400,8 +400,8 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.linkPreviewsEnabled, defaultValue: false, storageType: .userDefaults(store)) var linkPreviewsEnabled - @UserPreference(key: UserDefaultsKeys.newQRCodeLoginFlowsEnabled, defaultValue: false, storageType: .userDefaults(store)) - var newQRCodeLoginFlowsEnabled + @UserPreference(key: UserDefaultsKeys.linkNewDeviceEnabled, defaultValue: false, storageType: .userDefaults(store)) + var linkNewDeviceEnabled @UserPreference(key: UserDefaultsKeys.developerOptionsEnabled, defaultValue: isDevelopmentBuild, storageType: .userDefaults(store)) var developerOptionsEnabled diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 122acc0d0c..b4eba7b245 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -57,7 +57,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var linkPreviewsEnabled: Bool { get set } var spaceSettingsEnabled: Bool { get set } - var newQRCodeLoginFlowsEnabled: Bool { get set } + var linkNewDeviceEnabled: Bool { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 13c6321510..e3c939f4ec 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -34,8 +34,8 @@ struct DeveloperOptionsScreen: View { } Section("General") { - Toggle(isOn: $context.newQRCodeLoginFlowsEnabled) { - Text("New QR code login flows") + Toggle(isOn: $context.linkNewDeviceEnabled) { + Text("Link new device with QR code") } Toggle(isOn: $context.spaceSettingsEnabled) { Text("Space settings") diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index 08b9543cee..c7ee2ebed6 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -25,7 +25,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID, userID: userSession.clientProxy.userID, - showLinkNewDeviceButton: appSettings.newQRCodeLoginFlowsEnabled, + showLinkNewDeviceButton: appSettings.linkNewDeviceEnabled, showAccountDeactivation: userSession.clientProxy.canDeactivateAccount, showDeveloperOptions: appSettings.developerOptionsEnabled, showAnalyticsSettings: appSettings.canPromptForAnalytics, @@ -36,7 +36,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo .weakAssign(to: \.state.showDeveloperOptions, on: self) .store(in: &cancellables) - appSettings.$newQRCodeLoginFlowsEnabled + appSettings.$linkNewDeviceEnabled .weakAssign(to: \.state.showLinkNewDeviceButton, on: self) .store(in: &cancellables) diff --git a/UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift b/UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift deleted file mode 100644 index 042498d131..0000000000 --- a/UnitTests/Sources/LinkNewDeviceScreenViewModelTests.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright 2025 Element Creations Ltd. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -// Please see LICENSE files in the repository root for full details. -// - -import XCTest - -@testable import ElementX - -@MainActor -class LinkNewDeviceScreenViewModelTests: XCTestCase { }