Onboarding flow coordinator and FTUE changes (#2578)

Fixes #2595, fixes #2594, fixes #2593, fixes #2592, fixes #2591
This commit is contained in:
Stefan Ceriu 2024-03-21 14:01:23 +02:00 committed by GitHub
parent 9e6b6ba60a
commit a62c96f1c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
176 changed files with 1886 additions and 912 deletions

View File

@ -22,6 +22,7 @@
/* Begin PBXBuildFile section */
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; };
01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */; };
0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B746EFA112532A7B701FB914 /* RoomNotificationSettingsCustomSectionView.swift */; };
01B63F1A04A276B39AC17014 /* CallInviteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */; };
020C530986D7B97631877FEF /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */; };
@ -36,6 +37,7 @@
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; };
04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */; };
059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; };
05BAB510CBC2ED35C154ADD0 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFD012C3A9F5EF276DDD4AA /* AnalyticsPromptScreenViewModelProtocol.swift */; };
05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */; };
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
06AA515C7053FD7E17A5CF81 /* RoomNotificationSettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */; };
@ -56,13 +58,13 @@
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; };
09EF4222EEBBA1A7B8F4071E /* VoiceMessageRecordingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E61DDB42C0DE429C0955D8 /* VoiceMessageRecordingButton.swift */; };
0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; };
0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */; };
0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; };
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
0B57C2399B9E1CE5CE0D8005 /* ComposerToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D121B4FCFC38DBCC17BCC6D6 /* ComposerToolbar.swift */; };
0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; };
0BE4D5CBF86956410F071F91 /* CreateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */; };
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; };
0C346A4AD174F441EDB1414E /* IdentityConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */; };
0C47AE2CA7929CB3B0E2D793 /* ServerSelectionScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */; };
0C58A846F61949B1D545D661 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421E716C521F96D24ECE69B3 /* NoticeRoomTimelineItem.swift */; };
0C797CD650DFD2876BEC5173 /* CollapsibleReactionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7C6DDBB5D12F6EF6A3D6E1 /* CollapsibleReactionLayout.swift */; };
@ -82,6 +84,7 @@
0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */; };
1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0376C429FAB1687C3D905F3E /* MockCoder.swift */; };
119AE9A3FC6E0606C1146528 /* NotificationSettingsEditScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97F8963B14EB0AF3940DDBF /* NotificationSettingsEditScreenRoomCell.swift */; };
11A6B8E3CBDBF0A4107FF4CE /* OnboardingFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */; };
126EE01D8BEAEF26105D83C5 /* RoomDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */; };
12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */; };
12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; };
@ -149,6 +152,7 @@
241CDEFE23819867D9B39066 /* RoomChangePermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE75941583A033A9EDC9FE0 /* RoomChangePermissionsScreenViewModel.swift */; };
244407B18B2F2D6466BA5961 /* RoomChangeRolesScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82DFA1B7B088D033E0794B82 /* RoomChangeRolesScreenCoordinator.swift */; };
245F7FE5961BD10C145A26E0 /* UITimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA689E792E679F5E3956F21 /* UITimelineView.swift */; };
24A1BBADAC43DC3F3A7347DA /* AnalyticsPromptScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */; };
24A75F72EEB7561B82D726FD /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; };
24B7CD41342C143117ADA768 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B1CC9AA154F4D5435BF60A /* Comparable.swift */; };
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; };
@ -182,6 +186,7 @@
2BA59D0AEFB4B82A2EC2A326 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50009897F60FAE7D63EF5E5B /* Kingfisher */; };
2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; };
2BBA132149DEBED6624084A8 /* MessageForwardingScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */; };
2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */; };
2C4C750D0039AFABDF24236C /* TemplateScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */; };
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */; };
2CA6ABBC9A88EB89EA52FCCB /* ConfettiScene.scn in Resources */ = {isa = PBXBuildFile; fileRef = B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */; };
@ -250,6 +255,7 @@
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; };
3DAD62988F072607441CB7A5 /* PollFormScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */; };
3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861FE915C7B5466E6962BBA /* StartChatScreen.swift */; };
3E7B65C2C97748D5D65AAA8B /* NotificationPermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB2FC2AA9A07EE792DF65CF /* NotificationPermissionsScreenModels.swift */; };
3EC5A41F9FB7DD63A4DC6144 /* RoomChangeRolesScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14B1DE3E2D5D26732C49036 /* RoomChangeRolesScreenViewModel.swift */; };
3EC698F80DDEEFA273857841 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893777A4997BBDB68079D4F5 /* ArrayTests.swift */; };
3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
@ -278,6 +284,7 @@
44BDD670FF9095ACE240A3A2 /* VoiceMessageMediaManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */; };
44DA28B1E1F9C97C5795F7B3 /* AppLockSetupUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A1BBEF7318CA6B6ACCF4AE /* AppLockSetupUITests.swift */; };
44F0E1B576C7599DF8022071 /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 391D11F92DFC91666AA1503F /* SwiftOGG */; };
454311EAC17D778E19F46592 /* NotificationPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */; };
454F8DDC4442C0DE54094902 /* LABiometryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F219838588C62198E726E3 /* LABiometryType.swift */; };
4557192F5B15A8D9BB920232 /* AdvancedSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E492690C8B27A892C194CC4 /* AdvancedSettingsScreenCoordinator.swift */; };
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
@ -292,13 +299,14 @@
47FF70C051A991FB65CDBCF3 /* RoomScreenInteractionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0135A608FFAD86E6674EE730 /* RoomScreenInteractionHandler.swift */; };
4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */; };
484202C5D50983442D24D061 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
489BB6A733D3DA0FE7062650 /* IdentityConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */; };
491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */; };
492274DA6691EE985C2FCCAA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
4940B439681767BE9D78CFDB /* AppLockSetupBiometricsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5EE5DE3B55D59299DB5BC /* AppLockSetupBiometricsScreenViewModelTests.swift */; };
496CC9D59ACFAB84FD9B3B5F /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */; };
49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */; };
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; };
4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; };
4A8287E5281B44A8754BE509 /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */; };
4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */; };
4A9CEEE612D6D8B3DDBD28BA /* RoomListFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24EC819497BB5F8C4998D760 /* RoomListFilterView.swift */; };
4AAA8606FBA290E23D15422E /* AvatarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */; };
@ -326,6 +334,7 @@
50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0FF64B0E6470F66F42E182 /* EstimatedWaveformView.swift */; };
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542D4F49FABA056DEEEB3400 /* RustTracing.swift */; };
5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */; };
5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; };
518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; };
51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; };
520EEDAFBC778AB0B41F2F53 /* ClientMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE6170EFE6A161B0A68AB61 /* ClientMock.swift */; };
@ -349,9 +358,8 @@
565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; };
56BAB81A0D03C2EF09B86294 /* ComposerToolbarModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA188BB0A216D763E46E3279 /* ComposerToolbarModels.swift */; };
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; };
5770C4906668C6D3008A2AC9 /* SessionVerificationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */; };
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */; };
5780E444F405AA1304E1C23E /* DeveloperOptionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */; };
584590D0EA548152A393E72C /* HomeScreenSessionVerificationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */; };
588411C8FD72B2A2DFE5F7DE /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E992D7B8BE54B2AB454613AF /* XCUIElement.swift */; };
5894C2514400A4FBC9327632 /* ServerConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */; };
5897A59DDBD3592282092223 /* MediaSourceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49B9785E3AD7D1C15A29F2F /* MediaSourceProxy.swift */; };
@ -375,8 +383,8 @@
5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; };
5EE1D4E316D66943E97FDCF2 /* BloomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BEB970F500BFB248443FA1 /* BloomView.swift */; };
5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
5F28C9146694B381BB82E18C /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */; };
5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; };
601AB75BD52B0B4276CEB84A /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */; };
60ED66E63A169E47489348A8 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; };
6146996D5C4DDD5DA816FC87 /* AuthenticationTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCACD75595C40EACD6AD4A74 /* AuthenticationTextFieldStyle.swift */; };
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; };
@ -435,6 +443,7 @@
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; };
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
6D6E651ACACE27E9C5690818 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE47A97726F0675DEE387BF9 /* TypingIndicatorView.swift */; };
6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */; };
6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */; };
6E4E401BE97AC241DA7C7716 /* AppLockSetupSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502F986D57158674172C58E3 /* AppLockSetupSettingsScreenModels.swift */; };
6E63704717F17593A475D152 /* RoomNotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */; };
@ -449,6 +458,7 @@
70558528EF68CAAEF09972D5 /* RoomTimelineItemFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96ED747FF90332EA1333C22 /* RoomTimelineItemFixtures.swift */; };
706289B086B0A6B0C211763F /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; };
706F79A39BDB32F592B8C2C7 /* UIKitBackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */; };
707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */; };
70B83D44043293B4B77440B9 /* PollFormScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */; };
719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40C19719687984FD9478FBE /* Task.swift */; };
71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */; };
@ -466,6 +476,7 @@
755395927DDD6EBDDA5E217A /* SettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */; };
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; };
762DAF94846C7AC8550F1CC1 /* MediaPlayerProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E23D8EE6CBACF32F1EC874 /* MediaPlayerProviderProtocol.swift */; };
762DB0973865293F0C3D3D7B /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */; };
763D69741D58D2B650BC1FC9 /* CallScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37FA1A5D55633E1942B153B /* CallScreenCoordinator.swift */; };
7640A4B412CACF15D143CCD4 /* Strings+SAS.swift in Sources */ = {isa = PBXBuildFile; fileRef = B172057567E049007A5C4D92 /* Strings+SAS.swift */; };
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EEA67A6796BDC2761619C5 /* PaginationIndicatorRoomTimelineView.swift */; };
@ -474,6 +485,7 @@
76BA28216FBAF83B2D86A027 /* InvitesScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2A71915C1F075E403F559C /* InvitesScreenCell.swift */; };
7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */; };
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; };
77574A519A4E484880053EAD /* IdentityConfirmationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */; };
77693820498ABF3508814D49 /* AppLockServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */; };
77920AFA8091AC6B9F190C90 /* Signposter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */; };
77BB228AEA861E50FFD6A228 /* HomeScreenEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FEA560929DD73FFEF8C3DF /* HomeScreenEmptyStateView.swift */; };
@ -485,7 +497,6 @@
795A854F63301DC6B46217B9 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; };
79741C1953269FF1A211D246 /* RoomPollsHistoryScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */; };
7A02EB29F3B993AB20E0A198 /* RoomPollsHistoryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C8C368A611B9CB79C7F5FA /* RoomPollsHistoryScreen.swift */; };
7A0A0929556792FB19B812C5 /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */; };
7A170A5A4A352954BB2A1B96 /* AuthenticationStartScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E8C8817F59BEC7E358EB78 /* AuthenticationStartScreen.swift */; };
7A642EE5F1ADC5D520F21924 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; };
7A71AEF419904209BB8C2833 /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */; };
@ -598,6 +609,7 @@
934051B17A884AB0635DF81B /* BlockedUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */; };
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; };
93A549135E6C027A0D823BFE /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 593FBBF394712F2963E98A0B /* DTCoreText */; };
93AC1E8418D8C827671FB3A9 /* IdentityConfirmedScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 595EC503DA5517BBE6D39406 /* IdentityConfirmedScreenCoordinator.swift */; };
93BA4A81B6D893271101F9F0 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; };
93BAF04D9CCBC0A8841414D0 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */; };
9408CE8B8865C0C8DD4C9869 /* NoticeRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD51B4D5173F7FC886F5360 /* NoticeRoomTimelineItemContent.swift */; };
@ -619,7 +631,6 @@
988BA75A182738150894A23F /* UserIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AE4B3273BA189FDCD4055C /* UserIndicator.swift */; };
9912F9EB2D6589141A2957B4 /* AppLockScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C44BBC892499BE45B074F89 /* AppLockScreenCoordinator.swift */; };
992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; };
9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */; };
99ED42B8F8D6BFB1DBCF4C45 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = D661CAB418C075A94306A792 /* AnalyticsEvents */; };
9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */; };
@ -632,6 +643,7 @@
9BB91CABB10D8FE90C491BCD /* StaticLocationScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */; };
9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */; };
9BEA56957B3AF954E7321658 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44928D844E16EE48A311FCA /* ComposerToolbarViewModelProtocol.swift */; };
9C4EC28A921486B1775D7F8C /* IdentityConfirmedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */; };
9C55746D8F6A3E35CFCF4A7A /* AuthenticationStartLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 598F01EBD0C4CC550C644418 /* AuthenticationStartLogo.swift */; };
9C5A07E7C33F3F40287D7861 /* SettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */; };
9D2E03DB175A6AB14589076D /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; };
@ -641,7 +653,6 @@
9DD5AA10E85137140FEA86A3 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */; };
9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */; };
9DF3F6318A4402305F5EB869 /* AnalyticsPromptScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */; };
9E838A62918E47BC72D6640D /* UserIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB54B4F94686CCF0289B72F /* UserIndicatorPresenter.swift */; };
9EBDC79CAC9B63A0D626E333 /* LegalInformationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */; };
9F11E743EA01482E78A438B0 /* GlobalSearchScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22DB19219E6CC4D002E15D48 /* GlobalSearchScreenCell.swift */; };
@ -704,6 +715,7 @@
AA050DF4AEE54A641BA7CA22 /* RoomSummaryProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CC626F97AD70FF0420C115 /* RoomSummaryProviderProtocol.swift */; };
AA5924D3B67F7ACD98BBEFDC /* OrientationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4756240773D26AB74C22668 /* OrientationManagerProtocol.swift */; };
AA93B3F9B5DD097DEF79F981 /* NotificationSettingsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */; };
AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8319173DD66C07F45DC48848 /* IdentityConfirmedScreenViewModelProtocol.swift */; };
AAF0BBED840DF4A53EE85E77 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = C2C69B8BA5A9702E7A8BC08F /* MatrixRustSDK */; };
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; };
AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */; };
@ -722,7 +734,6 @@
AFA1F2543DFF7B45DF68ACD6 /* CompletionSuggestionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170BF6F7923A5C3792442F27 /* CompletionSuggestionModels.swift */; };
B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */; };
B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; };
B09DC6E3D0EE87C4D4ABFAB3 /* EncryptedHistoryRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */; };
B0CB16349B96262AA65A04AF /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; };
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; };
B13774779EA19FDD7A35A4A8 /* RoomRolesAndPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */; };
@ -731,7 +742,6 @@
B188D0907A4D38AAAF6FEFA8 /* AppLockSetupFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */; };
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; };
B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; };
B27D3190784F85916DA1C394 /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */; };
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */; };
B3D652AA1654270742072FB3 /* DeveloperOptionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */; };
B3EDDEC1839BB5A3747624BB /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1CCDEE545CB6453B084BF /* FormButtonStyles.swift */; };
@ -779,6 +789,7 @@
BB9B800C6094E34860E89DC5 /* AppLockSetupBiometricsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */; };
BCC864190651B3A3CF51E4DF /* MediaFileHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */; };
BD0BE20DBCE31253AE4490A1 /* RoomListFiltersEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC1DDB2293A51EA4C2739351 /* RoomListFiltersEmptyStateView.swift */; };
BD11E639CF566A9DA8FCA717 /* RoundedLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE7C80EF77AD102053D3646E /* RoundedLabelItem.swift */; };
BD203FC6A7AE7637EA003643 /* RoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */; };
BD6685592716CA957D7BAAC4 /* RoomChangeRolesScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9B45D584D232CB9E5C7734 /* RoomChangeRolesScreenSelectedItem.swift */; };
BD6D98676111DA8FC2BE4908 /* InvitesScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86873A768B13069BB5CAECF6 /* InvitesScreenViewModelProtocol.swift */; };
@ -804,6 +815,7 @@
C32765D740C81AD4C42E8F50 /* CreateRoomFlowParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935C2FB18EFB8EEE96B26330 /* CreateRoomFlowParameters.swift */; };
C3317EF833AB4060988DF098 /* SAS.strings in Resources */ = {isa = PBXBuildFile; fileRef = 135FC689EA39AE1D34153B58 /* SAS.strings */; };
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */; };
C3BB6887CF13B19182E81F87 /* IdentityConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */; };
C4078364FD9FA00EA9D00A15 /* RoomMembersListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CDF9A107BFE6C79B58D6B5 /* RoomMembersListScreenViewModelProtocol.swift */; };
C413D36D44F89DE63D3ADFA4 /* ReportContentScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */; };
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9873076F224E4CE09D8BD47D /* TemplateScreenUITests.swift */; };
@ -823,6 +835,7 @@
C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; };
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; };
C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */; };
C8A9C595038AFA2D707AC8C1 /* NotificationPermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */; };
C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; };
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; };
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
@ -855,6 +868,7 @@
CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; };
CFEC53440C572CEEABC4A6A0 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; };
D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; };
D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */; };
D050D7756E92CA061ED0ABF0 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */; };
D0550B8E0AE2C0CDBE52C88F /* MediaPlayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE87C931165F5E201CACBB87 /* MediaPlayerProtocol.swift */; };
D0A965852D6C04138FA55181 /* SecureBackupLogoutConfirmationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */; };
@ -865,6 +879,7 @@
D1E29F345F1220E1AF1BE9DF /* ReadReceiptsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0A77874B29D79DDFC051AC /* ReadReceiptsSummaryView.swift */; };
D1EEF0CB0F5D9C15E224E670 /* landscape_test_video.mov in Resources */ = {isa = PBXBuildFile; fileRef = 9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */; };
D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */; };
D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */; };
D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */; };
D29E046C1E3045E0346C479D /* RoomRolesAndPermissionsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45571C2EBD98ED7E0CEA7AF7 /* RoomRolesAndPermissionsUITests.swift */; };
D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; };
@ -874,8 +889,8 @@
D415764645491F10344FC6AC /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F18AECC9D38C2B6D85F99C /* Publisher.swift */; };
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */; };
D46C33F8B61B55F0C8C2D15F /* LocationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */; };
D4ACF3276F5D0DA28D4028C9 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */; };
D4D5595C4A2A702CFF4E94FF /* HeroImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC2F1622C5BBABED6012E12 /* HeroImage.swift */; };
D4D7CCECC6C0AAFC42E165BB /* NotificationPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */; };
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */; };
D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */; };
D5681C80D8281560AACE0035 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045253F9967A535EE5B16691 /* Label.swift */; };
@ -887,17 +902,18 @@
D6661A94DBD97658B2ADBD6A /* MapTilerStaticMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */; };
D7CDBAE82782BD0529DECB5F /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; };
D8385A51A3D0FA9283556281 /* RoundedLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */; };
D876EC0FED3B6D46C806912A /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */; };
D9473FC9B077A6EDB7A12001 /* LocationRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */; };
D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352359663A0E52BA20761EE /* LoadableImage.swift */; };
DA7E867F5EAFF8E20B2EE3B6 /* SecureBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */; };
DB079D1929B5A5F52D207C83 /* RoomDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */; };
DB65401349C143DFF883E2B0 /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */; };
DBC8D1DBFE9F9CA7662BC8AA /* RoomPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */; };
DC08ADC41E792086A340A8B3 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; };
DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */; };
DC77E9DB2CFBE84A2BDF20C5 /* RoomRolesAndPermissionsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */; };
DCFE7CB3B9A104330BBB96AD /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */; };
DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; };
DDFBDEE1DC32BDD5488F898C /* ClientProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */; };
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; };
@ -932,7 +948,6 @@
E4B07FF075C99D04D9AF792D /* AppLockSetupPINScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B410B32B72C90BF94E481F33 /* AppLockSetupPINScreenModels.swift */; };
E4BAEED438A843D7B01D8069 /* CompletionSuggestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F421E51DF00377DE1A01354 /* CompletionSuggestionView.swift */; };
E4F924DECC66389C1C810550 /* AuthenticationStartScreenBackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D685B4DB38BB5BD87C956A /* AuthenticationStartScreenBackgroundImage.swift */; };
E570117376826665640F0CFD /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */; };
E58F1F3276E98A93F7D39219 /* RoomPollsHistoryScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */; };
E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; };
E62EC30B39354A391E32A126 /* AudioRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2D505742FDA21FCDC4C18A /* AudioRoomTimelineView.swift */; };
@ -950,7 +965,6 @@
E9347F56CF0683208F4D9249 /* RoomNotificationSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */; };
E9560744F7B0292E20ECE5F2 /* RoomDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */; };
E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; };
E9F148072F9513EC2272AA21 /* SessionVerificationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */; };
EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */; };
EA78A7512AFB1E5451744EB1 /* AppRouteURLParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */; };
@ -997,7 +1011,6 @@
F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; };
F40B097470D3110DFDB1FAAA /* LegalInformationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */; };
F421FD5979EF53C8204BDC77 /* SecureBackupLogoutConfirmationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */; };
F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */; };
F4971845B5C4F270F6BC5745 /* ScaledFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */; };
F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */; };
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
@ -1104,6 +1117,7 @@
/* Begin PBXFileReference section */
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.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>"; };
0135A608FFAD86E6674EE730 /* RoomScreenInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenInteractionHandler.swift; sourceTree = "<group>"; };
01B795AAAB7B8747FE2FF311 /* LogViewerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenModels.swift; sourceTree = "<group>"; };
@ -1193,10 +1207,12 @@
15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModel.swift; sourceTree = "<group>"; };
15F30E7AE8A303E8FEC2499E /* ReadReceiptCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceiptCell.swift; sourceTree = "<group>"; };
16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenUITests.swift; sourceTree = "<group>"; };
161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = "<group>"; };
16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenModels.swift; sourceTree = "<group>"; };
170BF6F7923A5C3792442F27 /* CompletionSuggestionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionModels.swift; sourceTree = "<group>"; };
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = "<group>"; };
1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfigurationTests.swift; sourceTree = "<group>"; };
18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = "<group>"; };
184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = "<group>"; };
1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReadReceiptsView.swift; sourceTree = "<group>"; };
18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxy.swift; sourceTree = "<group>"; };
@ -1210,7 +1226,6 @@
1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = "<group>"; };
1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModel.swift; sourceTree = "<group>"; };
1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyMock.swift; sourceTree = "<group>"; };
1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = "<group>"; };
1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItem.swift; sourceTree = "<group>"; };
1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillAttachmentViewProvider.swift; sourceTree = "<group>"; };
1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenModels.swift; sourceTree = "<group>"; };
@ -1227,6 +1242,7 @@
1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
1D8866FE1CCCF10305FCACBC /* CallScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenUITests.swift; sourceTree = "<group>"; };
1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenViewModel.swift; sourceTree = "<group>"; };
1DB2FC2AA9A07EE792DF65CF /* NotificationPermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenModels.swift; sourceTree = "<group>"; };
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = "<group>"; };
1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenModels.swift; sourceTree = "<group>"; };
1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@ -1238,6 +1254,7 @@
1FD51B4D5173F7FC886F5360 /* NoticeRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItemContent.swift; sourceTree = "<group>"; };
201305507D7DFD16E544563A /* EmojiLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLoaderProtocol.swift; sourceTree = "<group>"; };
20872C3887F835958CE2F1D0 /* MapTilerStaticMapProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMapProtocol.swift; sourceTree = "<group>"; };
20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
2141693488CE5446BB391964 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItemContent.swift; sourceTree = "<group>"; };
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
@ -1282,7 +1299,6 @@
28C19F54A0C4FC9AB7ABD583 /* TextRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItemContent.swift; sourceTree = "<group>"; };
295E28C3B9EAADF519BF2F44 /* AuthenticationFlowCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationFlowCoordinatorUITests.swift; sourceTree = "<group>"; };
2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; };
2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = "<group>"; };
2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = "<group>"; };
2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenModels.swift; sourceTree = "<group>"; };
@ -1301,6 +1317,7 @@
2F36C5D9B37E50915ECBD3EE /* RoomMemberProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxy.swift; sourceTree = "<group>"; };
2FD0E68C42CA7DDCD4CAD68D /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = "<group>"; };
303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModel.swift; sourceTree = "<group>"; };
307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreen.swift; sourceTree = "<group>"; };
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriver.swift; sourceTree = "<group>"; };
30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = "<group>"; };
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = "<group>"; };
@ -1352,6 +1369,7 @@
3D65BCC659FD9087E49B3C25 /* AppAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearance.swift; sourceTree = "<group>"; };
3D9B45D584D232CB9E5C7734 /* RoomChangeRolesScreenSelectedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenSelectedItem.swift; sourceTree = "<group>"; };
3D9FCE4D1E3A81AC1CC5CB91 /* AppLockSetupSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenModels.swift; sourceTree = "<group>"; };
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskService.swift; sourceTree = "<group>"; };
3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = "<group>"; };
@ -1388,6 +1406,7 @@
45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterProtocol.swift; sourceTree = "<group>"; };
466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenModels.swift; sourceTree = "<group>"; };
46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = "<group>"; };
46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreen.swift; sourceTree = "<group>"; };
46D560DDA3B20C82766ACFAD /* NotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModel.swift; sourceTree = "<group>"; };
46F52419AEEDA2C006CB7181 /* NotificationSettingsEditScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenUITests.swift; sourceTree = "<group>"; };
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
@ -1412,7 +1431,6 @@
4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = "<group>"; };
4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; };
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = "<group>"; };
4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; };
4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
@ -1462,6 +1480,7 @@
58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
58DCB219D7B7B0299358FF81 /* SecureBackupKeyBackupScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenUITests.swift; sourceTree = "<group>"; };
592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = "<group>"; };
595EC503DA5517BBE6D39406 /* IdentityConfirmedScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenCoordinator.swift; sourceTree = "<group>"; };
596AA8843AC1A234F3387767 /* SecureBackupRecoveryKeyScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenCoordinator.swift; sourceTree = "<group>"; };
59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenUITests.swift; sourceTree = "<group>"; };
598F01EBD0C4CC550C644418 /* AuthenticationStartLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartLogo.swift; sourceTree = "<group>"; };
@ -1479,7 +1498,7 @@
5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenCoordinator.swift; sourceTree = "<group>"; };
5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = "<group>"; };
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = "<group>"; };
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
60F18AECC9D38C2B6D85F99C /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = "<group>"; };
612EF972F2A1800682D32C5E /* StickerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineView.swift; sourceTree = "<group>"; };
@ -1491,7 +1510,6 @@
62B07B296D7A9D2F09120853 /* OrderedSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSet.swift; sourceTree = "<group>"; };
638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveQuickLook.swift; sourceTree = "<group>"; };
6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; };
63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModel.swift; sourceTree = "<group>"; };
63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
645E027C112740573D27765C /* SecureBackupRecoveryKeyScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenModels.swift; sourceTree = "<group>"; };
648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridableAvatarImage.swift; sourceTree = "<group>"; };
@ -1522,6 +1540,7 @@
6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreen.swift; sourceTree = "<group>"; };
6B5E29E9A22F45534FBD5B58 /* EmojiPickerScreenHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenHeaderView.swift; sourceTree = "<group>"; };
6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiProvider.swift; sourceTree = "<group>"; };
6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModel.swift; sourceTree = "<group>"; };
6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsScreenIdentifier.swift; sourceTree = "<group>"; };
6D0A27607AB09784C8501B5C /* DeveloperOptionsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelTests.swift; sourceTree = "<group>"; };
6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationUITests.swift; sourceTree = "<group>"; };
@ -1550,7 +1569,6 @@
7310D8DFE01AF45F0689C3AA /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = "<group>"; };
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportFlowCoordinator.swift; sourceTree = "<group>"; };
7447C0AD7EF302CD027D6230 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SAS.strings; sourceTree = "<group>"; };
745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabelItem.swift; sourceTree = "<group>"; };
74611A4182DCF5F4D42696EC /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = "<group>"; };
7463464054DDF194C54F0B04 /* LogViewerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModelProtocol.swift; sourceTree = "<group>"; };
74653BE903970C0E36867D46 /* GlobalSearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenCoordinator.swift; sourceTree = "<group>"; };
@ -1561,7 +1579,6 @@
74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
7509AB72755DCC4B4E721B36 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/SAS.strings; sourceTree = "<group>"; };
752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signposter.swift; sourceTree = "<group>"; };
75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineView.swift; sourceTree = "<group>"; };
76310030C831D4610A705603 /* URLComponentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponentsTests.swift; sourceTree = "<group>"; };
772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = "<group>"; };
7773CBFDBD458E0B7E270507 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = "<group>"; };
@ -1569,11 +1586,14 @@
78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModelProtocol.swift; sourceTree = "<group>"; };
78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitCoordinatorTests.swift; sourceTree = "<group>"; };
7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerMock.swift; sourceTree = "<group>"; };
796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = "<group>"; };
7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
7A5D2323D7B6BF4913EB7EED /* landscape_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = landscape_test_image.jpg; sourceTree = "<group>"; };
7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = "<group>"; };
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelTests.swift; sourceTree = "<group>"; };
7AE094FCB6387D268C436161 /* SecureBackupScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModel.swift; sourceTree = "<group>"; };
7AE75941583A033A9EDC9FE0 /* RoomChangePermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModel.swift; sourceTree = "<group>"; };
7AFD012C3A9F5EF276DDD4AA /* AnalyticsPromptScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModelProtocol.swift; sourceTree = "<group>"; };
7B04BD3874D736127A8156B8 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunner.swift; sourceTree = "<group>"; };
@ -1582,6 +1602,7 @@
7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinatorProtocol.swift; sourceTree = "<group>"; };
7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenViewModel.swift; sourceTree = "<group>"; };
7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
7CA3F8E905DF50BF22ECC18F /* ThreadDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDecorator.swift; sourceTree = "<group>"; };
7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactoryProtocol.swift; sourceTree = "<group>"; };
@ -1593,22 +1614,21 @@
7EC2F1622C5BBABED6012E12 /* HeroImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeroImage.swift; sourceTree = "<group>"; };
7F615A00DB223FF3280204D2 /* UserDiscoveryServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoveryServiceProtocol.swift; sourceTree = "<group>"; };
7FB2253D36E81E045E1CB432 /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = "<group>"; };
7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenModels.swift; sourceTree = "<group>"; };
80C4927D09099497233E9980 /* WaitlistScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreen.swift; sourceTree = "<group>"; };
80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageEventStringBuilder.swift; sourceTree = "<group>"; };
818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineItem.swift; sourceTree = "<group>"; };
8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModelProtocol.swift; sourceTree = "<group>"; };
81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModel.swift; sourceTree = "<group>"; };
81B17B1F29448D1B9049B11C /* ReportContentScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModel.swift; sourceTree = "<group>"; };
81B17DB1BC3B0C62AF84D230 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
82B612853BFB68373249777B /* SecureBackupKeyBackupScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenViewModel.swift; sourceTree = "<group>"; };
82DFA1B7B088D033E0794B82 /* RoomChangeRolesScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenCoordinator.swift; sourceTree = "<group>"; };
8319173DD66C07F45DC48848 /* IdentityConfirmedScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenViewModelProtocol.swift; sourceTree = "<group>"; };
837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModel.swift; sourceTree = "<group>"; };
839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = "<group>"; };
840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = "<group>"; };
84311D707B09854D67F78BBF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersViewModelTests.swift; sourceTree = "<group>"; };
84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = "<group>"; };
848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelTests.swift; sourceTree = "<group>"; };
84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreen.swift; sourceTree = "<group>"; };
84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = "<group>"; };
@ -1661,6 +1681,7 @@
90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = "<group>"; };
913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachmentData.swift; sourceTree = "<group>"; };
91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenUITests.swift; sourceTree = "<group>"; };
91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
91CF6F7D08228D16BA69B63B /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCAuthenticationPresenter.swift; sourceTree = "<group>"; };
92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTask.swift; sourceTree = "<group>"; };
@ -1691,12 +1712,13 @@
9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; path = landscape_test_video.mov; sourceTree = "<group>"; };
9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = "<group>"; };
9B06663F7858E45882E63471 /* StaticLocationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreen.swift; sourceTree = "<group>"; };
9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenCoordinator.swift; sourceTree = "<group>"; };
9B663BE498BB39EADC24025D /* SettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenModels.swift; sourceTree = "<group>"; };
9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenCoordinator.swift; sourceTree = "<group>"; };
9B7D8D3638864B7482E148CC /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
9C3ACC093F88FD9888518561 /* AuthenticationStartScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModel.swift; sourceTree = "<group>"; };
9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMeasurementParser.swift; sourceTree = "<group>"; };
9C5E81214D27A6B898FC397D /* ElementX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; };
9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenViewModel.swift; sourceTree = "<group>"; };
9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackCoordinatorTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -1733,6 +1755,7 @@
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = "<group>"; };
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenCoordinator.swift; sourceTree = "<group>"; };
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
@ -1763,7 +1786,6 @@
AD9CB3B9DFA353AB2B7CD9F8 /* NotificationSettingsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenCoordinator.swift; sourceTree = "<group>"; };
ADB35E2DB4EFE8E6F3959629 /* InviteUsersScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenUITests.swift; sourceTree = "<group>"; };
ADCB8A232D3A8FB3E16A7303 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = "<group>"; };
ADE6170EFE6A161B0A68AB61 /* ClientMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientMock.swift; sourceTree = "<group>"; };
AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadingPreprocessorTests.swift; sourceTree = "<group>"; };
AE40D4A5DD857AC16EED945A /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
@ -1773,12 +1795,12 @@
AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = portrait_test_image.jpg; sourceTree = "<group>"; };
AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptUITests.swift; sourceTree = "<group>"; };
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderTests.swift; sourceTree = "<group>"; };
AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = "<group>"; };
AFEF489B8E2450E2BA1A314E /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/SAS.strings; sourceTree = "<group>"; };
B0A307A44F952CD73E63AE31 /* RoomEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomEventStringBuilder.swift; sourceTree = "<group>"; };
B0BA67B3E4EF9D29D14A78CE /* AppLockSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
B14B1DE3E2D5D26732C49036 /* RoomChangeRolesScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModel.swift; sourceTree = "<group>"; };
B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineItem.swift; sourceTree = "<group>"; };
B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
B172057567E049007A5C4D92 /* Strings+SAS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+SAS.swift"; sourceTree = "<group>"; };
B1E227F34BE43B08E098796E /* TestablePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreview.swift; sourceTree = "<group>"; };
B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = "<group>"; };
@ -1833,6 +1855,7 @@
BCF54536699ACEE3DB6BA3CB /* CompletionSuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionService.swift; sourceTree = "<group>"; };
BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = "<group>"; };
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = "<group>"; };
BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModel.swift; sourceTree = "<group>"; };
BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenCoordinator.swift; sourceTree = "<group>"; };
BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = "<group>"; };
BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderScreenCoordinator.swift; sourceTree = "<group>"; };
@ -1855,6 +1878,7 @@
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
C2E9B841EE4878283ECDB554 /* InviteUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreen.swift; sourceTree = "<group>"; };
C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKGeneratedMocks.swift; sourceTree = "<group>"; };
C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFlowCoordinator.swift; sourceTree = "<group>"; };
C352359663A0E52BA20761EE /* LoadableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableImage.swift; sourceTree = "<group>"; };
C4756240773D26AB74C22668 /* OrientationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManagerProtocol.swift; sourceTree = "<group>"; };
C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@ -1911,7 +1935,6 @@
CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = "<group>"; };
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = "<group>"; };
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
@ -1964,6 +1987,7 @@
DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollInteractionHandler.swift; sourceTree = "<group>"; };
DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = "<group>"; };
DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceTests.swift; sourceTree = "<group>"; };
DE7C80EF77AD102053D3646E /* RoundedLabelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabelItem.swift; sourceTree = "<group>"; };
DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenUITests.swift; sourceTree = "<group>"; };
DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = "<group>"; };
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = "<group>"; };
@ -1992,6 +2016,7 @@
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteURLParserTests.swift; sourceTree = "<group>"; };
E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = "<group>"; };
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = "<group>"; };
E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -2000,7 +2025,6 @@
E6E6BDF9D26DB05C88901416 /* RedactedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineItem.swift; sourceTree = "<group>"; };
E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxy.swift; sourceTree = "<group>"; };
E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactory.swift; sourceTree = "<group>"; };
E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenSessionVerificationBanner.swift; sourceTree = "<group>"; };
E76A706B3EEA32B882DA5E2D /* BlockedUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; };
E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProviderProtocol.swift; sourceTree = "<group>"; };
E8294DB9E95C0C0630418466 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -2017,6 +2041,7 @@
EA880E78AF4BD24E45A7808C /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilderTests.swift; sourceTree = "<group>"; };
EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenModels.swift; sourceTree = "<group>"; };
EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModel.swift; sourceTree = "<group>"; };
EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = "<group>"; };
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = "<group>"; };
ECB08484CD5D77C9BF97AA78 /* WaitlistScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenUITests.swift; sourceTree = "<group>"; };
@ -2025,6 +2050,7 @@
ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfiguration.swift; sourceTree = "<group>"; };
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = "<group>"; };
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = "<group>"; };
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
@ -2255,15 +2281,6 @@
path = Logging;
sourceTree = "<group>";
};
076087FC60064C702EF94796 /* View */ = {
isa = PBXGroup;
children = (
5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */,
745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */,
);
path = View;
sourceTree = "<group>";
};
0787F81684E503024BD0C051 /* Services */ = {
isa = PBXGroup;
children = (
@ -2621,19 +2638,6 @@
path = View;
sourceTree = "<group>";
};
3153FCA3F4B0E88B16D99D12 /* SessionVerificationScreen */ = {
isa = PBXGroup;
children = (
2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */,
4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */,
1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */,
ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */,
B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */,
914D71CD209A34A8C142CB93 /* View */,
);
path = SessionVerificationScreen;
sourceTree = "<group>";
};
31CE4DA53232AA534057F912 /* Mocks */ = {
isa = PBXGroup;
children = (
@ -2679,6 +2683,7 @@
648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */,
C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */,
839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */,
DE7C80EF77AD102053D3646E /* RoundedLabelItem.swift */,
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */,
);
path = Views;
@ -2704,6 +2709,18 @@
path = View;
sourceTree = "<group>";
};
336A13CA8A1DD526D9C41DD4 /* IdentityConfirmationScreen */ = {
isa = PBXGroup;
children = (
7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */,
7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */,
EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */,
7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */,
DDC32FD8B94AA19C4FC062AD /* View */,
);
path = IdentityConfirmationScreen;
sourceTree = "<group>";
};
337015ADFBA3AB96660DB3A6 /* Generated */ = {
isa = PBXGroup;
children = (
@ -2862,6 +2879,14 @@
path = SupportingFiles;
sourceTree = "<group>";
};
4044C040B64B9F077298C947 /* View */ = {
isa = PBXGroup;
children = (
E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
405B00F139AEE3994601B36A = {
isa = PBXGroup;
children = (
@ -3092,7 +3117,6 @@
05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */,
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */,
C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */,
E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */,
037A5661B26EC6BE068188D7 /* Filters */,
);
path = View;
@ -3166,6 +3190,7 @@
0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */,
A9B069D7772DDF6513E0F1B8 /* AuthenticationFlowCoordinator.swift */,
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */,
C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */,
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */,
D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */,
@ -3263,18 +3288,6 @@
path = SecureBackupLogoutConfirmationScreen;
sourceTree = "<group>";
};
669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */ = {
isa = PBXGroup;
children = (
9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */,
840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */,
63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */,
8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */,
076087FC60064C702EF94796 /* View */,
);
path = AnalyticsPromptScreen;
sourceTree = "<group>";
};
6709362D60732DED2069AE0F /* MediaPlayer */ = {
isa = PBXGroup;
children = (
@ -3333,6 +3346,14 @@
path = TimelineItemContent;
sourceTree = "<group>";
};
6D7503E64A458DD09E65A3F7 /* View */ = {
isa = PBXGroup;
children = (
307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
6DE13A7AE6587B079F4049D7 /* Notification */ = {
isa = PBXGroup;
children = (
@ -3701,6 +3722,11 @@
7DA2A18CFD03E0BACE6B5C4B /* AnalyticsPromptScreen */ = {
isa = PBXGroup;
children = (
9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */,
18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */,
6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */,
7AFD012C3A9F5EF276DDD4AA /* AnalyticsPromptScreenViewModelProtocol.swift */,
4044C040B64B9F077298C947 /* View */,
);
path = AnalyticsPromptScreen;
sourceTree = "<group>";
@ -3866,6 +3892,10 @@
isa = PBXGroup;
children = (
7DA2A18CFD03E0BACE6B5C4B /* AnalyticsPromptScreen */,
336A13CA8A1DD526D9C41DD4 /* IdentityConfirmationScreen */,
F6D661C666128C74BF0A7482 /* IdentityConfirmedScreen */,
F526F4FE387B32380592BA53 /* NotificationPermissionsScreen */,
C1CD278862878F9545608040 /* SessionVerificationScreen */,
);
path = Onboarding;
sourceTree = "<group>";
@ -3909,14 +3939,6 @@
path = LoginScreen;
sourceTree = "<group>";
};
914D71CD209A34A8C142CB93 /* View */ = {
isa = PBXGroup;
children = (
84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
92E99C57D7F92ED16F73282C /* ElementCall */ = {
isa = PBXGroup;
children = (
@ -4054,6 +4076,14 @@
path = Templates;
sourceTree = "<group>";
};
9C0C1CB9E16302C00AB4956D /* View */ = {
isa = PBXGroup;
children = (
46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
9C4193C4524B35FD6B94B5A9 /* Pills */ = {
isa = PBXGroup;
children = (
@ -4195,6 +4225,14 @@
path = LayoutTests;
sourceTree = "<group>";
};
A722D372674EE5687E1A67E4 /* View */ = {
isa = PBXGroup;
children = (
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
A78C2592419CA4C76FBA8FD2 /* Application */ = {
isa = PBXGroup;
children = (
@ -4360,7 +4398,6 @@
CA8F098AE48D958B4257EB24 /* CallInviteRoomTimelineView.swift */,
6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */,
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */,
75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */,
56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */,
E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */,
F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */,
@ -4515,6 +4552,19 @@
path = CreateRoom;
sourceTree = "<group>";
};
C1CD278862878F9545608040 /* SessionVerificationScreen */ = {
isa = PBXGroup;
children = (
796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */,
AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */,
161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */,
ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */,
A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */,
A722D372674EE5687E1A67E4 /* View */,
);
path = SessionVerificationScreen;
sourceTree = "<group>";
};
CA15BB3F6C62B35AE2C281A9 /* Provider */ = {
isa = PBXGroup;
children = (
@ -4634,7 +4684,6 @@
D977D4E565C06D3F41C8F8FC /* Virtual */ = {
isa = PBXGroup;
children = (
D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */,
0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */,
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */,
C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */,
@ -4663,6 +4712,14 @@
path = View;
sourceTree = "<group>";
};
DDC32FD8B94AA19C4FC062AD /* View */ = {
isa = PBXGroup;
children = (
00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
DDF77194AB6E167891D0A8F3 /* View */ = {
isa = PBXGroup;
children = (
@ -4716,7 +4773,6 @@
E59565F441830B19DBAE567C /* Screens */ = {
isa = PBXGroup;
children = (
669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */,
13263FFEA7749D822B51AA90 /* AppLock */,
E74CD7681375AD2EAA34D66B /* Authentication */,
295BCC81AB45927F5F2033B1 /* AuthenticationStartScreen */,
@ -4751,7 +4807,6 @@
7B890CCD20B037760BFDF957 /* RoomRolesAndPermissionsScreen */,
679E9837ECA8D6776079D16E /* RoomScreen */,
2565414373E6F68005966B8E /* SecureBackup */,
3153FCA3F4B0E88B16D99D12 /* SessionVerificationScreen */,
70B74A432C241E56A7ACE610 /* Settings */,
EC4545C7E37E8294D3FE6800 /* StartChatScreen */,
);
@ -4912,6 +4967,18 @@
path = View;
sourceTree = "<group>";
};
F526F4FE387B32380592BA53 /* NotificationPermissionsScreen */ = {
isa = PBXGroup;
children = (
91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */,
1DB2FC2AA9A07EE792DF65CF /* NotificationPermissionsScreenModels.swift */,
BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */,
20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */,
9C0C1CB9E16302C00AB4956D /* View */,
);
path = NotificationPermissionsScreen;
sourceTree = "<group>";
};
F5A65D1D3B83593598DC278D /* EmojiPickerScreen */ = {
isa = PBXGroup;
children = (
@ -4924,6 +4991,18 @@
path = EmojiPickerScreen;
sourceTree = "<group>";
};
F6D661C666128C74BF0A7482 /* IdentityConfirmedScreen */ = {
isa = PBXGroup;
children = (
595EC503DA5517BBE6D39406 /* IdentityConfirmedScreenCoordinator.swift */,
3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */,
9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */,
8319173DD66C07F45DC48848 /* IdentityConfirmedScreenViewModelProtocol.swift */,
6D7503E64A458DD09E65A3F7 /* View */,
);
path = IdentityConfirmedScreen;
sourceTree = "<group>";
};
F84100ED0C09031BAB7BB77E /* View */ = {
isa = PBXGroup;
children = (
@ -5638,11 +5717,11 @@
F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */,
54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */,
8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */,
9DF3F6318A4402305F5EB869 /* AnalyticsPromptScreen.swift in Sources */,
5F28C9146694B381BB82E18C /* AnalyticsPromptScreenCoordinator.swift in Sources */,
496CC9D59ACFAB84FD9B3B5F /* AnalyticsPromptScreenModels.swift in Sources */,
0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */,
D4ACF3276F5D0DA28D4028C9 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */,
24A1BBADAC43DC3F3A7347DA /* AnalyticsPromptScreen.swift in Sources */,
DCFE7CB3B9A104330BBB96AD /* AnalyticsPromptScreenCoordinator.swift in Sources */,
6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */,
DB65401349C143DFF883E2B0 /* AnalyticsPromptScreenViewModel.swift in Sources */,
05BAB510CBC2ED35C154ADD0 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */,
3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */,
020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */,
95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */,
@ -5812,8 +5891,6 @@
8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */,
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */,
8B1D5CE017EEC734CF5FE130 /* Encodable.swift in Sources */,
B09DC6E3D0EE87C4D4ABFAB3 /* EncryptedHistoryRoomTimelineItem.swift in Sources */,
9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */,
4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */,
B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */,
FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */,
@ -5850,9 +5927,18 @@
B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */,
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */,
A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */,
584590D0EA548152A393E72C /* HomeScreenSessionVerificationBanner.swift in Sources */,
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */,
C3BB6887CF13B19182E81F87 /* IdentityConfirmationScreenCoordinator.swift in Sources */,
77574A519A4E484880053EAD /* IdentityConfirmationScreenModels.swift in Sources */,
0C346A4AD174F441EDB1414E /* IdentityConfirmationScreenViewModel.swift in Sources */,
489BB6A733D3DA0FE7062650 /* IdentityConfirmationScreenViewModelProtocol.swift in Sources */,
9C4EC28A921486B1775D7F8C /* IdentityConfirmedScreen.swift in Sources */,
93AC1E8418D8C827671FB3A9 /* IdentityConfirmedScreenCoordinator.swift in Sources */,
D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */,
01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */,
AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */,
BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */,
85813D87DDD7F67A46BD9AF7 /* ImageProviderProtocol.swift in Sources */,
7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */,
@ -5970,6 +6056,11 @@
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */,
06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */,
C4C84901ABAC9B17564AB7EB /* NotificationName.swift in Sources */,
5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */,
454311EAC17D778E19F46592 /* NotificationPermissionsScreenCoordinator.swift in Sources */,
3E7B65C2C97748D5D65AAA8B /* NotificationPermissionsScreenModels.swift in Sources */,
D4D7CCECC6C0AAFC42E165BB /* NotificationPermissionsScreenViewModel.swift in Sources */,
C8A9C595038AFA2D707AC8C1 /* NotificationPermissionsScreenViewModelProtocol.swift in Sources */,
C0090506A52A1991BAF4BA68 /* NotificationSettingsChatType.swift in Sources */,
AA93B3F9B5DD097DEF79F981 /* NotificationSettingsEditScreen.swift in Sources */,
53A59720F4729D9BBFFB7CAB /* NotificationSettingsEditScreenCoordinator.swift in Sources */,
@ -5987,6 +6078,7 @@
CBD2ABE4C1A47ECD99E1488E /* NotificationSettingsScreenViewModelProtocol.swift in Sources */,
523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */,
9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */,
11A6B8E3CBDBF0A4107FF4CE /* OnboardingFlowCoordinator.swift in Sources */,
3CE4C5071B6D2576E2473989 /* OrderedSet.swift in Sources */,
AA5924D3B67F7ACD98BBEFDC /* OrientationManagerProtocol.swift in Sources */,
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */,
@ -6139,7 +6231,7 @@
9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */,
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */,
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */,
D8385A51A3D0FA9283556281 /* RoundedLabelItem.swift in Sources */,
BD11E639CF566A9DA8FCA717 /* RoundedLabelItem.swift in Sources */,
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */,
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */,
F4971845B5C4F270F6BC5745 /* ScaledFrameModifier.swift in Sources */,
@ -6187,12 +6279,12 @@
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
7A0A0929556792FB19B812C5 /* SessionVerificationScreen.swift in Sources */,
E9F148072F9513EC2272AA21 /* SessionVerificationScreenCoordinator.swift in Sources */,
5770C4906668C6D3008A2AC9 /* SessionVerificationScreenModels.swift in Sources */,
B27D3190784F85916DA1C394 /* SessionVerificationScreenStateMachine.swift in Sources */,
F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */,
E570117376826665640F0CFD /* SessionVerificationScreenViewModelProtocol.swift in Sources */,
707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */,
D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */,
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */,
601AB75BD52B0B4276CEB84A /* SessionVerificationScreenStateMachine.swift in Sources */,
4A8287E5281B44A8754BE509 /* SessionVerificationScreenViewModel.swift in Sources */,
762DB0973865293F0C3D3D7B /* SessionVerificationScreenViewModelProtocol.swift in Sources */,
755395927DDD6EBDDA5E217A /* SettingsFlowCoordinator.swift in Sources */,
34F1261CEF6D6A00D559B520 /* SettingsScreen.swift in Sources */,
AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */,
@ -6737,7 +6829,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 13.3;
MARKETING_VERSION = 1.5.13;
MARKETING_VERSION = 1.6.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -6813,7 +6905,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 13.3;
MARKETING_VERSION = 1.5.13;
MARKETING_VERSION = 1.6.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "background-bottom-light.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "background-bottom-dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,57 @@
<svg width="393" height="272" viewBox="0 0 393 272" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1_4123)">
<rect x="15" y="20" width="361" height="64" rx="14" fill="white" shape-rendering="crispEdges"/>
<rect x="15.75" y="20.75" width="359.5" height="62.5" rx="13.25" stroke="#F7F9FA" stroke-width="1.5" shape-rendering="crispEdges"/>
<rect x="31" y="36" width="32" height="32" rx="16" fill="#E0F8D9"/>
<path d="M40.3818 58.5V45.8174H43.4141L46.9297 55.0986H47.0791L50.5859 45.8174H53.627V58.5H51.2627V49.8691H49.9619L52.4932 46.1689L47.835 58.5H46.165L41.5156 46.1689L44.0469 49.8691H42.7461V58.5H40.3818Z" fill="#005F00"/>
<rect x="79" y="40" width="220" height="8" rx="4" fill="#052E61" fill-opacity="0.12"/>
<rect x="79" y="56" width="100" height="8" rx="4" fill="#052E61" fill-opacity="0.12"/>
</g>
<g filter="url(#filter1_d_1_4123)">
<rect x="15" y="100" width="361" height="64" rx="14" fill="white" shape-rendering="crispEdges"/>
<rect x="15.75" y="100.75" width="359.5" height="62.5" rx="13.25" stroke="#F7F9FA" stroke-width="1.5" shape-rendering="crispEdges"/>
<rect x="31" y="116" width="32" height="32" rx="16" fill="#FAEEFB"/>
<path d="M41.041 138.5L45.4443 125.817H47.7119V128.349H46.9297L43.8271 138.5H41.041ZM43.3965 135.424L44.0469 133.438H49.9092L50.5596 135.424H43.3965ZM50.1816 138.5L47.0791 128.349V125.817H48.5645L52.9766 138.5H50.1816Z" fill="#822198"/>
<rect x="79" y="120" width="219" height="8" rx="4" fill="#052E61" fill-opacity="0.12"/>
<rect x="79" y="136" width="219" height="8" rx="4" fill="#052E61" fill-opacity="0.12"/>
</g>
<g filter="url(#filter2_d_1_4123)">
<rect x="15" y="180" width="361" height="64" rx="14" fill="white" shape-rendering="crispEdges"/>
<rect x="15.75" y="180.75" width="359.5" height="62.5" rx="13.25" stroke="#F7F9FA" stroke-width="1.5" shape-rendering="crispEdges"/>
<rect x="31" y="196" width="32" height="32" rx="16" fill="#FFEFE4"/>
<path d="M45.6641 218.5V208.006H41.8672V205.817H52.124V208.006H48.3184V218.5H45.6641Z" fill="#9B2200"/>
<rect x="79" y="208" width="154" height="8" rx="4" fill="#052E61" fill-opacity="0.12"/>
</g>
<defs>
<filter id="filter0_d_1_4123" x="-9" y="0" width="409" height="112" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="12"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.105882 0 0 0 0 0.113725 0 0 0 0 0.133333 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_4123"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_4123" result="shape"/>
</filter>
<filter id="filter1_d_1_4123" x="-9" y="80" width="409" height="112" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="12"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.105882 0 0 0 0 0.113725 0 0 0 0 0.133333 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_4123"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_4123" result="shape"/>
</filter>
<filter id="filter2_d_1_4123" x="-9" y="160" width="409" height="112" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="12"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.105882 0 0 0 0 0.113725 0 0 0 0 0.133333 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_4123"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_4123" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Alerts.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -398,6 +398,10 @@
"screen_edit_profile_error_title" = "Unable to update profile";
"screen_edit_profile_title" = "Edit profile";
"screen_edit_profile_updating_details" = "Updating profile…";
"screen_identity_confirmation_subtitle" = "Verify this device to set up secure messaging.";
"screen_identity_confirmation_title" = "Confirm that it's you";
"screen_identity_confirmed_subtitle" = "Now you can read or send messages securely, and anyone you chat with can also trust this device.";
"screen_identity_confirmed_title" = "Device verified";
"screen_invites_decline_chat_message" = "Are you sure you want to decline the invitation to join %1$@?";
"screen_invites_decline_chat_title" = "Decline invite";
"screen_invites_decline_direct_chat_message" = "Are you sure you want to decline this private chat with %1$@?";

View File

@ -83,7 +83,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
MXLog.info("\(appName) \(appVersion) (\(appBuild))")
if ProcessInfo.processInfo.environment["RESET_APP_SETTINGS"].map(Bool.init) == true {
AppSettings.reset()
AppSettings.resetAllSettings()
}
self.appDelegate = appDelegate
@ -154,11 +154,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
return
}
if appSettings.appLockIsMandatory, !appLockFlowCoordinator.appLockService.isEnabled {
stateMachine.processEvent(.startWithAppLockSetup)
} else {
stateMachine.processEvent(.startWithExistingSession)
}
stateMachine.processEvent(.startWithExistingSession)
}
func stop() {
@ -319,13 +315,21 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
appSettings.migratedAccounts[userID] = true
}
}
if oldVersion < Version(1, 6, 0) {
MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.")
if !userSessionStore.userIDs.isEmpty {
appSettings.hasRunIdentityConfirmationOnboarding = true
appSettings.hasRunNotificationPermissionsOnboarding = true
}
}
}
/// Clears the keychain, app support directory etc ready for a fresh use.
/// - Parameter includingSettings: Whether to additionally wipe the user's app settings too.
private func wipeUserData(includingSettings: Bool = false) {
if includingSettings {
AppSettings.reset()
AppSettings.resetAllSettings()
appLockFlowCoordinator.appLockService.disable()
}
userSessionStore.reset()
@ -339,19 +343,14 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
case (.initial, .startWithAuthentication, .signedOut):
startAuthentication()
case (.signedOut, .createdUserSession, .signedIn):
setupUserSession()
setupUserSession(isNewLogin: true)
case (.initial, .startWithExistingSession, .restoringSession):
restoreUserSession()
case (.restoringSession, .failedRestoringSession, .signedOut):
showLoginErrorToast()
presentSplashScreen()
case (.restoringSession, .createdUserSession, .signedIn):
setupUserSession()
case (.initial, .startWithAppLockSetup, .mandatoryAppLockSetup):
startMandatoryAppLockSetup()
case (.mandatoryAppLockSetup, .appLockSetupComplete, .restoringSession):
restoreUserSession()
setupUserSession(isNewLogin: false)
case (.signingOut, .signOut, .signingOut):
// We can ignore signOut when already in the process of signing out,
@ -397,7 +396,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
encryptionKeyProvider: EncryptionKeyProvider(),
appSettings: appSettings)
authenticationFlowCoordinator = AuthenticationFlowCoordinator(authenticationService: authenticationService,
appLockService: appLockFlowCoordinator.appLockService,
bugReportService: ServiceLocator.shared.bugReportService,
navigationRootCoordinator: navigationRootCoordinator,
appSettings: appSettings,
@ -450,7 +448,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
}
}
private func setupUserSession() {
private func setupUserSession(isNewLogin: Bool) {
guard let userSession else {
fatalError("User session not setup")
}
@ -462,9 +460,11 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
bugReportService: ServiceLocator.shared.bugReportService,
roomTimelineControllerFactory: RoomTimelineControllerFactory(),
appSettings: appSettings,
analytics: ServiceLocator.shared.analytics)
analytics: ServiceLocator.shared.analytics,
notificationManager: notificationManager,
isNewLogin: isNewLogin)
userSessionFlowCoordinator.actions
userSessionFlowCoordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
@ -488,31 +488,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
}
}
/// Used to add a PIN code to an existing session that somehow missed out mandatory PIN setup.
private func startMandatoryAppLockSetup() {
MXLog.info("Mandatory App Lock enabled but no PIN is set. Showing the setup flow.")
let navigationCoordinator = NavigationStackCoordinator()
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding,
appLockService: appLockFlowCoordinator.appLockService,
navigationStackCoordinator: navigationCoordinator)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .complete:
stateMachine.processEvent(.appLockSetupComplete)
appLockSetupFlowCoordinator = nil
case .forceLogout:
fatalError("Creating a PIN shouldn't be able to fail in this way")
}
}
.store(in: &cancellables)
appLockSetupFlowCoordinator = coordinator
navigationRootCoordinator.setRootCoordinator(navigationCoordinator)
coordinator.start()
}
private func logout(isSoft: Bool) {
guard let userSession else {
fatalError("User session not setup")
@ -544,6 +519,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
userSessionStore.logout(userSession: userSession)
tearDownUserSession()
AppSettings.resetSessionSpecificSettings()
// Reset analytics
ServiceLocator.shared.analytics.optOut()
ServiceLocator.shared.analytics.resetConsentState()
@ -591,7 +568,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
private func configureNotificationManager() {
notificationManager.setUserSession(userSession)
notificationManager.requestAuthorization()
appDelegateObserver = appDelegate.callbacks
.receive(on: DispatchQueue.main)
@ -689,7 +665,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
// MARK: Toasts and loading indicators
private static let loadingIndicatorIdentifier = "AppCoordinatorLoading"
private static let loadingIndicatorIdentifier = "\(AppCoordinator.self)-Loading"
private func showLoadingIndicator() {
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,

View File

@ -29,11 +29,6 @@ class AppCoordinatorStateMachine {
/// Opening an existing session.
case restoringSession
/// Showing the mandatory app lock setup flow before restoring the session.
/// This state should only be allowed before restoring an existing session. For
/// new users the setup is inserted in the middle of the authentication flow.
case mandatoryAppLockSetup
/// User session started
case signedIn
@ -49,20 +44,12 @@ class AppCoordinatorStateMachine {
/// Start the `AppCoordinator` by restoring an existing account.
case startWithExistingSession
/// Start the `AppCoordinator` by showing the mandatory PIN creation flow.
/// This event should only be sent if an account exists and a mandatory PIN is
/// missing. Normally it will be handled as part of the authentication flow.
case startWithAppLockSetup
/// Restoring session failed.
case failedRestoringSession
/// A session has been created.
case createdUserSession
/// The app lock setup has been completed.
case appLockSetupComplete
/// Request sign out.
case signOut(isSoft: Bool, disableAppLock: Bool)
/// Request the soft logout screen.
@ -93,9 +80,6 @@ class AppCoordinatorStateMachine {
stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn])
stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
stateMachine.addRoutes(event: .startWithAppLockSetup, transitions: [.initial => .mandatoryAppLockSetup])
stateMachine.addRoutes(event: .appLockSetupComplete, transitions: [.mandatoryAppLockSetup => .restoringSession])
stateMachine.addRoutes(event: .completedSigningOut, transitions: [.signingOut(isSoft: false, disableAppLock: false) => .signedOut,
.signingOut(isSoft: false, disableAppLock: true) => .signedOut])
stateMachine.addRoutes(event: .showSoftLogout, transitions: [.signingOut(isSoft: true, disableAppLock: false) => .softLogout])

View File

@ -24,10 +24,13 @@ final class AppSettings {
case seenInvites
case appLockNumberOfPINAttempts
case appLockNumberOfBiometricAttempts
case lastLoginDate
case migratedAccounts
case timelineStyle
case analyticsConsentState
case hasRunNotificationPermissionsOnboarding
case hasRunIdentityConfirmationOnboarding
case enableNotifications
case enableInAppNotifications
case pusherProfileTag
@ -59,11 +62,16 @@ final class AppSettings {
#if IS_MAIN_APP
static func reset() {
static func resetAllSettings() {
MXLog.warning("Resetting the AppSettings.")
store.removePersistentDomain(forName: suiteName)
}
static func resetSessionSpecificSettings() {
MXLog.warning("Resetting the user session specific AppSettings.")
store.removeObject(forKey: UserDefaultsKeys.hasRunIdentityConfirmationOnboarding.rawValue)
}
static func configureWithSuiteName(_ name: String) {
suiteName = name
@ -120,7 +128,9 @@ final class AppSettings {
let privacyURL: URL = "https://element.io/privacy"
/// An email address that should be used for support requests.
let supportEmailAddress = "support@element.io"
// A URL where users can go read more about the chat backup.
/// A URL where users can go read more about encryption in general.
let encryptionURL: URL = "https://element.io/help#encryption"
/// A URL where users can go read more about the chat backup.
let chatBackupDetailsURL: URL = "https://element.io/help#encryption5"
@UserPreference(key: UserDefaultsKeys.appAppearance, defaultValue: .system, storageType: .userDefaults(store))
@ -154,13 +164,6 @@ final class AppSettings {
return url
}()
/// The date that the call to `/login` completed successfully. This is used to put
/// a hard wall on the history of encrypted messages until we have key backup.
///
/// Not a multi-account aware setting as key backup will come before multi-account.
@UserPreference(key: UserDefaultsKeys.lastLoginDate, defaultValue: nil, storageType: .userDefaults(store))
var lastLoginDate: Date?
/// A dictionary of accounts that have performed an initial sync through their proxy.
///
/// This is a temporary workaround. In the future we should be able to receive a signal from the
@ -211,6 +214,12 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store))
var analyticsConsentState
@UserPreference(key: UserDefaultsKeys.hasRunNotificationPermissionsOnboarding, defaultValue: false, storageType: .userDefaults(store))
var hasRunNotificationPermissionsOnboarding
@UserPreference(key: UserDefaultsKeys.hasRunIdentityConfirmationOnboarding, defaultValue: false, storageType: .userDefaults(store))
var hasRunIdentityConfirmationOnboarding
// MARK: - Home Screen
@UserPreference(key: UserDefaultsKeys.hideUnreadMessagesBadge, defaultValue: false, storageType: .userDefaults(store))

View File

@ -24,7 +24,6 @@ protocol AuthenticationFlowCoordinatorDelegate: AnyObject {
class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
private let authenticationService: AuthenticationServiceProxyProtocol
private let appLockService: AppLockServiceProtocol
private let bugReportService: BugReportServiceProtocol
private let navigationRootCoordinator: NavigationRootCoordinator
private let navigationStackCoordinator: NavigationStackCoordinator
@ -36,23 +35,18 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
private var oidcPresenter: OIDCAuthenticationPresenter?
// periphery: ignore - used to store the coordinator to avoid deallocation
private var appLockFlowCoordinator: AppLockSetupFlowCoordinator?
// periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
weak var delegate: AuthenticationFlowCoordinatorDelegate?
init(authenticationService: AuthenticationServiceProxyProtocol,
appLockService: AppLockServiceProtocol,
bugReportService: BugReportServiceProtocol,
navigationRootCoordinator: NavigationRootCoordinator,
appSettings: AppSettings,
analytics: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol) {
self.authenticationService = authenticationService
self.appLockService = appLockService
self.bugReportService = bugReportService
self.navigationRootCoordinator = navigationRootCoordinator
self.appSettings = appSettings
@ -108,7 +102,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
private func showReportProblemScreen() {
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(navigationStackCoordinator),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
userIndicatorController: userIndicatorController,
bugReportService: bugReportService,
userID: nil,
deviceID: nil))
@ -265,58 +259,10 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
}
private func userHasSignedIn(userSession: UserSessionProtocol) {
appSettings.lastLoginDate = .now
if appSettings.appLockIsMandatory, !appLockService.isEnabled {
showAppLockSetupFlow(userSession: userSession)
} else if analytics.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(userSession: userSession)
} else {
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
}
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
}
private func showAppLockSetupFlow(userSession: UserSessionProtocol) {
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding,
appLockService: appLockService,
navigationStackCoordinator: navigationStackCoordinator)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .complete:
appLockFlowCoordinator = nil
if analytics.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(userSession: userSession)
} else {
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
}
case .forceLogout:
fatalError("The PIN creation flow should not fail.")
}
}
.store(in: &cancellables)
appLockFlowCoordinator = coordinator
coordinator.start()
}
private func showAnalyticsPrompt(userSession: UserSessionProtocol) {
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analytics, termsURL: appSettings.analyticsConfiguration.termsURL)
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator)
}
private static let loadingIndicatorIdentifier = "authenticationFlowCoordinatorLoading"
private static let loadingIndicatorIdentifier = "\(AuthenticationFlowCoordinator.self)-Loading"
private func startLoading() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,

View File

@ -0,0 +1,343 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import Foundation
import SwiftState
class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
private let userSession: UserSessionProtocol
private let appLockService: AppLockServiceProtocol
private let analyticsService: AnalyticsService
private let appSettings: AppSettings
private let notificationManager: NotificationManagerProtocol
private let rootNavigationStackCoordinator: NavigationStackCoordinator
private let userIndicatorController: UserIndicatorControllerProtocol
private let isNewLogin: Bool
private var navigationStackCoordinator: NavigationStackCoordinator!
enum State: StateType {
case initial
case identityConfirmation
case identityConfirmed
case appLockSetup
case analyticsPrompt
case notificationPermissions
case finished
}
enum Event: EventType {
case next
}
private let stateMachine: StateMachine<State, Event>
private var cancellables = Set<AnyCancellable>()
// periphery: ignore - used to store the coordinator to avoid deallocation
private var appLockFlowCoordinator: AppLockSetupFlowCoordinator?
init(userSession: UserSessionProtocol,
appLockService: AppLockServiceProtocol,
analyticsService: AnalyticsService,
appSettings: AppSettings,
notificationManager: NotificationManagerProtocol,
navigationStackCoordinator: NavigationStackCoordinator,
userIndicatorController: UserIndicatorControllerProtocol,
isNewLogin: Bool) {
self.userSession = userSession
self.appLockService = appLockService
self.analyticsService = analyticsService
self.appSettings = appSettings
self.notificationManager = notificationManager
self.userIndicatorController = userIndicatorController
self.isNewLogin = isNewLogin
rootNavigationStackCoordinator = navigationStackCoordinator
self.navigationStackCoordinator = NavigationStackCoordinator()
stateMachine = .init(state: .initial)
}
var shouldStart: Bool {
guard stateMachine.state == .initial, !ProcessInfo.isRunningIntegrationTests else {
return false
}
return isNewLogin || requiresVerification || requiresAppLockSetup || requiresAnalyticsSetup || requiresNotificationsSetup
}
func start() {
guard shouldStart else {
fatalError("This flow coordinator shouldn't have been started")
}
configureStateMachine()
stateMachine.tryEvent(.next)
rootNavigationStackCoordinator.setFullScreenCoverCoordinator(navigationStackCoordinator, animated: !isNewLogin)
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
fatalError()
}
func clearRoute(animated: Bool) {
fatalError()
}
// MARK: - Private
private var requiresVerification: Bool {
// We want to make sure onboarding finishes but also every time the user becomes unverified (e.g. account reset)
!appSettings.hasRunIdentityConfirmationOnboarding || userSession.sessionSecurityStatePublisher.value.verificationState == .unverified
}
private var requiresAppLockSetup: Bool {
appSettings.appLockIsMandatory && !appLockService.isEnabled
}
private var requiresAnalyticsSetup: Bool {
analyticsService.shouldShowAnalyticsPrompt
}
private var requiresNotificationsSetup: Bool {
!appSettings.hasRunNotificationPermissionsOnboarding
}
private func configureStateMachine() {
stateMachine.addRouteMapping { [weak self] _, fromState, _ in
guard let self else {
return nil
}
switch (fromState, requiresVerification, requiresAppLockSetup, requiresAnalyticsSetup, requiresNotificationsSetup) {
case (.initial, true, _, _, _):
return .identityConfirmation
case (.initial, false, true, _, _):
return .appLockSetup
case (.initial, false, false, true, _):
return .analyticsPrompt
case (.initial, false, false, false, true):
return .notificationPermissions
case (.identityConfirmation, _, _, _, _):
return .identityConfirmed
case (.identityConfirmed, _, true, _, _):
return .appLockSetup
case (.identityConfirmed, _, false, true, _):
return .analyticsPrompt
case (.identityConfirmed, _, false, false, true):
return .notificationPermissions
case (.identityConfirmed, _, false, false, false):
return .finished
case (.appLockSetup, _, _, true, _):
return .analyticsPrompt
case (.appLockSetup, _, _, false, true):
return .notificationPermissions
case (.appLockSetup, _, _, false, false):
return .finished
case (.analyticsPrompt, _, _, _, true):
return .notificationPermissions
case (.analyticsPrompt, _, _, _, false):
return .finished
case (.notificationPermissions, _, _, _, _):
return .finished
default:
return nil
}
}
stateMachine.addAnyHandler(.any => .any) { [weak self] context in
guard let self else { return }
switch (context.fromState, context.event, context.toState) {
case (_, _, .identityConfirmation):
presentIdentityConfirmationScreen()
case (_, _, .identityConfirmed):
presentIdentityConfirmedScreen()
case (_, _, .appLockSetup):
presentAppLockSetupFlow()
case (_, _, .analyticsPrompt):
presentAnalyticsPromptScreen()
case (_, _, .notificationPermissions):
presentNotificationPermissionsScreen()
case (_, _, .finished):
rootNavigationStackCoordinator.setFullScreenCoverCoordinator(nil)
default:
fatalError("Unknown transition: \(context)")
}
}
stateMachine.addErrorHandler { context in
fatalError("Unexpected transition: \(context)")
}
}
private func presentIdentityConfirmationScreen() {
let parameters = IdentityConfirmationScreenCoordinatorParameters(userSession: userSession,
appSettings: appSettings,
userIndicatorController: userIndicatorController)
let coordinator = IdentityConfirmationScreenCoordinator(parameters: parameters)
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .otherDevice:
Task {
await self.presentSessionVerificationScreen()
}
case .recoveryKey:
presentRecoveryKeyScreen()
}
}
.store(in: &cancellables)
presentCoordinator(coordinator)
}
private func presentSessionVerificationScreen() async {
guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else {
fatalError("The sessionVerificationController should aways be valid at this point")
}
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
appSettings.hasRunIdentityConfirmationOnboarding = true
stateMachine.tryEvent(.next)
}
}
.store(in: &cancellables)
presentCoordinator(coordinator)
}
private func presentRecoveryKeyScreen() {
let parameters = SecureBackupRecoveryKeyScreenCoordinatorParameters(secureBackupController: userSession.clientProxy.secureBackupController,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false)
let coordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: parameters)
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .recoveryFixed:
appSettings.hasRunIdentityConfirmationOnboarding = true
stateMachine.tryEvent(.next)
default:
fatalError("Other flows shouldn't be possible")
}
}
.store(in: &cancellables)
presentCoordinator(coordinator)
}
private func presentIdentityConfirmedScreen() {
let coordinator = IdentityConfirmedScreenCoordinator(parameters: .init())
coordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
stateMachine.tryEvent(.next)
}
}
.store(in: &cancellables)
presentCoordinator(coordinator)
}
private func presentAppLockSetupFlow() {
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding,
appLockService: appLockService,
navigationStackCoordinator: navigationStackCoordinator)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .complete:
appLockFlowCoordinator = nil
stateMachine.tryEvent(.next)
case .forceLogout:
fatalError("The PIN creation flow should not fail.")
}
}
.store(in: &cancellables)
appLockFlowCoordinator = coordinator
coordinator.start()
}
private func presentAnalyticsPromptScreen() {
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: appSettings.analyticsConfiguration.termsURL)
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
stateMachine.tryEvent(.next)
}
}
.store(in: &cancellables)
presentCoordinator(coordinator)
}
private func presentNotificationPermissionsScreen() {
let coordinator = NotificationPermissionsScreenCoordinator(parameters: .init(notificationManager: notificationManager))
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
appSettings.hasRunNotificationPermissionsOnboarding = true
stateMachine.tryEvent(.next)
}
}
.store(in: &cancellables)
presentCoordinator(coordinator)
}
private func presentCoordinator(_ coordinator: CoordinatorProtocol) {
if navigationStackCoordinator.rootCoordinator == nil {
navigationStackCoordinator.setRootCoordinator(coordinator)
} else {
navigationStackCoordinator.push(coordinator)
}
}
}

