vector-im/element-x-ios/issues/31 - Implement simple SaS sesson verification
* vector-im/element-x-ios/issues/31 - Session verification unit and UI tests * Fix ImageAnonymizer tests when running on Apple Silicon * Rename SessionVerification to SessionVerificationScreen
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -18,11 +18,13 @@
|
||||
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 */; };
|
||||
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
||||
06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */; };
|
||||
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; };
|
||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
||||
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; };
|
||||
0E8C480700870BB34A2A360F /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; };
|
||||
0EA6537A07E2DC882AEA5962 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 187853A7E643995EE49FAD43 /* Localizable.stringsdict */; };
|
||||
0ED951768EC443A8728DE1D7 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
||||
@@ -35,6 +37,7 @@
|
||||
132D241B09F9044711FD70A5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; };
|
||||
13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */; };
|
||||
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */; };
|
||||
1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */; };
|
||||
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */; };
|
||||
15D1F9C415D9C921643BA82E /* UserIndicatorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B73D5E21F524A9BE44448D /* UserIndicatorRequest.swift */; };
|
||||
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */; };
|
||||
@@ -52,6 +55,7 @@
|
||||
224A55EEAEECF5336B14A4A5 /* EmoteRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2DF459F1737A594667CC46 /* EmoteRoomMessage.swift */; };
|
||||
226027BE23AF64FA61C7A4C0 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
||||
22DADD537401E79D66132134 /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4173A48FD8542CD4AD3645C /* NavigationRouter.swift */; };
|
||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */; };
|
||||
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CF12478983A5EB390FB26 /* MessageComposer.swift */; };
|
||||
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; };
|
||||
2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
||||
@@ -63,7 +67,6 @@
|
||||
2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; };
|
||||
2C0CE61E5DC177938618E0B1 /* RootRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90733775209F4D4D366A268F /* RootRouterType.swift */; };
|
||||
2E59008365E01F0AFB3A6B24 /* ImageRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF686BA36D0C2FA3C63DFDF /* ImageRoomMessage.swift */; };
|
||||
2E68C57E7D644E94778743D5 /* TemplateScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B66E05B6009B0EB1BDBFA6E /* TemplateScreenUITests.swift */; };
|
||||
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 */; };
|
||||
@@ -78,7 +81,6 @@
|
||||
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
|
||||
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
|
||||
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; };
|
||||
3772354754450F2B54107E17 /* TemplateViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4EDB32B97910AAAFE632B2 /* TemplateViewModelProtocol.swift */; };
|
||||
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; };
|
||||
388FD50AC66E9E684DDFA9D8 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D2C0950F8196232D88045C /* ServerSelectionScreen.swift */; };
|
||||
38C76D586404C1FDED095F3A /* LoginModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B01468022EC826CB2FD2C0 /* LoginModels.swift */; };
|
||||
@@ -90,6 +92,7 @@
|
||||
418B4AEFD03DC7A6D2C9D5C8 /* EventBriefFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36322DD0D4E29D31B0945ADC /* EventBriefFactory.swift */; };
|
||||
41DFDD212D1BE57CA50D783B /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
|
||||
438FB9BC535BC95948AA5F34 /* SettingsViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */; };
|
||||
43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */; };
|
||||
462813B93C39DF93B1249403 /* RoundedToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFABDF2E19D349DAAAC18C65 /* RoundedToastView.swift */; };
|
||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
|
||||
4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; };
|
||||
@@ -116,13 +119,14 @@
|
||||
59C41313AED7566C3AC51163 /* RoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */; };
|
||||
5B2C4C17888FC095ED6880B2 /* SplashViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 48971F1FFD7FC5C466889FC7 /* SplashViewController.xib */; };
|
||||
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; };
|
||||
5CABC57F620FBB39F4EC127C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9BA045DC4CA12D030ACF558 /* TemplateScreen.swift */; };
|
||||
5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */; };
|
||||
5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; };
|
||||
5E1FCC43B738941D5A5F1794 /* SplashScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B73A8C3118EAC7BF3F3EE7A /* SplashScreenViewModelProtocol.swift */; };
|
||||
5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */; };
|
||||
5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; };
|
||||
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; };
|
||||
62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8210612D17A39369480FC183 /* MediaSource.swift */; };
|
||||
63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB10E673916D2B8D21FD197 /* TemplateModels.swift */; };
|
||||
6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */; };
|
||||
67E391A2E00709FB41903B36 /* MockMediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6920A4869821BF72FFC58842 /* MockMediaProvider.swift */; };
|
||||
6832733838C57A7D3FE8FEB5 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 04C28663564E008DB32B5972 /* Introspect */; };
|
||||
@@ -140,8 +144,10 @@
|
||||
72F6E890820FF606A7E276C8 /* SplashScreenPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A534A4619D8FEFB6439FCC /* SplashScreenPageView.swift */; };
|
||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; };
|
||||
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 */; };
|
||||
77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */; };
|
||||
@@ -149,6 +155,7 @@
|
||||
7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */; };
|
||||
79A6E08ADE6E7C460A8A17A5 /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */; };
|
||||
7A54700193DC1F264368746A /* UserIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E077F76026C85ED96FEBB810 /* UserIndicatorPresenter.swift */; };
|
||||
7AE1FFB132F2B84EB8A2AEBC /* TemplateViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3340ABAE3A4647E80163AE18 /* TemplateViewModelTests.swift */; };
|
||||
7BB31E67648CF32D2AB5E502 /* RoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */; };
|
||||
7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; };
|
||||
7D1DAAA364A9A29D554BD24E /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0950733DD4BA83EEE752E259 /* PlaceholderAvatarImage.swift */; };
|
||||
@@ -160,6 +167,7 @@
|
||||
80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193FB285430D3956B6E61E4D /* UserIndicatorViewPresentable.swift */; };
|
||||
83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */; };
|
||||
85AFBB433AD56704A880F8A0 /* FramePreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */; };
|
||||
86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */; };
|
||||
86C2E93920FD15AD17E193A9 /* BugReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E532D95330139D118A9BF88 /* BugReportViewModel.swift */; };
|
||||
872A6457DF573AF8CEAE927A /* LoginHomeserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9349F590E35CE514A71E6764 /* LoginHomeserver.swift */; };
|
||||
87756CA950ED55870A1AAE8F /* ServerSelectionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D7D706FFF438CAF16F44D8C /* ServerSelectionCoordinator.swift */; };
|
||||
@@ -173,6 +181,7 @@
|
||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; };
|
||||
93BA4A81B6D893271101F9F0 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 5986E300FC849DEAB2EE7AEB /* Introspect */; };
|
||||
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */; };
|
||||
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
|
||||
94E062D08E27B0387658E364 /* SplashScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5CF94E124616FD89424B73 /* SplashScreenViewModelTests.swift */; };
|
||||
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; };
|
||||
@@ -190,6 +199,7 @@
|
||||
9CB5129C83F75921E5E28028 /* ToastViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C82DAE0B8EB28234E84E6CF /* ToastViewState.swift */; };
|
||||
9D2E03DB175A6AB14589076D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 020597E28A4BC8E1BE8EDF6E /* KeychainAccess */; };
|
||||
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 */; };
|
||||
A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */; };
|
||||
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */; };
|
||||
@@ -201,10 +211,10 @@
|
||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
|
||||
A8EC7C9D886244DAE9433E37 /* SessionVerificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C18FAAD59AE7F1462D817E /* SessionVerificationViewModel.swift */; };
|
||||
AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9760103CF316DF68698BCFE6 /* LaunchScreen.storyboard */; };
|
||||
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; };
|
||||
B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; };
|
||||
B0EDAF55877DE19B67837C22 /* TemplateModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C29670CEC77346F31EE94C /* TemplateModels.swift */; };
|
||||
B245583C63F8F90357B87FAE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
|
||||
B3357B00F1AA930E54F76609 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
||||
B3FDB1D9CF40777695DBBD1D /* AppCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */; };
|
||||
@@ -215,14 +225,15 @@
|
||||
B80C4FABB5529DF12436FFDA /* AppIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */; };
|
||||
B94368839BDB69172E28E245 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B698739E3410E2CDB7144 /* MXLog.swift */; };
|
||||
BB01CC19C3D3322308D1B2CF /* ServerSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167521635A1CC27624FCEB7F /* ServerSelectionViewModel.swift */; };
|
||||
BB6B0B91CE11E06330017000 /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC1A01C3A745BDF1D3697D3 /* SessionVerificationScreen.swift */; };
|
||||
BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = EF188681D6B6068CFAEAFC3F /* MXLogger.m */; };
|
||||
BCEC41FB1F2BB663183863E4 /* LoginServerInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D379E13DD9D987470A3C70C /* LoginServerInfoSection.swift */; };
|
||||
BE3237142FA6E1A13C0E7D11 /* RoomSummaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21ECC295F4DE8DAA86D62AC /* RoomSummaryProtocol.swift */; };
|
||||
BEEC06EFD30BFCA02F0FD559 /* UserIndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8EA85D4F10D7445BB6368A /* UserIndicatorTests.swift */; };
|
||||
BF35062D06888FA80BD139FF /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB7F9D6FC121204D59E18DF /* Presentable.swift */; };
|
||||
C052A8CDC7A8E7A2D906674F /* UserIndicatorStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAA6438B00FDB130F404E31 /* UserIndicatorStore.swift */; };
|
||||
C1156BBE4F977AEEE1E80C48 /* TemplateCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2869CFFF6CD2A642AB4B743 /* TemplateCoordinator.swift */; };
|
||||
C2CF93B067FD935E4F82FE44 /* SplashScreenPageIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850064FF8D7DB9C875E7AA1A /* SplashScreenPageIndicator.swift */; };
|
||||
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9873076F224E4CE09D8BD47D /* TemplateScreenUITests.swift */; };
|
||||
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */; };
|
||||
C55A44C99F64A479ABA85B46 /* RoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */; };
|
||||
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; };
|
||||
@@ -237,8 +248,10 @@
|
||||
CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; };
|
||||
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */; };
|
||||
D013E70C8E28E43497820444 /* TextRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4110685D9CA159F3FD2D6BA1 /* TextRoomMessage.swift */; };
|
||||
D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.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 */; };
|
||||
D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; };
|
||||
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */; };
|
||||
D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */; };
|
||||
@@ -249,13 +262,12 @@
|
||||
DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; };
|
||||
E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */; };
|
||||
E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
|
||||
E75CE800B3E64D0F7F8E228D /* TemplateViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08E9043618AE5B0BF7B07E1 /* TemplateViewModelTests.swift */; };
|
||||
E5895C74615CBE8462FB840F /* SessionVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */; };
|
||||
E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.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 */; };
|
||||
EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */; };
|
||||
ED4F663C783E9A8C0E80B983 /* TemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47543EB19F3DCF308751F53C /* TemplateViewModel.swift */; };
|
||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
||||
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; };
|
||||
EF99A92701E401C4CD5ADC50 /* SplashScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */; };
|
||||
@@ -334,6 +346,7 @@
|
||||
1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = "<group>"; };
|
||||
1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
1C429043E986008B97736636 /* ab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ab; path = ab.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = "<group>"; };
|
||||
1E1FB768A24FDD2A5CA16E3C /* LoginViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DTHTMLElement+AttributedStringBuilder.swift"; sourceTree = "<group>"; };
|
||||
@@ -352,6 +365,7 @@
|
||||
29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = "<group>"; };
|
||||
2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; };
|
||||
2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskProtocol.swift; sourceTree = "<group>"; };
|
||||
2CF9FE7E0CF9F40D1509E63A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = bg; path = bg.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertInfo.swift; sourceTree = "<group>"; };
|
||||
@@ -359,6 +373,7 @@
|
||||
31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenUITests.swift; sourceTree = "<group>"; };
|
||||
32CE6D4FF64C9A3C18619224 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = "<group>"; };
|
||||
3340ABAE3A4647E80163AE18 /* TemplateViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModelTests.swift; sourceTree = "<group>"; };
|
||||
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
36322DD0D4E29D31B0945ADC /* EventBriefFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactory.swift; sourceTree = "<group>"; };
|
||||
@@ -390,9 +405,9 @@
|
||||
4488F5F92A64A137665C96CD /* pa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pa; path = pa.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
44AEEE13AC1BF303AE48CBF8 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
453E722A43D092C06FB8E3FA /* tzm */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tzm; path = tzm.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4549FCB53F43DB0B278374BC /* TemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreen.swift; sourceTree = "<group>"; };
|
||||
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
47543EB19F3DCF308751F53C /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = "<group>"; };
|
||||
475EB595D7527E9A8A14043E /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
||||
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||
@@ -404,7 +419,6 @@
|
||||
49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benchmark.swift; sourceTree = "<group>"; };
|
||||
4B40B7F6FCCE2D8C242492D9 /* ga */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ga; path = ga.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
|
||||
4B66E05B6009B0EB1BDBFA6E /* TemplateScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenUITests.swift; sourceTree = "<group>"; };
|
||||
4C82DAE0B8EB28234E84E6CF /* ToastViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewState.swift; sourceTree = "<group>"; };
|
||||
4C8D988E82A8DFA13BE46F7C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pl; path = pl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -454,6 +468,7 @@
|
||||
6A152791A2F56BD193BFE986 /* MemberDetailsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailsProvider.swift; sourceTree = "<group>"; };
|
||||
6A901D95158B02CA96C79C7F /* InfoPlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoPlist.swift; sourceTree = "<group>"; };
|
||||
6B73A8C3118EAC7BF3F3EE7A /* SplashScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationUITests.swift; sourceTree = "<group>"; };
|
||||
6DB53055CB130F0651C70763 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskTests.swift; sourceTree = "<group>"; };
|
||||
6E5E9C044BEB7C70B1378E91 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = "<group>"; };
|
||||
@@ -496,6 +511,7 @@
|
||||
8888D13645C04AC9818F5778 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = "<group>"; };
|
||||
8A9AE4967817E9608E22EB44 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
8AC1A01C3A745BDF1D3697D3 /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = "<group>"; };
|
||||
8BF686BA36D0C2FA3C63DFDF /* ImageRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomMessage.swift; sourceTree = "<group>"; };
|
||||
8C0AA893D6F8A2F563E01BB9 /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = in; path = in.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
8C2ABC1A9B62BDB3D216E7FD /* MemberDetailProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailProviderManager.swift; sourceTree = "<group>"; };
|
||||
@@ -517,6 +533,7 @@
|
||||
9772C1D2223108EB3131AEE4 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationIconImage.swift; sourceTree = "<group>"; };
|
||||
97F893DBB5F88D746C6DCDE5 /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ku; path = ku.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
9873076F224E4CE09D8BD47D /* TemplateScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenUITests.swift; sourceTree = "<group>"; };
|
||||
98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemBubbledStylerView.swift; sourceTree = "<group>"; };
|
||||
997783054A2E95F9E624217E /* kaa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kaa; path = kaa.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
99DE232F24EAD72A3DF7EF1A /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = kab; path = kab.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@@ -528,7 +545,7 @@
|
||||
A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
A05707BF550D770168A406DB /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = "<group>"; };
|
||||
A0A20AE75FF4FF35B1FF6CA7 /* MockServerSelectionScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServerSelectionScreenState.swift; sourceTree = "<group>"; };
|
||||
A1C29670CEC77346F31EE94C /* TemplateModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModels.swift; sourceTree = "<group>"; };
|
||||
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; };
|
||||
A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
A2B6433F516F1E6DFA0E2D89 /* vls */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vls; path = vls.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = "<group>"; };
|
||||
@@ -558,8 +575,10 @@
|
||||
B1183B55FF4B01022DA721CB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
B12969CEC0051BC750DA5068 /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = "<group>"; };
|
||||
B1D1532B5D9FB0C8461A1453 /* UserIndicatorDismissal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorDismissal.swift; sourceTree = "<group>"; };
|
||||
B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
B4173A48FD8542CD4AD3645C /* NavigationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = "<group>"; };
|
||||
B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemContextMenu.swift; sourceTree = "<group>"; };
|
||||
B4C18FAAD59AE7F1462D817E /* SessionVerificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModel.swift; sourceTree = "<group>"; };
|
||||
B516212D9FE785DDD5E490D1 /* BugReportModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportModels.swift; sourceTree = "<group>"; };
|
||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActivityIndicatorView.xib; sourceTree = "<group>"; };
|
||||
@@ -578,11 +597,12 @@
|
||||
BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = "<group>"; };
|
||||
C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXAttributeScope.swift; sourceTree = "<group>"; };
|
||||
C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachine.swift; sourceTree = "<group>"; };
|
||||
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
|
||||
C08E9043618AE5B0BF7B07E1 /* TemplateViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModelTests.swift; sourceTree = "<group>"; };
|
||||
C21ECC295F4DE8DAA86D62AC /* RoomSummaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProtocol.swift; sourceTree = "<group>"; };
|
||||
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
|
||||
C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
||||
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = "<group>"; };
|
||||
C88508B6F7974CFABEC4B261 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -597,6 +617,7 @@
|
||||
CC680E0E79D818706CB28CF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CC7CCC6DE5FA623E31BA8546 /* RoomTimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsRootView.swift; sourceTree = "<group>"; };
|
||||
CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationCoordinator.swift; sourceTree = "<group>"; };
|
||||
CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CED34C87277BA3CCC6B6EC7A /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CF3EDF23226895776553F04A /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -604,6 +625,7 @@
|
||||
CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementNavigationController.swift; sourceTree = "<group>"; };
|
||||
CF847B3C1873B8E81CEE7FAC /* SplashScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||
D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = "<group>"; };
|
||||
D31DC8105C6233E5FFD9B84C /* element-x-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "element-x-ios"; path = .; sourceTree = SOURCE_ROOT; };
|
||||
D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||
@@ -612,18 +634,20 @@
|
||||
D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
|
||||
D6D094C15E8DB424F1C6FC94 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
|
||||
DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateCoordinator.swift; sourceTree = "<group>"; };
|
||||
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = "<group>"; };
|
||||
DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenModels.swift; sourceTree = "<group>"; };
|
||||
DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = "<group>"; };
|
||||
DD73FAAA4A76CE4A1F3014D9 /* UserIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicator.swift; sourceTree = "<group>"; };
|
||||
DED59F9EFF273BFA2055FFDF /* BugReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreen.swift; sourceTree = "<group>"; };
|
||||
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = "<group>"; };
|
||||
E077F76026C85ED96FEBB810 /* UserIndicatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorPresenter.swift; sourceTree = "<group>"; };
|
||||
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
|
||||
E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
E18CF12478983A5EB390FB26 /* MessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposer.swift; sourceTree = "<group>"; };
|
||||
E2869CFFF6CD2A642AB4B743 /* TemplateCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateCoordinator.swift; sourceTree = "<group>"; };
|
||||
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = "<group>"; };
|
||||
E45C57120F28F8D619150219 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationModels.swift; sourceTree = "<group>"; };
|
||||
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||
E579A0DA01F488C97B771EF6 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lv; path = lv.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
E5D2C0950F8196232D88045C /* ServerSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreen.swift; sourceTree = "<group>"; };
|
||||
@@ -634,6 +658,7 @@
|
||||
EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
||||
EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = "<group>"; };
|
||||
EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactoryProtocol.swift; sourceTree = "<group>"; };
|
||||
EF188681D6B6068CFAEAFC3F /* MXLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = "<group>"; };
|
||||
@@ -651,12 +676,11 @@
|
||||
F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = "<group>"; };
|
||||
F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntitityRegex.swift; sourceTree = "<group>"; };
|
||||
F9BA045DC4CA12D030ACF558 /* TemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreen.swift; sourceTree = "<group>"; };
|
||||
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
FA154570F693D93513E584C1 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = "<group>"; };
|
||||
FAB10E673916D2B8D21FD197 /* TemplateModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModels.swift; sourceTree = "<group>"; };
|
||||
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerTests.swift; sourceTree = "<group>"; };
|
||||
FE2DF459F1737A594667CC46 /* EmoteRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomMessage.swift; sourceTree = "<group>"; };
|
||||
FF4EDB32B97910AAAFE632B2 /* TemplateViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -735,6 +759,7 @@
|
||||
79E560F5113ED25D172E550C /* Media */,
|
||||
40E6246F03D1FE377BC5D963 /* Room */,
|
||||
82D5AD3EAE3A5C1068A44A88 /* Session */,
|
||||
5329E48968EB951235E83DAE /* SessionVerification */,
|
||||
FCDF06BDB123505F0334B4F9 /* Timeline */,
|
||||
90C85A862720155C0CF63B02 /* UserSessionStore */,
|
||||
);
|
||||
@@ -753,6 +778,14 @@
|
||||
path = SupportingFiles;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0BA8C419737BDA72B553B129 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8AC1A01C3A745BDF1D3697D3 /* SessionVerificationScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0ED3F5C21537519389C07644 /* BugReport */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -915,7 +948,7 @@
|
||||
4AC3BA2B379A928301E21004 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F9BA045DC4CA12D030ACF558 /* TemplateScreen.swift */,
|
||||
4549FCB53F43DB0B278374BC /* TemplateScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@@ -945,6 +978,16 @@
|
||||
path = Scripts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5329E48968EB951235E83DAE /* SessionVerification */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */,
|
||||
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */,
|
||||
1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */,
|
||||
);
|
||||
path = SessionVerification;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
58F951CB7BD7F96C37BE5CAD /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1020,7 +1063,7 @@
|
||||
73AB116809AE89292624CD8E /* Unit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C08E9043618AE5B0BF7B07E1 /* TemplateViewModelTests.swift */,
|
||||
3340ABAE3A4647E80163AE18 /* TemplateViewModelTests.swift */,
|
||||
);
|
||||
path = Unit;
|
||||
sourceTree = "<group>";
|
||||
@@ -1042,6 +1085,8 @@
|
||||
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
|
||||
F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */,
|
||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */,
|
||||
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */,
|
||||
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */,
|
||||
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */,
|
||||
7B5CF94E124616FD89424B73 /* SplashScreenViewModelTests.swift */,
|
||||
AF552BB969DC98A4BB8CF8D5 /* UserIndicators */,
|
||||
@@ -1073,10 +1118,10 @@
|
||||
789DD6B31BA8BB4B3A40EF7C /* ElementX */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2869CFFF6CD2A642AB4B743 /* TemplateCoordinator.swift */,
|
||||
A1C29670CEC77346F31EE94C /* TemplateModels.swift */,
|
||||
47543EB19F3DCF308751F53C /* TemplateViewModel.swift */,
|
||||
FF4EDB32B97910AAAFE632B2 /* TemplateViewModelProtocol.swift */,
|
||||
DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */,
|
||||
FAB10E673916D2B8D21FD197 /* TemplateModels.swift */,
|
||||
EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */,
|
||||
2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */,
|
||||
4AC3BA2B379A928301E21004 /* View */,
|
||||
);
|
||||
path = ElementX;
|
||||
@@ -1191,6 +1236,7 @@
|
||||
4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */,
|
||||
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */,
|
||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */,
|
||||
6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */,
|
||||
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */,
|
||||
325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */,
|
||||
);
|
||||
@@ -1296,7 +1342,7 @@
|
||||
AD5FCF9340D670C526AD17E4 /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B66E05B6009B0EB1BDBFA6E /* TemplateScreenUITests.swift */,
|
||||
9873076F224E4CE09D8BD47D /* TemplateScreenUITests.swift */,
|
||||
);
|
||||
path = UI;
|
||||
sourceTree = "<group>";
|
||||
@@ -1389,6 +1435,19 @@
|
||||
path = Layout;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D958761758AA1110476DE6A3 /* SessionVerification */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */,
|
||||
E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */,
|
||||
C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */,
|
||||
B4C18FAAD59AE7F1462D817E /* SessionVerificationViewModel.swift */,
|
||||
B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */,
|
||||
0BA8C419737BDA72B553B129 /* View */,
|
||||
);
|
||||
path = SessionVerification;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E59565F441830B19DBAE567C /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1396,6 +1455,7 @@
|
||||
4009BE2E791C16AC6EE39A7E /* BugReport */,
|
||||
B53CA9BECD3F97805E1432D0 /* HomeScreen */,
|
||||
679E9837ECA8D6776079D16E /* RoomScreen */,
|
||||
D958761758AA1110476DE6A3 /* SessionVerification */,
|
||||
70B74A432C241E56A7ACE610 /* Settings */,
|
||||
02175C9269C4632DB6D12C25 /* Splash */,
|
||||
B1A847595434E3DD177F5143 /* SplashScreen */,
|
||||
@@ -1604,7 +1664,7 @@
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 7AE41FCCF9D1352E2770D1F9 /* Build configuration list for PBXProject "ElementX" */;
|
||||
compatibilityVersion = "Xcode 11.0";
|
||||
compatibilityVersion = "Xcode 10.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@@ -1808,9 +1868,11 @@
|
||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
|
||||
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */,
|
||||
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */,
|
||||
86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */,
|
||||
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */,
|
||||
206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */,
|
||||
94E062D08E27B0387658E364 /* SplashScreenViewModelTests.swift in Sources */,
|
||||
E75CE800B3E64D0F7F8E228D /* TemplateViewModelTests.swift in Sources */,
|
||||
7AE1FFB132F2B84EB8A2AEBC /* TemplateViewModelTests.swift in Sources */,
|
||||
226027BE23AF64FA61C7A4C0 /* TimelineStyle.swift in Sources */,
|
||||
1151DCC5EC2C6585826545EC /* UserIndicatorPresenterSpy.swift in Sources */,
|
||||
4B8A2C45FF906ADBB1F5C3B4 /* UserIndicatorQueueTests.swift in Sources */,
|
||||
@@ -1907,6 +1969,7 @@
|
||||
29AEE68A604940180AB9EBFF /* MockRoomSummary.swift in Sources */,
|
||||
E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */,
|
||||
9BE7A9CF6C593251D734B461 /* MockServerSelectionScreenState.swift in Sources */,
|
||||
D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */,
|
||||
4ED453A61AF45EBE18D8BC69 /* NavigationModule.swift in Sources */,
|
||||
22DADD537401E79D66132134 /* NavigationRouter.swift in Sources */,
|
||||
12F70C493FB69F4D7E9A37EA /* NavigationRouterStore.swift in Sources */,
|
||||
@@ -1953,6 +2016,14 @@
|
||||
388FD50AC66E9E684DDFA9D8 /* ServerSelectionScreen.swift in Sources */,
|
||||
BB01CC19C3D3322308D1B2CF /* ServerSelectionViewModel.swift in Sources */,
|
||||
19839F3526CE8C35AAF241AD /* ServerSelectionViewModelProtocol.swift in Sources */,
|
||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
||||
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
||||
E5895C74615CBE8462FB840F /* SessionVerificationCoordinator.swift in Sources */,
|
||||
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */,
|
||||
BB6B0B91CE11E06330017000 /* SessionVerificationScreen.swift in Sources */,
|
||||
9E8AE387FD03E4F1C1B8815A /* SessionVerificationStateMachine.swift in Sources */,
|
||||
A8EC7C9D886244DAE9433E37 /* SessionVerificationViewModel.swift in Sources */,
|
||||
D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */,
|
||||
34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */,
|
||||
3B770CB4DED51CC362C66D47 /* SettingsModels.swift in Sources */,
|
||||
7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */,
|
||||
@@ -1970,11 +2041,11 @@
|
||||
2F94054F50E312AF30BE07F3 /* String.swift in Sources */,
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */,
|
||||
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */,
|
||||
5CABC57F620FBB39F4EC127C /* TemplateScreen.swift in Sources */,
|
||||
C1156BBE4F977AEEE1E80C48 /* TemplateCoordinator.swift in Sources */,
|
||||
B0EDAF55877DE19B67837C22 /* TemplateModels.swift in Sources */,
|
||||
ED4F663C783E9A8C0E80B983 /* TemplateViewModel.swift in Sources */,
|
||||
3772354754450F2B54107E17 /* TemplateViewModelProtocol.swift in Sources */,
|
||||
43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */,
|
||||
63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */,
|
||||
1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */,
|
||||
75EA4ABBFAA810AFF289D6F4 /* TemplateViewModel.swift in Sources */,
|
||||
5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */,
|
||||
D013E70C8E28E43497820444 /* TextRoomMessage.swift in Sources */,
|
||||
7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */,
|
||||
5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */,
|
||||
@@ -2026,11 +2097,12 @@
|
||||
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */,
|
||||
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */,
|
||||
2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */,
|
||||
05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */,
|
||||
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */,
|
||||
A00DFC1DD3567B1EDC9F8D16 /* SplashScreenUITests.swift in Sources */,
|
||||
DD9B70DE54B24E0694A35D8A /* Strings+Untranslated.swift in Sources */,
|
||||
B3357B00F1AA930E54F76609 /* Strings.swift in Sources */,
|
||||
2E68C57E7D644E94778743D5 /* TemplateScreenUITests.swift in Sources */,
|
||||
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */,
|
||||
0ED951768EC443A8728DE1D7 /* TimelineStyle.swift in Sources */,
|
||||
75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
private let window: UIWindow
|
||||
|
||||
private var stateMachine: AppCoordinatorStateMachine
|
||||
private let stateMachine: AppCoordinatorStateMachine
|
||||
|
||||
private let mainNavigationController: UINavigationController
|
||||
private let splashViewController: UIViewController
|
||||
@@ -120,7 +121,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
case (.signingIn, .succeededSigningIn, .homeScreen):
|
||||
self.hideLoadingIndicator()
|
||||
self.presentHomeScreen()
|
||||
|
||||
|
||||
case (.initial, .startWithExistingSession, .restoringSession):
|
||||
self.showLoadingIndicator()
|
||||
self.restoreUserSession()
|
||||
@@ -142,10 +143,17 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
self.tearDownUserSession()
|
||||
case (.signingOut, .failedSigningOut, _):
|
||||
self.showLogoutErrorToast()
|
||||
|
||||
case (.homeScreen, .showSettingsScreen, .settingsScreen):
|
||||
self.presentSettingsScreen()
|
||||
case (.settingsScreen, .dismissedSettingsScreen, .homeScreen):
|
||||
self.tearDownDismissedSettingsScreen()
|
||||
|
||||
case (.homeScreen, .showSessionVerificationScreen, .sessionVerificationScreen):
|
||||
self.presentSessionVerification()
|
||||
case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .homeScreen):
|
||||
self.tearDownDismissedSessionVerificationScreen()
|
||||
|
||||
default:
|
||||
fatalError("Unknown transition: \(context)")
|
||||
}
|
||||
@@ -205,6 +213,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
|
||||
case .presentSettings:
|
||||
self.stateMachine.processEvent(.showSettingsScreen)
|
||||
case .verifySession:
|
||||
self.stateMachine.processEvent(.showSessionVerificationScreen)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,28 +225,9 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
showCrashPopup()
|
||||
}
|
||||
}
|
||||
|
||||
private func presentSettingsScreen() {
|
||||
let parameters = SettingsCoordinatorParameters(navigationRouter: navigationRouter,
|
||||
bugReportService: bugReportService)
|
||||
let coordinator = SettingsCoordinator(parameters: parameters)
|
||||
coordinator.callback = { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
switch action {
|
||||
case .logout:
|
||||
self.stateMachine.processEvent(.attemptSignOut)
|
||||
}
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
coordinator.start()
|
||||
navigationRouter.push(coordinator) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.stateMachine.processEvent(.dismissedSettingsScreen)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Rooms
|
||||
|
||||
private func presentRoomWithIdentifier(_ roomIdentifier: String) {
|
||||
guard let roomProxy = userSession.clientProxy.rooms.first(where: { $0.id == roomIdentifier }) else {
|
||||
MXLog.error("Invalid room identifier: \(roomIdentifier)")
|
||||
@@ -276,7 +267,30 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
remove(childCoordinator: coordinator)
|
||||
}
|
||||
|
||||
// MARK: Settings
|
||||
|
||||
private func presentSettingsScreen() {
|
||||
let parameters = SettingsCoordinatorParameters(navigationRouter: navigationRouter,
|
||||
bugReportService: bugReportService)
|
||||
let coordinator = SettingsCoordinator(parameters: parameters)
|
||||
coordinator.callback = { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
switch action {
|
||||
case .logout:
|
||||
self.stateMachine.processEvent(.attemptSignOut)
|
||||
}
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
coordinator.start()
|
||||
navigationRouter.push(coordinator) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.stateMachine.processEvent(.dismissedSettingsScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private func tearDownDismissedSettingsScreen() {
|
||||
guard let coordinator = childCoordinators.last as? SettingsCoordinator else {
|
||||
fatalError("Invalid coordinator hierarchy: \(childCoordinators)")
|
||||
@@ -285,22 +299,6 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
remove(childCoordinator: coordinator)
|
||||
}
|
||||
|
||||
private func showLoadingIndicator() {
|
||||
loadingIndicator = indicatorPresenter.present(.loading(label: ElementL10n.loading, isInteractionBlocking: true))
|
||||
}
|
||||
|
||||
private func hideLoadingIndicator() {
|
||||
loadingIndicator = nil
|
||||
}
|
||||
|
||||
private func showLoginErrorToast() {
|
||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging in"))
|
||||
}
|
||||
|
||||
private func showLogoutErrorToast() {
|
||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging out"))
|
||||
}
|
||||
|
||||
private func showCrashPopup() {
|
||||
let alert = UIAlertController(title: nil,
|
||||
message: ElementL10n.sendBugReportAppCrashed,
|
||||
@@ -360,4 +358,54 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
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() {
|
||||
loadingIndicator = indicatorPresenter.present(.loading(label: ElementL10n.loading, isInteractionBlocking: true))
|
||||
}
|
||||
|
||||
private func hideLoadingIndicator() {
|
||||
loadingIndicator = nil
|
||||
}
|
||||
|
||||
private func showLoginErrorToast() {
|
||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging in"))
|
||||
}
|
||||
|
||||
private func showLogoutErrorToast() {
|
||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging out"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,17 @@ class AppCoordinatorStateMachine {
|
||||
case restoringSession
|
||||
/// Showing the home screen
|
||||
case homeScreen
|
||||
/// Showing the settings screen
|
||||
case settingsScreen
|
||||
|
||||
/// 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
|
||||
|
||||
/// Processing a sign out request
|
||||
case signingOut
|
||||
}
|
||||
@@ -61,10 +67,16 @@ class AppCoordinatorStateMachine {
|
||||
case showRoomScreen(roomId: String)
|
||||
/// The room screen has been dismissed
|
||||
case dismissedRoomScreen
|
||||
/// The settings screen has been dismissed
|
||||
case dismissedSettingsScreen
|
||||
|
||||
/// Request the start of the session verification flow
|
||||
case showSessionVerificationScreen
|
||||
/// Session verification has finished
|
||||
case dismissedSessionVerificationScreen
|
||||
|
||||
/// Request settings screen presentation
|
||||
case showSettingsScreen
|
||||
/// The settings screen has been dismissed
|
||||
case dismissedSettingsScreen
|
||||
}
|
||||
|
||||
private let stateMachine: StateMachine<State, Event>
|
||||
@@ -84,9 +96,13 @@ class AppCoordinatorStateMachine {
|
||||
|
||||
machine.addRoutes(event: .succeededSigningOut, transitions: [ .signingOut => .signedOut ])
|
||||
machine.addRoutes(event: .failedSigningOut, transitions: [ .signingOut => .settingsScreen ])
|
||||
|
||||
machine.addRoutes(event: .showSettingsScreen, transitions: [ .homeScreen => .settingsScreen ])
|
||||
machine.addRoutes(event: .dismissedSettingsScreen, transitions: [ .settingsScreen => .homeScreen ])
|
||||
|
||||
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) {
|
||||
|
||||
@@ -61,6 +61,12 @@ struct ImageAnonymizer {
|
||||
}
|
||||
// revision3 doesn't work!
|
||||
faceRequest.revision = VNDetectFaceRectanglesRequestRevision2
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
// Avoid `Could not create inference context` errors on Apple Silicon
|
||||
// https://www.caseyliss.com/2022/6/20/feedback-is-broken-stop-trying-to-make-radar-happen
|
||||
faceRequest.usesCPUOnly = true
|
||||
#endif
|
||||
|
||||
// perform requests
|
||||
try handler.perform([
|
||||
|
||||
@@ -26,6 +26,7 @@ struct HomeScreenCoordinatorParameters {
|
||||
enum HomeScreenCoordinatorAction {
|
||||
case presentRoom(roomIdentifier: String)
|
||||
case presentSettings
|
||||
case verifySession
|
||||
}
|
||||
|
||||
final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
@@ -66,19 +67,31 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
self.callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||
case .tapUserAvatar:
|
||||
self.callback?(.presentSettings)
|
||||
case .verifySession:
|
||||
self.callback?(.verifySession)
|
||||
}
|
||||
}
|
||||
|
||||
parameters.userSession.clientProxy
|
||||
.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] action in
|
||||
switch action {
|
||||
case .updatedRoomsList:
|
||||
.sink { [weak self] callback in
|
||||
if case .updatedRoomsList = callback {
|
||||
self?.updateRoomsList()
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
parameters.userSession.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
switch callback {
|
||||
case .sessionVerificationNeeded:
|
||||
self?.viewModel.showSessionVerificationBanner()
|
||||
case .didVerifySession:
|
||||
self?.viewModel.hideSessionVerificationBanner()
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
updateRoomsList()
|
||||
|
||||
Task {
|
||||
|
||||
@@ -20,18 +20,22 @@ import UIKit
|
||||
enum HomeScreenViewModelAction {
|
||||
case selectRoom(roomIdentifier: String)
|
||||
case tapUserAvatar
|
||||
case verifySession
|
||||
}
|
||||
|
||||
enum HomeScreenViewAction {
|
||||
case loadRoomData(roomIdentifier: String)
|
||||
case selectRoom(roomIdentifier: String)
|
||||
case tapUserAvatar
|
||||
case verifySession
|
||||
}
|
||||
|
||||
struct HomeScreenViewState: BindableState {
|
||||
var userDisplayName: String?
|
||||
var userAvatar: UIImage?
|
||||
|
||||
var showSessionVerificationBanner: Bool = false
|
||||
|
||||
var rooms: [HomeScreenRoom] = []
|
||||
|
||||
var isLoadingRooms: Bool = false
|
||||
|
||||
@@ -27,7 +27,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
private var roomSummaries: [RoomSummaryProtocol]? {
|
||||
didSet {
|
||||
self.state.isLoadingRooms = (roomSummaries?.count ?? 0 == 0)
|
||||
state.isLoadingRooms = (roomSummaries?.count ?? 0 == 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
callback?(.selectRoom(roomIdentifier: roomIdentifier))
|
||||
case .tapUserAvatar:
|
||||
callback?(.tapUserAvatar)
|
||||
case .verifySession:
|
||||
callback?(.verifySession)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,11 +86,19 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
}
|
||||
|
||||
func updateWithUserAvatar(_ avatar: UIImage) {
|
||||
self.state.userAvatar = avatar
|
||||
state.userAvatar = avatar
|
||||
}
|
||||
|
||||
func updateWithUserDisplayName(_ displayName: String) {
|
||||
self.state.userDisplayName = displayName
|
||||
state.userDisplayName = displayName
|
||||
}
|
||||
|
||||
func showSessionVerificationBanner() {
|
||||
state.showSessionVerificationBanner = true
|
||||
}
|
||||
|
||||
func hideSessionVerificationBanner() {
|
||||
state.showSessionVerificationBanner = false
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -105,7 +115,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom {
|
||||
let lastMessage = lastMessageFromEventBrief(roomSummary.lastMessage)
|
||||
|
||||
guard var room = self.state.rooms.first(where: { $0.id == roomSummary.id }) else {
|
||||
guard var room = state.rooms.first(where: { $0.id == roomSummary.id }) else {
|
||||
return HomeScreenRoom(id: roomSummary.id,
|
||||
displayName: roomSummary.displayName,
|
||||
topic: roomSummary.topic,
|
||||
|
||||
@@ -26,4 +26,7 @@ protocol HomeScreenViewModelProtocol {
|
||||
func updateWithUserAvatar(_ avatar: UIImage)
|
||||
func updateWithUserDisplayName(_ displayName: String)
|
||||
func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol])
|
||||
|
||||
func showSessionVerificationBanner()
|
||||
func hideSessionVerificationBanner()
|
||||
}
|
||||
|
||||
@@ -23,13 +23,27 @@ struct HomeScreen: View {
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16.0) {
|
||||
VStack(spacing: 0.0) {
|
||||
if context.viewState.isLoadingRooms {
|
||||
VStack {
|
||||
Text(ElementL10n.loading)
|
||||
ProgressView()
|
||||
}
|
||||
} else {
|
||||
|
||||
if context.viewState.showSessionVerificationBanner {
|
||||
HStack {
|
||||
Text(ElementL10n.verificationVerifyDevice)
|
||||
Spacer()
|
||||
Button(ElementL10n.startVerification) {
|
||||
context.send(viewAction: .verifySession)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.element.quaternaryContent)
|
||||
.padding(.top, 1)
|
||||
}
|
||||
|
||||
List {
|
||||
Section(ElementL10n.rooms) {
|
||||
ForEach(context.viewState.visibleRooms) { room in
|
||||
@@ -52,6 +66,8 @@ struct HomeScreen: View {
|
||||
Spacer()
|
||||
}
|
||||
.background(Color.element.background)
|
||||
.transition(.slide)
|
||||
.animation(.default, value: context.viewState.showSessionVerificationBanner)
|
||||
.ignoresSafeArea(.all, edges: .bottom)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
@@ -100,6 +116,8 @@ struct HomeScreen: View {
|
||||
|
||||
struct RoomCell: View {
|
||||
|
||||
@ScaledMetric private var avatarSize = 32.0
|
||||
|
||||
let room: HomeScreenRoom
|
||||
let context: HomeScreenViewModel.Context
|
||||
|
||||
@@ -112,12 +130,12 @@ struct RoomCell: View {
|
||||
Image(uiImage: avatar)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40)
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
PlaceholderAvatarImage(text: room.displayName ?? room.id)
|
||||
.clipShape(Circle())
|
||||
.frame(width: 40, height: 40)
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 2.0) {
|
||||
@@ -182,6 +200,8 @@ struct HomeScreen_Previews: PreviewProvider {
|
||||
viewModel.updateWithUserAvatar(avatarImage)
|
||||
}
|
||||
|
||||
viewModel.showSessionVerificationBanner()
|
||||
|
||||
return NavigationView {
|
||||
HomeScreen(context: viewModel.context)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
struct SessionVerificationCoordinatorParameters {
|
||||
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||
}
|
||||
|
||||
final class SessionVerificationCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: SessionVerificationCoordinatorParameters
|
||||
private let sessionVerificationHostingController: UIViewController
|
||||
private var sessionVerificationViewModel: SessionVerificationViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var callback: (() -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: SessionVerificationCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy)
|
||||
let view = SessionVerificationScreen(context: viewModel.context)
|
||||
sessionVerificationViewModel = viewModel
|
||||
sessionVerificationHostingController = UIHostingController(rootView: view)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
MXLog.debug("[SessionVerificationCoordinator] did start.")
|
||||
sessionVerificationViewModel.callback = { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch action {
|
||||
case .finished:
|
||||
self.callback?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
sessionVerificationHostingController
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum SessionVerificationViewModelAction {
|
||||
case finished
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct SessionVerificationViewState: BindableState {
|
||||
var verificationState: SessionVerificationStateMachine.State = .initial
|
||||
|
||||
var shouldDisableDismissButton: Bool {
|
||||
verificationState != .verified
|
||||
}
|
||||
|
||||
var shouldDisableCancelButton: Bool {
|
||||
verificationState == .verified
|
||||
}
|
||||
}
|
||||
|
||||
enum SessionVerificationViewAction {
|
||||
case start
|
||||
case restart
|
||||
case accept
|
||||
case decline
|
||||
case dismiss
|
||||
case cancel
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// SessionVerificationStateMachine.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 15/06/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftState
|
||||
|
||||
class SessionVerificationStateMachine {
|
||||
/// States the SessionVerificationViewModel can find itself in
|
||||
enum State: StateType {
|
||||
/// The initial state, before verification started
|
||||
case initial
|
||||
/// Waiting for verification acceptance
|
||||
case requestingVerification
|
||||
/// Verification accepted and emojis received
|
||||
case showingChallenge(emojis: [SessionVerificationEmoji])
|
||||
/// Emojis match locally
|
||||
case acceptingChallenge(emojis: [SessionVerificationEmoji])
|
||||
/// Emojis do not match locally
|
||||
case decliningChallenge(emojis: [SessionVerificationEmoji])
|
||||
/// Verification successful
|
||||
case verified
|
||||
/// User requested verification cancellation
|
||||
case cancelling
|
||||
/// The verification has been cancelled, remotely or locally
|
||||
case cancelled
|
||||
}
|
||||
|
||||
/// Events that can be triggered on the SessionVerification state machine
|
||||
enum Event: EventType {
|
||||
/// Request verification
|
||||
case requestVerification
|
||||
/// Has received emojis
|
||||
case didReceiveChallenge(emojis: [SessionVerificationEmoji])
|
||||
/// Emojis match
|
||||
case acceptChallenge
|
||||
/// Emojis do not match
|
||||
case declineChallenge
|
||||
/// Remote accepted challenge
|
||||
case didAcceptChallenge
|
||||
/// Request cancellation
|
||||
case cancel
|
||||
/// Verification cancelled
|
||||
case didCancel
|
||||
/// Request failed
|
||||
case didFail
|
||||
/// Restart the verification flow
|
||||
case restart
|
||||
}
|
||||
|
||||
private let stateMachine: StateMachine<State, Event>
|
||||
|
||||
var state: State {
|
||||
stateMachine.state
|
||||
}
|
||||
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
init() {
|
||||
stateMachine = StateMachine(state: .initial) { machine in
|
||||
machine.addRoutes(event: .requestVerification, transitions: [ .initial => .requestingVerification ])
|
||||
machine.addRoutes(event: .didFail, transitions: [ .requestingVerification => .initial ])
|
||||
|
||||
machine.addRoutes(event: .cancel, transitions: [ .requestingVerification => .cancelling ])
|
||||
machine.addRoutes(event: .didCancel, transitions: [ .requestingVerification => .cancelled ])
|
||||
|
||||
// Cancellation request from the other party should either take us from `.cancelling`
|
||||
// to `.cancelled` or keep us in `.cancelled` if already there. There is more `.didCancel`
|
||||
// handling in `addRouteMapping` for states containing associated values
|
||||
machine.addRoutes(event: .didCancel, transitions: [ .cancelling => .cancelled ])
|
||||
machine.addRoutes(event: .didCancel, transitions: [ .cancelled => .cancelled ])
|
||||
machine.addRoutes(event: .didFail, transitions: [ .cancelled => .cancelled ])
|
||||
|
||||
machine.addRoutes(event: .restart, transitions: [ .cancelled => .initial ])
|
||||
|
||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||
machine.addRouteMapping { event, fromState, _ in
|
||||
switch (event, fromState) {
|
||||
case (.didReceiveChallenge(let emojis), .requestingVerification):
|
||||
return .showingChallenge(emojis: emojis)
|
||||
|
||||
case (.acceptChallenge, .showingChallenge(let emojis)):
|
||||
return .acceptingChallenge(emojis: emojis)
|
||||
case (.didFail, .acceptingChallenge(let emojis)):
|
||||
return .showingChallenge(emojis: emojis)
|
||||
|
||||
case (.didAcceptChallenge, .acceptingChallenge):
|
||||
return .verified
|
||||
|
||||
case (.declineChallenge, .showingChallenge(let emojis)):
|
||||
return .decliningChallenge(emojis: emojis)
|
||||
case (.didFail, .decliningChallenge(let emojis)):
|
||||
return .showingChallenge(emojis: emojis)
|
||||
|
||||
case (.cancel, .showingChallenge):
|
||||
return .cancelling
|
||||
case (.cancel, .acceptingChallenge):
|
||||
return .cancelling
|
||||
case (.cancel, .decliningChallenge):
|
||||
return .cancelling
|
||||
|
||||
case (.didCancel, .showingChallenge):
|
||||
return .cancelled
|
||||
case (.didCancel, .acceptingChallenge):
|
||||
return .cancelled
|
||||
case (.didCancel, .decliningChallenge):
|
||||
return .cancelled
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// swiftlint:enable cyclomatic_complexity
|
||||
|
||||
/// 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<State, Event>.Handler) {
|
||||
stateMachine.addAnyHandler(.any => .any, handler: handler)
|
||||
}
|
||||
|
||||
/// Registers a callback for processing state machine errors
|
||||
func addErrorHandler(_ handler: @escaping StateMachine<State, Event>.Handler) {
|
||||
stateMachine.addErrorHandler(handler: handler)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
typealias SessionVerificationViewModelType = StateStoreViewModel<SessionVerificationViewState, SessionVerificationViewAction>
|
||||
|
||||
class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVerificationViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||
|
||||
private var stateMachine: SessionVerificationStateMachine
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var callback: ((SessionVerificationViewModelAction) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
|
||||
initialState: SessionVerificationViewState = SessionVerificationViewState()) {
|
||||
|
||||
self.sessionVerificationControllerProxy = sessionVerificationControllerProxy
|
||||
|
||||
stateMachine = SessionVerificationStateMachine()
|
||||
|
||||
super.init(initialViewState: initialState)
|
||||
|
||||
setupStateMachine()
|
||||
|
||||
sessionVerificationControllerProxy.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch callback {
|
||||
case .receivedVerificationData(let emojis):
|
||||
self.stateMachine.processEvent(.didReceiveChallenge(emojis: emojis))
|
||||
case .finished:
|
||||
self.stateMachine.processEvent(.didAcceptChallenge)
|
||||
case .cancelled:
|
||||
self.stateMachine.processEvent(.didCancel)
|
||||
case .failed:
|
||||
self.stateMachine.processEvent(.didFail)
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: SessionVerificationViewAction) async {
|
||||
switch viewAction {
|
||||
case .start:
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
case .restart:
|
||||
stateMachine.processEvent(.restart)
|
||||
case .dismiss:
|
||||
callback?(.finished)
|
||||
case .cancel:
|
||||
guard stateMachine.state == .initial ||
|
||||
stateMachine.state == .verified ||
|
||||
stateMachine.state == .cancelled else {
|
||||
stateMachine.processEvent(.cancel)
|
||||
return
|
||||
}
|
||||
|
||||
callback?(.finished)
|
||||
case .accept:
|
||||
stateMachine.processEvent(.acceptChallenge)
|
||||
case .decline:
|
||||
stateMachine.processEvent(.declineChallenge)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupStateMachine() {
|
||||
stateMachine.addTransitionHandler { [weak self] context in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.state.verificationState = context.toState
|
||||
|
||||
switch (context.fromState, context.event, context.toState) {
|
||||
case (.initial, .requestVerification, .requestingVerification):
|
||||
self.requestVerification()
|
||||
case (.showingChallenge, .acceptChallenge, .acceptingChallenge):
|
||||
self.acceptChallenge()
|
||||
case (.showingChallenge, .declineChallenge, .decliningChallenge):
|
||||
self.declineChallenge()
|
||||
case (_, .cancel, .cancelling):
|
||||
self.cancelVerification()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
stateMachine.addErrorHandler { context in
|
||||
fatalError("Failed transition with context: \(context)")
|
||||
}
|
||||
}
|
||||
|
||||
private func requestVerification() {
|
||||
Task {
|
||||
switch await sessionVerificationControllerProxy.requestVerification() {
|
||||
case.success:
|
||||
// Need to wait for the callback from the remote
|
||||
break
|
||||
case .failure:
|
||||
stateMachine.processEvent(.didFail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cancelVerification() {
|
||||
Task {
|
||||
switch await sessionVerificationControllerProxy.cancelVerification() {
|
||||
case.success:
|
||||
stateMachine.processEvent(.didCancel)
|
||||
case .failure:
|
||||
stateMachine.processEvent(.didFail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func acceptChallenge() {
|
||||
Task {
|
||||
switch await sessionVerificationControllerProxy.approveVerification() {
|
||||
case.success:
|
||||
// Need to wait for the callback from the remote
|
||||
break
|
||||
case .failure:
|
||||
stateMachine.processEvent(.didFail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func declineChallenge() {
|
||||
Task {
|
||||
switch await sessionVerificationControllerProxy.declineVerification() {
|
||||
case.success:
|
||||
stateMachine.processEvent(.didCancel)
|
||||
case .failure:
|
||||
stateMachine.processEvent(.didFail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 Foundation
|
||||
|
||||
@MainActor
|
||||
protocol SessionVerificationViewModelProtocol {
|
||||
var callback: ((SessionVerificationViewModelAction) -> Void)? { get set }
|
||||
var context: SessionVerificationViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// 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
|
||||
import MatrixRustSDK
|
||||
|
||||
struct SessionVerificationScreen: View {
|
||||
|
||||
@ObservedObject var context: SessionVerificationViewModel.Context
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 32.0) {
|
||||
Text(heading)
|
||||
.font(.body)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.accessibilityIdentifier("titleLabel")
|
||||
|
||||
switch context.viewState.verificationState {
|
||||
case .initial:
|
||||
StateIcon(systemName: "lock.shield")
|
||||
Button(ElementL10n.startVerification) {
|
||||
context.send(viewAction: .start)
|
||||
}
|
||||
.buttonStyle(.elementAction(.regular))
|
||||
.accessibilityIdentifier("startButton")
|
||||
|
||||
case .cancelled:
|
||||
StateIcon(systemName: "xmark.shield")
|
||||
.accessibilityIdentifier("sessionVerificationFailedIcon")
|
||||
|
||||
Button(ElementL10n.globalRetry) {
|
||||
context.send(viewAction: .restart)
|
||||
}
|
||||
.buttonStyle(.elementAction(.regular))
|
||||
.accessibilityIdentifier("restartButton")
|
||||
|
||||
case .requestingVerification:
|
||||
ProgressView()
|
||||
.accessibilityIdentifier("requestingVerificationProgressView")
|
||||
case .cancelling:
|
||||
ProgressView()
|
||||
.accessibilityIdentifier("cancellingVerificationProgressView")
|
||||
case .acceptingChallenge:
|
||||
ProgressView()
|
||||
.accessibilityIdentifier("acceptingChallengeProgressView")
|
||||
case .decliningChallenge:
|
||||
ProgressView()
|
||||
.accessibilityIdentifier("decliningChallengeProgressView")
|
||||
|
||||
case .showingChallenge(let emojis):
|
||||
HStack(spacing: 8.0) {
|
||||
ForEach(emojis.prefix(4), id: \.self) { emoji in
|
||||
EmojiView(emoji: emoji)
|
||||
}
|
||||
}
|
||||
HStack(spacing: 8.0) {
|
||||
ForEach(emojis.suffix(from: 4), id: \.self) { emoji in
|
||||
EmojiView(emoji: emoji)
|
||||
}
|
||||
}
|
||||
|
||||
actionButtons
|
||||
case .verified:
|
||||
StateIcon(systemName: "checkmark.shield")
|
||||
.accessibilityIdentifier("sessionVerificationSucceededIcon")
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.padding(.top, 64)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle(ElementL10n.verificationVerifyDevice)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(ElementL10n.done) {
|
||||
context.send(viewAction: .dismiss)
|
||||
}
|
||||
.disabled(context.viewState.shouldDisableDismissButton)
|
||||
.accessibilityIdentifier("dismissButton")
|
||||
}
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(ElementL10n.actionCancel) {
|
||||
context.send(viewAction: .cancel)
|
||||
}
|
||||
.disabled(context.viewState.shouldDisableCancelButton)
|
||||
.accessibilityIdentifier("cancelButton")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var heading: String {
|
||||
switch context.viewState.verificationState {
|
||||
case .initial:
|
||||
return ElementL10n.verificationOpenOtherToVerify
|
||||
case .requestingVerification:
|
||||
return ElementL10n.verificationRequestWaiting
|
||||
case .acceptingChallenge:
|
||||
return ElementL10n.verificationRequestWaiting
|
||||
case .decliningChallenge:
|
||||
return ElementL10n.verificationRequestWaiting
|
||||
case .cancelling:
|
||||
return ElementL10n.verificationRequestWaiting
|
||||
case .showingChallenge:
|
||||
return ElementL10n.verificationEmojiNotice
|
||||
case .verified:
|
||||
return ElementL10n.verificationConclusionOkSelfNotice
|
||||
case .cancelled:
|
||||
return ElementL10n.verificationCancelled
|
||||
}
|
||||
}
|
||||
|
||||
private var actionButtons: some View {
|
||||
HStack(spacing: 16.0) {
|
||||
Button(ElementL10n.verificationSasDoNotMatch) {
|
||||
context.send(viewAction: .decline)
|
||||
}
|
||||
.buttonStyle(.elementAction(.regular, color: .red))
|
||||
.accessibilityLabel("challengeDeclineButton")
|
||||
|
||||
Button(ElementL10n.verificationSasMatch) {
|
||||
context.send(viewAction: .accept)
|
||||
}
|
||||
.buttonStyle(.elementAction(.regular))
|
||||
.accessibilityLabel("challengeAcceptButton")
|
||||
}
|
||||
.padding(32.0)
|
||||
}
|
||||
|
||||
struct EmojiView: View {
|
||||
let emoji: SessionVerificationEmoji
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16.0) {
|
||||
Text(emoji.symbol)
|
||||
.font(.largeTitle)
|
||||
Text(emoji.description)
|
||||
.font(.body)
|
||||
}
|
||||
.padding(8.0)
|
||||
}
|
||||
}
|
||||
|
||||
struct StateIcon: View {
|
||||
let systemName: String
|
||||
|
||||
var body: some View {
|
||||
Image(systemName: systemName)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundColor(.element.accent)
|
||||
.frame(width: 100, height: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct SessionVerification_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
body.preferredColorScheme(.light)
|
||||
body.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
static var body: some View {
|
||||
Group {
|
||||
sessionVerificationScreen(state: .initial)
|
||||
sessionVerificationScreen(state: .requestingVerification)
|
||||
sessionVerificationScreen(state: .cancelled)
|
||||
|
||||
sessionVerificationScreen(state: .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
sessionVerificationScreen(state: .verified)
|
||||
}
|
||||
}
|
||||
|
||||
static func sessionVerificationScreen(state: SessionVerificationStateMachine.State) -> some View {
|
||||
let viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: MockSessionVerificationControllerProxy(),
|
||||
initialState: SessionVerificationViewState(verificationState: state))
|
||||
|
||||
return SessionVerificationScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
private let client: Client
|
||||
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
||||
private var sessionVerificationControllerProxy: SessionVerificationControllerProxy?
|
||||
|
||||
private(set) var rooms: [RoomProxy] = [] {
|
||||
didSet {
|
||||
@@ -96,11 +97,25 @@ class ClientProxy: ClientProxyProtocol {
|
||||
return Data(bytes: bytes, count: bytes.count)
|
||||
}
|
||||
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
||||
await Task.detached {
|
||||
do {
|
||||
let sessionVerificationController = try self.client.getSessionVerificationController()
|
||||
return .success(SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController))
|
||||
} catch {
|
||||
return .failure(.failedRetrievingSessionVerificationController)
|
||||
}
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
fileprivate func didReceiveSyncUpdate() {
|
||||
Benchmark.logElapsedDurationForIdentifier("ClientSync", message: "Received sync update")
|
||||
|
||||
callbacks.send(.receivedSyncUpdate)
|
||||
|
||||
Task.detached {
|
||||
await self.updateRooms()
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ import Combine
|
||||
|
||||
enum ClientProxyCallback {
|
||||
case updatedRoomsList
|
||||
case receivedSyncUpdate
|
||||
}
|
||||
|
||||
enum ClientProxyError: Error {
|
||||
case failedRetrievingAvatarURL
|
||||
case failedRetrievingDisplayName
|
||||
case failedRetrievingSessionVerificationController
|
||||
}
|
||||
|
||||
protocol ClientProxyProtocol {
|
||||
@@ -33,4 +35,6 @@ protocol ClientProxyProtocol {
|
||||
func mediaSourceForURLString(_ urlString: String) -> MatrixRustSDK.MediaSource
|
||||
|
||||
func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) throws -> Data
|
||||
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@ class RoomProxy: RoomProxyProtocol {
|
||||
backwardStream = room.startLiveEventListener()
|
||||
}
|
||||
|
||||
deinit {
|
||||
room.setDelegate(delegate: nil)
|
||||
}
|
||||
|
||||
var id: String {
|
||||
room.id()
|
||||
}
|
||||
|
||||
@@ -7,13 +7,64 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class UserSession: UserSessionProtocol {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var checkForSessionVerificationControllerCancellable: AnyCancellable?
|
||||
|
||||
let clientProxy: ClientProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let callbacks = PassthroughSubject<UserSessionCallback, Never>()
|
||||
private(set) var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||
|
||||
init(clientProxy: ClientProxyProtocol, mediaProvider: MediaProviderProtocol) {
|
||||
self.clientProxy = clientProxy
|
||||
self.mediaProvider = mediaProvider
|
||||
|
||||
setupSessionVerificationWatchdog()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupSessionVerificationWatchdog() {
|
||||
checkForSessionVerificationControllerCancellable = clientProxy.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
if case .receivedSyncUpdate = callback {
|
||||
self?.attemptSessionVerification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func attemptSessionVerification() {
|
||||
Task {
|
||||
switch await clientProxy.sessionVerificationControllerProxy() {
|
||||
case .success(let sessionVerificationController):
|
||||
tearDownSessionVerificationControllerWatchdog()
|
||||
|
||||
if !sessionVerificationController.isVerified {
|
||||
callbacks.send(.sessionVerificationNeeded)
|
||||
}
|
||||
|
||||
self.sessionVerificationController = sessionVerificationController
|
||||
|
||||
sessionVerificationController.callbacks.sink { callback in
|
||||
switch callback {
|
||||
case .finished:
|
||||
self.callbacks.send(.didVerifySession)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed getting session verification controller with error: \(error). Will retry on the next sync update.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func tearDownSessionVerificationControllerWatchdog() {
|
||||
checkForSessionVerificationControllerCancellable = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,18 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
enum UserSessionCallback {
|
||||
case sessionVerificationNeeded
|
||||
case didVerifySession
|
||||
}
|
||||
|
||||
protocol UserSessionProtocol {
|
||||
var clientProxy: ClientProxyProtocol { get }
|
||||
var mediaProvider: MediaProviderProtocol { get }
|
||||
|
||||
var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get }
|
||||
|
||||
var callbacks: PassthroughSubject<UserSessionCallback, Never> { get }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// MockSessionVerificationControllerProxy.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 07/06/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
import Combine
|
||||
|
||||
struct MockSessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks = PassthroughSubject<SessionVerificationControllerProxyCallback, Never>()
|
||||
|
||||
var isVerified: Bool = false
|
||||
|
||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2.0) {
|
||||
callbacks.send(.receivedVerificationData(Self.emojis))
|
||||
}
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2.0) {
|
||||
callbacks.send(.finished)
|
||||
}
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2.0) {
|
||||
callbacks.send(.cancelled)
|
||||
}
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2.0) {
|
||||
callbacks.send(.cancelled)
|
||||
}
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
static var emojis: [SessionVerificationEmoji] {
|
||||
[SessionVerificationEmoji(symbol: "🦋", description: "Butterfly"),
|
||||
SessionVerificationEmoji(symbol: "🐘", description: "Elephant"),
|
||||
SessionVerificationEmoji(symbol: "🦋", description: "Butterfly"),
|
||||
SessionVerificationEmoji(symbol: "🎂", description: "Cake"),
|
||||
SessionVerificationEmoji(symbol: "🎂", description: "Cake"),
|
||||
SessionVerificationEmoji(symbol: "🏁", description: "Flag"),
|
||||
SessionVerificationEmoji(symbol: "🌏", description: "Globe")
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// SessionVerificationControllerProxy.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 06/06/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
import Combine
|
||||
|
||||
private class WeakSessionVerificationControllerProxy: SessionVerificationControllerDelegate {
|
||||
private weak var proxy: SessionVerificationControllerProxy?
|
||||
|
||||
init(proxy: SessionVerificationControllerProxy) {
|
||||
self.proxy = proxy
|
||||
}
|
||||
|
||||
// MARK: - SessionVerificationControllerDelegate
|
||||
|
||||
func didReceiveVerificationData(data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
||||
proxy?.didReceiveData(data)
|
||||
}
|
||||
|
||||
func didFail() {
|
||||
proxy?.didFail()
|
||||
}
|
||||
|
||||
func didCancel() {
|
||||
proxy?.didCancel()
|
||||
}
|
||||
|
||||
func didFinish() {
|
||||
proxy?.didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
class SessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol {
|
||||
private let sessionVerificationController: SessionVerificationController
|
||||
|
||||
init(sessionVerificationController: SessionVerificationController) {
|
||||
self.sessionVerificationController = sessionVerificationController
|
||||
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
||||
}
|
||||
|
||||
deinit {
|
||||
sessionVerificationController.setDelegate(delegate: nil)
|
||||
}
|
||||
|
||||
let callbacks = PassthroughSubject<SessionVerificationControllerProxyCallback, Never>()
|
||||
|
||||
var isVerified: Bool {
|
||||
sessionVerificationController.isVerified()
|
||||
}
|
||||
|
||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
await Task.detached {
|
||||
do {
|
||||
try self.sessionVerificationController.requestVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedRequestingVerification)
|
||||
}
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
await Task.detached {
|
||||
do {
|
||||
try self.sessionVerificationController.approveVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedApprovingVerification)
|
||||
}
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
await Task.detached {
|
||||
do {
|
||||
try self.sessionVerificationController.declineVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedDecliningVerification)
|
||||
}
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
await Task.detached {
|
||||
do {
|
||||
try self.sessionVerificationController.cancelVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedCancellingVerification)
|
||||
}
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
||||
callbacks.send(.receivedVerificationData(data.map { emoji in
|
||||
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
||||
}))
|
||||
}
|
||||
|
||||
fileprivate func didFail() {
|
||||
callbacks.send(.failed)
|
||||
}
|
||||
|
||||
fileprivate func didFinish() {
|
||||
callbacks.send(.finished)
|
||||
}
|
||||
|
||||
fileprivate func didCancel() {
|
||||
callbacks.send(.cancelled)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// SessionVerificationControllerProxyProtocol.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 07/06/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
enum SessionVerificationControllerProxyError: Error {
|
||||
case failedRequestingVerification
|
||||
case failedApprovingVerification
|
||||
case failedDecliningVerification
|
||||
case failedCancellingVerification
|
||||
}
|
||||
|
||||
enum SessionVerificationControllerProxyCallback {
|
||||
case receivedVerificationData([SessionVerificationEmoji])
|
||||
case finished
|
||||
case cancelled
|
||||
case failed
|
||||
}
|
||||
|
||||
struct SessionVerificationEmoji: Hashable {
|
||||
let symbol: String
|
||||
let description: String
|
||||
}
|
||||
|
||||
protocol SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> { get }
|
||||
|
||||
var isVerified: Bool { get }
|
||||
|
||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||
|
||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||
|
||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||
|
||||
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||
}
|
||||
@@ -18,7 +18,9 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
init(roomProxy: RoomProxyProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
|
||||
self.roomProxy.callbacks.sink { [weak self] callback in
|
||||
self.roomProxy.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch callback {
|
||||
|
||||
@@ -20,6 +20,7 @@ enum UITestScreenIdentifier: String {
|
||||
case splash
|
||||
case roomPlainNoAvatar
|
||||
case roomEncryptedWithAvatar
|
||||
case sessionVerification
|
||||
}
|
||||
|
||||
extension UITestScreenIdentifier: CustomStringConvertible {
|
||||
|
||||
@@ -22,13 +22,13 @@ class UITestsAppCoordinator: Coordinator {
|
||||
window.tintColor = .element.accent
|
||||
|
||||
let screens = mockScreens()
|
||||
screens.forEach { $0.coordinator.start() }
|
||||
|
||||
let rootView = UITestsRootView(mockScreens: screens) { id in
|
||||
guard let screen = screens.first(where: { $0.id == id }) else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
screen.coordinator.start()
|
||||
|
||||
self.mainNavigationController.pushViewController(screen.coordinator.toPresentable(), animated: true)
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ class UITestsAppCoordinator: Coordinator {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct MockScreen: Identifiable {
|
||||
class MockScreen: Identifiable {
|
||||
let id: UITestScreenIdentifier
|
||||
var coordinator: Coordinator & Presentable {
|
||||
lazy var coordinator: Coordinator & Presentable = {
|
||||
switch id {
|
||||
case .login:
|
||||
let router = NavigationRouter(navigationController: ElementNavigationController())
|
||||
@@ -78,17 +78,24 @@ struct MockScreen: Identifiable {
|
||||
case .splash:
|
||||
return SplashScreenCoordinator()
|
||||
case .roomPlainNoAvatar:
|
||||
let params = RoomScreenCoordinatorParameters(timelineController: MockRoomTimelineController(),
|
||||
roomName: "Some room name",
|
||||
roomAvatar: nil,
|
||||
roomEncryptionBadge: nil)
|
||||
return RoomScreenCoordinator(parameters: params)
|
||||
let parameters = RoomScreenCoordinatorParameters(timelineController: MockRoomTimelineController(),
|
||||
roomName: "Some room name",
|
||||
roomAvatar: nil,
|
||||
roomEncryptionBadge: nil)
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
case .roomEncryptedWithAvatar:
|
||||
let params = RoomScreenCoordinatorParameters(timelineController: MockRoomTimelineController(),
|
||||
roomName: "Some room name",
|
||||
roomAvatar: Asset.Images.appLogo.image,
|
||||
roomEncryptionBadge: Asset.Images.encryptionTrusted.image)
|
||||
return RoomScreenCoordinator(parameters: params)
|
||||
let parameters = RoomScreenCoordinatorParameters(timelineController: MockRoomTimelineController(),
|
||||
roomName: "Some room name",
|
||||
roomAvatar: Asset.Images.appLogo.image,
|
||||
roomEncryptionBadge: Asset.Images.encryptionTrusted.image)
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
case .sessionVerification:
|
||||
let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: MockSessionVerificationControllerProxy())
|
||||
return SessionVerificationCoordinator(parameters: parameters)
|
||||
}
|
||||
}()
|
||||
|
||||
init(id: UITestScreenIdentifier) {
|
||||
self.id = id
|
||||
}
|
||||
}
|
||||
|
||||
103
UITests/Sources/SessionVerificationUITests.swift
Normal file
103
UITests/Sources/SessionVerificationUITests.swift
Normal file
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// 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 XCTest
|
||||
import ElementX
|
||||
|
||||
class SessionVerificationUITests: XCTestCase {
|
||||
|
||||
func testChallengeMatches() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.sessionVerification)
|
||||
|
||||
XCTAssert(app.navigationBars["Verify this session"].exists)
|
||||
|
||||
XCTAssert(app.buttons["startButton"].exists)
|
||||
XCTAssert(app.buttons["dismissButton"].exists)
|
||||
XCTAssert(app.staticTexts["titleLabel"].exists)
|
||||
|
||||
app.buttons["startButton"].tap()
|
||||
|
||||
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists)
|
||||
XCTAssert(app.buttons["cancelButton"].exists)
|
||||
|
||||
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0))
|
||||
XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0))
|
||||
XCTAssert(app.buttons["cancelButton"].waitForExistence(timeout: 5.0))
|
||||
|
||||
app.buttons["challengeAcceptButton"].tap()
|
||||
|
||||
XCTAssert(app.activityIndicators["acceptingChallengeProgressView"].exists)
|
||||
XCTAssert(app.buttons["cancelButton"].exists)
|
||||
|
||||
XCTAssert(app.images["sessionVerificationSucceededIcon"].waitForExistence(timeout: 5.0))
|
||||
|
||||
XCTAssert(app.buttons["dismissButton"].exists)
|
||||
app.buttons["dismissButton"].tap()
|
||||
}
|
||||
|
||||
func testChallengeDoesNotMatch() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.sessionVerification)
|
||||
|
||||
XCTAssert(app.navigationBars["Verify this session"].exists)
|
||||
|
||||
XCTAssert(app.buttons["startButton"].exists)
|
||||
XCTAssert(app.buttons["dismissButton"].exists)
|
||||
XCTAssert(app.staticTexts["titleLabel"].exists)
|
||||
|
||||
app.buttons["startButton"].tap()
|
||||
|
||||
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists)
|
||||
XCTAssert(app.buttons["cancelButton"].exists)
|
||||
|
||||
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0))
|
||||
XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0))
|
||||
XCTAssert(app.buttons["cancelButton"].waitForExistence(timeout: 5.0))
|
||||
|
||||
app.buttons["challengeDeclineButton"].tap()
|
||||
|
||||
XCTAssert(app.images["sessionVerificationFailedIcon"].exists)
|
||||
XCTAssert(app.buttons["restartButton"].exists)
|
||||
|
||||
XCTAssert(app.buttons["dismissButton"].exists)
|
||||
app.buttons["dismissButton"].tap()
|
||||
}
|
||||
|
||||
func testSessionVerificationCancelation() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.sessionVerification)
|
||||
|
||||
XCTAssert(app.navigationBars["Verify this session"].exists)
|
||||
|
||||
XCTAssert(app.buttons["startButton"].exists)
|
||||
XCTAssert(app.buttons["dismissButton"].exists)
|
||||
XCTAssert(app.staticTexts["titleLabel"].exists)
|
||||
|
||||
app.buttons["startButton"].tap()
|
||||
|
||||
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists)
|
||||
XCTAssert(app.buttons["cancelButton"].exists)
|
||||
|
||||
app.buttons["cancelButton"].tap()
|
||||
|
||||
XCTAssert(app.images["sessionVerificationFailedIcon"].exists)
|
||||
XCTAssert(app.buttons["restartButton"].exists)
|
||||
|
||||
XCTAssert(app.buttons["dismissButton"].exists)
|
||||
app.buttons["dismissButton"].tap()
|
||||
}
|
||||
}
|
||||
94
UnitTests/Sources/SessionVerificationStateMachineTests.swift
Normal file
94
UnitTests/Sources/SessionVerificationStateMachineTests.swift
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// SessionVerificationStateMachineTests.swift
|
||||
// UnitTests
|
||||
//
|
||||
// Created by Stefan Ceriu on 28/06/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class SessionVerificationStateMachineTests: XCTestCase {
|
||||
private var stateMachine: SessionVerificationStateMachine!
|
||||
|
||||
@MainActor
|
||||
override func setUpWithError() throws {
|
||||
stateMachine = SessionVerificationStateMachine()
|
||||
}
|
||||
|
||||
func testAcceptChallenge() {
|
||||
XCTAssertEqual(stateMachine.state, .initial)
|
||||
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
||||
|
||||
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
|
||||
stateMachine.processEvent(.acceptChallenge)
|
||||
XCTAssertEqual(stateMachine.state, .acceptingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
|
||||
stateMachine.processEvent(.didAcceptChallenge)
|
||||
XCTAssertEqual(stateMachine.state, .verified)
|
||||
}
|
||||
|
||||
func testDeclineChallenge() {
|
||||
XCTAssertEqual(stateMachine.state, .initial)
|
||||
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
||||
|
||||
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
|
||||
stateMachine.processEvent(.declineChallenge)
|
||||
XCTAssertEqual(stateMachine.state, .decliningChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
|
||||
stateMachine.processEvent(.didCancel)
|
||||
XCTAssertEqual(stateMachine.state, .cancelled)
|
||||
|
||||
stateMachine.processEvent(.restart)
|
||||
XCTAssertEqual(stateMachine.state, .initial)
|
||||
}
|
||||
|
||||
func testCancellation() {
|
||||
XCTAssertEqual(stateMachine.state, .initial)
|
||||
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
||||
|
||||
stateMachine.processEvent(.cancel)
|
||||
XCTAssertEqual(stateMachine.state, .cancelling)
|
||||
|
||||
stateMachine.processEvent(.didCancel)
|
||||
XCTAssertEqual(stateMachine.state, .cancelled)
|
||||
|
||||
// This duplication is intentional
|
||||
stateMachine.processEvent(.didCancel)
|
||||
XCTAssertEqual(stateMachine.state, .cancelled)
|
||||
|
||||
stateMachine.processEvent(.restart)
|
||||
XCTAssertEqual(stateMachine.state, .initial)
|
||||
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
||||
|
||||
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
|
||||
stateMachine.processEvent(.cancel)
|
||||
XCTAssertEqual(stateMachine.state, .cancelling)
|
||||
|
||||
stateMachine.processEvent(.didCancel)
|
||||
XCTAssertEqual(stateMachine.state, .cancelled)
|
||||
|
||||
stateMachine.processEvent(.restart)
|
||||
XCTAssertEqual(stateMachine.state, .initial)
|
||||
|
||||
stateMachine.processEvent(.restart)
|
||||
XCTAssertEqual(stateMachine.state, .initial)
|
||||
}
|
||||
}
|
||||
154
UnitTests/Sources/SessionVerificationViewModelTests.swift
Normal file
154
UnitTests/Sources/SessionVerificationViewModelTests.swift
Normal file
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// 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 XCTest
|
||||
import Combine
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class SessionVerificationViewModelTests: XCTestCase {
|
||||
|
||||
var viewModel: SessionVerificationViewModelProtocol!
|
||||
var context: SessionVerificationViewModelType.Context!
|
||||
var sessionVerificationController: SessionVerificationControllerProxyProtocol!
|
||||
|
||||
@MainActor
|
||||
override func setUpWithError() throws {
|
||||
sessionVerificationController = MockSessionVerificationControllerProxy()
|
||||
viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: sessionVerificationController)
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testRequestVerification() async {
|
||||
XCTAssertEqual(context.viewState.verificationState, .initial)
|
||||
|
||||
context.send(viewAction: .start)
|
||||
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .requestingVerification)
|
||||
}
|
||||
|
||||
func testVerificationCancellation() async {
|
||||
XCTAssertEqual(context.viewState.verificationState, .initial)
|
||||
|
||||
context.send(viewAction: .start)
|
||||
|
||||
context.send(viewAction: .cancel)
|
||||
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .cancelling)
|
||||
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .cancelled)
|
||||
|
||||
context.send(viewAction: .restart)
|
||||
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .initial)
|
||||
}
|
||||
|
||||
func testReceiveChallenge() {
|
||||
setupChallengeReceived()
|
||||
}
|
||||
|
||||
func testAcceptChallenge() {
|
||||
|
||||
setupChallengeReceived()
|
||||
|
||||
let waitForAcceptance = XCTestExpectation(description: "Wait for acceptance")
|
||||
|
||||
let cancellable = sessionVerificationController.callbacks
|
||||
.debounce(for: .seconds(2.0), scheduler: DispatchQueue.main)
|
||||
.sink { callback in
|
||||
switch callback {
|
||||
case .finished:
|
||||
waitForAcceptance.fulfill()
|
||||
default:
|
||||
XCTFail("Unexpected session verification controller callback")
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
context.send(viewAction: .accept)
|
||||
|
||||
wait(for: [waitForAcceptance], timeout: 10.0)
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .verified)
|
||||
}
|
||||
|
||||
func testDeclineChallenge() {
|
||||
|
||||
setupChallengeReceived()
|
||||
|
||||
let expectation = XCTestExpectation(description: "Wait for cancellation")
|
||||
|
||||
let cancellable = sessionVerificationController.callbacks
|
||||
.debounce(for: .seconds(2.0), scheduler: DispatchQueue.main)
|
||||
.sink { callback in
|
||||
switch callback {
|
||||
case .cancelled:
|
||||
expectation.fulfill()
|
||||
default:
|
||||
XCTFail("Unexpected session verification controller callback")
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
context.send(viewAction: .decline)
|
||||
|
||||
wait(for: [expectation], timeout: 10.0)
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .cancelled)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupChallengeReceived() {
|
||||
let expectation = XCTestExpectation(description: "Wait for challenge")
|
||||
|
||||
let cancellable = sessionVerificationController.callbacks
|
||||
.debounce(for: .seconds(2.0), scheduler: DispatchQueue.main)
|
||||
.sink { callback in
|
||||
switch callback {
|
||||
case .receivedVerificationData:
|
||||
expectation.fulfill()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
context.send(viewAction: .start)
|
||||
|
||||
wait(for: [expectation], timeout: 10.0)
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ packages:
|
||||
MatrixRustSDK:
|
||||
url: https://github.com/matrix-org/matrix-rust-components-swift.git
|
||||
exactVersion: 1.0.11-alpha
|
||||
# path: ../matrix-rust-components-swift
|
||||
DesignKit:
|
||||
path: ./
|
||||
DTCoreText:
|
||||
|
||||
Reference in New Issue
Block a user