Add a service and flow coordinator for the LinkNewDevice feature. (#4859)
* Add a LinkNewDeviceService that exposes the SDK's grant QR code login methods. * Add a flow coordinator for linking a new device. Changes the presentation too.
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; };
|
||||
00C3023B6DF55024D8876B76 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3D8BEEFCA07BEA43F4F4BF77 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
012D9DDCDE6278E4E0CDCC0F /* LinkNewDeviceFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94EE2C5F0A06F146BBE3A1B1 /* LinkNewDeviceFlowCoordinator.swift */; };
|
||||
01373C1AC4839604C4FDA404 /* test_apple_image.heic in Resources */ = {isa = PBXBuildFile; fileRef = BB576F4118C35E6B5124FA22 /* test_apple_image.heic */; };
|
||||
01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */; };
|
||||
0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B746EFA112532A7B701FB914 /* RoomNotificationSettingsCustomSectionView.swift */; };
|
||||
@@ -783,6 +784,7 @@
|
||||
88F348E2CB14FF71CBBB665D /* AudioRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7475C5AE20BA896930907EA8 /* AudioRoomTimelineItemContent.swift */; };
|
||||
890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C687844F60BFF532D49A994C /* AnalyticsTests.swift */; };
|
||||
89198AE2649DD77673D5793B /* ExtensionLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A8571A8A071FB41778C016 /* ExtensionLogger.swift */; };
|
||||
89261D215E4A432E887CD156 /* GrantLoginWithQrCodeHandlerSDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD288168C48D3F76177FCBF /* GrantLoginWithQrCodeHandlerSDKMock.swift */; };
|
||||
8944548A684F1C837CEC47F4 /* RoomMembersListScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0946F77B696176E062D037 /* RoomMembersListScreenModels.swift */; };
|
||||
89658A44C9FC19B58FD1C226 /* ServerConfirmationScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */; };
|
||||
899793EFC63DF93C3E0141E7 /* RoomMemberDetailsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA60F848D1C14F873F9621A /* RoomMemberDetailsScreenCoordinator.swift */; };
|
||||
@@ -936,6 +938,7 @@
|
||||
A32384E3D85CA65342D3A908 /* TimelineMediaPreviewRedactConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75FE3F524B575D53787868C /* TimelineMediaPreviewRedactConfirmationView.swift */; };
|
||||
A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
|
||||
A36AD251013402EDBD666C75 /* AppMediatorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAC027034248429A438886B /* AppMediatorMock.swift */; };
|
||||
A37BFB32EAB8AEF6DD5BA0DC /* LinkNewDeviceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 007C16779FDCF10DA4F1A510 /* LinkNewDeviceService.swift */; };
|
||||
A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287FC98AF2664EAD79C0D902 /* UIDevice.swift */; };
|
||||
A3A7A05E8F9B7EB0E1A09A2A /* SoftLogoutScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05596E4A11A8C9346E9E54AE /* SoftLogoutScreenCoordinator.swift */; };
|
||||
A3D7110C1E75E7B4A73BE71C /* VoiceMessageRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */; };
|
||||
@@ -1253,6 +1256,7 @@
|
||||
DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */; };
|
||||
DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; };
|
||||
E010DDE938032D3B8E84CC35 /* TracingHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */; };
|
||||
E02DAD9FD8D62587049FFFEC /* LinkNewDeviceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80F04B12FA231E797B7151A8 /* LinkNewDeviceTests.swift */; };
|
||||
E0B6A569AC3E81D233B43D60 /* SettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E625B0EB2F86B37C14EF7E6 /* SettingsScreenViewModel.swift */; };
|
||||
E0C167D41A48EDB30B447DE3 /* VoiceMessageRecordingComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A5C3F7C9C1DA10CAEC6A98 /* VoiceMessageRecordingComposer.swift */; };
|
||||
E14E469CD97550D0FC58F3CA /* CancellableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52983FAFB4E0998C00EE8A /* CancellableTask.swift */; };
|
||||
@@ -1526,6 +1530,7 @@
|
||||
/* Begin PBXFileReference section */
|
||||
002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreen.swift; sourceTree = "<group>"; };
|
||||
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = "<group>"; };
|
||||
007C16779FDCF10DA4F1A510 /* LinkNewDeviceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceService.swift; sourceTree = "<group>"; };
|
||||
00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreen.swift; sourceTree = "<group>"; };
|
||||
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
011AFA4990C585D157829679 /* DeclineAndBlockScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
@@ -1719,6 +1724,7 @@
|
||||
22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||
22DB19219E6CC4D002E15D48 /* GlobalSearchScreenCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenCell.swift; sourceTree = "<group>"; };
|
||||
233D5F7E5E9F49ABF3413291 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hr; path = hr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProvider.swift; sourceTree = "<group>"; };
|
||||
2363DB6162BBCC511B67B527 /* AccessibilityTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = AccessibilityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
23674BF78CE814366EBD8762 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@@ -1985,6 +1991,7 @@
|
||||
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
|
||||
5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreen.swift; sourceTree = "<group>"; };
|
||||
54A5E6F398C269AD52C9AE21 /* EncryptionResetPasswordScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenModels.swift; sourceTree = "<group>"; };
|
||||
54A7923F0115CF17ABC8047F /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||
54AD70D6E03D2031AE1B5A52 /* TimelineReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReactionsView.swift; sourceTree = "<group>"; };
|
||||
54C4E7B46099462F12000C91 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
5557DDA438841AF5DC003D0B /* SpaceListScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
@@ -2198,6 +2205,7 @@
|
||||
7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenModels.swift; sourceTree = "<group>"; };
|
||||
8063E65441E771200108C558 /* ReadReceiptsSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceiptsSummaryView.swift; sourceTree = "<group>"; };
|
||||
80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageEventStringBuilder.swift; sourceTree = "<group>"; };
|
||||
80F04B12FA231E797B7151A8 /* LinkNewDeviceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceTests.swift; sourceTree = "<group>"; };
|
||||
810133CF215075C285FC6F3A /* test_image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = test_image.png; sourceTree = "<group>"; };
|
||||
8112846C9D9D3817689CBAF8 /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = "<group>"; };
|
||||
811E8BF34E931D51552C9C13 /* EncryptionResetScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreen.swift; sourceTree = "<group>"; };
|
||||
@@ -2317,6 +2325,7 @@
|
||||
94028A227645FA880B966211 /* WaveformSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformSource.swift; sourceTree = "<group>"; };
|
||||
9475FD81B13D50103E5290EB /* SpaceSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceSettingsScreen.swift; sourceTree = "<group>"; };
|
||||
94D670124FC3E84F23A62CCF /* APNSPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNSPayload.swift; sourceTree = "<group>"; };
|
||||
94EE2C5F0A06F146BBE3A1B1 /* LinkNewDeviceFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||
9501D11B4258DFA33BA3B40F /* ServerSelectionScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenModels.swift; sourceTree = "<group>"; };
|
||||
951F277E0585E50AC91987C8 /* DeclineAndBlockScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
955336CBD5ED73C792D1F580 /* EncryptionAuthenticity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionAuthenticity.swift; sourceTree = "<group>"; };
|
||||
@@ -2389,6 +2398,7 @@
|
||||
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; };
|
||||
A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPicker.swift; sourceTree = "<group>"; };
|
||||
A243A6E6207297123E60DE48 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
|
||||
A2723A4AF3AB9F5E18D26E49 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenContent.swift; sourceTree = "<group>"; };
|
||||
A3FBD9C2B9A5479526920399 /* BugReportScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
A40C19719687984FD9478FBE /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
|
||||
@@ -2668,6 +2678,7 @@
|
||||
D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
D66B5D86A9AB95E0E01BED82 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D67CBAFA48ED0B6FCE74F88F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D6D094C15E8DB424F1C6FC94 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
|
||||
D7149BDDE47F8AD104E644E2 /* TraceLogPack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceLogPack.swift; sourceTree = "<group>"; };
|
||||
D751AA05AD2182BFC4608DE6 /* ur */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ur; path = ur.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@@ -2864,6 +2875,7 @@
|
||||
FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformCursorView.swift; sourceTree = "<group>"; };
|
||||
FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = "<group>"; };
|
||||
FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorProtocol.swift; sourceTree = "<group>"; };
|
||||
FBD288168C48D3F76177FCBF /* GrantLoginWithQrCodeHandlerSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrantLoginWithQrCodeHandlerSDKMock.swift; sourceTree = "<group>"; };
|
||||
FC3797A2325BE44FFB478BE9 /* LeaveSpaceRoomDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceRoomDetailsCell.swift; sourceTree = "<group>"; };
|
||||
FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderMock.swift; sourceTree = "<group>"; };
|
||||
FC9044BE0E4A66F5B963E834 /* AudioFileEventsTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFileEventsTimelineView.swift; sourceTree = "<group>"; };
|
||||
@@ -4360,6 +4372,7 @@
|
||||
566FB9DA81607C2739D8C6A0 /* ChatsFlowCoordinatorStateMachine.swift */,
|
||||
A07B011547B201A836C03052 /* EncryptionResetFlowCoordinator.swift */,
|
||||
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */,
|
||||
94EE2C5F0A06F146BBE3A1B1 /* LinkNewDeviceFlowCoordinator.swift */,
|
||||
2178B951602AA921A5FD9DC8 /* MediaEventsTimelineFlowCoordinator.swift */,
|
||||
C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */,
|
||||
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */,
|
||||
@@ -5340,6 +5353,7 @@
|
||||
27DF257F5D968E5DD719583C /* BugReportTests.swift */,
|
||||
89BB11A792EF6F70B95B467E /* EncryptionResetTests.swift */,
|
||||
57AD14D3ADADE8F6A10F9E88 /* EncryptionSettingsTests.swift */,
|
||||
80F04B12FA231E797B7151A8 /* LinkNewDeviceTests.swift */,
|
||||
DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */,
|
||||
E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */,
|
||||
44DDC82DB6A84E700CD5DEC0 /* RoomRolesAndPermissionsTests.swift */,
|
||||
@@ -5683,6 +5697,7 @@
|
||||
EBD19057FDB154A44335CE62 /* AuthenticationClientFactory.swift */,
|
||||
F3A1AB5A84D843B6AC8D5F1E /* AuthenticationService.swift */,
|
||||
5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */,
|
||||
007C16779FDCF10DA4F1A510 /* LinkNewDeviceService.swift */,
|
||||
);
|
||||
path = Authentication;
|
||||
sourceTree = "<group>";
|
||||
@@ -6380,6 +6395,7 @@
|
||||
children = (
|
||||
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */,
|
||||
0A81FD0C60175FA081EB19AD /* EventTimelineItem.swift */,
|
||||
FBD288168C48D3F76177FCBF /* GrantLoginWithQrCodeHandlerSDKMock.swift */,
|
||||
5EFB1D29B0870AFB6A56E9B8 /* IdentityResetHandleSDKMock.swift */,
|
||||
580BDCD23DD02481AB5FFB47 /* LeaveSpaceHandleSDKMock.swift */,
|
||||
);
|
||||
@@ -7025,6 +7041,7 @@
|
||||
fa,
|
||||
fi,
|
||||
fr,
|
||||
hr,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
@@ -7932,6 +7949,7 @@
|
||||
D34E328E9E65904358248FDD /* GlobalSearchScreenModels.swift in Sources */,
|
||||
55D18AA4F4A2257642EBDB94 /* GlobalSearchScreenViewModel.swift in Sources */,
|
||||
E32A18802EB37EEE3EF7B965 /* GlobalSearchScreenViewModelProtocol.swift in Sources */,
|
||||
89261D215E4A432E887CD156 /* GrantLoginWithQrCodeHandlerSDKMock.swift in Sources */,
|
||||
F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */,
|
||||
E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.swift in Sources */,
|
||||
0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */,
|
||||
@@ -8024,11 +8042,13 @@
|
||||
A9D349478F7D4A2B1E40CEF9 /* LegalInformationScreenViewModelProtocol.swift in Sources */,
|
||||
5E597B9959BDAE7A67DBD5B2 /* LinkMetadataProvider.swift in Sources */,
|
||||
112C6F1C493B3F26AB22716A /* LinkMetadataProviderProtocol.swift in Sources */,
|
||||
012D9DDCDE6278E4E0CDCC0F /* LinkNewDeviceFlowCoordinator.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 */,
|
||||
A37BFB32EAB8AEF6DD5BA0DC /* LinkNewDeviceService.swift in Sources */,
|
||||
866FA35E7A2339EF8B6D91CA /* LinkPreviewView.swift in Sources */,
|
||||
6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */,
|
||||
D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */,
|
||||
@@ -8670,6 +8690,7 @@
|
||||
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */,
|
||||
8D24671992A1C1753B211221 /* EncryptionResetTests.swift in Sources */,
|
||||
E5E43A0CA99AF5BA11B194A2 /* EncryptionSettingsTests.swift in Sources */,
|
||||
E02DAD9FD8D62587049FFFEC /* LinkNewDeviceTests.swift in Sources */,
|
||||
A950C95855C474F75B30CA7B /* PollFormScreenTests.swift in Sources */,
|
||||
B5BCE012F9E7C45D1C76108E /* RoomMembersListScreenTests.swift in Sources */,
|
||||
DBC3FDE1540B39702A117D8E /* RoomRolesAndPermissionsTests.swift in Sources */,
|
||||
@@ -8751,6 +8772,7 @@
|
||||
C715CFE00686DACA59D836EA /* fa */,
|
||||
A9E88667D393612FD5D84718 /* fi */,
|
||||
CEE20623EB4A9B88FB29F2BA /* fr */,
|
||||
54A7923F0115CF17ABC8047F /* hr */,
|
||||
D196116D2DD3F2757D45FCB7 /* hu */,
|
||||
330AF4D121C3396F7A14B21D /* id */,
|
||||
61B33F23681660E940BA57F4 /* it */,
|
||||
@@ -8783,6 +8805,7 @@
|
||||
48CE6BF18E542B32FA52CE06 /* fa */,
|
||||
057B747CF045D3C6C30EAB2C /* fi */,
|
||||
653610CB5F9776EAAAB98155 /* fr */,
|
||||
233D5F7E5E9F49ABF3413291 /* hr */,
|
||||
C95ADE8D9527523572532219 /* hu */,
|
||||
475D47D0BFE961B02BAC5D49 /* id */,
|
||||
6FC5015B9634698BDB8701AF /* it */,
|
||||
@@ -8826,6 +8849,7 @@
|
||||
A9873374E72AA53260AE90A2 /* fa */,
|
||||
434522ED2BDED08759048077 /* fi */,
|
||||
CC680E0E79D818706CB28CF8 /* fr */,
|
||||
D6D094C15E8DB424F1C6FC94 /* hr */,
|
||||
624244C398804ADC885239AA /* hu */,
|
||||
EF98A02DED04075F7CF0C721 /* id */,
|
||||
7B04BD3874D736127A8156B8 /* it */,
|
||||
@@ -8867,6 +8891,7 @@
|
||||
70F8DAEF1A8131BDFD4CDE83 /* eu */,
|
||||
24E637CF570711FB5FD63DEA /* fi */,
|
||||
ACD7BD6BEE21264F6677904A /* fr */,
|
||||
A2723A4AF3AB9F5E18D26E49 /* hr */,
|
||||
1D652E78832289CD9EB64488 /* hu */,
|
||||
7199693797B66245EF97BCF5 /* id */,
|
||||
44C314C00533E2C297796B60 /* it */,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
enum LinkNewDeviceFlowCoordinatorAction {
|
||||
case requestOIDCAuthorisation(URL)
|
||||
case dismiss
|
||||
}
|
||||
|
||||
class LinkNewDeviceFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let flowParameters: CommonFlowParameters
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<LinkNewDeviceFlowCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<LinkNewDeviceFlowCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(navigationStackCoordinator: NavigationStackCoordinator,
|
||||
flowParameters: CommonFlowParameters) {
|
||||
self.navigationStackCoordinator = navigationStackCoordinator
|
||||
self.flowParameters = flowParameters
|
||||
}
|
||||
|
||||
func start(animated: Bool) {
|
||||
presentLinkNewDeviceScreen()
|
||||
}
|
||||
|
||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func clearRoute(animated: Bool) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
private func presentLinkNewDeviceScreen() {
|
||||
let coordinator = LinkNewDeviceScreenCoordinator(parameters: .init(clientProxy: flowParameters.userSession.clientProxy))
|
||||
coordinator.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .linkMobileDevice(let progressPublisher):
|
||||
break
|
||||
case .linkDesktopComputer:
|
||||
break
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
||||
// periphery:ignore - retaining purpose
|
||||
private var encryptionSettingsFlowCoordinator: EncryptionSettingsFlowCoordinator?
|
||||
// periphery:ignore - retaining purpose
|
||||
private var linkNewDeviceFlowCoordinator: LinkNewDeviceFlowCoordinator?
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@@ -84,7 +86,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .userDetails:
|
||||
presentUserDetailsEditScreen()
|
||||
case .linkNewDevice:
|
||||
presentLinkNewDeviceScreen()
|
||||
startLinkNewDeviceFlow()
|
||||
case let .manageAccount(url):
|
||||
presentAccountManagementURL(url)
|
||||
case .analytics:
|
||||
@@ -169,6 +171,31 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
}
|
||||
|
||||
private func startLinkNewDeviceFlow() {
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
let flowCoordinator = LinkNewDeviceFlowCoordinator(navigationStackCoordinator: stackCoordinator,
|
||||
flowParameters: flowParameters)
|
||||
flowCoordinator.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
case .requestOIDCAuthorisation(let url):
|
||||
presentAccountManagementURL(url)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
linkNewDeviceFlowCoordinator = flowCoordinator
|
||||
flowCoordinator.start()
|
||||
|
||||
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
||||
self?.linkNewDeviceFlowCoordinator = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func presentAnalyticsScreen() {
|
||||
let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: flowParameters.appSettings,
|
||||
analytics: flowParameters.analytics))
|
||||
@@ -259,9 +286,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
}
|
||||
|
||||
|
||||
// MARK: OIDC Account Management
|
||||
|
||||
|
||||
private var accountSettingsPresenter: OIDCAccountSettingsPresenter?
|
||||
private func presentAccountManagementURL(_ url: URL) {
|
||||
// Note to anyone in the future if you come back here to make this open in Safari instead of a WAS.
|
||||
@@ -271,27 +298,4 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4019,6 +4019,70 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
return removeUserAvatarReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - linkNewDeviceService
|
||||
|
||||
var linkNewDeviceServiceUnderlyingCallsCount = 0
|
||||
var linkNewDeviceServiceCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return linkNewDeviceServiceUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = linkNewDeviceServiceUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
linkNewDeviceServiceUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
linkNewDeviceServiceUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var linkNewDeviceServiceCalled: Bool {
|
||||
return linkNewDeviceServiceCallsCount > 0
|
||||
}
|
||||
|
||||
var linkNewDeviceServiceUnderlyingReturnValue: LinkNewDeviceService!
|
||||
var linkNewDeviceServiceReturnValue: LinkNewDeviceService! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return linkNewDeviceServiceUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: LinkNewDeviceService? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = linkNewDeviceServiceUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
linkNewDeviceServiceUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
linkNewDeviceServiceUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var linkNewDeviceServiceClosure: (() -> LinkNewDeviceService)?
|
||||
|
||||
func linkNewDeviceService() -> LinkNewDeviceService {
|
||||
linkNewDeviceServiceCallsCount += 1
|
||||
if let linkNewDeviceServiceClosure = linkNewDeviceServiceClosure {
|
||||
return linkNewDeviceServiceClosure()
|
||||
} else {
|
||||
return linkNewDeviceServiceReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - deactivateAccount
|
||||
|
||||
var deactivateAccountPasswordEraseDataUnderlyingCallsCount = 0
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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
|
||||
import MatrixRustSDK
|
||||
|
||||
extension GrantLoginWithQrCodeHandlerSDKMock {
|
||||
struct Configuration {
|
||||
var generateDelay: Duration = .seconds(0)
|
||||
var generatedBase64QRCode = """
|
||||
TUFUUklYAgS0yzZ1QVpQ1jlnoxWX3d5jrWRFfELxjS2gN7pz9y+3PABaaHR0
|
||||
cHM6Ly9zeW5hcHNlLW9pZGMubGFiLmVsZW1lbnQuZGV2L19zeW5hcHNlL2Ns
|
||||
aWVudC9yZW5kZXp2b3VzLzAxSFg5SzAwUTFINktQRDQ3RUc0RzFUM1hHACVo
|
||||
dHRwczovL3N5bmFwc2Utb2lkYy5sYWIuZWxlbWVudC5kZXYv
|
||||
"""
|
||||
}
|
||||
|
||||
convenience init(_ configuration: Configuration) {
|
||||
self.init()
|
||||
|
||||
generateProgressListenerClosure = { listener in
|
||||
Task {
|
||||
try await Task.sleep(for: configuration.generateDelay)
|
||||
let bytes = Data(base64Encoded: configuration.generatedBase64QRCode) ?? Data()
|
||||
try listener.onUpdate(state: .qrReady(qrCode: .fromBytes(bytes: bytes)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ enum A11yIdentifiers {
|
||||
static let roomPollsHistoryScreen = RoomPollsHistoryScreen()
|
||||
static let manageRoomMemberSheet = ManageRoomMemberSheet()
|
||||
static let spaceListScreen = SpaceListScreen()
|
||||
static let linkNewDeviceScreen = LinkNewDeviceScreen()
|
||||
|
||||
struct AlertInfo {
|
||||
let primaryButton = "alert_info-primary_button"
|
||||
@@ -314,4 +315,10 @@ enum A11yIdentifiers {
|
||||
"\(roomNamePrefix):\(name)"
|
||||
}
|
||||
}
|
||||
|
||||
struct LinkNewDeviceScreen {
|
||||
let cancel = "link_new_device_screen-cancel"
|
||||
let mobileDevice = "link_new_device_screen-mobile_device"
|
||||
let desktopComputer = "link_new_device_screen-desktop_computer"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@ extension SDKListener: QrLoginProgressListener where T == QrLoginProgress {
|
||||
func onUpdate(state: QrLoginProgress) { onUpdateClosure(state) }
|
||||
}
|
||||
|
||||
extension SDKListener: GrantQrLoginProgressListener where T == GrantQrLoginProgress {
|
||||
func onUpdate(state: GrantQrLoginProgress) { onUpdateClosure(state) }
|
||||
}
|
||||
|
||||
extension SDKListener: GrantGeneratedQrLoginProgressListener where T == GrantGeneratedQrLoginProgress {
|
||||
func onUpdate(state: GrantGeneratedQrLoginProgress) { onUpdateClosure(state) }
|
||||
}
|
||||
|
||||
// MARK: ClientProxy
|
||||
|
||||
extension SDKListener: MediaPreviewConfigListener where T == MediaPreviewConfig? {
|
||||
|
||||
@@ -9,7 +9,7 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
enum LinkNewDeviceScreenCoordinatorAction {
|
||||
case linkMobileDevice
|
||||
case linkMobileDevice(LinkNewDeviceService.GenerateProgressPublisher)
|
||||
case linkDesktopComputer
|
||||
case dismiss
|
||||
}
|
||||
@@ -38,8 +38,8 @@ final class LinkNewDeviceScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .linkMobileDevice:
|
||||
actionsSubject.send(.linkMobileDevice)
|
||||
case .linkMobileDevice(let progressPublisher):
|
||||
actionsSubject.send(.linkMobileDevice(progressPublisher))
|
||||
case .linkDesktopComputer:
|
||||
actionsSubject.send(.linkDesktopComputer)
|
||||
case .dismiss:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
enum LinkNewDeviceScreenViewModelAction {
|
||||
case linkMobileDevice
|
||||
case linkMobileDevice(LinkNewDeviceService.GenerateProgressPublisher)
|
||||
case linkDesktopComputer
|
||||
case dismiss
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
typealias LinkNewDeviceScreenViewModelType = StateStoreViewModelV2<LinkNewDeviceScreenViewState, LinkNewDeviceScreenViewAction>
|
||||
@@ -33,7 +34,7 @@ class LinkNewDeviceScreenViewModel: LinkNewDeviceScreenViewModelType, LinkNewDev
|
||||
|
||||
switch viewAction {
|
||||
case .linkMobileDevice:
|
||||
linkMobileDevice()
|
||||
Task { await linkMobileDevice() }
|
||||
case .linkDesktopComputer:
|
||||
actionsSubject.send(.linkDesktopComputer)
|
||||
case .dismiss:
|
||||
@@ -51,11 +52,27 @@ class LinkNewDeviceScreenViewModel: LinkNewDeviceScreenViewModelType, LinkNewDev
|
||||
}
|
||||
}
|
||||
|
||||
private func linkMobileDevice() {
|
||||
private func linkMobileDevice() async {
|
||||
state.mode = .readyToLink(isGeneratingCode: true)
|
||||
|
||||
// TODO: Generate a QR code.
|
||||
let linkNewDeviceService = clientProxy.linkNewDeviceService()
|
||||
|
||||
actionsSubject.send(.linkMobileDevice)
|
||||
let progressPublisher = linkNewDeviceService.generateQRCode()
|
||||
|
||||
do {
|
||||
_ = try await progressPublisher.values
|
||||
.first { progress in
|
||||
switch progress {
|
||||
case .qrReady: true
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
actionsSubject.send(.linkMobileDevice(progressPublisher))
|
||||
state.mode = .readyToLink(isGeneratingCode: false)
|
||||
} catch {
|
||||
#warning("Needs some form of re-usable error handling, will handle with the next screen.")
|
||||
state.mode = .notSupported
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ struct LinkNewDeviceScreen: View {
|
||||
.backgroundStyle(.compound.bgSubtleSecondary)
|
||||
.navigationTitle(L10n.commonLinkNewDevice)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { toolbar }
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -86,11 +87,13 @@ struct LinkNewDeviceScreen: View {
|
||||
}
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.accessibilityIdentifier(A11yIdentifiers.linkNewDeviceScreen.mobileDevice)
|
||||
|
||||
Button { context.send(viewAction: .linkDesktopComputer) } label: {
|
||||
Label(L10n.screenLinkNewDeviceRootDesktopComputer, icon: \.computer)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.accessibilityIdentifier(A11yIdentifiers.linkNewDeviceScreen.desktopComputer)
|
||||
}
|
||||
.disabled(isGeneratingCode)
|
||||
case .notSupported:
|
||||
@@ -100,6 +103,15 @@ struct LinkNewDeviceScreen: View {
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
}
|
||||
|
||||
var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(L10n.actionCancel) {
|
||||
context.send(viewAction: .dismiss)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.linkNewDeviceScreen.cancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
@@ -148,6 +160,7 @@ struct LinkNewDeviceScreen_Previews: PreviewProvider, TestablePreview {
|
||||
return false
|
||||
}
|
||||
}
|
||||
clientProxy.linkNewDeviceServiceReturnValue = .init(handler: GrantLoginWithQrCodeHandlerSDKMock(.init(generateDelay: .seconds(20))))
|
||||
|
||||
let viewModel = LinkNewDeviceScreenViewModel(clientProxy: clientProxy)
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
state.state = .error(.expired)
|
||||
case .deviceNotSupported:
|
||||
state.state = .error(.deviceNotSupported)
|
||||
case .unknown:
|
||||
case .deviceAlreadySignedIn, .unknown:
|
||||
state.state = .error(.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ enum QRCodeLoginError: Error, Equatable {
|
||||
case expired
|
||||
case deviceNotSupported
|
||||
case deviceNotSignedIn
|
||||
case deviceAlreadySignedIn
|
||||
case unknown
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
class LinkNewDeviceService {
|
||||
typealias GenerateProgressPublisher = CurrentValuePublisher<GenerateProgress, QRCodeLoginError>
|
||||
typealias ScanProgressPublisher = CurrentValuePublisher<ScanProgress, QRCodeLoginError>
|
||||
|
||||
enum GenerateProgress {
|
||||
case starting
|
||||
case qrReady(QrCodeData)
|
||||
case qrScanned(CheckCodeSenderProtocol)
|
||||
case waitingForAuthorisation(verificationURI: String)
|
||||
case syncingSecrets
|
||||
case done
|
||||
}
|
||||
|
||||
enum ScanProgress {
|
||||
case starting
|
||||
case establishingSecureChannel(checkCode: UInt8, checkCodeString: String)
|
||||
case waitingForAuth(verificationURI: String)
|
||||
case syncingSecrets
|
||||
case done
|
||||
}
|
||||
|
||||
private let grantLoginHandler: GrantLoginWithQrCodeHandlerProtocol
|
||||
|
||||
init(handler: GrantLoginWithQrCodeHandlerProtocol) {
|
||||
grantLoginHandler = handler
|
||||
}
|
||||
|
||||
func generateQRCode() -> GenerateProgressPublisher {
|
||||
let progressSubject = CurrentValueSubject<GenerateProgress, QRCodeLoginError>(.starting)
|
||||
let listener = SDKListener { progressSubject.send(.init(rustProgress: $0)) }
|
||||
|
||||
Task {
|
||||
do {
|
||||
// TODO: we need a way to cancel the in progress grant if the user hit the cancel button
|
||||
try await grantLoginHandler.generate(progressListener: listener) // The success state is handled by the listener.
|
||||
} catch let error as HumanQrGrantLoginError {
|
||||
MXLog.error("QR code reciprocate error: \(error)")
|
||||
progressSubject.send(completion: .failure(.init(rustError: error)))
|
||||
} catch {
|
||||
MXLog.error("QR code reciprocate unknown error: \(error)")
|
||||
progressSubject.send(completion: .failure(.unknown))
|
||||
}
|
||||
}
|
||||
|
||||
return progressSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
func scanQRCode(_ scannedQRData: Data) -> ScanProgressPublisher {
|
||||
let progressSubject = CurrentValueSubject<ScanProgress, QRCodeLoginError>(.starting)
|
||||
let listener = SDKListener { progressSubject.send(.init(rustProgress: $0)) }
|
||||
|
||||
let qrCodeData: QrCodeData
|
||||
do {
|
||||
qrCodeData = try QrCodeData.fromBytes(bytes: scannedQRData)
|
||||
} catch {
|
||||
MXLog.error("QR code decode error: \(error)")
|
||||
progressSubject.send(completion: .failure(.invalidQRCode))
|
||||
return progressSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
#warning("Check intent/server name here…")
|
||||
|
||||
Task {
|
||||
do {
|
||||
// TODO: it would be nice to be able to cancel the grant at the SDK level if the user hits the cancel button
|
||||
try await grantLoginHandler.scan(qrCodeData: qrCodeData, progressListener: listener) // The success state is handled by the listener.
|
||||
} catch let error as HumanQrGrantLoginError {
|
||||
MXLog.error("QR code reciprocate error: \(error)")
|
||||
progressSubject.send(completion: .failure(.init(rustError: error)))
|
||||
} catch {
|
||||
MXLog.error("QR code reciprocate unknown error: \(error)")
|
||||
progressSubject.send(completion: .failure(.unknown))
|
||||
}
|
||||
}
|
||||
|
||||
return progressSubject.asCurrentValuePublisher()
|
||||
}
|
||||
}
|
||||
|
||||
extension LinkNewDeviceService.GenerateProgress: CustomStringConvertible {
|
||||
init(rustProgress: GrantGeneratedQrLoginProgress) {
|
||||
self = switch rustProgress {
|
||||
case .starting: .starting
|
||||
case .qrReady(let qrCode): .qrReady(qrCode)
|
||||
case .qrScanned(let checkCodeSender): .qrScanned(checkCodeSender)
|
||||
case .waitingForAuth(let verificationUri): .waitingForAuthorisation(verificationURI: verificationUri)
|
||||
case .syncingSecrets: .syncingSecrets
|
||||
case .done: .done
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .starting: "starting"
|
||||
case .qrReady: "qrReady"
|
||||
case .qrScanned: "qrScanned"
|
||||
case .waitingForAuthorisation: "waitingForAuthorisation"
|
||||
case .syncingSecrets: "syncingSecrets"
|
||||
case .done: "done"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LinkNewDeviceService.ScanProgress: CustomStringConvertible {
|
||||
init(rustProgress: GrantQrLoginProgress) {
|
||||
self = switch rustProgress {
|
||||
case .starting: .starting
|
||||
case .establishingSecureChannel(let checkCode, let checkCodeString): .establishingSecureChannel(checkCode: checkCode, checkCodeString: checkCodeString)
|
||||
case .waitingForAuth(let verificationUri): .waitingForAuth(verificationURI: verificationUri)
|
||||
case .syncingSecrets: .syncingSecrets
|
||||
case .done: .done
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .starting: "starting"
|
||||
case .establishingSecureChannel: "establishingSecureChannel"
|
||||
case .waitingForAuth: "waitingForAuth"
|
||||
case .syncingSecrets: "syncingSecrets"
|
||||
case .done: "done"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension QRCodeLoginError {
|
||||
init(rustError: HumanQrGrantLoginError) {
|
||||
self = switch rustError {
|
||||
case .InvalidCheckCode:
|
||||
.connectionInsecure
|
||||
case .UnsupportedProtocol:
|
||||
.linkingNotSupported
|
||||
case .Unknown, .NotFound, .MissingSecretsBackup, .DeviceIdAlreadyInUse, .UnableToCreateDevice:
|
||||
.unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -715,13 +715,9 @@ class ClientProxy: ClientProxyProtocol {
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
func logout() async {
|
||||
do {
|
||||
try await client.logout()
|
||||
} catch {
|
||||
MXLog.error("Failed logging out with error: \(error)")
|
||||
}
|
||||
|
||||
func linkNewDeviceService() -> LinkNewDeviceService {
|
||||
LinkNewDeviceService(handler: client.newGrantLoginWithQrCodeHandler())
|
||||
}
|
||||
|
||||
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError> {
|
||||
@@ -734,6 +730,14 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func logout() async {
|
||||
do {
|
||||
try await client.logout()
|
||||
} catch {
|
||||
MXLog.error("Failed logging out with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func setPusher(with configuration: PusherConfiguration) async throws {
|
||||
try await client.setPusher(identifiers: configuration.identifiers,
|
||||
kind: configuration.kind,
|
||||
|
||||
@@ -194,7 +194,9 @@ protocol ClientProxyProtocol: AnyObject {
|
||||
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError>
|
||||
|
||||
func removeUserAvatar() async -> Result<Void, ClientProxyError>
|
||||
|
||||
|
||||
func linkNewDeviceService() -> LinkNewDeviceService
|
||||
|
||||
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
|
||||
|
||||
func logout() async
|
||||
|
||||
@@ -734,6 +734,40 @@ class MockScreen: Identifiable {
|
||||
coordinator.start()
|
||||
|
||||
return navigationStackCoordinator
|
||||
case .linkNewDevice:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let flowCoordinator = LinkNewDeviceFlowCoordinator(navigationStackCoordinator: navigationStackCoordinator,
|
||||
flowParameters: CommonFlowParameters(userSession: UserSessionMock(.init()),
|
||||
bugReportService: BugReportServiceMock(.init()),
|
||||
elementCallService: ElementCallServiceMock(.init()),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
linkMetadataProvider: LinkMetadataProvider(),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
notificationManager: NotificationManagerMock(),
|
||||
stateMachineFactory: StateMachineFactory()))
|
||||
flowCoordinator.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss:
|
||||
navigationRootCoordinator.setSheetCoordinator(nil)
|
||||
case .requestOIDCAuthorisation:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
retainedState.append(flowCoordinator)
|
||||
flowCoordinator.start()
|
||||
|
||||
// Use a sheet on top the the placeholder so we can test the dismissal.
|
||||
navigationRootCoordinator.setSheetCoordinator(navigationStackCoordinator)
|
||||
return PlaceholderScreenCoordinator(hideBrandChrome: false)
|
||||
case .autoUpdatingTimeline:
|
||||
let appSettings: AppSettings = ServiceLocator.shared.settings
|
||||
appSettings.hasRunIdentityConfirmationOnboarding = true
|
||||
|
||||
@@ -25,6 +25,7 @@ enum UITestsScreenIdentifier: String {
|
||||
case encryptionSettings
|
||||
case encryptionSettingsOutOfSync
|
||||
case encryptionReset
|
||||
case linkNewDevice
|
||||
case roomLayoutBottom
|
||||
case roomLayoutMiddle
|
||||
case roomLayoutTop
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ad18b20c43c8f8127c485c8b434064de0b237d25ee177c98a989eb65113248e4
|
||||
size 109374
|
||||
oid sha256:46aea5299c049f35dae8f1600616c6223a596fd2f8679f92989c16664cf4dbc1
|
||||
size 111944
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5645b5827fdf244280e80f6fc16b421a7dc0635b16b6f80b2b50a1907844b4cf
|
||||
size 123395
|
||||
oid sha256:6a186ec9227165649b719f4dd798e3ac890369b308e1dd96bd21fa433d2af9b2
|
||||
size 126198
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cc447ed7e8194814387d698437d3adaeb4b7b6f687842598fb84240d58d4e0fe
|
||||
size 61504
|
||||
oid sha256:6a4f19b2fe16544a545520570eb2015aab26546ec8050a3b282715e2ec38826e
|
||||
size 63808
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8510dfa5fe61fc0fe236ccf4bf69d3fec03a3cd5fb14182c93da27fb93b94356
|
||||
size 82818
|
||||
oid sha256:61b9690e8d8521fcf9d64303caab7d6daf08bafb6d4b175cfdb3ec000d151ffb
|
||||
size 85214
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5a9223f99dfb8209fc21042a2656ca4a050a4f8f1a1d7723ef613bcf54706b5
|
||||
size 98823
|
||||
oid sha256:8dfcf396e71ca8a417e88b2c27d452030b1ba092f7e91b2b20737394ca74e7db
|
||||
size 101393
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:79aba2f30b36dc42149b05844746297c3959cce54108fb6e9043d22a1e441b55
|
||||
size 109776
|
||||
oid sha256:f95f8fe398860d1fad8b18babed1e1806c524af15cb9d9685c221cf1a0fb00a0
|
||||
size 112579
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c49ca4038efc3d6059b8f8cd715b67741c4c45e4b817ff61aa5569ea3d76ed38
|
||||
size 65128
|
||||
oid sha256:ed16c902c68752356fa5ee71aa684988a4b4bdcf4e505f75d970cb1f750aba96
|
||||
size 64763
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7f94dff4155601b73d57df0294c051463ff330fe431a2c46a5a5b198cdc372ea
|
||||
size 109664
|
||||
oid sha256:cc4f1056724de24ad5aaf7181a831e8b8c16e42b5e8771915dde72b957c98fbb
|
||||
size 112234
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a049866c334facc710309e02999e4869a46839cf0448b68660d21e0cb6842a43
|
||||
size 123411
|
||||
oid sha256:a152660e6fa14a8ae66af91ce8e43020fc4718dd300a8049e4375b697e705264
|
||||
size 126214
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c0d7ce68e28cf45d36e04d4f6b9e62722ed67e7fc5be42287b43e1bc68924a67
|
||||
size 61909
|
||||
oid sha256:844e98b0b406dafa543d47f773b5aef1367fa6e7bca1ed0b0735f26c55ffec6f
|
||||
size 64213
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c637c11e208a467b4e4d77a2fb9f1ea0fe6602dcc0788934659328074ed4f3bc
|
||||
size 81263
|
||||
oid sha256:37a8a52edf87070a3c325822dac1c0a29be41ed46f9d803ad5bbe6e2d4bd3015
|
||||
size 83659
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d1959686b35a78a6c49ab16e9eb96f5ca879151ecf95969e13acf31d8c61f461
|
||||
size 106107
|
||||
oid sha256:5ee9d2b8142ec642ea6f8d606769aaf2b4c1742855e5d67370f8d1b270a6e15a
|
||||
size 108670
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f15c62942085467d9e450c48bd5233ba51a3ae3b2c7f7c4cce2428ef66675b87
|
||||
size 120137
|
||||
oid sha256:67621aa88efeeb7c589bc91087970211de6fa1ff35794c26245af3e483712f31
|
||||
size 123533
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d85de510b52b116591c0e24b0ba647e3aa404a77b5e645f512a3cbf47d5291dd
|
||||
size 60430
|
||||
oid sha256:a958c859edf66c6e11503fd590c04dc6937e8c322ade159bdaa4c1158f237cb2
|
||||
size 63031
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a504b62f6c259b91a9a0a6d8a7b1cf18b513e86a69f5f5f9399aedf69053f65d
|
||||
size 80204
|
||||
oid sha256:ddb567a2b6e080169771b6dece8ad4b47d80f83b207d6c0147531a8882fac7cb
|
||||
size 82338
|
||||
|
||||
26
UITests/Sources/LinkNewDeviceTests.swift
Normal file
26
UITests/Sources/LinkNewDeviceTests.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@MainActor
|
||||
class LinkNewDeviceTests: XCTestCase {
|
||||
enum Step {
|
||||
static let selectDevice = 1
|
||||
static let dismissed = 99
|
||||
}
|
||||
|
||||
func testFlow() async throws {
|
||||
let app = Application.launch(.linkNewDevice)
|
||||
try await app.assertScreenshot(step: Step.selectDevice)
|
||||
|
||||
let cancelButton = app.buttons[A11yIdentifiers.linkNewDeviceScreen.cancel]
|
||||
cancelButton.tap()
|
||||
|
||||
try await app.assertScreenshot(step: Step.dismissed)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9dbfadaaf361b79c626e35ab3cf5f42149e18de2538175e796a704cc21dc8411
|
||||
size 158261
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:da689e9ce1dc5b5fb809eda3e1886a9cec9d4b7c04271b9c8652a9893f910674
|
||||
size 115287
|
||||
Binary file not shown.
Reference in New Issue
Block a user