View File

@ -434,8 +434,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
secureBackupController: userSession.clientProxy.secureBackupController)
timelineItemFactory: timelineItemFactory)
self.timelineController = timelineController
analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace)
@ -918,8 +917,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
secureBackupController: userSession.clientProxy.secureBackupController)
timelineItemFactory: timelineItemFactory)
let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: roomProxy,
pollInteractionHandler: PollInteractionHandler(analyticsService: analytics, roomProxy: roomProxy),

View File

@ -130,10 +130,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
presentLegalInformationScreen()
case .blockedUsers:
presentBlockedUsersScreen()
case .sessionVerification:
Task {
await self.presentSessionVerificationScreen()
}
case .accountSessions:
presentAccountSessionsListURL()
case .notifications:
@ -213,34 +209,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.push(coordinator)
}
private func presentSessionVerificationScreen() async {
guard case let .success(sessionVerificationController) = await parameters.userSession.clientProxy.sessionVerificationControllerProxy() else {
fatalError("The sessionVerificationController should aways be valid at this point")
}
let verificationParameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
recoveryState: parameters.userSession.sessionSecurityStatePublisher.value.recoveryState)
let coordinator = SessionVerificationScreenCoordinator(parameters: verificationParameters)
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .recoveryKey:
navigationStackCoordinator.setSheetCoordinator(nil)
handleAppRoute(.chatBackupSettings, animated: true)
case .done:
navigationStackCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
navigationStackCoordinator.setSheetCoordinator(coordinator) { [weak self] in
self?.navigationStackCoordinator.setSheetCoordinator(nil)
}
}
private func presentNotificationSettings() {
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
userSession: parameters.userSession,

View File

@ -31,7 +31,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let windowManager: WindowManagerProtocol
private let bugReportService: BugReportServiceProtocol
private let appSettings: AppSettings
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
private let stateMachine: UserSessionFlowCoordinatorStateMachine
@ -39,6 +38,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let settingsFlowCoordinator: SettingsFlowCoordinator
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
// periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
@ -52,7 +53,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let selectedRoomSubject = CurrentValueSubject<String?, Never>(nil)
var actions: AnyPublisher<UserSessionFlowCoordinatorAction, Never> {
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<UserSessionFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
@ -63,7 +65,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
bugReportService: BugReportServiceProtocol,
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
appSettings: AppSettings,
analytics: AnalyticsService) {
analytics: AnalyticsService,
notificationManager: NotificationManagerProtocol,
isNewLogin: Bool) {
stateMachine = UserSessionFlowCoordinatorStateMachine()
self.userSession = userSession
self.navigationRootCoordinator = navigationRootCoordinator
@ -98,8 +102,29 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationSplitCoordinator: navigationSplitCoordinator,
userIndicatorController: ServiceLocator.shared.userIndicatorController))
onboardingFlowCoordinator = OnboardingFlowCoordinator(userSession: userSession,
appLockService: appLockService,
analyticsService: analytics,
appSettings: appSettings,
notificationManager: notificationManager,
navigationStackCoordinator: detailNavigationStackCoordinator,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isNewLogin: isNewLogin)
setupStateMachine()
userSession.sessionSecurityStatePublisher
.map(\.verificationState)
.filter { $0 != .unknown }
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let self else { return }
attemptStartingOnboarding()
}
.store(in: &cancellables)
roomFlowCoordinator.actions.sink { [weak self] action in
guard let self else { return }
@ -208,11 +233,18 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
func clearRoute(animated: Bool) {
fatalError("not necessary as of right now")
roomFlowCoordinator.clearRoute(animated: animated)
}
// MARK: - Private
func attemptStartingOnboarding() {
if onboardingFlowCoordinator.shouldStart {
clearRoute(animated: false)
onboardingFlowCoordinator.start()
}
}
private func clearPresentedSheets(animated: Bool, completion: @escaping () -> Void) {
if navigationSplitCoordinator.sheetCoordinator == nil {
completion()
@ -234,6 +266,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
switch (context.fromState, context.event, context.toState) {
case (.initial, .start, .roomList):
presentHomeScreen()
attemptStartingOnboarding()
case(.roomList, .selectRoom, .roomList):
break
@ -245,13 +278,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
case (.invitesScreen, .deselectRoom, .invitesScreen):
break
case (.roomList, .showSessionVerificationScreen, .sessionVerificationScreen):
Task {
await self.presentSessionVerification(animated: animated)
}
case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .roomList):
break
case (.roomList, .showSettingsScreen, .settingsScreen):
break
case (.settingsScreen, .dismissedSettingsScreen, .roomList):
@ -334,8 +360,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
case .presentFeedbackScreen:
stateMachine.processEvent(.feedbackScreen)
case .presentSessionVerificationScreen:
stateMachine.processEvent(.showSessionVerificationScreen)
case .presentSecureBackupSettings:
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
case .presentStartChatScreen:
@ -400,36 +424,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
presentSecureBackupLogoutConfirmationScreen()
}
// MARK: Session verification
private func presentSessionVerification(animated: Bool) async {
guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else {
fatalError("The sessionVerificationController should aways be valid at this point")
}
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
recoveryState: userSession.sessionSecurityStatePublisher.value.recoveryState)
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .recoveryKey:
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
case .done:
navigationSplitCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
navigationSplitCoordinator.setSheetCoordinator(coordinator, animated: animated) { [weak self] in
self?.stateMachine.processEvent(.dismissedSessionVerificationScreen)
}
}
// MARK: Start Chat
private func presentStartChat(animated: Bool) {

View File

@ -26,9 +26,6 @@ class UserSessionFlowCoordinatorStateMachine {
/// Showing the home screen. The `selectedRoomID` represents the timeline shown on the detail panel (if any)
case roomList(selectedRoomID: String?)
/// Showing the session verification flows
case sessionVerificationScreen(selectedRoomID: String?)
/// Showing the session verification flows
case feedbackScreen(selectedRoomID: String?)
@ -70,11 +67,6 @@ class UserSessionFlowCoordinatorStateMachine {
/// The feedback screen has been dismissed
case dismissedFeedbackScreen
/// Request the start of the session verification flow
case showSessionVerificationScreen
/// Session verification has finished
case dismissedSessionVerificationScreen
/// Request the start of the start chat flow
case showStartChatScreen
/// Start chat has been dismissed
@ -126,11 +118,6 @@ class UserSessionFlowCoordinatorStateMachine {
case (.feedbackScreen(let selectedRoomID), .dismissedFeedbackScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let selectedRoomID), .showSessionVerificationScreen):
return .sessionVerificationScreen(selectedRoomID: selectedRoomID)
case (.sessionVerificationScreen(let selectedRoomID), .dismissedSessionVerificationScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let selectedRoomID), .showStartChatScreen):
return .startChatScreen(selectedRoomID: selectedRoomID)
case (.startChatScreen(let selectedRoomID), .dismissedStartChatScreen):

View File

@ -33,6 +33,7 @@ internal enum Asset {
internal enum Images {
internal static let appLogo = ImageAsset(name: "images/app-logo")
internal static let serverSelectionIcon = ImageAsset(name: "images/server-selection-icon")
internal static let backgroundBottom = ImageAsset(name: "images/background-bottom")
internal static let closeRte = ImageAsset(name: "images/close-rte")
internal static let composerAttachment = ImageAsset(name: "images/composer-attachment")
internal static let stopRecording = ImageAsset(name: "images/stop-recording")
@ -41,6 +42,7 @@ internal enum Asset {
internal static let locationMarkerShape = ImageAsset(name: "images/location-marker-shape")
internal static let mediaPause = ImageAsset(name: "images/media-pause")
internal static let mediaPlay = ImageAsset(name: "images/media-play")
internal static let notificationsPromptGraphic = ImageAsset(name: "images/notifications-prompt-graphic")
internal static let waitingGradient = ImageAsset(name: "images/waiting-gradient")
}
}

View File

@ -991,6 +991,14 @@ internal enum L10n {
internal static var screenEditProfileTitle: String { return L10n.tr("Localizable", "screen_edit_profile_title") }
/// Updating profile
internal static var screenEditProfileUpdatingDetails: String { return L10n.tr("Localizable", "screen_edit_profile_updating_details") }
/// Verify this device to set up secure messaging.
internal static var screenIdentityConfirmationSubtitle: String { return L10n.tr("Localizable", "screen_identity_confirmation_subtitle") }
/// Confirm that it's you
internal static var screenIdentityConfirmationTitle: String { return L10n.tr("Localizable", "screen_identity_confirmation_title") }
/// Now you can read or send messages securely, and anyone you chat with can also trust this device.
internal static var screenIdentityConfirmedSubtitle: String { return L10n.tr("Localizable", "screen_identity_confirmed_subtitle") }
/// Device verified
internal static var screenIdentityConfirmedTitle: String { return L10n.tr("Localizable", "screen_identity_confirmed_title") }
/// Are you sure you want to decline the invitation to join %1$@?
internal static func screenInvitesDeclineChatMessage(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_invites_decline_chat_message", String(describing: p1))

View File

@ -35,9 +35,17 @@ extension ProcessInfo {
#endif
}
static var isRunningIntegrationTests: Bool {
#if DEBUG
processInfo.environment["IS_RUNNING_INTEGRATION_TESTS"] == "1"
#else
false
#endif
}
/// Flag indicating whether the app is running the UI tests or unit tests.
static var isRunningTests: Bool {
isRunningUITests || isRunningUnitTests
isRunningUITests || isRunningUnitTests || isRunningIntegrationTests
}
/// The identifier of the screen to be loaded when running UI tests.

View File

@ -33,6 +33,8 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
/// The spacing between the content and the buttons.
var spacing: CGFloat = 16
var showBackgroundGradient = false
/// The main content shown at the top of the layout.
@ViewBuilder var content: () -> Content
/// The content shown at the bottom of the layout.
@ -62,23 +64,32 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
}
.scrollBounceBehavior(.basedOnSize)
.safeAreaInset(edge: .bottom) {
VStack(spacing: 0) {
bottomContent()
.readableFrame()
.padding(.horizontal, horizontalPadding)
.padding(.top, spacing)
.padding(.bottom, UIConstants.actionButtonBottomPadding)
Spacer()
.frame(height: UIConstants.spacerHeight(in: geometry))
if showBackgroundGradient {
adjustedBottomContent(geometry: geometry)
.background(Asset.Images.backgroundBottom.swiftUIImage.resizable().ignoresSafeArea())
} else {
adjustedBottomContent(geometry: geometry)
.background()
}
.background()
}
}
}
private func adjustedBottomContent(geometry: GeometryProxy) -> some View {
VStack(spacing: 0) {
bottomContent()
.readableFrame()
.padding(.horizontal, horizontalPadding)
.padding(.top, spacing)
.padding(.bottom, UIConstants.actionButtonBottomPadding)
Spacer()
.frame(height: UIConstants.spacerHeight(in: geometry))
}
}
/// A continuously scrolling layout used for accessibility font sizes.
var accessibilityLayout: some View {
private var accessibilityLayout: some View {
GeometryReader { geometry in
ScrollView {
VStack(spacing: 0) {

View File

@ -21,35 +21,61 @@ import SwiftUI
/// takes a compound icon. If you would like to apply it to an SFSymbol, you can call
/// the `heroImage()` modifier directly on the Image.
struct HeroImage: View {
enum Style {
case normal
case positive
var foregroundColor: Color {
switch self {
case .normal:
return .compound.iconPrimary
case .positive:
return .compound.iconSuccessPrimary
}
}
var backgroundFillColor: Color {
switch self {
case .normal:
return .compound.bgSubtleSecondary
case .positive:
return .compound.bgSuccessSubtle
}
}
}
/// The icon that is shown.
let icon: KeyPath<CompoundIcons, Image>
var style: Style = .normal
var body: some View {
CompoundIcon(icon, size: .custom(42), relativeTo: .title)
.modifier(HeroImageModifier())
.modifier(HeroImageModifier(style: style))
}
}
extension Image {
/// Styles the image for use as the main/top/hero screen icon. You should prefer
/// the HeroImage component when possible, by using an icon from Compound.
func heroImage(insets: CGFloat = 16) -> some View {
func heroImage(insets: CGFloat = 16, style: HeroImage.Style = .normal) -> some View {
resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.scaledPadding(insets, relativeTo: .title)
.modifier(HeroImageModifier())
.modifier(HeroImageModifier(style: style))
}
}
private struct HeroImageModifier: ViewModifier {
let style: HeroImage.Style
func body(content: Content) -> some View {
content
.foregroundColor(.compound.iconPrimary)
.scaledFrame(size: 70, relativeTo: .title)
.foregroundColor(style.foregroundColor)
.background {
RoundedRectangle(cornerRadius: 14)
.fill(Color.compound.bgSubtleSecondary)
.fill(style.backgroundFillColor)
}
.accessibilityHidden(true)
}

View File

@ -85,7 +85,7 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
// MARK: - Private
private static let loadingIndicatorIdentifier = "LoginCoordinatorLoading"
private static let loadingIndicatorIdentifier = "\(LoginScreenCoordinatorAction.self)-Loading"
private func startLoading(isInteractionBlocking: Bool) {
if isInteractionBlocking {

View File

@ -104,7 +104,7 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
// MARK: - Private
private static let loadingIndicatorIdentifier = "SoftLogoutLoading"
private static let loadingIndicatorIdentifier = "\(SoftLogoutScreenCoordinator.self)-Loading"
/// Show an activity indicator whilst loading.
@MainActor private func startLoading() {

View File

@ -82,7 +82,7 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers
// MARK: Loading indicator
private static let loadingIndicatorIdentifier = "BlockedUsersLoading"
private static let loadingIndicatorIdentifier = "\(BlockedUsersScreenViewModel.self)-Loading"
private func showLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,

View File

@ -90,7 +90,7 @@ final class BugReportScreenCoordinator: CoordinatorProtocol {
// MARK: - Private
private static let loadingIndicatorIdentifier = "BugReportLoading"
private static let loadingIndicatorIdentifier = "\(BugReportScreenCoordinator.self)-Loading"
private func startLoading(label: String = L10n.commonLoading, progressPublisher: CurrentValuePublisher<Double, Never>) {
parameters.userIndicatorController?.submitIndicator(

View File

@ -171,7 +171,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
// MARK: Loading indicator
private static let loadingIndicatorIdentifier = "CreateRoomLoading"
private static let loadingIndicatorIdentifier = "\(CreateRoomViewModel.self)-Loading"
private func showLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,

View File

@ -31,7 +31,6 @@ enum HomeScreenCoordinatorAction {
case roomLeft(roomIdentifier: String)
case presentSettingsScreen
case presentFeedbackScreen
case presentSessionVerificationScreen
case presentSecureBackupSettings
case presentStartChatScreen
case presentInvitesScreen
@ -74,8 +73,6 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentFeedbackScreen)
case .presentSettingsScreen:
actionsSubject.send(.presentSettingsScreen)
case .presentSessionVerificationScreen:
actionsSubject.send(.presentSessionVerificationScreen)
case .presentSecureBackupSettings:
actionsSubject.send(.presentSecureBackupSettings)
case .logout:

View File

@ -22,7 +22,6 @@ enum HomeScreenViewModelAction {
case presentRoom(roomIdentifier: String)
case presentRoomDetails(roomIdentifier: String)
case roomLeft(roomIdentifier: String)
case presentSessionVerificationScreen
case presentSecureBackupSettings
case presentSettingsScreen
case presentFeedbackScreen
@ -39,9 +38,7 @@ enum HomeScreenViewAction {
case confirmLeaveRoom(roomIdentifier: String)
case showSettings
case startChat
case verifySession
case confirmRecoveryKey
case skipSessionVerification
case skipRecoveryKeyConfirmation
case updateVisibleItemRange(range: Range<Int>, isScrolling: Bool)
case selectInvites
@ -74,7 +71,6 @@ enum HomeScreenRoomListMode: CustomStringConvertible {
enum SecurityBannerMode {
case none
case dismissed
case sessionVerification
case recoveryKeyConfirmation
}

View File

@ -70,11 +70,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
guard let self else { return }
switch (securityState.verificationState, securityState.recoveryState) {
case (.unverified, _):
state.requiresExtraAccountSetup = true
if state.securityBannerMode != .dismissed {
state.securityBannerMode = .sessionVerification
}
case (.verified, .disabled):
state.requiresExtraAccountSetup = true
state.securityBannerMode = .none
@ -134,12 +129,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
leaveRoom(roomId: roomIdentifier)
case .showSettings:
actionsSubject.send(.presentSettingsScreen)
case .verifySession:
actionsSubject.send(.presentSessionVerificationScreen)
case .confirmRecoveryKey:
actionsSubject.send(.presentSecureBackupSettings)
case .skipSessionVerification:
state.securityBannerMode = .dismissed
case .skipRecoveryKeyConfirmation:
state.securityBannerMode = .dismissed
case .updateVisibleItemRange(let range, let isScrolling):

View File

@ -103,16 +103,13 @@ struct HomeScreenContent: View {
}
@ViewBuilder
/// The session verification banner and invites button if either are needed.
private var topSection: some View {
VStack(spacing: 0) {
if !context.isSearchFieldFocused {
RoomListFiltersView(state: $context.filtersState)
}
if context.viewState.securityBannerMode == .sessionVerification {
HomeScreenSessionVerificationBanner(context: context)
} else if context.viewState.securityBannerMode == .recoveryKeyConfirmation {
if context.viewState.securityBannerMode == .recoveryKeyConfirmation {
HomeScreenRecoveryKeyConfirmationBanner(context: context)
}

View File

@ -1,81 +0,0 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
struct HomeScreenSessionVerificationBanner: View {
@ObservedObject var context: HomeScreenViewModel.Context
var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 16) {
Text(L10n.sessionVerificationBannerTitle)
.font(.compound.bodyLGSemibold)
.foregroundColor(.compound.textPrimary)
Spacer()
Button {
context.send(viewAction: .skipSessionVerification)
} label: {
Image(systemName: "xmark")
.foregroundColor(.compound.iconSecondary)
.frame(width: 12, height: 12)
}
}
Text(L10n.sessionVerificationBannerMessage)
.font(.compound.bodyMD)
.foregroundColor(.compound.textSecondary)
}
Button(L10n.actionContinue) {
context.send(viewAction: .verifySession)
}
.frame(maxWidth: .infinity)
.buttonStyle(.compound(.primary, size: .medium))
.accessibilityIdentifier(A11yIdentifiers.homeScreen.verificationBannerContinue)
}
.padding(16)
.background(Color.compound.bgSubtleSecondary)
.cornerRadius(14)
.padding(.horizontal, 16)
}
}
struct HomeScreenSessionVerificationBanner_Previews: PreviewProvider, TestablePreview {
static let viewModel = buildViewModel()
static var previews: some View {
HomeScreenSessionVerificationBanner(context: viewModel.context)
}
static func buildViewModel() -> HomeScreenViewModel {
let clientProxy = ClientProxyMock(.init(userID: "@alice:example.com",
roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loading))))
let userSession = MockUserSession(clientProxy: clientProxy,
mediaProvider: MockMediaProvider(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock())
return HomeScreenViewModel(userSession: userSession,
analyticsService: ServiceLocator.shared.analytics,
appSettings: ServiceLocator.shared.settings,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}
}

View File

@ -63,7 +63,7 @@ struct CameraPicker: UIViewControllerRepresentable {
self.cameraPicker = cameraPicker
}
private static let loadingIndicatorIdentifier = "CameraPickerLoadingIndicator"
private static let loadingIndicatorIdentifier = "\(CameraPicker.self)-Loading"
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
picker.delegate = nil

View File

@ -62,7 +62,7 @@ struct DocumentPicker: UIViewControllerRepresentable {
documentPicker.callback(.cancel)
}
private static let loadingIndicatorIdentifier = "DocumentPickerLoadingIndicator"
private static let loadingIndicatorIdentifier = "\(DocumentPicker.self)-Loading"
func documentPicker(_ picker: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else {

View File

@ -62,7 +62,7 @@ struct PhotoLibraryPicker: UIViewControllerRepresentable {
// MARK: PHPickerViewControllerDelegate
private static let loadingIndicatorIdentifier = "PhotoLibraryPickerLoadingIndicator"
private static let loadingIndicatorIdentifier = "\(PhotoLibraryPicker.self)-Loading"
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard let provider = results.first?.itemProvider,

View File

@ -102,7 +102,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
}
}
private static let loadingIndicatorIdentifier = "MediaUploadPreviewLoading"
private static let loadingIndicatorIdentifier = "\(MediaUploadPreviewScreenViewModel.self)-Loading"
private func startLoading(progressPublisher: CurrentValuePublisher<Double, Never>) {
userIndicatorController.submitIndicator(

View File

@ -21,7 +21,7 @@ struct AnalyticsPromptScreen: View {
@ObservedObject var context: AnalyticsPromptScreenViewModel.Context
var body: some View {
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding) {
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, showBackgroundGradient: true) {
mainContent
} bottomContent: {
buttons
@ -30,6 +30,7 @@ struct AnalyticsPromptScreen: View {
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.interactiveDismissDisabled()
}
/// The main content of the screen that is shown inside the scroll view.
@ -88,12 +89,9 @@ struct AnalyticsPromptScreen: View {
/// The stack of enable/disable buttons.
private var buttons: some View {
VStack(spacing: 16) {
Button { context.send(viewAction: .enable) } label: {
Text(L10n.actionOk)
.font(.compound.bodyLGSemibold)
}
.buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.analyticsPromptScreen.enable)
Button(L10n.actionOk) { context.send(viewAction: .enable) }
.buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.analyticsPromptScreen.enable)
Button { context.send(viewAction: .disable) } label: {
Text(L10n.actionNotNow)

View File

@ -0,0 +1,70 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// periphery:ignore:all - this is just a identityConfirmation remove this comment once generating the final file
import Combine
import SwiftUI
struct IdentityConfirmationScreenCoordinatorParameters {
let userSession: UserSessionProtocol
let appSettings: AppSettings
let userIndicatorController: UserIndicatorControllerProtocol
}
enum IdentityConfirmationScreenCoordinatorAction {
case otherDevice
case recoveryKey
}
final class IdentityConfirmationScreenCoordinator: CoordinatorProtocol {
private let parameters: IdentityConfirmationScreenCoordinatorParameters
private let viewModel: IdentityConfirmationScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<IdentityConfirmationScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<IdentityConfirmationScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: IdentityConfirmationScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = IdentityConfirmationScreenViewModel(userSession: parameters.userSession,
appSettings: parameters.appSettings,
userIndicatorController: parameters.userIndicatorController)
}
func start() {
viewModel.actionsPublisher.sink { [weak self] action in
guard let self else { return }
MXLog.info("Coordinator: received view model action: \(action)")
switch action {
case .otherDevice:
actionsSubject.send(.otherDevice)
case .recoveryKey:
actionsSubject.send(.recoveryKey)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(IdentityConfirmationScreen(context: viewModel.context))
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum IdentityConfirmationScreenViewModelAction {
case otherDevice
case recoveryKey
}
struct IdentityConfirmationScreenViewState: BindableState {
enum Mode {
case recoveryOnly
case recoveryAndVerification
}
var mode = Mode.recoveryOnly
let learnMoreURL: URL
}
enum IdentityConfirmationScreenViewAction {
case otherDevice
case recoveryKey
}

View File

@ -0,0 +1,95 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias IdentityConfirmationScreenViewModelType = StateStoreViewModel<IdentityConfirmationScreenViewState, IdentityConfirmationScreenViewAction>
class IdentityConfirmationScreenViewModel: IdentityConfirmationScreenViewModelType, IdentityConfirmationScreenViewModelProtocol {
private let userSession: UserSessionProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let actionsSubject: PassthroughSubject<IdentityConfirmationScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<IdentityConfirmationScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(userSession: UserSessionProtocol, appSettings: AppSettings, userIndicatorController: UserIndicatorControllerProtocol) {
self.userSession = userSession
self.userIndicatorController = userIndicatorController
super.init(initialViewState: IdentityConfirmationScreenViewState(learnMoreURL: appSettings.encryptionURL))
userSession.sessionSecurityStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] state in
Task {
await self?.updateWithSessionSecurityState(state)
}
}
.store(in: &cancellables)
Task {
await updateWithSessionSecurityState(userSession.sessionSecurityStatePublisher.value)
}
}
// MARK: - Public
override func process(viewAction: IdentityConfirmationScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .otherDevice:
actionsSubject.send(.otherDevice)
case .recoveryKey:
actionsSubject.send(.recoveryKey)
}
}
// MARK: - Private
private func updateWithSessionSecurityState(_ sessionSecurityState: SessionSecurityState) async {
if sessionSecurityState.verificationState == .unknown {
showLoadingIndicator()
} else {
hideLoadingIndicator()
}
guard sessionSecurityState.verificationState == .unverified else {
return
}
guard case let .success(isOnlyDeviceLeft) = await userSession.clientProxy.isOnlyDeviceLeft() else {
return
}
state.mode = isOnlyDeviceLeft ? .recoveryOnly : .recoveryAndVerification
}
private static let loadingIndicatorIdentifier = "\(IdentityConfirmationScreenViewModel.self)-Loading"
private func showLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal,
title: L10n.commonLoading,
persistent: true))
}
private func hideLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}
}

View File

@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol IdentityConfirmationScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<IdentityConfirmationScreenViewModelAction, Never> { get }
var context: IdentityConfirmationScreenViewModelType.Context { get }
}

View File

@ -0,0 +1,107 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
struct IdentityConfirmationScreen: View {
@ObservedObject var context: IdentityConfirmationScreenViewModel.Context
var body: some View {
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding) {
screenHeader
} bottomContent: {
actionButtons
}
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.interactiveDismissDisabled()
}
// MARK: - Private
@ViewBuilder
private var screenHeader: some View {
VStack(spacing: 0) {
HeroImage(icon: \.lockSolid)
.padding(.bottom, 16)
Text(L10n.screenIdentityConfirmationTitle)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
.padding(.bottom, 8)
Text(L10n.screenIdentityConfirmationSubtitle)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
Button(L10n.actionLearnMore) {
UIApplication.shared.open(context.viewState.learnMoreURL)
}
.buttonStyle(.compound(.plain))
.padding(.top, 16)
}
}
@ViewBuilder
private var actionButtons: some View {
VStack(spacing: 32) {
switch context.viewState.mode {
case .recoveryOnly:
Button(L10n.screenSessionVerificationEnterRecoveryKey) {
context.send(viewAction: .recoveryKey)
}
.buttonStyle(.compound(.primary))
case .recoveryAndVerification:
Button(L10n.actionStartVerification) {
context.send(viewAction: .otherDevice)
}
.buttonStyle(.compound(.primary))
Button(L10n.screenSessionVerificationEnterRecoveryKey) {
context.send(viewAction: .recoveryKey)
}
.buttonStyle(.compound(.plain))
}
}
}
}
// MARK: - Previews
struct IdentityConfirmationScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
NavigationStack {
IdentityConfirmationScreen(context: viewModel.context)
}
}
private static var viewModel: IdentityConfirmationScreenViewModel {
let userSession = MockUserSession(clientProxy: ClientProxyMock(.init(userID: "@user:example.com",
roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))))),
mediaProvider: MockMediaProvider(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock())
return IdentityConfirmationScreenViewModel(userSession: userSession,
appSettings: ServiceLocator.shared.settings,
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}
}

View File

@ -0,0 +1,61 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// periphery:ignore:all - this is just a identityConfirmed remove this comment once generating the final file
import Combine
import SwiftUI
struct IdentityConfirmedScreenCoordinatorParameters { }
enum IdentityConfirmedScreenCoordinatorAction {
case done
}
final class IdentityConfirmedScreenCoordinator: CoordinatorProtocol {
private let parameters: IdentityConfirmedScreenCoordinatorParameters
private let viewModel: IdentityConfirmedScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<IdentityConfirmedScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<IdentityConfirmedScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: IdentityConfirmedScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = IdentityConfirmedScreenViewModel()
}
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 .done:
self.actionsSubject.send(.done)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(IdentityConfirmedScreen(context: viewModel.context))
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright 2023 New Vector Ltd
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -16,7 +16,12 @@
import Foundation
struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let isSessionVerified: Bool
enum IdentityConfirmedScreenViewModelAction {
case done
}
struct IdentityConfirmedScreenViewState: BindableState { }
enum IdentityConfirmedScreenViewAction {
case done
}

View File

@ -0,0 +1,42 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias IdentityConfirmedScreenViewModelType = StateStoreViewModel<IdentityConfirmedScreenViewState, IdentityConfirmedScreenViewAction>
class IdentityConfirmedScreenViewModel: IdentityConfirmedScreenViewModelType, IdentityConfirmedScreenViewModelProtocol {
private let actionsSubject: PassthroughSubject<IdentityConfirmedScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<IdentityConfirmedScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init() {
super.init(initialViewState: .init())
}
// MARK: - Public
override func process(viewAction: IdentityConfirmedScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .done:
actionsSubject.send(.done)
}
}
}

View File

@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol IdentityConfirmedScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<IdentityConfirmedScreenViewModelAction, Never> { get }
var context: IdentityConfirmedScreenViewModelType.Context { get }
}

View File

@ -0,0 +1,75 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
struct IdentityConfirmedScreen: View {
@ObservedObject var context: IdentityConfirmedScreenViewModel.Context
var body: some View {
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding) {
screenHeader
} bottomContent: {
actionButtons
}
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.interactiveDismissDisabled()
}
// MARK: - Private
@ViewBuilder
private var screenHeader: some View {
VStack(spacing: 0) {
HeroImage(icon: \.checkCircle, style: .positive)
.padding(.bottom, 16)
Text(L10n.screenIdentityConfirmedTitle)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
.padding(.bottom, 8)
Text(L10n.screenIdentityConfirmedSubtitle)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
}
}
@ViewBuilder
private var actionButtons: some View {
Button(L10n.actionContinue) {
context.send(viewAction: .done)
}
.buttonStyle(.compound(.primary))
}
}
// MARK: - Previews
struct IdentityConfirmedScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = IdentityConfirmedScreenViewModel()
static var previews: some View {
NavigationStack {
IdentityConfirmedScreen(context: viewModel.context)
}
}
}

View File

@ -0,0 +1,59 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
struct NotificationPermissionsScreenCoordinatorParameters {
let notificationManager: NotificationManagerProtocol
}
enum NotificationPermissionsScreenCoordinatorAction {
case done
}
final class NotificationPermissionsScreenCoordinator: CoordinatorProtocol {
private var viewModel: NotificationPermissionsScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<NotificationPermissionsScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<NotificationPermissionsScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: NotificationPermissionsScreenCoordinatorParameters) {
viewModel = NotificationPermissionsScreenViewModel(notificationManager: parameters.notificationManager)
}
// MARK: - Public
func start() {
viewModel.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
actionsSubject.send(.done)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(NotificationPermissionsScreen(context: viewModel.context))
}
}

View File

@ -0,0 +1,28 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum NotificationPermissionsScreenViewAction {
case enable
case notNow
}
enum NotificationPermissionsScreenViewModelAction {
case done
}
struct NotificationPermissionsScreenViewState: BindableState { }

View File

@ -0,0 +1,48 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias NotificationPermissionsScreenViewModelType = StateStoreViewModel<NotificationPermissionsScreenViewState, NotificationPermissionsScreenViewAction>
class NotificationPermissionsScreenViewModel: NotificationPermissionsScreenViewModelType, NotificationPermissionsScreenViewModelProtocol {
private let notificationManager: NotificationManagerProtocol
private var actionsSubject: PassthroughSubject<NotificationPermissionsScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<NotificationPermissionsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(notificationManager: NotificationManagerProtocol) {
self.notificationManager = notificationManager
super.init(initialViewState: .init())
}
// MARK: - Public
override func process(viewAction: NotificationPermissionsScreenViewAction) {
switch viewAction {
case .enable:
notificationManager.requestAuthorization()
actionsSubject.send(.done)
case .notNow:
actionsSubject.send(.done)
}
}
}

View File

@ -0,0 +1,23 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol NotificationPermissionsScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<NotificationPermissionsScreenViewModelAction, Never> { get }
var context: NotificationPermissionsScreenViewModelType.Context { get }
}

View File

@ -0,0 +1,77 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
/// A prompt that asks the user whether they would like to enable Analytics or not.
struct NotificationPermissionsScreen: View {
@ObservedObject var context: NotificationPermissionsScreenViewModel.Context
var body: some View {
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, showBackgroundGradient: true) {
mainContent
} bottomContent: {
buttons
}
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.interactiveDismissDisabled()
}
/// The main content of the screen that is shown inside the scroll view.
private var mainContent: some View {
VStack(spacing: 8) {
HeroImage(icon: \.notificationsSolid)
.padding(.bottom, 8)
Text(L10n.screenNotificationOptinTitle)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
Text(L10n.screenNotificationOptinSubtitle)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
Asset.Images.notificationsPromptGraphic.swiftUIImage.resizable().aspectRatio(contentMode: .fit)
}
}
private var buttons: some View {
VStack(spacing: 16) {
Button(L10n.actionOk) { context.send(viewAction: .enable) }
.buttonStyle(.compound(.primary))
Button { context.send(viewAction: .notNow) } label: {
Text(L10n.actionNotNow)
.font(.compound.bodyLGSemibold)
.padding(14)
}
}
}
}
// MARK: - Previews
struct NotificationPermissionsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = NotificationPermissionsScreenViewModel(notificationManager: NotificationManagerMock())
static var previews: some View {
NotificationPermissionsScreen(context: viewModel.context)
}
}

View File

@ -18,13 +18,11 @@ import Combine
import SwiftUI
enum SessionVerificationScreenCoordinatorAction {
case recoveryKey
case done
}
struct SessionVerificationScreenCoordinatorParameters {
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
let recoveryState: SecureBackupRecoveryState
}
final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
@ -38,8 +36,7 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
}
init(parameters: SessionVerificationScreenCoordinatorParameters) {
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy,
recoveryState: parameters.recoveryState)
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy)
}
// MARK: - Public
@ -50,8 +47,6 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
guard let self else { return }
switch action {
case .recoveryKey:
actionsSubject.send(.recoveryKey)
case .finished:
actionsSubject.send(.done)
}
@ -59,6 +54,10 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
.store(in: &cancellables)
}
func stop() {
viewModel.stop()
}
func toPresentable() -> AnyView {
AnyView(SessionVerificationScreen(context: viewModel.context))
}

View File

@ -17,13 +17,10 @@
import Foundation
enum SessionVerificationScreenViewModelAction {
case recoveryKey
case finished
}
struct SessionVerificationScreenViewState: BindableState {
let showRecoveryOption: Bool
var verificationState: SessionVerificationScreenStateMachine.State = .initial
var title: String? {
@ -86,11 +83,9 @@ struct SessionVerificationScreenViewState: BindableState {
}
enum SessionVerificationScreenViewAction {
case recoveryKey
case requestVerification
case startSasVerification
case restart
case accept
case decline
case close
}

View File

@ -31,13 +31,12 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
}
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
recoveryState: SecureBackupRecoveryState,
verificationState: SessionVerificationScreenStateMachine.State = .initial) {
self.sessionVerificationControllerProxy = sessionVerificationControllerProxy
stateMachine = SessionVerificationScreenStateMachine()
super.init(initialViewState: .init(showRecoveryOption: recoveryState == .incomplete, verificationState: verificationState))
super.init(initialViewState: .init(verificationState: verificationState))
setupStateMachine()
@ -71,23 +70,12 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
override func process(viewAction: SessionVerificationScreenViewAction) {
switch viewAction {
case .recoveryKey:
actionsSubject.send(.recoveryKey)
case .requestVerification:
stateMachine.processEvent(.requestVerification)
case .startSasVerification:
stateMachine.processEvent(.startSasVerification)
case .restart:
stateMachine.processEvent(.restart)
case .close:
guard stateMachine.state == .initial ||
stateMachine.state == .verified ||
stateMachine.state == .cancelled else {
stateMachine.processEvent(.cancel)
return
}
actionsSubject.send(.finished)
case .accept:
stateMachine.processEvent(.acceptChallenge)
case .decline:
@ -95,6 +83,14 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
}
}
func stop() {
let uncancellableStates: [SessionVerificationScreenStateMachine.State] = [.initial, .verified, .cancelled]
if !uncancellableStates.contains(stateMachine.state) {
stateMachine.processEvent(.cancel)
}
}
// MARK: - Private
private func setupStateMachine() {
@ -115,11 +111,7 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
case (_, .cancel, .cancelling):
cancelVerification()
case (_, _, .verified):
// Dismiss the success screen automatically.
Task {
try? await Task.sleep(for: .seconds(2))
self.actionsSubject.send(.finished)
}
actionsSubject.send(.finished)
default:
break
}

View File

@ -20,4 +20,6 @@ import Combine
protocol SessionVerificationScreenViewModelProtocol {
var actions: AnyPublisher<SessionVerificationScreenViewModelAction, Never> { get }
var context: SessionVerificationViewModelType.Context { get }
func stop()
}

View File

@ -20,23 +20,18 @@ struct SessionVerificationScreen: View {
@ObservedObject var context: SessionVerificationScreenViewModel.Context
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 32) {
screenHeader
Spacer()
mainContent
}
.padding(.horizontal, 16)
.padding(.top, 24)
.frame(maxWidth: .infinity)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbarContent }
FullscreenDialog {
VStack(spacing: 32) {
screenHeader
Spacer()
mainContent
}
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.safeAreaInset(edge: .bottom) { actionButtons.padding() }
} bottomContent: {
actionButtons
}
.interactiveDismissDisabled() // Make sure dismissal goes through the state machine(s).
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.interactiveDismissDisabled()
}
// MARK: - Private
@ -44,7 +39,7 @@ struct SessionVerificationScreen: View {
private var headerImageName: String {
switch context.viewState.verificationState {
case .initial:
return "macbook.and.iphone"
return "lock"
case .cancelled:
return "exclamationmark.shield"
case .requestingVerification:
@ -71,19 +66,24 @@ struct SessionVerificationScreen: View {
@ViewBuilder
private var screenHeader: some View {
VStack(spacing: 0) {
Image(systemName: headerImageName)
.heroImage()
.padding(.bottom, 16)
if context.viewState.verificationState == .initial {
HeroImage(icon: \.lockSolid)
.padding(.bottom, 16)
} else {
Image(systemName: headerImageName)
.heroImage()
.padding(.bottom, 16)
}
Text(context.viewState.title ?? "")
.font(.title2.bold())
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
.padding(.bottom, 8)
.accessibilityIdentifier(context.viewState.titleAccessibilityIdentifier)
Text(context.viewState.message)
.font(.subheadline)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
}
@ -134,14 +134,6 @@ struct SessionVerificationScreen: View {
}
.buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
if context.viewState.showRecoveryOption {
Button(L10n.screenSessionVerificationEnterRecoveryKey) {
context.send(viewAction: .recoveryKey)
}
.buttonStyle(.compound(.plain))
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.enterRecoveryKey)
}
}
case .cancelled:
Button(L10n.actionRetry) {
@ -157,9 +149,9 @@ struct SessionVerificationScreen: View {
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.startSasVerification)
case .showingChallenge:
VStack(spacing: 30) {
Button { context.send(viewAction: .accept) } label: {
Label(L10n.screenSessionVerificationTheyMatch, systemImage: "checkmark")
VStack(spacing: 32) {
Button(L10n.screenSessionVerificationTheyMatch) {
context.send(viewAction: .accept)
}
.buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
@ -172,12 +164,12 @@ struct SessionVerificationScreen: View {
}
case .acceptingChallenge:
VStack(spacing: 30) {
VStack(spacing: 32) {
Button { context.send(viewAction: .accept) } label: {
HStack(spacing: 16) {
ProgressView()
.tint(.compound.textOnSolidPrimary)
Label(L10n.screenSessionVerificationTheyMatch, systemImage: "checkmark")
Text(L10n.screenSessionVerificationTheyMatch)
}
}
.buttonStyle(.compound(.primary))
@ -197,16 +189,6 @@ struct SessionVerificationScreen: View {
}
}
@ToolbarContentBuilder
private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .close)
}
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.close)
}
}
struct EmojiView: View {
let emoji: SessionVerificationEmoji
@ -244,7 +226,6 @@ struct SessionVerification_Previews: PreviewProvider, TestablePreview {
static func sessionVerificationScreen(state: SessionVerificationScreenStateMachine.State) -> some View {
let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(),
recoveryState: .incomplete,
verificationState: state)
return SessionVerificationScreen(context: viewModel.context)

View File

@ -82,7 +82,7 @@ final class ReportContentScreenCoordinator: CoordinatorProtocol {
// MARK: - Private
private static let loadingIndicatorIdentifier = "ReportContentLoading"
private static let loadingIndicatorIdentifier = "\(ReportContentScreenCoordinator.self)-Loading"
private func startLoading() {
parameters.userIndicatorController.submitIndicator(

View File

@ -171,7 +171,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
// MARK: Loading indicator
private static let loadingIndicatorIdentifier = "RoomMemberLoading"
private static let loadingIndicatorIdentifier = "\(RoomMemberDetailsScreenViewModel.self)-Loading"
private func showMemberLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,

View File

@ -97,9 +97,8 @@ struct TimelineItemPlainStylerView<Content: View>: View {
HStack(spacing: 8) {
TimelineSenderAvatarView(timelineItem: timelineItem)
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
.font(.subheadline)
.font(.compound.bodyMDSemibold)
.foregroundColor(.compound.decorativeColor(for: timelineItem.sender.id).text)
.fontWeight(.semibold)
.lineLimit(1)
}
.onTapGesture {

View File

@ -1,68 +0,0 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
/// The item shown when all previous items are un-decryptable due to
/// key backup not yet being supported in the app.
struct EncryptedHistoryRoomTimelineView: View {
let timelineItem: EncryptedHistoryRoomTimelineItem
var body: some View {
Label {
Text(title)
.font(.compound.bodyMDSemibold)
.foregroundColor(.compound.textInfoPrimary)
} icon: {
CompoundIcon(\.infoSolid, size: .small, relativeTo: .compound.bodyMDSemibold)
.foregroundColor(.compound.iconInfoPrimary)
}
.labelStyle(EncryptedHistoryLabelStyle())
.padding(16)
.background {
RoundedRectangle(cornerRadius: 8)
.fill(Color.compound.bgInfoSubtle)
RoundedRectangle(cornerRadius: 8)
.stroke(Color.compound.borderInfoSubtle)
}
.frame(maxWidth: .infinity)
.padding(.horizontal, 8)
.padding(.vertical, 16)
}
private var title: String {
timelineItem.isSessionVerified ? L10n.screenRoomEncryptedHistoryBanner : L10n.screenRoomEncryptedHistoryBannerUnverified
}
}
private struct EncryptedHistoryLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .top, spacing: 16) {
configuration.icon
configuration.title
}
}
}
struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
VStack(spacing: 8) {
EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: true))
EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: false))
}
}
}

View File

@ -86,7 +86,7 @@ class SecureBackupKeyBackupScreenViewModel: SecureBackupKeyBackupScreenViewModel
}
}
private static let loadingIndicatorIdentifier = "SecureBackupKeyBackupScreenLoading"
private static let loadingIndicatorIdentifier = "\(SecureBackupKeyBackupScreenViewModel.self)-Loading"
private func showLoadingIndicator() {
userIndicatorController?.submitIndicator(.init(id: Self.loadingIndicatorIdentifier, type: .modal, title: L10n.commonLoading, persistent: true))

View File

@ -22,12 +22,21 @@ struct SecureBackupKeyBackupScreen: View {
@ObservedObject var context: SecureBackupKeyBackupScreenViewModel.Context
var body: some View {
mainContent
.padding()
.interactiveDismissDisabled()
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.alert(item: $context.alertInfo)
FullscreenDialog {
mainContent
} bottomContent: {
Button(role: .destructive) {
context.send(viewAction: .toggleBackup)
} label: {
Text(L10n.screenChatBackupKeyBackupActionDisable)
}
.buttonStyle(.compound(.primary))
}
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.interactiveDismissDisabled()
.toolbar { toolbar }
.alert(item: $context.alertInfo)
}
@ViewBuilder
@ -71,15 +80,6 @@ struct SecureBackupKeyBackupScreen: View {
.foregroundColor(.compound.iconCriticalPrimary)
}
}
Spacer()
Button(role: .destructive) {
context.send(viewAction: .toggleBackup)
} label: {
Text(L10n.screenChatBackupKeyBackupActionDisable)
}
.buttonStyle(.compound(.primary))
}
}

View File

@ -22,24 +22,19 @@ struct SecureBackupLogoutConfirmationScreen: View {
@ObservedObject var context: SecureBackupLogoutConfirmationScreenViewModel.Context
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 16) {
header
content
}
.padding()
FullscreenDialog {
VStack(spacing: 16) {
HeroImage(icon: \.keyOffSolid)
content
}
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.safeAreaInset(edge: .bottom) { footer.padding() }
.alert(item: $context.alertInfo)
.padding()
} bottomContent: {
footer.padding()
}
}
@ViewBuilder
private var header: some View {
HeroImage(icon: \.keyOffSolid)
.toolbar { toolbar }
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.alert(item: $context.alertInfo)
}
@ViewBuilder
@ -62,21 +57,23 @@ struct SecureBackupLogoutConfirmationScreen: View {
@ViewBuilder
private var footer: some View {
if context.viewState.mode == .saveRecoveryKey {
Button {
context.send(viewAction: .settings)
VStack(spacing: 16.0) {
if context.viewState.mode == .saveRecoveryKey {
Button {
context.send(viewAction: .settings)
} label: {
Text(L10n.commonSettings)
}
.buttonStyle(.compound(.primary))
}
Button(role: .destructive) {
context.send(viewAction: .logout)
} label: {
Text(L10n.commonSettings)
Text(L10n.actionSignout)
}
.buttonStyle(.compound(.primary))
}
Button(role: .destructive) {
context.send(viewAction: .logout)
} label: {
Text(L10n.actionSignout)
}
.buttonStyle(.compound(.primary))
}
@ToolbarContentBuilder
@ -88,7 +85,7 @@ struct SecureBackupLogoutConfirmationScreen: View {
}
}
var title: String {
private var title: String {
switch context.viewState.mode {
case .saveRecoveryKey:
return L10n.screenSignoutSaveRecoveryKeyTitle
@ -99,7 +96,7 @@ struct SecureBackupLogoutConfirmationScreen: View {
}
}
var subtitle: String {
private var subtitle: String {
switch context.viewState.mode {
case .saveRecoveryKey:
return L10n.screenSignoutSaveRecoveryKeySubtitle

View File

@ -20,6 +20,7 @@ import SwiftUI
struct SecureBackupRecoveryKeyScreenCoordinatorParameters {
let secureBackupController: SecureBackupControllerProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let isModallyPresented: Bool
}
enum SecureBackupRecoveryKeyScreenCoordinatorAction {
@ -40,7 +41,8 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
init(parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters) {
viewModel = SecureBackupRecoveryKeyScreenViewModel(secureBackupController: parameters.secureBackupController,
userIndicatorController: parameters.userIndicatorController)
userIndicatorController: parameters.userIndicatorController,
isModallyPresented: parameters.isModallyPresented)
}
func start() {

View File

@ -28,6 +28,9 @@ enum SecureBackupRecoveryKeyScreenViewMode {
}
struct SecureBackupRecoveryKeyScreenViewState: BindableState {
/// Whether the screen is presented modally or within a navigation stack.
var isModallyPresented: Bool
let mode: SecureBackupRecoveryKeyScreenViewMode
var recoveryKey: String?

View File

@ -28,11 +28,15 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM
actionsSubject.eraseToAnyPublisher()
}
init(secureBackupController: SecureBackupControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol) {
init(secureBackupController: SecureBackupControllerProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
isModallyPresented: Bool) {
self.secureBackupController = secureBackupController
self.userIndicatorController = userIndicatorController
super.init(initialViewState: .init(mode: secureBackupController.recoveryState.value.viewMode, bindings: .init()))
super.init(initialViewState: .init(isModallyPresented: isModallyPresented,
mode: secureBackupController.recoveryState.value.viewMode,
bindings: .init()))
secureBackupController.recoveryState
.receive(on: DispatchQueue.main)

View File

@ -24,7 +24,7 @@ struct SecureBackupRecoveryKeyScreen: View {
private let textFieldIdentifier = "textFieldIdentifier"
var body: some View {
ScrollView {
FullscreenDialog {
ScrollViewReader { reader in
mainContent
.padding(16)
@ -33,17 +33,14 @@ struct SecureBackupRecoveryKeyScreen: View {
reader.scrollTo(textFieldIdentifier)
}
}
}
.safeAreaInset(edge: .bottom) {
} bottomContent: {
footer
.padding([.horizontal, .bottom], 16)
.padding(.top, 8)
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
}
.interactiveDismissDisabled()
.toolbar { toolbar }
.toolbar(.visible, for: .navigationBar)
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.interactiveDismissDisabled()
.alert(item: $context.alertInfo)
}
@ -101,7 +98,7 @@ struct SecureBackupRecoveryKeyScreen: View {
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
if context.viewState.recoveryKey == nil {
if context.viewState.isModallyPresented == true, context.viewState.recoveryKey == nil {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
@ -235,6 +232,8 @@ struct SecureBackupRecoveryKeyScreen_Previews: PreviewProvider, TestablePreview
let backupController = SecureBackupControllerMock()
backupController.underlyingRecoveryState = CurrentValueSubject<SecureBackupRecoveryState, Never>(recoveryState).asCurrentValuePublisher()
return SecureBackupRecoveryKeyScreenViewModel(secureBackupController: backupController, userIndicatorController: UserIndicatorControllerMock())
return SecureBackupRecoveryKeyScreenViewModel(secureBackupController: backupController,
userIndicatorController: UserIndicatorControllerMock(),
isModallyPresented: true)
}
}

View File

@ -46,7 +46,8 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
let navigationStackCoordinator = NavigationStackCoordinator()
let recoveryKeyCoordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: .init(secureBackupController: parameters.secureBackupController,
userIndicatorController: parameters.userIndicatorController))
userIndicatorController: parameters.userIndicatorController,
isModallyPresented: true))
recoveryKeyCoordinator.actions.sink { [weak self] action in
guard let self else { return }

View File

@ -33,7 +33,6 @@ enum SettingsScreenCoordinatorAction {
case bugReport
case about
case blockedUsers
case sessionVerification
case accountSessions
case notifications
case advancedSettings
@ -77,8 +76,6 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.about)
case .blockedUsers:
actionsSubject.send(.blockedUsers)
case .sessionVerification:
actionsSubject.send(.sessionVerification)
case .secureBackup:
actionsSubject.send(.secureBackup)
case .accountSessionsList:

View File

@ -26,7 +26,6 @@ enum SettingsScreenViewModelAction {
case reportBug
case about
case blockedUsers
case sessionVerification
case secureBackup
case accountSessionsList
case notifications
@ -37,7 +36,6 @@ enum SettingsScreenViewModelAction {
enum SettingsScreenSecuritySectionMode {
case none
case sessionVerification
case secureBackup
}
@ -65,7 +63,6 @@ enum SettingsScreenViewAction {
case reportBug
case about
case blockedUsers
case sessionVerification
case secureBackup
case accountSessionsList
case notifications

View File

@ -50,9 +50,6 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
guard let self else { return }
switch (securityState.verificationState, securityState.recoveryState) {
case (.unverified, _):
state.showSecuritySectionBadge = true
state.securitySectionMode = .sessionVerification
case (.verified, .disabled):
state.showSecuritySectionBadge = true
state.securitySectionMode = .secureBackup
@ -107,8 +104,6 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
actionsSubject.send(.blockedUsers)
case .logout:
actionsSubject.send(.logout)
case .sessionVerification:
actionsSubject.send(.sessionVerification)
case .secureBackup:
actionsSubject.send(.secureBackup)
case .notifications:

View File

@ -81,11 +81,6 @@ struct SettingsScreen: View {
private var accountSecuritySection: some View {
Section {
switch context.viewState.securitySectionMode {
case .sessionVerification:
ListRow(label: .default(title: L10n.actionCompleteVerification,
icon: \.checkCircle),
details: context.viewState.showSecuritySectionBadge ? .icon(securitySectionBadge) : nil,
kind: .button { context.send(viewAction: .sessionVerification) })
case .secureBackup:
ListRow(label: .default(title: L10n.commonChatBackup,
icon: \.key),

View File

@ -190,7 +190,7 @@ final class StartChatScreenCoordinator: CoordinatorProtocol {
// MARK: Loading indicator
private static let loadingIndicatorIdentifier = "StartChatCoordinatorLoading"
private static let loadingIndicatorIdentifier = "\(StartChatScreenCoordinator.self)-Loading"
private func showLoadingIndicator() {
parameters.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,

View File

@ -145,7 +145,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
// MARK: Loading indicator
private static let loadingIndicatorIdentifier = "StartChatLoading"
private static let loadingIndicatorIdentifier = "\(StartChatScreenViewModel.self)-Loading"
private func showLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,

View File

@ -64,13 +64,6 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
self?.enableNotifications(newValue)
}
.store(in: &cancellables)
// Request authorization uppon UIApplication.didBecomeActiveNotification notification
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { [weak self] _ in
self?.requestAuthorization()
}
.store(in: &cancellables)
}
func requestAuthorization() {

View File

@ -38,5 +38,6 @@ protocol NotificationManagerProtocol: AnyObject {
func registrationFailed(with error: Error)
func showLocalNotification(with title: String, subtitle: String?) async
func setUserSession(_ userSession: UserSessionProtocol?)
func requestAuthorization()
}

View File

@ -18,8 +18,7 @@ import Foundation
struct MockRoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol {
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
return timelineController

View File

@ -24,7 +24,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private let timelineProvider: RoomTimelineProviderProtocol
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
private let appSettings: AppSettings
private let secureBackupController: SecureBackupControllerProtocol
private let serialDispatchQueue: DispatchQueue
private var cancellables = Set<AnyCancellable>()
@ -39,13 +38,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
init(roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
appSettings: AppSettings,
secureBackupController: SecureBackupControllerProtocol) {
appSettings: AppSettings) {
self.roomProxy = roomProxy
timelineProvider = roomProxy.timeline.timelineProvider
self.timelineItemFactory = timelineItemFactory
self.appSettings = appSettings
self.secureBackupController = secureBackupController
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
timelineProvider
@ -253,7 +250,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
var newTimelineItems = [RoomTimelineItemProtocol]()
var canBackPaginate = !roomProxy.timeline.timelineStartReached
var isBackPaginating = false
var lastEncryptedHistoryItemIndex: Int?
let collapsibleChunks = timelineProvider.itemProxies.groupBy { isItemCollapsible($0) }
@ -264,10 +260,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
let timelineItem = buildTimelineItem(for: itemProxy)
if timelineItem is EncryptedHistoryRoomTimelineItem {
canBackPaginate = false
}
return timelineItem
}
@ -282,35 +274,25 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
continue
}
if timelineItem is EncryptedHistoryRoomTimelineItem {
lastEncryptedHistoryItemIndex = newTimelineItems.endIndex
}
newTimelineItems.append(timelineItem)
} else {
newTimelineItems.append(CollapsibleTimelineItem(items: items))
}
}
if let lastEncryptedHistoryItemIndex {
// Remove everything up to the last encrypted history item.
// It only contains encrypted messages, state changes and date separators.
newTimelineItems.removeFirst(lastEncryptedHistoryItemIndex)
} else {
// Otherwise check if we need to add anything to the top of the timeline.
switch timelineProvider.backPaginationState {
case .timelineStartReached:
if !roomProxy.isEncryptedOneToOneRoom {
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name)
newTimelineItems.insert(timelineStart, at: 0)
}
canBackPaginate = false
case .paginating:
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(), at: 0)
isBackPaginating = true
case .idle:
break
// Check if we need to add anything to the top of the timeline.
switch timelineProvider.backPaginationState {
case .timelineStartReached:
if !roomProxy.isEncryptedOneToOneRoom {
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name)
newTimelineItems.insert(timelineStart, at: 0)
}
canBackPaginate = false
case .paginating:
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(), at: 0)
isBackPaginating = true
case .idle:
break
}
DispatchQueue.main.sync {
@ -327,16 +309,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
case .event(let eventTimelineItem):
let timelineItem = timelineItemFactory.buildTimelineItem(for: eventTimelineItem, isDM: roomProxy.isEncryptedOneToOneRoom)
// When backup is enabled just show the timeline items, they will most likely
// resolve eventually. If we don't know the backup state then we assume the session is not verified yet,
// otherwise we just treat it as fully disabled.
if secureBackupController.keyBackupState.value != .enabled {
if timelineItem is EncryptedRoomTimelineItem, isItemInEncryptionHistory(eventTimelineItem) {
return EncryptedHistoryRoomTimelineItem(id: eventTimelineItem.id,
isSessionVerified: secureBackupController.keyBackupState.value != .unknown)
}
}
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {
// Avoid fetching this over and over again as it changes states if it keeps failing to load
// Errors will be handled again on appearance
@ -359,13 +331,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
}
/// Whether or not a specific item is part of the room's history that can't be decrypted due
/// to the lack of key-backup. This is handled differently so we only show a single item.
private func isItemInEncryptionHistory(_ itemProxy: EventTimelineItemProxy) -> Bool {
guard roomProxy.isEncrypted, let lastLoginDate = appSettings.lastLoginDate else { return false }
return itemProxy.timestamp < lastLoginDate
}
private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool {
if !appSettings.shouldCollapseRoomStateEvents {
return false

View File

@ -18,11 +18,9 @@ import Foundation
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol {
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
RoomTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
appSettings: ServiceLocator.shared.settings,
secureBackupController: secureBackupController)
appSettings: ServiceLocator.shared.settings)
}
}

View File

@ -19,6 +19,5 @@ import Foundation
@MainActor
protocol RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol
}

View File

@ -64,8 +64,6 @@ struct RoomTimelineItemView: View {
UnsupportedRoomTimelineView(timelineItem: item)
case .timelineStart(let item):
TimelineStartRoomTimelineView(timelineItem: item)
case .encryptedHistory(let item):
EncryptedHistoryRoomTimelineView(timelineItem: item)
case .state(let item):
StateRoomTimelineView(timelineItem: item)
case .group(let item):

View File

@ -60,7 +60,6 @@ enum RoomTimelineItemType: Equatable {
case sticker(StickerRoomTimelineItem)
case unsupported(UnsupportedRoomTimelineItem)
case timelineStart(TimelineStartRoomTimelineItem)
case encryptedHistory(EncryptedHistoryRoomTimelineItem)
case state(StateRoomTimelineItem)
case group(CollapsibleTimelineItem)
case location(LocationRoomTimelineItem)
@ -100,8 +99,6 @@ enum RoomTimelineItemType: Equatable {
self = .unsupported(item)
case let item as TimelineStartRoomTimelineItem:
self = .timelineStart(item)
case let item as EncryptedHistoryRoomTimelineItem:
self = .encryptedHistory(item)
case let item as StateRoomTimelineItem:
self = .state(item)
case let item as CollapsibleTimelineItem:
@ -136,7 +133,6 @@ enum RoomTimelineItemType: Equatable {
.sticker(let item as RoomTimelineItemProtocol),
.unsupported(let item as RoomTimelineItemProtocol),
.timelineStart(let item as RoomTimelineItemProtocol),
.encryptedHistory(let item as RoomTimelineItemProtocol),
.state(let item as RoomTimelineItemProtocol),
.group(let item as RoomTimelineItemProtocol),
.location(let item as RoomTimelineItemProtocol),

View File

@ -42,7 +42,7 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, WindowManagerDelegate {
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
AppSettings.configureWithSuiteName("io.element.elementx.uitests")
AppSettings.reset()
AppSettings.resetAllSettings()
ServiceLocator.shared.register(appSettings: AppSettings())
ServiceLocator.shared.register(bugReportService: BugReportServiceMock())
ServiceLocator.shared.register(analytics: AnalyticsService(client: AnalyticsClientMock(),
@ -149,7 +149,6 @@ class MockScreen: Identifiable {
return navigationStackCoordinator
case .authenticationFlow:
let flowCoordinator = AuthenticationFlowCoordinator(authenticationService: MockAuthenticationServiceProxy(),
appLockService: AppLockServiceMock(),
bugReportService: BugReportServiceMock(),
navigationRootCoordinator: navigationRootCoordinator,
appSettings: ServiceLocator.shared.settings,
@ -567,8 +566,7 @@ class MockScreen: Identifiable {
return navigationStackCoordinator
case .sessionVerification:
var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5))
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy,
recoveryState: .unknown)
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy)
return SessionVerificationScreenCoordinator(parameters: parameters)
case .userSessionScreen, .userSessionScreenReply, .userSessionScreenRTE:
let appSettings: AppSettings = ServiceLocator.shared.settings
@ -586,7 +584,9 @@ class MockScreen: Identifiable {
bugReportService: BugReportServiceMock(),
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
appSettings: appSettings,
analytics: ServiceLocator.shared.analytics)
analytics: ServiceLocator.shared.analytics,
notificationManager: NotificationManagerMock(),
isNewLogin: false)
flowCoordinator.start()

View File

@ -24,7 +24,7 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol {
ServiceLocator.shared.register(userIndicatorController: UserIndicatorControllerMock.default)
AppSettings.configureWithSuiteName("io.element.elementx.unittests")
AppSettings.reset()
AppSettings.resetAllSettings()
ServiceLocator.shared.register(appSettings: AppSettings())
ServiceLocator.shared.register(bugReportService: BugReportServiceMock())
ServiceLocator.shared.register(analytics: AnalyticsService(client: AnalyticsClientMock(),

View File

@ -19,7 +19,14 @@ import XCTest
enum Application {
@discardableResult static func launch() -> XCUIApplication {
let app = XCUIApplication()
var launchEnvironment = [
"IS_RUNNING_INTEGRATION_TESTS": "1"
]
app.launchEnvironment = launchEnvironment
app.launch()
return app
}
}

View File

@ -66,32 +66,12 @@ extension XCUIApplication {
currentTestCase.expectation(for: doesNotExistPredicate, evaluatedWith: usernameTextField)
currentTestCase.waitForExpectations(timeout: 300.0)
// Handle analytics prompt screen
if staticTexts[A11yIdentifiers.analyticsPromptScreen.title].waitForExistence(timeout: 10.0) {
// Wait for login and then handle save password sheet
let savePasswordButton = buttons["Save Password"]
if savePasswordButton.waitForExistence(timeout: 10.0) {
savePasswordButton.tap()
}
let enableButton = buttons[A11yIdentifiers.analyticsPromptScreen.enable]
XCTAssertTrue(enableButton.waitForExistence(timeout: 10.0))
enableButton.tap()
}
// This might come in a different order, wait for both.
let savePasswordButton = buttons["Save Password"]
if savePasswordButton.waitForExistence(timeout: 10.0) {
savePasswordButton.tap()
}
// Handle the notifications permission alert https://stackoverflow.com/a/58171074/730924
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let notificationAlertDeclineButton = springboard.buttons.element(boundBy: 0)
if notificationAlertDeclineButton.waitForExistence(timeout: 10.0) {
notificationAlertDeclineButton.tap()
}
// Migration screen may be shown as an overlay.
// if that pops up soon enough, we just let that happen and wait
let message = staticTexts[A11yIdentifiers.migrationScreen.message]

Some files were not shown because too many files have changed in this diff Show More