From cbb0ec5063ca32c4b20bba26bf1b31960c37d11a Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Sun, 25 Sep 2022 12:34:11 +0300 Subject: [PATCH] Introduced a UserSessionFlowCoordinator and split up the AppCoordinator state machine (#212) --- ElementX.xcodeproj/project.pbxproj | 96 ++++-- .../{ => Application}/AppCoordinator.swift | 253 ++-------------- .../AppCoordinatorStateMachine.swift | 34 +-- .../{ => Application}/AppDelegate.swift | 0 .../{ => Application}/BuildSettings.swift | 4 +- .../Generated/Strings+Untranslated.swift | 2 +- .../Other/Routers/NavigationRouterType.swift | 12 + .../UITestScreenIdentifier.swift | 0 .../{ => UITests}/UITestsAppCoordinator.swift | 0 .../{ => UITests}/UITestsRootView.swift | 0 .../UserSessionFlowCoordinator.swift | 275 ++++++++++++++++++ ...erSessionFlowCoordinatorStateMachine.swift | 97 ++++++ UITests/SupportingFiles/target.yml | 4 +- project.yml | 2 +- 14 files changed, 484 insertions(+), 295 deletions(-) rename ElementX/Sources/{ => Application}/AppCoordinator.swift (56%) rename ElementX/Sources/{ => Application}/AppCoordinatorStateMachine.swift (74%) rename ElementX/Sources/{ => Application}/AppDelegate.swift (100%) rename ElementX/Sources/{ => Application}/BuildSettings.swift (93%) rename ElementX/Sources/{ => UITests}/UITestScreenIdentifier.swift (100%) rename ElementX/Sources/{ => UITests}/UITestsAppCoordinator.swift (100%) rename ElementX/Sources/{ => UITests}/UITestsRootView.swift (100%) create mode 100644 ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift create mode 100644 ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index ed9f428ae..8ebcb9e42 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -8,16 +8,13 @@ /* Begin PBXBuildFile section */ 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; }; - 004561D297DC8B9786AE136F /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */; }; 00EA14F62DCEF62CDE4808D6 /* RedactedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B577F829C693B8DFB7014FD /* RedactedRoomTimelineItem.swift */; }; 00F3059B1E0CFCA019710C3E /* BugReportModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B516212D9FE785DDD5E490D1 /* BugReportModels.swift */; }; 01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */; }; 01F4A40C1EDCEC8DC4EC9CFA /* WeakDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 109C0201D8CB3F947340DC80 /* WeakDictionary.swift */; }; 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; }; - 03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */; }; 03D684A3AE85A23B3DA3B43F /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26747B3154A5DBC3A7E24A5 /* Image.swift */; }; 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; }; - 05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; }; 059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; }; 05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */; }; 0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA072E995316CD18BC29313 /* UserIndicatorPresentationContext.swift */; }; @@ -26,6 +23,7 @@ 071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; }; 07240B7159A3990C4C2E8FFC /* LoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D256FEE2F1AF1E51D39B622 /* LoginTests.swift */; }; 072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; }; + 095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; }; 09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; }; 0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; }; 0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; }; @@ -50,13 +48,13 @@ 165A883C29998EC779465068 /* SoftLogoutViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC38904A9663F7FAFD47457 /* SoftLogoutViewModelProtocol.swift */; }; 1702981A8085BE4FB0EC001B /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33116993D54FADC0C721C1F /* Application.swift */; }; 172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */; }; - 17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */; }; 187E18F21EF4DA244E436E58 /* BugReportViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28959C7DB36C7688A01D4045 /* BugReportViewModelProtocol.swift */; }; 191161FE9E0DA89704301F37 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; }; 1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */; }; 19839F3526CE8C35AAF241AD /* ServerSelectionViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F52BF30D12BA3BD3D3DBB8F /* ServerSelectionViewModelProtocol.swift */; }; 1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; }; 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; + 1CA5FC60B93D5AA60972ACFE /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04A526B2825F72617D57461 /* UserSessionFlowCoordinatorStateMachine.swift */; }; 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; }; 1F3232BD368DF430AB433907 /* DesignKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5A56C4F47C368EBE5C5E870 /* DesignKit */; }; 206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */; }; @@ -73,6 +71,7 @@ 28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */; }; 290FDB0FFDC2F1DDF660343E /* TestMeasurementParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */; }; 297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */; }; + 29E20505F321071E8375F99B /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; }; 29EE1791E0AFA1ABB7F23D2F /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */; }; 2A90D9F91A836E30B7D78838 /* MXLogObjcWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */; }; 2BA59D0AEFB4B82A2EC2A326 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50009897F60FAE7D63EF5E5B /* Kingfisher */; }; @@ -81,7 +80,6 @@ 2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086B997409328F091EBA43CE /* RoomScreenUITests.swift */; }; 2F30EFEB7BD39242D1AD96F3 /* LoginViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1FB768A24FDD2A5CA16E3C /* LoginViewModelProtocol.swift */; }; 2F94054F50E312AF30BE07F3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; }; - 2FE4EEF780553B25A446BBFB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */; }; 30122AB3484AC6C3A7F6A717 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */; }; 308BD9343B95657FAA583FB7 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = AD2AC190E55B2BD4D0F1D4A7 /* SwiftyBeaver */; }; 3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; }; @@ -117,7 +115,6 @@ 4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; }; 490E606044B18985055FF690 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */; }; 492274DA6691EE985C2FCCAA /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; }; - 499A26EB06C97E48C27A2DB9 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; }; 49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D58333B377888012740101 /* LoginViewModel.swift */; }; 49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; }; 4A2E0DBB63919AC8309B6D40 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */; }; @@ -127,6 +124,7 @@ 4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */; }; 4ED453A61AF45EBE18D8BC69 /* NavigationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F77E8010D41AA3F5F9A1FCA /* NavigationModule.swift */; }; 4FC1EFE4968A259CBBACFAFB /* RoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */; }; + 4FF90E2242DBD596E1ED2E27 /* AppCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */; }; 500CB65ED116B81DA52FDAEE /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874A1842477895F199567BD7 /* TimelineView.swift */; }; 50391038BC50C8ED9A4D88A0 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B23371BC8BF6164D9F6A05 /* WeakDictionaryReference.swift */; }; 51DB67C5B5BC68B0A6FF54D4 /* MockRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */; }; @@ -175,7 +173,6 @@ 74604ACFDBE7F54260E7B617 /* ApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */; }; 755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; }; 758BF44CA565AB0AB84F2185 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; }; - 75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */; }; 75EA4ABBFAA810AFF289D6F4 /* TemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */; }; 7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; }; 77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */; }; @@ -230,6 +227,7 @@ 989029A28C9E2F828AD6658A /* AppIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */; }; 992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; }; 99ED42B8F8D6BFB1DBCF4C45 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = D661CAB418C075A94306A792 /* AnalyticsEvents */; }; + 9A47B7EFE3793760EEF68FFE /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */; }; 9AC5F8142413862A9E3A2D98 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; }; 9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */; }; 9B8DE1D424E37581C7D99CCC /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC7CCC6DE5FA623E31BA8546 /* RoomTimelineControllerProtocol.swift */; }; @@ -240,6 +238,7 @@ 9CB5129C83F75921E5E28028 /* ToastViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C82DAE0B8EB28234E84E6CF /* ToastViewState.swift */; }; 9CCC77C31CB399661A034739 /* UserProperties+Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6C4BE591FE5C38CE9C7EF3 /* UserProperties+Element.swift */; }; 9D2E03DB175A6AB14589076D /* AppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = AA4E1BEB4E9BC2467006E12B /* AppAuth */; }; + 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; }; 9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */; }; 9E8AE387FD03E4F1C1B8815A /* SessionVerificationStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */; }; A00DFC1DD3567B1EDC9F8D16 /* SplashScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */; }; @@ -250,7 +249,6 @@ A4E885358D7DD5A072A06824 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCE5BF78B125320CBF3BB834 /* PostHog */; }; A50849766F056FD1DB942DEA /* AlertInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */; }; A5C8F013ED9FB8AA6FEE18A7 /* InfoPlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A901D95158B02CA96C79C7F /* InfoPlist.swift */; }; - A636D4900E0D98ED91536482 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3EDF23226895776553F04A /* AppCoordinator.swift */; }; A663FE6704CB500EBE782AE1 /* AnalyticsPromptCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DE1CF8F5EFD353B1A5E36F /* AnalyticsPromptCoordinator.swift */; }; A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; @@ -263,13 +261,13 @@ AB4C5D62A21AD712811CE8CD /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68232D336E2B546AD95B78B5 /* XCUIElement.swift */; }; ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; }; ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */; }; + AE9EDA4805FCD42553255807 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562B0F8048BB22FB0C086CB8 /* UserSessionFlowCoordinator.swift */; }; B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; }; B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; }; B09514A0A3EB3C19A4FD0B71 /* SoftLogoutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCBDE671A613B3EB70794C4 /* SoftLogoutScreen.swift */; }; B245583C63F8F90357B87FAE /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 04C28663564E008DB32B5972 /* Introspect */; }; B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */; }; B3357B00F1AA930E54F76609 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; }; - B3FDB1D9CF40777695DBBD1D /* AppCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */; }; B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; }; B5111BAF5F601C139EBBD8BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; }; B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; }; @@ -296,6 +294,7 @@ C7B251DC896C0867C51B616D /* AnalyticsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541542F5AC323709D8563458 /* AnalyticsPrompt.swift */; }; C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; }; C8E82786DE1B6A400DA9BA25 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289FA233E896FBC5956C67E0 /* RoomTimelineItemProperties.swift */; }; + CA9558C0B40C1EE2AD00124A /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; }; CB137BFB3E083C33E398A6CB /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 5986E300FC849DEAB2EE7AEB /* Introspect */; }; CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */; }; CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; }; @@ -306,6 +305,7 @@ CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; }; CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */; }; D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */; }; + D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */; }; D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */; }; D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; }; D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; }; @@ -330,6 +330,7 @@ E481C8FDCB6C089963C95344 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 527578916BD388A09F5A8036 /* DTCoreText */; }; E5895C74615CBE8462FB840F /* SessionVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */; }; E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */; }; + E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; }; EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; }; EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; }; EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; }; @@ -349,6 +350,7 @@ F656F92A63D3DC1978D79427 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; }; F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; }; F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; }; + F75C4222D52B643214D5E623 /* UITestsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81740EEAFDF0D34C5E10D0DF /* UITestsRootView.swift */; }; F99FB21EFC6D99D247FE7CBE /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = D82E84F90358CC1118E6034B /* Introspect */; }; FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; }; FC6B7436C3A5B3D0565227D5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF05352F28D4E7336228E9F4 /* ActivityIndicatorView.swift */; }; @@ -391,6 +393,7 @@ 054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionUITests.swift; sourceTree = ""; }; 057B747CF045D3C6C30EAB2C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = ""; }; 0776771332259AB1C9661430 /* MXLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLog.h; sourceTree = ""; }; + 077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = ""; }; 086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = ""; }; 08F64963396A6A23538EFCEC /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = is; path = is.lproj/Localizable.stringsdict; sourceTree = ""; }; 0950733DD4BA83EEE752E259 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = ""; }; @@ -447,6 +450,7 @@ 24B0C97D2F560BCB72BE73B1 /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = ""; }; 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = ""; }; 2583416C8974272ADBADDBE1 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-TW"; path = "zh-TW.lproj/Localizable.stringsdict"; sourceTree = ""; }; + 263B3B811C2B900F12C6F695 /* BuildSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSettings.swift; sourceTree = ""; }; 26C4D226FCD20BAC53F1E092 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ml; path = ml.lproj/Localizable.strings; sourceTree = ""; }; 287FC98AF2664EAD79C0D902 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; 28959C7DB36C7688A01D4045 /* BugReportViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelProtocol.swift; sourceTree = ""; }; @@ -490,7 +494,6 @@ 3DD6E7C1D8B53F47789778CD /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskService.swift; sourceTree = ""; }; 3F40F48279322E504153AB0D /* MockClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockClientProxy.swift; sourceTree = ""; }; - 3F87116470221880017CF522 /* BuildSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSettings.swift; sourceTree = ""; }; 3FAA6438B00FDB130F404E31 /* UserIndicatorStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorStore.swift; sourceTree = ""; }; 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = ""; }; 40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; @@ -503,6 +506,7 @@ 44D8C8431416EB8DFEC7E235 /* ApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationTests.swift; sourceTree = ""; }; 453E722A43D092C06FB8E3FA /* tzm */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tzm; path = tzm.lproj/Localizable.strings; sourceTree = ""; }; 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreen.swift; sourceTree = ""; }; + 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = ""; }; 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; 475EB595D7527E9A8A14043E /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/Localizable.strings; sourceTree = ""; }; @@ -540,10 +544,10 @@ 55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = ""; }; 55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = ""; }; 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = ""; }; + 562B0F8048BB22FB0C086CB8 /* UserSessionFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinator.swift; sourceTree = ""; }; 56F01DD1BBD4450E18115916 /* LabelledActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelledActivityIndicatorView.swift; sourceTree = ""; }; 5773C86AF04AEF26515AD00C /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = ""; }; 5872785B9C7934940146BFBA /* MXLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLogger.h; sourceTree = ""; }; - 5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = ""; }; 5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelProtocol.swift; sourceTree = ""; }; 5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStoreProtocol.swift; sourceTree = ""; }; 5CB7F9D6FC121204D59E18DF /* Presentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = ""; }; @@ -553,7 +557,6 @@ 5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = ""; }; 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = ""; }; 5F77E8010D41AA3F5F9A1FCA /* NavigationModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = ""; }; - 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestScreenIdentifier.swift; sourceTree = ""; }; 5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = ""; }; 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = ""; }; 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizerTests.swift; sourceTree = ""; }; @@ -610,6 +613,7 @@ 804F9B0FABE093C7284CD09B /* TimelineItemList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemList.swift; sourceTree = ""; }; 8140010A796DB2C7977B6643 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 8166F121C79C7B62BF01D508 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pt; path = pt.lproj/Localizable.stringsdict; sourceTree = ""; }; + 81740EEAFDF0D34C5E10D0DF /* UITestsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsRootView.swift; sourceTree = ""; }; 81B17DB1BC3B0C62AF84D230 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8210612D17A39369480FC183 /* MediaSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSource.swift; sourceTree = ""; }; 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = ""; }; @@ -634,6 +638,7 @@ 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; 8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummaryProvider.swift; sourceTree = ""; }; + 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; 9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = ""; }; 90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTask.swift; sourceTree = ""; }; @@ -644,7 +649,6 @@ 9414DCADBDF9D6C4B806F61E /* sample_screenshot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sample_screenshot.png; sourceTree = ""; }; 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemPlainStylerView.swift; sourceTree = ""; }; 95CC95CD75B688E946438165 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; - 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = ""; }; 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactoryProtocol.swift; sourceTree = ""; }; 9772C1D2223108EB3131AEE4 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = ""; }; 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationIconImage.swift; sourceTree = ""; }; @@ -728,6 +732,7 @@ BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = ""; }; BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = ""; }; C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXAttributeScope.swift; sourceTree = ""; }; + C04A526B2825F72617D57461 /* UserSessionFlowCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorStateMachine.swift; sourceTree = ""; }; C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachine.swift; sourceTree = ""; }; C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = ""; }; C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = ""; }; @@ -735,6 +740,7 @@ C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = ""; }; C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = ""; }; C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = ""; }; + C75EF87651B00A176AB08E97 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C88508B6F7974CFABEC4B261 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = ""; }; C8F2A7A4E3F5060F52ACFFB0 /* RedactedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineView.swift; sourceTree = ""; }; @@ -748,13 +754,12 @@ CBBCC6E74774E79B599625D0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoinedRoomSize+MemberCount.swift"; sourceTree = ""; }; CC680E0E79D818706CB28CF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestScreenIdentifier.swift; sourceTree = ""; }; CC7CCC6DE5FA623E31BA8546 /* RoomTimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerProtocol.swift; sourceTree = ""; }; - CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsRootView.swift; sourceTree = ""; }; CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationCoordinator.swift; sourceTree = ""; }; CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; CED34C87277BA3CCC6B6EC7A /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; - CF3EDF23226895776553F04A /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; CF47564C584F614B7287F3EB /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementNavigationController.swift; sourceTree = ""; }; @@ -809,7 +814,6 @@ EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = ""; }; EF188681D6B6068CFAEAFC3F /* MXLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = ""; }; EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = ""; }; - EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = ""; }; F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetectorTests.swift; sourceTree = ""; }; @@ -1009,6 +1013,16 @@ path = Resources; sourceTree = ""; }; + 2ECFF6B05DAA37EB10DBF7E8 /* UITests */ = { + isa = PBXGroup; + children = ( + 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */, + CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */, + 81740EEAFDF0D34C5E10D0DF /* UITestsRootView.swift */, + ); + path = UITests; + sourceTree = ""; + }; 328DD5DA1281F758B72006C7 /* Views */ = { isa = PBXGroup; children = ( @@ -1594,6 +1608,17 @@ path = UnitTests; sourceTree = ""; }; + A78C2592419CA4C76FBA8FD2 /* Application */ = { + isa = PBXGroup; + children = ( + 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */, + 077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */, + C75EF87651B00A176AB08E97 /* AppDelegate.swift */, + 263B3B811C2B900F12C6F695 /* BuildSettings.swift */, + ); + path = Application; + sourceTree = ""; + }; AAFDD509929A0CCF8BCE51EB /* Authentication */ = { isa = PBXGroup; children = ( @@ -1675,6 +1700,15 @@ path = SoftLogout; sourceTree = ""; }; + BA21F24D618D719E70E1EDAE /* UserSession */ = { + isa = PBXGroup; + children = ( + 562B0F8048BB22FB0C086CB8 /* UserSessionFlowCoordinator.swift */, + C04A526B2825F72617D57461 /* UserSessionFlowCoordinatorStateMachine.swift */, + ); + path = UserSession; + sourceTree = ""; + }; C0937E3B06A8F0E2DB7C8241 /* Other */ = { isa = PBXGroup; children = ( @@ -1795,16 +1829,12 @@ E68740F873AB18A5C26844EA /* Sources */ = { isa = PBXGroup; children = ( - CF3EDF23226895776553F04A /* AppCoordinator.swift */, - 5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */, - EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */, - 3F87116470221880017CF522 /* BuildSettings.swift */, - 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */, - 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */, - CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */, + A78C2592419CA4C76FBA8FD2 /* Application */, + BA21F24D618D719E70E1EDAE /* UserSession */, 0787F81684E503024BD0C051 /* Services */, E59565F441830B19DBAE567C /* Screens */, C0937E3B06A8F0E2DB7C8241 /* Other */, + 2ECFF6B05DAA37EB10DBF7E8 /* UITests */, 337015ADFBA3AB96660DB3A6 /* Generated */, ); path = Sources; @@ -2321,9 +2351,9 @@ 744C029EB6C43429926A0499 /* AnalyticsPromptViewModelProtocol.swift in Sources */, 3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */, EC4C31963E755EEC77BD778C /* AnalyticsSettings.swift in Sources */, - A636D4900E0D98ED91536482 /* AppCoordinator.swift in Sources */, - B3FDB1D9CF40777695DBBD1D /* AppCoordinatorStateMachine.swift in Sources */, - 2FE4EEF780553B25A446BBFB /* AppDelegate.swift in Sources */, + 095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */, + 4FF90E2242DBD596E1ED2E27 /* AppCoordinatorStateMachine.swift in Sources */, + 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */, 74604ACFDBE7F54260E7B617 /* ApplicationProtocol.swift in Sources */, 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */, 3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */, @@ -2345,7 +2375,7 @@ 172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */, 86C2E93920FD15AD17E193A9 /* BugReportViewModel.swift in Sources */, 187E18F21EF4DA244E436E58 /* BugReportViewModelProtocol.swift in Sources */, - 05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */, + CA9558C0B40C1EE2AD00124A /* BuildSettings.swift in Sources */, E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */, 6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */, 1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */, @@ -2523,9 +2553,9 @@ 0EE5EBA18BA1FE10254BB489 /* UIFont+AttributedStringBuilder.m in Sources */, 706F79A39BDB32F592B8C2C7 /* UIKitBackgroundTask.swift in Sources */, 3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */, - 004561D297DC8B9786AE136F /* UITestScreenIdentifier.swift in Sources */, - 03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */, - 17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */, + D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */, + E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */, + F75C4222D52B643214D5E623 /* UITestsRootView.swift in Sources */, 071A017E415AD378F2961B11 /* URL.swift in Sources */, 8775F46AE3234A5A5688C19D /* UserIndicator.swift in Sources */, 7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */, @@ -2537,6 +2567,8 @@ 80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */, 9CCC77C31CB399661A034739 /* UserProperties+Element.swift in Sources */, 8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */, + AE9EDA4805FCD42553255807 /* UserSessionFlowCoordinator.swift in Sources */, + 1CA5FC60B93D5AA60972ACFE /* UserSessionFlowCoordinatorStateMachine.swift in Sources */, 978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */, 79A6E08ADE6E7C460A8A17A5 /* UserSessionStore.swift in Sources */, EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */, @@ -2557,7 +2589,7 @@ 7405B4824D45BA7C3D943E76 /* Application.swift in Sources */, ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */, 7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */, - 499A26EB06C97E48C27A2DB9 /* BuildSettings.swift in Sources */, + 29E20505F321071E8375F99B /* BuildSettings.swift in Sources */, 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */, 9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */, BB4C6F362F75933DDDE30F3E /* InfoPlist.swift in Sources */, @@ -2572,7 +2604,7 @@ B3357B00F1AA930E54F76609 /* Strings.swift in Sources */, C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */, 0ED951768EC443A8728DE1D7 /* TimelineStyle.swift in Sources */, - 75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */, + 9A47B7EFE3793760EEF68FFE /* UITestScreenIdentifier.swift in Sources */, 35C57543D245E82CBFE15DF0 /* URL.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift similarity index 56% rename from ElementX/Sources/AppCoordinator.swift rename to ElementX/Sources/Application/AppCoordinator.swift index 8e064f17e..bb7b139ac 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -52,8 +52,9 @@ class AppCoordinator: Coordinator { } } + private var userSessionFlowCoordinator: UserSessionFlowCoordinator? + private let bugReportService: BugReportServiceProtocol - private let screenshotDetector: ScreenshotDetector private let backgroundTaskService: BackgroundTaskServiceProtocol private var loadingIndicator: UserIndicator? @@ -89,9 +90,6 @@ class AppCoordinator: Coordinator { userSessionStore = UserSessionStore(bundleIdentifier: bundleIdentifier, backgroundTaskService: backgroundTaskService) - - screenshotDetector = ScreenshotDetector() - screenshotDetector.callback = processScreenshotDetection setupStateMachine() @@ -108,7 +106,7 @@ class AppCoordinator: Coordinator { func stop() { hideLoadingIndicator() } - + // MARK: - Private private func setupLogging() { @@ -142,43 +140,29 @@ class AppCoordinator: Coordinator { switch (context.fromState, context.event, context.toState) { case (.initial, .startWithAuthentication, .signedOut): self.startAuthentication() - case (.signedOut, .succeededSigningIn, .homeScreen): - self.presentHomeScreen() - + case (.signedOut, .succeededSigningIn, .signedIn): + self.setupUserSession() case (.initial, .startWithExistingSession, .restoringSession): self.showLoadingIndicator() self.restoreUserSession() case (.restoringSession, .failedRestoringSession, .signedOut): self.hideLoadingIndicator() self.showLoginErrorToast() - case (.restoringSession, .succeededRestoringSession, .homeScreen): + case (.restoringSession, .succeededRestoringSession, .signedIn): self.hideLoadingIndicator() - self.presentHomeScreen() - - case(_, _, .roomScreen(let roomId)): - self.presentRoomWithIdentifier(roomId) - case(.roomScreen(let roomId), .dismissedRoomScreen, .homeScreen): - self.tearDownDismissedRoomScreen(roomId) - + self.setupUserSession() case (_, .signOut, .signingOut): self.showLoadingIndicator() self.tearDownUserSession() case (.signingOut, .completedSigningOut, .signedOut): self.presentSplashScreen() self.hideLoadingIndicator() - case (_, .remoteSignOut(let isSoft), .remoteSigningOut): self.showLoadingIndicator() self.tearDownUserSession(isSoftLogout: isSoft) case (.remoteSigningOut(let isSoft), .completedSigningOut, .signedOut): self.presentSplashScreen(isSoftLogout: isSoft) self.hideLoadingIndicator() - - case (.homeScreen, .showSessionVerificationScreen, .sessionVerificationScreen): - self.presentSessionVerification() - case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .homeScreen): - self.tearDownDismissedSessionVerificationScreen() - default: fatalError("Unknown transition: \(context)") } @@ -257,8 +241,26 @@ class AppCoordinator: Coordinator { } } + private func setupUserSession() { + let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession, + navigationRouter: navigationRouter, + bugReportService: bugReportService) + + userSessionFlowCoordinator.callback = { [weak self] action in + switch action { + case .signOut: + self?.stateMachine.processEvent(.signOut) + } + } + + userSessionFlowCoordinator.start() + + self.userSessionFlowCoordinator = userSessionFlowCoordinator + } + private func tearDownUserSession(isSoftLogout: Bool = false) { userSession.clientProxy.stopSync() + userSessionFlowCoordinator?.stop() deobserveUserSessionChanges() @@ -291,38 +293,6 @@ class AppCoordinator: Coordinator { } } - private func presentHomeScreen() { - userSession.clientProxy.startSync() - - let parameters = HomeScreenCoordinatorParameters(userSession: userSession, - attributedStringBuilder: AttributedStringBuilder()) - let coordinator = HomeScreenCoordinator(parameters: parameters) - - coordinator.callback = { [weak self] action in - guard let self = self else { return } - - switch action { - case .presentRoom(let roomIdentifier): - self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier)) - case .presentSettings: - self.presentSettingsScreen() - case .presentBugReport: - self.presentBugReportScreen() - case .verifySession: - self.stateMachine.processEvent(.showSessionVerificationScreen) - case .signOut: - self.stateMachine.processEvent(.signOut) - } - } - - add(childCoordinator: coordinator) - navigationRouter.setRootModule(coordinator) - - if bugReportService.crashedLastRun { - showCrashPopup() - } - } - private func observeUserSessionChanges() { userSession.callbacks .receive(on: DispatchQueue.main) @@ -347,179 +317,6 @@ class AppCoordinator: Coordinator { cancellables.removeAll() } - // MARK: Rooms - - private func presentRoomWithIdentifier(_ roomIdentifier: String) { - guard let roomProxy = userSession.clientProxy.roomForIdentifier(roomIdentifier) else { - MXLog.error("Invalid room identifier: \(roomIdentifier)") - return - } - let userId = userSession.clientProxy.userIdentifier - - let timelineItemFactory = RoomTimelineItemFactory(userID: userId, - mediaProvider: userSession.mediaProvider, - roomProxy: roomProxy, - attributedStringBuilder: AttributedStringBuilder()) - - let timelineController = RoomTimelineController(userId: userId, - roomId: roomIdentifier, - timelineProvider: roomProxy.timelineProvider, - timelineItemFactory: timelineItemFactory, - mediaProvider: userSession.mediaProvider, - roomProxy: roomProxy) - - let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, - mediaProvider: userSession.mediaProvider, - roomName: roomProxy.displayName ?? roomProxy.name, - roomAvatarUrl: roomProxy.avatarURL) - let coordinator = RoomScreenCoordinator(parameters: parameters) - - add(childCoordinator: coordinator) - navigationRouter.push(coordinator) { [weak self] in - guard let self = self else { return } - self.stateMachine.processEvent(.dismissedRoomScreen) - } - } - - private func tearDownDismissedRoomScreen(_ roomId: String) { - guard let coordinator = childCoordinators.last as? RoomScreenCoordinator else { - fatalError("Invalid coordinator hierarchy: \(childCoordinators)") - } - - remove(childCoordinator: coordinator) - } - - // MARK: Settings - - private func presentSettingsScreen() { - let navController = ElementNavigationController() - let newNavigationRouter = NavigationRouter(navigationController: navController) - - let parameters = SettingsCoordinatorParameters(navigationRouter: newNavigationRouter, - userSession: userSession, - bugReportService: bugReportService) - let coordinator = SettingsCoordinator(parameters: parameters) - coordinator.callback = { [weak self] action in - guard let self = self else { return } - switch action { - case .dismiss: - self.dismissSettingsScreen() - case .logout: - self.dismissSettingsScreen() - self.stateMachine.processEvent(.signOut) - } - } - - add(childCoordinator: coordinator) - coordinator.start() - navController.viewControllers = [coordinator.toPresentable()] - navigationRouter.present(navController, animated: true) - } - - @objc - private func dismissSettingsScreen() { - MXLog.debug("dismissSettingsScreen") - - guard let coordinator = childCoordinators.first(where: { $0 is SettingsCoordinator }) else { - return - } - - navigationRouter.dismissModule() - remove(childCoordinator: coordinator) - } - - private func showCrashPopup() { - let alert = UIAlertController(title: nil, - message: ElementL10n.sendBugReportAppCrashed, - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel)) - alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in - self?.presentBugReportScreen() - }) - - navigationRouter.present(alert, animated: true) - } - - private func processScreenshotDetection(image: UIImage?, error: Error?) { - MXLog.debug("Detected screenshot: \(String(describing: image)), error: \(String(describing: error))") - - let alert = UIAlertController(title: ElementL10n.screenshotDetectedTitle, - message: ElementL10n.screenshotDetectedMessage, - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel)) - alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in - self?.presentBugReportScreen(for: image) - }) - - navigationRouter.present(alert, animated: true) - } - - private func presentBugReportScreen(for image: UIImage? = nil) { - let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService, - screenshot: image) - let coordinator = BugReportCoordinator(parameters: parameters) - coordinator.completion = { [weak self, weak coordinator] in - guard let self = self, let coordinator = coordinator else { return } - self.navigationRouter.dismissModule(animated: true) - self.remove(childCoordinator: coordinator) - } - - add(childCoordinator: coordinator) - coordinator.start() - let navController = ElementNavigationController(rootViewController: coordinator.toPresentable()) - navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, - target: self, - action: #selector(dismissBugReportScreen)) - navController.isModalInPresentation = true - navigationRouter.present(navController, animated: true) - } - - @objc - private func dismissBugReportScreen() { - MXLog.debug("dismissBugReportScreen") - - guard let bugReportCoordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else { - return - } - - navigationRouter.dismissModule() - remove(childCoordinator: bugReportCoordinator) - } - - // MARK: Session verification - - private func presentSessionVerification() { - Task { - guard let sessionVerificationController = userSession.sessionVerificationController else { - fatalError("The sessionVerificationController should aways be valid at this point") - } - - let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController) - - let coordinator = SessionVerificationCoordinator(parameters: parameters) - - coordinator.callback = { [weak self] in - self?.navigationRouter.dismissModule() - self?.stateMachine.processEvent(.dismissedSessionVerificationScreen) - } - - add(childCoordinator: coordinator) - navigationRouter.present(coordinator) - - coordinator.start() - } - } - - private func tearDownDismissedSessionVerificationScreen() { - guard let coordinator = childCoordinators.last as? SessionVerificationCoordinator else { - fatalError("Invalid coordinator hierarchy: \(childCoordinators)") - } - - remove(childCoordinator: coordinator) - } - // MARK: Toasts and loading indicators private func showLoadingIndicator() { diff --git a/ElementX/Sources/AppCoordinatorStateMachine.swift b/ElementX/Sources/Application/AppCoordinatorStateMachine.swift similarity index 74% rename from ElementX/Sources/AppCoordinatorStateMachine.swift rename to ElementX/Sources/Application/AppCoordinatorStateMachine.swift index be3b16e46..a11f8a1f2 100644 --- a/ElementX/Sources/AppCoordinatorStateMachine.swift +++ b/ElementX/Sources/Application/AppCoordinatorStateMachine.swift @@ -26,15 +26,9 @@ class AppCoordinatorStateMachine { case signedOut /// Opening an existing session. case restoringSession - /// Showing the home screen - case homeScreen - /// Showing a particular room's timeline - /// - Parameter roomId: that room's identifier - case roomScreen(roomId: String) - - /// Showing the session verification flows - case sessionVerificationScreen + /// User session started + case signedIn /// Processing a sign out request case signingOut @@ -63,17 +57,6 @@ class AppCoordinatorStateMachine { case remoteSignOut(isSoft: Bool) /// Signing out completed case completedSigningOut - - /// Request presentation for a particular room - /// - Parameter roomId:the room identifier - case showRoomScreen(roomId: String) - /// The room screen has been dismissed - case dismissedRoomScreen - - /// Request the start of the session verification flow - case showSessionVerificationScreen - /// Session verification has finished - case dismissedSessionVerificationScreen } private let stateMachine: StateMachine @@ -81,25 +64,18 @@ class AppCoordinatorStateMachine { init() { stateMachine = StateMachine(state: .initial) { machine in machine.addRoutes(event: .startWithAuthentication, transitions: [.initial => .signedOut]) - machine.addRoutes(event: .succeededSigningIn, transitions: [.signedOut => .homeScreen]) + machine.addRoutes(event: .succeededSigningIn, transitions: [.signedOut => .signedIn]) machine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession]) - machine.addRoutes(event: .succeededRestoringSession, transitions: [.restoringSession => .homeScreen]) + machine.addRoutes(event: .succeededRestoringSession, transitions: [.restoringSession => .signedIn]) machine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut]) machine.addRoutes(event: .signOut, transitions: [.any => .signingOut]) machine.addRoutes(event: .completedSigningOut, transitions: [.signingOut => .signedOut]) - - machine.addRoutes(event: .showSessionVerificationScreen, transitions: [.homeScreen => .sessionVerificationScreen]) - machine.addRoutes(event: .dismissedSessionVerificationScreen, transitions: [.sessionVerificationScreen => .homeScreen]) - + // Transitions with associated values need to be handled through `addRouteMapping` machine.addRouteMapping { event, fromState, _ in switch (event, fromState) { - case (.showRoomScreen(let roomId), .homeScreen): - return .roomScreen(roomId: roomId) - case (.dismissedRoomScreen, .roomScreen): - return .homeScreen case (.remoteSignOut(let isSoft), _): return .remoteSigningOut(isSoft: isSoft) case (.completedSigningOut, .remoteSigningOut): diff --git a/ElementX/Sources/AppDelegate.swift b/ElementX/Sources/Application/AppDelegate.swift similarity index 100% rename from ElementX/Sources/AppDelegate.swift rename to ElementX/Sources/Application/AppDelegate.swift diff --git a/ElementX/Sources/BuildSettings.swift b/ElementX/Sources/Application/BuildSettings.swift similarity index 93% rename from ElementX/Sources/BuildSettings.swift rename to ElementX/Sources/Application/BuildSettings.swift index 29bdd23cd..19736e777 100644 --- a/ElementX/Sources/BuildSettings.swift +++ b/ElementX/Sources/Application/BuildSettings.swift @@ -40,14 +40,14 @@ final class BuildSettings { static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: ElementInfoPlist.cfBundleIdentifier.starts(with: "io.element.elementx"), host: "https://posthog.element.dev", apiKey: "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN", - termsURL: URL(string: "https://element.io/cookie-policy")!) // swiftlint:disable:this force_unwrapping + termsURL: URL(staticString: "https://element.io/cookie-policy")) #else /// The configuration to use for analytics. Set `isEnabled` to false to disable analytics. /// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations. static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: ElementInfoPlist.cfBundleIdentifier.starts(with: "io.element.elementx"), host: "https://posthog.hss.element.io", apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", - termsURL: URL(string: "https://element.io/cookie-policy")!) // swiftlint:disable:this force_unwrapping + termsURL: URL(staticString: "https://element.io/cookie-policy")) #endif // MARK: - Settings screen diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index ffc6f997c..82c45598c 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -10,7 +10,7 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces extension ElementL10n { - /// Main menu + /// User menu public static let a11yAllChatsUserAvatarMenu = ElementL10n.tr("Untranslated", "a11y_all_chats_user_avatar_menu") /// Confirm public static let actionConfirm = ElementL10n.tr("Untranslated", "action_confirm") diff --git a/ElementX/Sources/Other/Routers/NavigationRouterType.swift b/ElementX/Sources/Other/Routers/NavigationRouterType.swift index c7cebec02..bf63b8d99 100755 --- a/ElementX/Sources/Other/Routers/NavigationRouterType.swift +++ b/ElementX/Sources/Other/Routers/NavigationRouterType.swift @@ -128,4 +128,16 @@ extension NavigationRouterType { push(modules.map { $0.toModule() }, animated: animated) } + + func dismissModule(animated: Bool = true, completion: (() -> Void)? = nil) { + dismissModule(animated: animated, completion: completion) + } + + func push(_ module: Presentable, animated: Bool = true, popCompletion: (() -> Void)? = nil) { + push(module, animated: animated, popCompletion: popCompletion) + } + + func present(_ module: Presentable, animated: Bool = true) { + present(module, animated: animated) + } } diff --git a/ElementX/Sources/UITestScreenIdentifier.swift b/ElementX/Sources/UITests/UITestScreenIdentifier.swift similarity index 100% rename from ElementX/Sources/UITestScreenIdentifier.swift rename to ElementX/Sources/UITests/UITestScreenIdentifier.swift diff --git a/ElementX/Sources/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift similarity index 100% rename from ElementX/Sources/UITestsAppCoordinator.swift rename to ElementX/Sources/UITests/UITestsAppCoordinator.swift diff --git a/ElementX/Sources/UITestsRootView.swift b/ElementX/Sources/UITests/UITestsRootView.swift similarity index 100% rename from ElementX/Sources/UITestsRootView.swift rename to ElementX/Sources/UITests/UITestsRootView.swift diff --git a/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift b/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift new file mode 100644 index 000000000..ab019ad48 --- /dev/null +++ b/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift @@ -0,0 +1,275 @@ +// +// 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 UIKit + +enum UserSessionFlowCoordinatorAction { + case signOut +} + +class UserSessionFlowCoordinator: Coordinator { + private let stateMachine: UserSessionFlowCoordinatorStateMachine + + private let userSession: UserSessionProtocol + private let navigationRouter: NavigationRouterType + private let bugReportService: BugReportServiceProtocol + + var childCoordinators: [Coordinator] = [] + + var callback: ((UserSessionFlowCoordinatorAction) -> Void)? + + init(userSession: UserSessionProtocol, navigationRouter: NavigationRouterType, bugReportService: BugReportServiceProtocol) { + stateMachine = UserSessionFlowCoordinatorStateMachine() + self.userSession = userSession + self.navigationRouter = navigationRouter + self.bugReportService = bugReportService + + setupStateMachine() + } + + func start() { + stateMachine.processEvent(.start) + } + + func stop() { } + + // MARK: - Private + + private func setupStateMachine() { + stateMachine.addTransitionHandler { [weak self] context in + guard let self = self else { return } + + switch (context.fromState, context.event, context.toState) { + case (.initial, .start, .homeScreen): + self.presentHomeScreen() + + case(_, _, .roomScreen(let roomId)): + self.presentRoomWithIdentifier(roomId) + case(.roomScreen(let roomId), .dismissedRoomScreen, .homeScreen): + self.tearDownDismissedRoomScreen(roomId) + + case (.homeScreen, .showSessionVerificationScreen, .sessionVerificationScreen): + self.presentSessionVerification() + case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .homeScreen): + self.tearDownDismissedSessionVerificationScreen() + + case (.homeScreen, .showSettingsScreen, .settingsScreen): + self.presentSettingsScreen() + case (.settingsScreen, .dismissedSettingsScreen, .homeScreen): + self.dismissSettingsScreen() + + default: + fatalError("Unknown transition: \(context)") + } + } + + stateMachine.addErrorHandler { context in + fatalError("Failed transition with context: \(context)") + } + } + + private func presentHomeScreen() { + userSession.clientProxy.startSync() + + let parameters = HomeScreenCoordinatorParameters(userSession: userSession, + attributedStringBuilder: AttributedStringBuilder()) + let coordinator = HomeScreenCoordinator(parameters: parameters) + + coordinator.callback = { [weak self] action in + guard let self = self else { return } + + switch action { + case .presentRoom(let roomIdentifier): + self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier)) + case .presentSettings: + self.presentSettingsScreen() + case .presentBugReport: + self.presentBugReportScreen() + case .verifySession: + self.stateMachine.processEvent(.showSessionVerificationScreen) + case .signOut: + self.callback?(.signOut) + } + } + + add(childCoordinator: coordinator) + navigationRouter.setRootModule(coordinator) + + if bugReportService.crashedLastRun { + showCrashPopup() + } + } + + // MARK: Rooms + + private func presentRoomWithIdentifier(_ roomIdentifier: String) { + guard let roomProxy = userSession.clientProxy.roomForIdentifier(roomIdentifier) else { + MXLog.error("Invalid room identifier: \(roomIdentifier)") + return + } + let userId = userSession.clientProxy.userIdentifier + + let timelineItemFactory = RoomTimelineItemFactory(userID: userId, + mediaProvider: userSession.mediaProvider, + roomProxy: roomProxy, + attributedStringBuilder: AttributedStringBuilder()) + + let timelineController = RoomTimelineController(userId: userId, + roomId: roomIdentifier, + timelineProvider: roomProxy.timelineProvider, + timelineItemFactory: timelineItemFactory, + mediaProvider: userSession.mediaProvider, + roomProxy: roomProxy) + + let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, + mediaProvider: userSession.mediaProvider, + roomName: roomProxy.displayName ?? roomProxy.name, + roomAvatarUrl: roomProxy.avatarURL) + let coordinator = RoomScreenCoordinator(parameters: parameters) + + add(childCoordinator: coordinator) + navigationRouter.push(coordinator) { [weak self] in + guard let self = self else { return } + self.stateMachine.processEvent(.dismissedRoomScreen) + } + } + + private func tearDownDismissedRoomScreen(_ roomId: String) { + guard let coordinator = childCoordinators.last as? RoomScreenCoordinator else { + fatalError("Invalid coordinator hierarchy: \(childCoordinators)") + } + + remove(childCoordinator: coordinator) + } + + // MARK: Settings + + private func presentSettingsScreen() { + let navController = ElementNavigationController() + let newNavigationRouter = NavigationRouter(navigationController: navController) + + let parameters = SettingsCoordinatorParameters(navigationRouter: newNavigationRouter, + userSession: userSession, + bugReportService: bugReportService) + let coordinator = SettingsCoordinator(parameters: parameters) + coordinator.callback = { [weak self] action in + guard let self = self else { return } + switch action { + case .dismiss: + self.dismissSettingsScreen() + case .logout: + self.dismissSettingsScreen() + self.callback?(.signOut) + } + } + + add(childCoordinator: coordinator) + coordinator.start() + navController.viewControllers = [coordinator.toPresentable()] + navigationRouter.present(navController, animated: true) + } + + @objc + private func dismissSettingsScreen() { + MXLog.debug("dismissSettingsScreen") + + guard let coordinator = childCoordinators.first(where: { $0 is SettingsCoordinator }) else { + return + } + + navigationRouter.dismissModule() + remove(childCoordinator: coordinator) + } + + // MARK: Session verification + + private func presentSessionVerification() { + Task { + guard let sessionVerificationController = userSession.sessionVerificationController else { + fatalError("The sessionVerificationController should aways be valid at this point") + } + + let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController) + + let coordinator = SessionVerificationCoordinator(parameters: parameters) + + coordinator.callback = { [weak self] in + self?.navigationRouter.dismissModule() + self?.stateMachine.processEvent(.dismissedSessionVerificationScreen) + } + + add(childCoordinator: coordinator) + navigationRouter.present(coordinator) + + coordinator.start() + } + } + + private func tearDownDismissedSessionVerificationScreen() { + guard let coordinator = childCoordinators.last as? SessionVerificationCoordinator else { + fatalError("Invalid coordinator hierarchy: \(childCoordinators)") + } + + remove(childCoordinator: coordinator) + } + + // MARK: Bug reporting + + private func showCrashPopup() { + let alert = UIAlertController(title: nil, + message: ElementL10n.sendBugReportAppCrashed, + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel)) + alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in + self?.presentBugReportScreen() + }) + + navigationRouter.present(alert, animated: true) + } + + private func presentBugReportScreen(for image: UIImage? = nil) { + let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService, + screenshot: image) + let coordinator = BugReportCoordinator(parameters: parameters) + coordinator.completion = { [weak self, weak coordinator] in + guard let self = self, let coordinator = coordinator else { return } + self.navigationRouter.dismissModule(animated: true) + self.remove(childCoordinator: coordinator) + } + + add(childCoordinator: coordinator) + coordinator.start() + let navController = ElementNavigationController(rootViewController: coordinator.toPresentable()) + navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, + target: self, + action: #selector(dismissBugReportScreen)) + navController.isModalInPresentation = true + navigationRouter.present(navController, animated: true) + } + + @objc + private func dismissBugReportScreen() { + MXLog.debug("dismissBugReportScreen") + + guard let bugReportCoordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else { + return + } + + navigationRouter.dismissModule() + remove(childCoordinator: bugReportCoordinator) + } +} diff --git a/ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift b/ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift new file mode 100644 index 000000000..ae9f7f8ba --- /dev/null +++ b/ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift @@ -0,0 +1,97 @@ +// +// 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 +import SwiftState + +class UserSessionFlowCoordinatorStateMachine { + /// States the AppCoordinator can find itself in + enum State: StateType { + /// The initial state, used before the coordinator starts + case initial + + /// Showing the home screen + case homeScreen + + /// Showing a particular room's timeline + /// - Parameter roomId: that room's identifier + case roomScreen(roomId: String) + + /// Showing the session verification flows + case sessionVerificationScreen + + /// Showing the settings screen + case settingsScreen + } + + /// Events that can be triggered on the AppCoordinator state machine + enum Event: EventType { + /// Start the user session flows + case start + + /// Request presentation for a particular room + /// - Parameter roomId:the room identifier + case showRoomScreen(roomId: String) + /// The room screen has been dismissed + case dismissedRoomScreen + + /// Request the start of the session verification flow + case showSessionVerificationScreen + /// Session verification has finished + case dismissedSessionVerificationScreen + + /// Request presentation of the settings screen + case showSettingsScreen + /// The settings screen has been dismissed + case dismissedSettingsScreen + } + + private let stateMachine: StateMachine + + init() { + stateMachine = StateMachine(state: .initial) { machine in + machine.addRoutes(event: .start, transitions: [.initial => .homeScreen]) + + // Transitions with associated values need to be handled through `addRouteMapping` + machine.addRouteMapping { event, fromState, _ in + switch (event, fromState) { + case (.showRoomScreen(let roomId), .homeScreen): + return .roomScreen(roomId: roomId) + case (.dismissedRoomScreen, .roomScreen): + return .homeScreen + default: + return nil + } + } + } + } + + /// Attempt to move the state machine to another state through an event + /// It will either invoke the `transitionHandler` or the `errorHandler` depending on its current state + func processEvent(_ event: Event) { + stateMachine.tryEvent(event) + } + + /// Registers a callback for processing state machine transitions + func addTransitionHandler(_ handler: @escaping StateMachine.Handler) { + stateMachine.addAnyHandler(.any => .any, handler: handler) + } + + /// Registers a callback for processing state machine errors + func addErrorHandler(_ handler: @escaping StateMachine.Handler) { + stateMachine.addErrorHandler(handler: handler) + } +} diff --git a/UITests/SupportingFiles/target.yml b/UITests/SupportingFiles/target.yml index 6ff385f04..cf6fbcb9e 100644 --- a/UITests/SupportingFiles/target.yml +++ b/UITests/SupportingFiles/target.yml @@ -78,10 +78,10 @@ targets: - "**/__Snapshots__/**" - path: ../SupportingFiles - path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/UI - - path: ../../ElementX/Sources/BuildSettings.swift + - path: ../../ElementX/Sources/Application/BuildSettings.swift - path: ../../ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyle.swift - path: ../../ElementX/Sources/Services/Analytics/AnalyticsConfiguration.swift - - path: ../../ElementX/Sources/UITestScreenIdentifier.swift + - path: ../../ElementX/Sources/UITests/UITestScreenIdentifier.swift - path: ../../ElementX/Sources/Generated/Strings.swift - path: ../../ElementX/Sources/Generated/Strings+Untranslated.swift - path: ../../ElementX/Sources/Generated/InfoPlist.swift diff --git a/project.yml b/project.yml index 0d9029fe4..f60b8e675 100644 --- a/project.yml +++ b/project.yml @@ -16,7 +16,7 @@ options: - pattern: ElementX order: [Sources, Resources, SupportingFiles] - pattern: Sources - order: [Services, Screens, Other] + order: [Application, UserSession, Services, Screens, Other, UITests] postGenCommand: cd Tools/XcodeGen && sh postGenCommand.sh settings: