mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:11:45 +00:00
#40: Add basic AuthenticationService and missing UI tests.
* Add MockAuthenticationService and ServerSelectionUITests. * Add tests covering the Authentication flow.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 52;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -73,6 +73,7 @@
|
|||||||
2FE4EEF780553B25A446BBFB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */; };
|
2FE4EEF780553B25A446BBFB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */; };
|
||||||
30122AB3484AC6C3A7F6A717 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */; };
|
30122AB3484AC6C3A7F6A717 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */; };
|
||||||
3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; };
|
3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; };
|
||||||
|
32FC143630CE22A9E403370B /* MockAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA38899517F08FE2AF34EB45 /* MockAuthenticationService.swift */; };
|
||||||
33B4E59D408AE6E02323EE41 /* NoticeRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDA364DFFC3AC71C4771251 /* NoticeRoomMessage.swift */; };
|
33B4E59D408AE6E02323EE41 /* NoticeRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDA364DFFC3AC71C4771251 /* NoticeRoomMessage.swift */; };
|
||||||
344AF4CBB6D8786214878642 /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */; };
|
344AF4CBB6D8786214878642 /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */; };
|
||||||
34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */; };
|
34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */; };
|
||||||
@@ -115,6 +116,7 @@
|
|||||||
5375902175B2FEA2949D7D74 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */; };
|
5375902175B2FEA2949D7D74 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */; };
|
||||||
53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235E1CE00A6D989D7DB6D47 /* RectangleToastView.swift */; };
|
53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235E1CE00A6D989D7DB6D47 /* RectangleToastView.swift */; };
|
||||||
541374590CA7E8318BD480FD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 187853A7E643995EE49FAD43 /* Localizable.stringsdict */; };
|
541374590CA7E8318BD480FD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 187853A7E643995EE49FAD43 /* Localizable.stringsdict */; };
|
||||||
|
56DACDD379A86A1F5DEFE7BE /* AuthenticationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */; };
|
||||||
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; };
|
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; };
|
||||||
59C41313AED7566C3AC51163 /* RoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */; };
|
59C41313AED7566C3AC51163 /* RoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */; };
|
||||||
5B2C4C17888FC095ED6880B2 /* SplashViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 48971F1FFD7FC5C466889FC7 /* SplashViewController.xib */; };
|
5B2C4C17888FC095ED6880B2 /* SplashViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 48971F1FFD7FC5C466889FC7 /* SplashViewController.xib */; };
|
||||||
@@ -133,6 +135,7 @@
|
|||||||
684BDE198AE5AA1392288A73 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CE6D4FF64C9A3C18619224 /* SplashScreen.swift */; };
|
684BDE198AE5AA1392288A73 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CE6D4FF64C9A3C18619224 /* SplashScreen.swift */; };
|
||||||
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
|
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
|
||||||
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
||||||
|
6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A267106B9585D3D0CFC0D /* ClientError.swift */; };
|
||||||
6A367F3D7A437A79B7D9A31C /* FullscreenLoadingViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4112D04077F6709C5CA0A13E /* FullscreenLoadingViewPresenter.swift */; };
|
6A367F3D7A437A79B7D9A31C /* FullscreenLoadingViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4112D04077F6709C5CA0A13E /* FullscreenLoadingViewPresenter.swift */; };
|
||||||
6AC1DC1EAD9F7568360DA1BA /* ServerSelectionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */; };
|
6AC1DC1EAD9F7568360DA1BA /* ServerSelectionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */; };
|
||||||
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
|
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
|
||||||
@@ -151,6 +154,7 @@
|
|||||||
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; };
|
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; };
|
||||||
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */; };
|
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */; };
|
||||||
77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */; };
|
77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */; };
|
||||||
|
77FACC29F98FE2E65BBB6A5F /* ServerSelectionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */; };
|
||||||
78B71D53C1FC55FB7A9B75F0 /* RoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B0C97D2F560BCB72BE73B1 /* RoomTimelineController.swift */; };
|
78B71D53C1FC55FB7A9B75F0 /* RoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B0C97D2F560BCB72BE73B1 /* RoomTimelineController.swift */; };
|
||||||
7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */; };
|
7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */; };
|
||||||
79A6E08ADE6E7C460A8A17A5 /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */; };
|
79A6E08ADE6E7C460A8A17A5 /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */; };
|
||||||
@@ -187,6 +191,7 @@
|
|||||||
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; };
|
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; };
|
||||||
9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1027BB9A852F445B7623897F /* ElementSettings.swift */; };
|
9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1027BB9A852F445B7623897F /* ElementSettings.swift */; };
|
||||||
978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */; };
|
978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */; };
|
||||||
|
9847B056C1A216C314D21E68 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A1AB5A84D843B6AC8D5F1E /* AuthenticationService.swift */; };
|
||||||
989029A28C9E2F828AD6658A /* AppIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */; };
|
989029A28C9E2F828AD6658A /* AppIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */; };
|
||||||
992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; };
|
992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; };
|
||||||
99ED42B8F8D6BFB1DBCF4C45 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
|
99ED42B8F8D6BFB1DBCF4C45 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
|
||||||
@@ -214,6 +219,7 @@
|
|||||||
A8EC7C9D886244DAE9433E37 /* SessionVerificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C18FAAD59AE7F1462D817E /* SessionVerificationViewModel.swift */; };
|
A8EC7C9D886244DAE9433E37 /* SessionVerificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C18FAAD59AE7F1462D817E /* SessionVerificationViewModel.swift */; };
|
||||||
AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9760103CF316DF68698BCFE6 /* LaunchScreen.storyboard */; };
|
AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9760103CF316DF68698BCFE6 /* LaunchScreen.storyboard */; };
|
||||||
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; };
|
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; };
|
||||||
|
ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */; };
|
||||||
B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; };
|
B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; };
|
||||||
B245583C63F8F90357B87FAE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
|
B245583C63F8F90357B87FAE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
|
||||||
B3357B00F1AA930E54F76609 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
B3357B00F1AA930E54F76609 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
||||||
@@ -253,6 +259,7 @@
|
|||||||
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; };
|
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; };
|
||||||
D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; };
|
D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; };
|
||||||
D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; };
|
D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; };
|
||||||
|
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; };
|
||||||
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */; };
|
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */; };
|
||||||
D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */; };
|
D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */; };
|
||||||
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC95CD75B688E946438165 /* Coordinator.swift */; };
|
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC95CD75B688E946438165 /* Coordinator.swift */; };
|
||||||
@@ -268,10 +275,11 @@
|
|||||||
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; };
|
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; };
|
||||||
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; };
|
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; };
|
||||||
EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */; };
|
EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */; };
|
||||||
|
EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; };
|
||||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
||||||
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; };
|
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; };
|
||||||
EF99A92701E401C4CD5ADC50 /* SplashScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */; };
|
EF99A92701E401C4CD5ADC50 /* SplashScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */; };
|
||||||
F03E16ED043C62FED5A07AE0 /* MatrixEntitityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */; };
|
F03E16ED043C62FED5A07AE0 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B81C8227BBEA95CCE86037 /* MatrixEntityRegex.swift */; };
|
||||||
F040ABFEB0A2B142D948BA12 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
F040ABFEB0A2B142D948BA12 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
||||||
F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */; };
|
F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */; };
|
||||||
F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */; };
|
F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */; };
|
||||||
@@ -309,6 +317,7 @@
|
|||||||
02A07FF019724B6ACEA73076 /* szl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = szl; path = szl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
02A07FF019724B6ACEA73076 /* szl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = szl; path = szl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
04BBC9E08250EF92ADE89CFD /* sr-Latn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sr-Latn"; path = "sr-Latn.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
04BBC9E08250EF92ADE89CFD /* sr-Latn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sr-Latn"; path = "sr-Latn.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
04E1273CC3BC3E471AF87BE5 /* UserIndicatorQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorQueueTests.swift; sourceTree = "<group>"; };
|
04E1273CC3BC3E471AF87BE5 /* UserIndicatorQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorQueueTests.swift; sourceTree = "<group>"; };
|
||||||
|
054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionUITests.swift; sourceTree = "<group>"; };
|
||||||
057B747CF045D3C6C30EAB2C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
057B747CF045D3C6C30EAB2C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = "<group>"; };
|
086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
08F64963396A6A23538EFCEC /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = is; path = is.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
08F64963396A6A23538EFCEC /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = is; path = is.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
@@ -392,6 +401,7 @@
|
|||||||
3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
|
3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
|
||||||
3DD6E7C1D8B53F47789778CD /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3DD6E7C1D8B53F47789778CD /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskService.swift; sourceTree = "<group>"; };
|
3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskService.swift; sourceTree = "<group>"; };
|
||||||
|
3F40F48279322E504153AB0D /* MockClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockClientProxy.swift; sourceTree = "<group>"; };
|
||||||
3F87116470221880017CF522 /* BuildSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSettings.swift; sourceTree = "<group>"; };
|
3F87116470221880017CF522 /* BuildSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSettings.swift; sourceTree = "<group>"; };
|
||||||
3FAA6438B00FDB130F404E31 /* UserIndicatorStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorStore.swift; sourceTree = "<group>"; };
|
3FAA6438B00FDB130F404E31 /* UserIndicatorStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorStore.swift; sourceTree = "<group>"; };
|
||||||
3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = "<group>"; };
|
3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = "<group>"; };
|
||||||
@@ -443,7 +453,9 @@
|
|||||||
5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStoreProtocol.swift; sourceTree = "<group>"; };
|
5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStoreProtocol.swift; sourceTree = "<group>"; };
|
||||||
5CB7F9D6FC121204D59E18DF /* Presentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = "<group>"; };
|
5CB7F9D6FC121204D59E18DF /* Presentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = "<group>"; };
|
||||||
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
|
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
|
||||||
|
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinatorUITests.swift; sourceTree = "<group>"; };
|
||||||
5D8EA85D4F10D7445BB6368A /* UserIndicatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorTests.swift; sourceTree = "<group>"; };
|
5D8EA85D4F10D7445BB6368A /* UserIndicatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorTests.swift; sourceTree = "<group>"; };
|
||||||
|
5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProtocol.swift; sourceTree = "<group>"; };
|
||||||
5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
|
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
|
||||||
5F77E8010D41AA3F5F9A1FCA /* NavigationModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = "<group>"; };
|
5F77E8010D41AA3F5F9A1FCA /* NavigationModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = "<group>"; };
|
||||||
@@ -551,6 +563,7 @@
|
|||||||
A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = "<group>"; };
|
A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = "<group>"; };
|
||||||
A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
|
A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
|
||||||
A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; };
|
A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserSession.swift; sourceTree = "<group>"; };
|
||||||
A64F0DB78E0AC23C91AD89EF /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = mk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
A64F0DB78E0AC23C91AD89EF /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = mk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
||||||
A72232816DCE2B76D48E1367 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
A72232816DCE2B76D48E1367 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
@@ -624,6 +637,7 @@
|
|||||||
CF47564C584F614B7287F3EB /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = "<group>"; };
|
CF47564C584F614B7287F3EB /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = "<group>"; };
|
||||||
CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementNavigationController.swift; sourceTree = "<group>"; };
|
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>"; };
|
CF847B3C1873B8E81CEE7FAC /* SplashScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
|
||||||
D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.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>"; };
|
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>"; };
|
D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = "<group>"; };
|
||||||
@@ -634,6 +648,7 @@
|
|||||||
D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
|
||||||
|
DA38899517F08FE2AF34EB45 /* MockAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationService.swift; sourceTree = "<group>"; };
|
||||||
DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateCoordinator.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>"; };
|
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>"; };
|
DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenModels.swift; sourceTree = "<group>"; };
|
||||||
@@ -670,12 +685,13 @@
|
|||||||
F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBugReportService.swift; sourceTree = "<group>"; };
|
F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBugReportService.swift; sourceTree = "<group>"; };
|
||||||
F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
F2D58333B377888012740101 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
|
F2D58333B377888012740101 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
F3A1AB5A84D843B6AC8D5F1E /* AuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = "<group>"; };
|
||||||
F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = "<group>"; };
|
F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = "<group>"; };
|
||||||
F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
F7B81C8227BBEA95CCE86037 /* MatrixEntityRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegex.swift; sourceTree = "<group>"; };
|
||||||
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.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>"; };
|
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>"; };
|
FAB10E673916D2B8D21FD197 /* TemplateModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModels.swift; sourceTree = "<group>"; };
|
||||||
@@ -753,6 +769,7 @@
|
|||||||
0787F81684E503024BD0C051 /* Services */ = {
|
0787F81684E503024BD0C051 /* Services */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
AAFDD509929A0CCF8BCE51EB /* Authentication */,
|
||||||
EBBEB5471737E9D116DF4738 /* Background */,
|
EBBEB5471737E9D116DF4738 /* Background */,
|
||||||
0ED3F5C21537519389C07644 /* BugReport */,
|
0ED3F5C21537519389C07644 /* BugReport */,
|
||||||
8039515BAA53B7C3275AC64A /* Client */,
|
8039515BAA53B7C3275AC64A /* Client */,
|
||||||
@@ -825,6 +842,13 @@
|
|||||||
path = Resources;
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
3180C73BA7B8F5F7447C99B0 /* React */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
path = React;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
328DD5DA1281F758B72006C7 /* Views */ = {
|
328DD5DA1281F758B72006C7 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1159,8 +1183,10 @@
|
|||||||
8039515BAA53B7C3275AC64A /* Client */ = {
|
8039515BAA53B7C3275AC64A /* Client */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D09A267106B9585D3D0CFC0D /* ClientError.swift */,
|
||||||
18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */,
|
18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */,
|
||||||
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */,
|
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */,
|
||||||
|
3F40F48279322E504153AB0D /* MockClientProxy.swift */,
|
||||||
);
|
);
|
||||||
path = Client;
|
path = Client;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1176,6 +1202,7 @@
|
|||||||
82D5AD3EAE3A5C1068A44A88 /* Session */ = {
|
82D5AD3EAE3A5C1068A44A88 /* Session */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */,
|
||||||
6E5E9C044BEB7C70B1378E91 /* UserSession.swift */,
|
6E5E9C044BEB7C70B1378E91 /* UserSession.swift */,
|
||||||
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */,
|
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */,
|
||||||
);
|
);
|
||||||
@@ -1232,10 +1259,12 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7D0CBC76C80E04345E11F2DB /* Application.swift */,
|
7D0CBC76C80E04345E11F2DB /* Application.swift */,
|
||||||
|
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */,
|
||||||
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
|
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
|
||||||
4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */,
|
4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */,
|
||||||
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */,
|
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */,
|
||||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */,
|
086B997409328F091EBA43CE /* RoomScreenUITests.swift */,
|
||||||
|
054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */,
|
||||||
6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */,
|
6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */,
|
||||||
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */,
|
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */,
|
||||||
325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */,
|
325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */,
|
||||||
@@ -1339,6 +1368,16 @@
|
|||||||
path = UnitTests;
|
path = UnitTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
AAFDD509929A0CCF8BCE51EB /* Authentication */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F3A1AB5A84D843B6AC8D5F1E /* AuthenticationService.swift */,
|
||||||
|
5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */,
|
||||||
|
DA38899517F08FE2AF34EB45 /* MockAuthenticationService.swift */,
|
||||||
|
);
|
||||||
|
path = Authentication;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
AD5FCF9340D670C526AD17E4 /* UI */ = {
|
AD5FCF9340D670C526AD17E4 /* UI */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1404,7 +1443,7 @@
|
|||||||
CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */,
|
CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */,
|
||||||
1027BB9A852F445B7623897F /* ElementSettings.swift */,
|
1027BB9A852F445B7623897F /* ElementSettings.swift */,
|
||||||
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
||||||
F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */,
|
F7B81C8227BBEA95CCE86037 /* MatrixEntityRegex.swift */,
|
||||||
44BBB96FAA2F0D53C507396B /* Extensions */,
|
44BBB96FAA2F0D53C507396B /* Extensions */,
|
||||||
8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */,
|
8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */,
|
||||||
06501F0E978B2D5C92771DC7 /* Logging */,
|
06501F0E978B2D5C92771DC7 /* Logging */,
|
||||||
@@ -1454,6 +1493,7 @@
|
|||||||
E74CD7681375AD2EAA34D66B /* Authentication */,
|
E74CD7681375AD2EAA34D66B /* Authentication */,
|
||||||
4009BE2E791C16AC6EE39A7E /* BugReport */,
|
4009BE2E791C16AC6EE39A7E /* BugReport */,
|
||||||
B53CA9BECD3F97805E1432D0 /* HomeScreen */,
|
B53CA9BECD3F97805E1432D0 /* HomeScreen */,
|
||||||
|
3180C73BA7B8F5F7447C99B0 /* React */,
|
||||||
679E9837ECA8D6776079D16E /* RoomScreen */,
|
679E9837ECA8D6776079D16E /* RoomScreen */,
|
||||||
D958761758AA1110476DE6A3 /* SessionVerification */,
|
D958761758AA1110476DE6A3 /* SessionVerification */,
|
||||||
70B74A432C241E56A7ACE610 /* Settings */,
|
70B74A432C241E56A7ACE610 /* Settings */,
|
||||||
@@ -1664,7 +1704,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 7AE41FCCF9D1352E2770D1F9 /* Build configuration list for PBXProject "ElementX" */;
|
buildConfigurationList = 7AE41FCCF9D1352E2770D1F9 /* Build configuration list for PBXProject "ElementX" */;
|
||||||
compatibilityVersion = "Xcode 10.0";
|
compatibilityVersion = "Xcode 11.0";
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
@@ -1897,6 +1937,8 @@
|
|||||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */,
|
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */,
|
||||||
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */,
|
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */,
|
||||||
B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */,
|
B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */,
|
||||||
|
9847B056C1A216C314D21E68 /* AuthenticationService.swift in Sources */,
|
||||||
|
56DACDD379A86A1F5DEFE7BE /* AuthenticationServiceProtocol.swift in Sources */,
|
||||||
E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */,
|
E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */,
|
||||||
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */,
|
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */,
|
||||||
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */,
|
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */,
|
||||||
@@ -1911,6 +1953,7 @@
|
|||||||
187E18F21EF4DA244E436E58 /* BugReportViewModelProtocol.swift in Sources */,
|
187E18F21EF4DA244E436E58 /* BugReportViewModelProtocol.swift in Sources */,
|
||||||
05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */,
|
05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */,
|
||||||
E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */,
|
E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */,
|
||||||
|
6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */,
|
||||||
1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */,
|
1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */,
|
||||||
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */,
|
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */,
|
||||||
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */,
|
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */,
|
||||||
@@ -1954,7 +1997,7 @@
|
|||||||
2F30EFEB7BD39242D1AD96F3 /* LoginViewModelProtocol.swift in Sources */,
|
2F30EFEB7BD39242D1AD96F3 /* LoginViewModelProtocol.swift in Sources */,
|
||||||
B94368839BDB69172E28E245 /* MXLog.swift in Sources */,
|
B94368839BDB69172E28E245 /* MXLog.swift in Sources */,
|
||||||
BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */,
|
BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */,
|
||||||
F03E16ED043C62FED5A07AE0 /* MatrixEntitityRegex.swift in Sources */,
|
F03E16ED043C62FED5A07AE0 /* MatrixEntityRegex.swift in Sources */,
|
||||||
EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */,
|
EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */,
|
||||||
7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */,
|
7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */,
|
||||||
62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */,
|
62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */,
|
||||||
@@ -1963,13 +2006,16 @@
|
|||||||
A5EC21A071F58FC1229C20D0 /* MemberDetailsProviderProtocol.swift in Sources */,
|
A5EC21A071F58FC1229C20D0 /* MemberDetailsProviderProtocol.swift in Sources */,
|
||||||
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */,
|
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */,
|
||||||
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */,
|
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */,
|
||||||
|
32FC143630CE22A9E403370B /* MockAuthenticationService.swift in Sources */,
|
||||||
28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */,
|
28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */,
|
||||||
|
EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */,
|
||||||
67E391A2E00709FB41903B36 /* MockMediaProvider.swift in Sources */,
|
67E391A2E00709FB41903B36 /* MockMediaProvider.swift in Sources */,
|
||||||
51DB67C5B5BC68B0A6FF54D4 /* MockRoomProxy.swift in Sources */,
|
51DB67C5B5BC68B0A6FF54D4 /* MockRoomProxy.swift in Sources */,
|
||||||
29AEE68A604940180AB9EBFF /* MockRoomSummary.swift in Sources */,
|
29AEE68A604940180AB9EBFF /* MockRoomSummary.swift in Sources */,
|
||||||
E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */,
|
E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */,
|
||||||
9BE7A9CF6C593251D734B461 /* MockServerSelectionScreenState.swift in Sources */,
|
9BE7A9CF6C593251D734B461 /* MockServerSelectionScreenState.swift in Sources */,
|
||||||
D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */,
|
D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */,
|
||||||
|
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */,
|
||||||
4ED453A61AF45EBE18D8BC69 /* NavigationModule.swift in Sources */,
|
4ED453A61AF45EBE18D8BC69 /* NavigationModule.swift in Sources */,
|
||||||
22DADD537401E79D66132134 /* NavigationRouter.swift in Sources */,
|
22DADD537401E79D66132134 /* NavigationRouter.swift in Sources */,
|
||||||
12F70C493FB69F4D7E9A37EA /* NavigationRouterStore.swift in Sources */,
|
12F70C493FB69F4D7E9A37EA /* NavigationRouterStore.swift in Sources */,
|
||||||
@@ -2091,12 +2137,14 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */,
|
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */,
|
||||||
|
ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */,
|
||||||
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */,
|
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */,
|
||||||
499A26EB06C97E48C27A2DB9 /* BuildSettings.swift in Sources */,
|
499A26EB06C97E48C27A2DB9 /* BuildSettings.swift in Sources */,
|
||||||
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */,
|
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */,
|
||||||
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */,
|
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */,
|
||||||
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */,
|
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */,
|
||||||
2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */,
|
2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */,
|
||||||
|
77FACC29F98FE2E65BBB6A5F /* ServerSelectionUITests.swift in Sources */,
|
||||||
05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */,
|
05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */,
|
||||||
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */,
|
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */,
|
||||||
A00DFC1DD3567B1EDC9F8D16 /* SplashScreenUITests.swift in Sources */,
|
A00DFC1DD3567B1EDC9F8D16 /* SplashScreenUITests.swift in Sources */,
|
||||||
|
|||||||
@@ -89,23 +89,15 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
|
|
||||||
// MARK: - AuthenticationCoordinatorDelegate
|
// MARK: - AuthenticationCoordinatorDelegate
|
||||||
|
|
||||||
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator) {
|
|
||||||
stateMachine.processEvent(.attemptedSignIn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSessionProtocol) {
|
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSessionProtocol) {
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
remove(childCoordinator: authenticationCoordinator)
|
remove(childCoordinator: authenticationCoordinator)
|
||||||
stateMachine.processEvent(.succeededSigningIn)
|
stateMachine.processEvent(.succeededSigningIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didFailWithError error: AuthenticationCoordinatorError) {
|
|
||||||
stateMachine.processEvent(.failedSigningIn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
// swiftlint:disable cyclomatic_complexity function_body_length
|
// swiftlint:disable cyclomatic_complexity
|
||||||
private func setupStateMachine() {
|
private func setupStateMachine() {
|
||||||
stateMachine.addTransitionHandler { [weak self] context in
|
stateMachine.addTransitionHandler { [weak self] context in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
@@ -113,13 +105,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
switch (context.fromState, context.event, context.toState) {
|
switch (context.fromState, context.event, context.toState) {
|
||||||
case (.initial, .startWithAuthentication, .signedOut):
|
case (.initial, .startWithAuthentication, .signedOut):
|
||||||
self.startAuthentication()
|
self.startAuthentication()
|
||||||
case (.signedOut, .attemptedSignIn, .signingIn):
|
case (.signedOut, .succeededSigningIn, .homeScreen):
|
||||||
self.showLoadingIndicator()
|
|
||||||
case (.signingIn, .failedSigningIn, .signedOut):
|
|
||||||
self.hideLoadingIndicator()
|
|
||||||
self.showLoginErrorToast()
|
|
||||||
case (.signingIn, .succeededSigningIn, .homeScreen):
|
|
||||||
self.hideLoadingIndicator()
|
|
||||||
self.presentHomeScreen()
|
self.presentHomeScreen()
|
||||||
|
|
||||||
case (.initial, .startWithExistingSession, .restoringSession):
|
case (.initial, .startWithExistingSession, .restoringSession):
|
||||||
@@ -179,7 +165,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func startAuthentication() {
|
private func startAuthentication() {
|
||||||
let coordinator = AuthenticationCoordinator(userSessionStore: userSessionStore,
|
let authenticationService = AuthenticationService(userSessionStore: userSessionStore)
|
||||||
|
let coordinator = AuthenticationCoordinator(authenticationService: authenticationService,
|
||||||
navigationRouter: navigationRouter)
|
navigationRouter: navigationRouter)
|
||||||
coordinator.delegate = self
|
coordinator.delegate = self
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ class AppCoordinatorStateMachine {
|
|||||||
case initial
|
case initial
|
||||||
/// Showing the login screen
|
/// Showing the login screen
|
||||||
case signedOut
|
case signedOut
|
||||||
/// Processing sign in request
|
|
||||||
case signingIn
|
|
||||||
/// Opening an existing session.
|
/// Opening an existing session.
|
||||||
case restoringSession
|
case restoringSession
|
||||||
/// Showing the home screen
|
/// Showing the home screen
|
||||||
@@ -41,12 +39,8 @@ class AppCoordinatorStateMachine {
|
|||||||
enum Event: EventType {
|
enum Event: EventType {
|
||||||
/// Start the `AppCoordinator` by showing authentication.
|
/// Start the `AppCoordinator` by showing authentication.
|
||||||
case startWithAuthentication
|
case startWithAuthentication
|
||||||
/// A sign in request has been started
|
|
||||||
case attemptedSignIn
|
|
||||||
/// Signing in succeeded
|
/// Signing in succeeded
|
||||||
case succeededSigningIn
|
case succeededSigningIn
|
||||||
/// Signing in failed
|
|
||||||
case failedSigningIn
|
|
||||||
|
|
||||||
/// Start the `AppCoordinator` by restoring an existing account.
|
/// Start the `AppCoordinator` by restoring an existing account.
|
||||||
case startWithExistingSession
|
case startWithExistingSession
|
||||||
@@ -84,9 +78,7 @@ class AppCoordinatorStateMachine {
|
|||||||
init() {
|
init() {
|
||||||
stateMachine = StateMachine(state: .initial) { machine in
|
stateMachine = StateMachine(state: .initial) { machine in
|
||||||
machine.addRoutes(event: .startWithAuthentication, transitions: [ .initial => .signedOut ])
|
machine.addRoutes(event: .startWithAuthentication, transitions: [ .initial => .signedOut ])
|
||||||
machine.addRoutes(event: .attemptedSignIn, transitions: [ .signedOut => .signingIn ])
|
machine.addRoutes(event: .succeededSigningIn, transitions: [ .signedOut => .homeScreen ])
|
||||||
machine.addRoutes(event: .succeededSigningIn, transitions: [ .signingIn => .homeScreen ])
|
|
||||||
machine.addRoutes(event: .failedSigningIn, transitions: [ .signingIn => .signedOut ])
|
|
||||||
|
|
||||||
machine.addRoutes(event: .startWithExistingSession, transitions: [ .initial => .restoringSession ])
|
machine.addRoutes(event: .startWithExistingSession, transitions: [ .initial => .restoringSession ])
|
||||||
machine.addRoutes(event: .succeededRestoringSession, transitions: [ .restoringSession => .homeScreen ])
|
machine.addRoutes(event: .succeededRestoringSession, transitions: [ .restoringSession => .homeScreen ])
|
||||||
|
|||||||
@@ -31,4 +31,12 @@ extension String {
|
|||||||
|
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether or not the string is a Matrix user ID.
|
||||||
|
var isMatrixUserID: Bool {
|
||||||
|
let range = NSRange(location: 0, length: count)
|
||||||
|
|
||||||
|
let detector = try? NSRegularExpression(pattern: MatrixEntityRegex.userId.rawValue, options: .caseInsensitive)
|
||||||
|
return detector?.numberOfMatches(in: self, range: range) ?? 0 == 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,28 +6,18 @@
|
|||||||
// Copyright © 2022 Element. All rights reserved.
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import UIKit
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
enum AuthenticationCoordinatorError: Error {
|
|
||||||
case failedLoggingIn
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol AuthenticationCoordinatorDelegate: AnyObject {
|
protocol AuthenticationCoordinatorDelegate: AnyObject {
|
||||||
|
|
||||||
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator)
|
|
||||||
|
|
||||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
|
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
|
||||||
didLoginWithSession userSession: UserSessionProtocol)
|
didLoginWithSession userSession: UserSessionProtocol)
|
||||||
|
|
||||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
|
|
||||||
didFailWithError error: AuthenticationCoordinatorError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthenticationCoordinator: Coordinator {
|
class AuthenticationCoordinator: Coordinator, Presentable {
|
||||||
|
|
||||||
private let userSessionStore: UserSessionStoreProtocol
|
private let authenticationService: AuthenticationServiceProtocol
|
||||||
private let navigationRouter: NavigationRouter
|
private let navigationRouter: NavigationRouter
|
||||||
|
|
||||||
private(set) var clientProxy: ClientProxyProtocol?
|
private(set) var clientProxy: ClientProxyProtocol?
|
||||||
@@ -35,9 +25,9 @@ class AuthenticationCoordinator: Coordinator {
|
|||||||
|
|
||||||
weak var delegate: AuthenticationCoordinatorDelegate?
|
weak var delegate: AuthenticationCoordinatorDelegate?
|
||||||
|
|
||||||
init(userSessionStore: UserSessionStoreProtocol,
|
init(authenticationService: AuthenticationServiceProtocol,
|
||||||
navigationRouter: NavigationRouter) {
|
navigationRouter: NavigationRouter) {
|
||||||
self.userSessionStore = userSessionStore
|
self.authenticationService = authenticationService
|
||||||
self.navigationRouter = navigationRouter
|
self.navigationRouter = navigationRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +35,10 @@ class AuthenticationCoordinator: Coordinator {
|
|||||||
showSplashScreen()
|
showSplashScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toPresentable() -> UIViewController {
|
||||||
|
navigationRouter.toPresentable()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func showSplashScreen() {
|
private func showSplashScreen() {
|
||||||
@@ -67,28 +61,18 @@ class AuthenticationCoordinator: Coordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func showLoginScreen() {
|
private func showLoginScreen() {
|
||||||
let homeserver = LoginHomeserver(address: BuildSettings.defaultHomeserverURLString)
|
let parameters = LoginCoordinatorParameters(authenticationService: authenticationService,
|
||||||
let parameters = LoginCoordinatorParameters(navigationRouter: navigationRouter, homeserver: homeserver)
|
navigationRouter: navigationRouter)
|
||||||
let coordinator = LoginCoordinator(parameters: parameters)
|
let coordinator = LoginCoordinator(parameters: parameters)
|
||||||
|
|
||||||
coordinator.callback = { [weak self, weak coordinator] action in
|
coordinator.callback = { [weak self, weak coordinator] action in
|
||||||
guard let self = self, let coordinator = coordinator else {
|
guard let self = self, let coordinator = coordinator else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .login(let username, let password):
|
case .signedIn(let userSession):
|
||||||
Task {
|
self.delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
|
||||||
switch await self.login(username: username, password: password) {
|
self.remove(childCoordinator: coordinator)
|
||||||
case .success(let userSession):
|
self.navigationRouter.dismissModule()
|
||||||
self.delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
|
|
||||||
self.remove(childCoordinator: coordinator)
|
|
||||||
self.navigationRouter.dismissModule()
|
|
||||||
case .failure(let error):
|
|
||||||
self.delegate?.authenticationCoordinator(self, didFailWithError: error)
|
|
||||||
MXLog.error("Failed logging in user with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .continueWithOIDC:
|
case .continueWithOIDC:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -101,38 +85,4 @@ class AuthenticationCoordinator: Coordinator {
|
|||||||
self?.remove(childCoordinator: coordinator)
|
self?.remove(childCoordinator: coordinator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func login(username: String, password: String) async -> Result<UserSession, AuthenticationCoordinatorError> {
|
|
||||||
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")
|
|
||||||
|
|
||||||
delegate?.authenticationCoordinatorDidStartLoading(self)
|
|
||||||
|
|
||||||
let basePath = userSessionStore.baseDirectoryPath(for: username)
|
|
||||||
let builder = ClientBuilder()
|
|
||||||
.basePath(path: basePath)
|
|
||||||
.username(username: username)
|
|
||||||
|
|
||||||
let loginTask: Task<Client, Error> = Task.detached {
|
|
||||||
let client = try builder.build()
|
|
||||||
try client.login(username: username, password: password)
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
switch await loginTask.result {
|
|
||||||
case .success(let client):
|
|
||||||
return await userSession(for: client)
|
|
||||||
case .failure(let error):
|
|
||||||
MXLog.error("Failed logging in with error: \(error)")
|
|
||||||
return .failure(.failedLoggingIn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func userSession(for client: Client) async -> Result<UserSession, AuthenticationCoordinatorError> {
|
|
||||||
switch await userSessionStore.userSession(for: client) {
|
|
||||||
case .success(let clientProxy):
|
|
||||||
return .success(clientProxy)
|
|
||||||
case .failure:
|
|
||||||
return .failure(.failedLoggingIn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,26 +18,17 @@ import SwiftUI
|
|||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
struct LoginCoordinatorParameters {
|
struct LoginCoordinatorParameters {
|
||||||
|
/// The service used to authenticate the user.
|
||||||
|
let authenticationService: AuthenticationServiceProtocol
|
||||||
|
/// The navigation router used to present the server selection screen.
|
||||||
let navigationRouter: NavigationRouterType
|
let navigationRouter: NavigationRouterType
|
||||||
/// The homeserver to be shown initially.
|
|
||||||
let homeserver: LoginHomeserver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LoginCoordinatorAction: CustomStringConvertible {
|
enum LoginCoordinatorAction {
|
||||||
/// Login with the associated username and password.
|
/// Login was successful.
|
||||||
case login(username: String, password: String)
|
case signedIn(UserSessionProtocol)
|
||||||
/// Continue using OIDC.
|
/// Continue using OIDC.
|
||||||
case continueWithOIDC
|
case continueWithOIDC
|
||||||
|
|
||||||
/// A string representation of the action, ignoring any associated values that could leak PII.
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .login:
|
|
||||||
return "login"
|
|
||||||
case .continueWithOIDC:
|
|
||||||
return "continueWithOIDC"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class LoginCoordinator: Coordinator, Presentable {
|
final class LoginCoordinator: Coordinator, Presentable {
|
||||||
@@ -56,6 +47,7 @@ final class LoginCoordinator: Coordinator, Presentable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService }
|
||||||
private var navigationRouter: NavigationRouterType { parameters.navigationRouter }
|
private var navigationRouter: NavigationRouterType { parameters.navigationRouter }
|
||||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||||
private var activityIndicator: UserIndicator?
|
private var activityIndicator: UserIndicator?
|
||||||
@@ -71,7 +63,7 @@ final class LoginCoordinator: Coordinator, Presentable {
|
|||||||
init(parameters: LoginCoordinatorParameters) {
|
init(parameters: LoginCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
let viewModel = LoginViewModel(homeserver: parameters.homeserver)
|
let viewModel = LoginViewModel(homeserver: parameters.authenticationService.homeserver)
|
||||||
loginViewModel = viewModel
|
loginViewModel = viewModel
|
||||||
|
|
||||||
let view = LoginScreen(context: viewModel.context)
|
let view = LoginScreen(context: viewModel.context)
|
||||||
@@ -135,56 +127,68 @@ final class LoginCoordinator: Coordinator, Presentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Processes an error to either update the flow or display it to the user.
|
/// Processes an error to either update the flow or display it to the user.
|
||||||
private func handleError(_ error: Error) {
|
private func handleError(_ error: AuthenticationServiceError) {
|
||||||
loginViewModel.displayError(.alert(error.localizedDescription))
|
switch error {
|
||||||
|
case .invalidCredentials:
|
||||||
|
loginViewModel.displayError(.alert(ElementL10n.authInvalidLoginParam))
|
||||||
|
case .accountDeactivated:
|
||||||
|
loginViewModel.displayError(.alert(ElementL10n.authInvalidLoginDeactivatedAccount))
|
||||||
|
default:
|
||||||
|
loginViewModel.displayError(.alert(ElementL10n.unknownError))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests the authentication coordinator to log in using the specified credentials.
|
/// Requests the authentication coordinator to log in using the specified credentials.
|
||||||
private func login(username: String, password: String) {
|
private func login(username: String, password: String) {
|
||||||
var username = loginViewModel.context.username
|
startLoading(isInteractionBlocking: true)
|
||||||
|
|
||||||
if !isMXID(username: username) {
|
Task {
|
||||||
let homeserver = loginViewModel.context.viewState.homeserver
|
switch await authenticationService.login(username: username, password: password) {
|
||||||
username = "@\(username):\(homeserver.address)"
|
case .success(let userSession):
|
||||||
|
callback?(.signedIn(userSession))
|
||||||
|
stopLoading()
|
||||||
|
case .failure(let error):
|
||||||
|
stopLoading()
|
||||||
|
handleError(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callback?(.login(username: username, password: password))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the specified username and looks up the homeserver when a Matrix ID is entered.
|
/// Parses the specified username and looks up the homeserver when a Matrix ID is entered.
|
||||||
private func parseUsername(_ username: String) {
|
private func parseUsername(_ username: String) {
|
||||||
guard isMXID(username: username) else { return }
|
guard username.isMatrixUserID else { return }
|
||||||
|
|
||||||
let domain = String(username.split(separator: ":")[1])
|
let homeserverDomain = String(username.split(separator: ":")[1])
|
||||||
|
|
||||||
let homeserver = LoginHomeserver(address: domain)
|
startLoading(isInteractionBlocking: false)
|
||||||
updateViewModel(homeserver: homeserver)
|
|
||||||
indicateSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether the specified username is a Matrix ID or not.
|
|
||||||
private func isMXID(username: String) -> Bool {
|
|
||||||
let range = NSRange(location: 0, length: username.count)
|
|
||||||
|
|
||||||
let detector = try? NSRegularExpression(pattern: MatrixEntityRegex.userId.rawValue, options: .caseInsensitive)
|
Task {
|
||||||
return detector?.numberOfMatches(in: username, range: range) ?? 0 > 0
|
switch await authenticationService.startLogin(for: homeserverDomain) {
|
||||||
|
case .success:
|
||||||
|
updateViewModel()
|
||||||
|
stopLoading()
|
||||||
|
case .failure(let error):
|
||||||
|
stopLoading()
|
||||||
|
handleError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the view model with a different homeserver.
|
/// Updates the view model with a different homeserver.
|
||||||
private func updateViewModel(homeserver: LoginHomeserver) {
|
private func updateViewModel() {
|
||||||
loginViewModel.update(homeserver: homeserver)
|
loginViewModel.update(homeserver: authenticationService.homeserver)
|
||||||
indicateSuccess()
|
indicateSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Presents the server selection screen as a modal.
|
/// Presents the server selection screen as a modal.
|
||||||
private func presentServerSelectionScreen() {
|
private func presentServerSelectionScreen() {
|
||||||
MXLog.debug("[LoginCoordinator] presentServerSelectionScreen")
|
MXLog.debug("[LoginCoordinator] presentServerSelectionScreen")
|
||||||
let parameters = ServerSelectionCoordinatorParameters(homeserver: loginViewModel.context.viewState.homeserver,
|
let parameters = ServerSelectionCoordinatorParameters(authenticationService: authenticationService,
|
||||||
hasModalPresentation: true)
|
hasModalPresentation: true)
|
||||||
let coordinator = ServerSelectionCoordinator(parameters: parameters)
|
let coordinator = ServerSelectionCoordinator(parameters: parameters)
|
||||||
coordinator.callback = { [weak self, weak coordinator] result in
|
coordinator.callback = { [weak self, weak coordinator] action in
|
||||||
guard let self = self, let coordinator = coordinator else { return }
|
guard let self = self, let coordinator = coordinator else { return }
|
||||||
self.serverSelectionCoordinator(coordinator, didCompleteWith: result)
|
self.serverSelectionCoordinator(coordinator, didCompleteWith: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
coordinator.start()
|
coordinator.start()
|
||||||
@@ -198,10 +202,10 @@ final class LoginCoordinator: Coordinator, Presentable {
|
|||||||
|
|
||||||
/// Handles the result from the server selection modal, dismissing it after updating the view.
|
/// Handles the result from the server selection modal, dismissing it after updating the view.
|
||||||
private func serverSelectionCoordinator(_ coordinator: ServerSelectionCoordinator,
|
private func serverSelectionCoordinator(_ coordinator: ServerSelectionCoordinator,
|
||||||
didCompleteWith result: ServerSelectionCoordinatorResult) {
|
didCompleteWith action: ServerSelectionCoordinatorAction) {
|
||||||
navigationRouter.dismissModule(animated: true) { [weak self] in
|
navigationRouter.dismissModule(animated: true) { [weak self] in
|
||||||
if case let .selected(homeserver) = result {
|
if action == .updated {
|
||||||
self?.updateViewModel(homeserver: homeserver)
|
self?.updateViewModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
self?.remove(childCoordinator: coordinator)
|
self?.remove(childCoordinator: coordinator)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ struct LoginServerInfoSection: View {
|
|||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
}
|
}
|
||||||
.buttonStyle(.elementGhost())
|
.buttonStyle(.elementGhost())
|
||||||
|
.accessibilityIdentifier("editServerButton")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-13
@@ -17,14 +17,14 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ServerSelectionCoordinatorParameters {
|
struct ServerSelectionCoordinatorParameters {
|
||||||
/// The homeserver to be shown initially.
|
/// The service used to authenticate the user.
|
||||||
let homeserver: LoginHomeserver
|
let authenticationService: AuthenticationServiceProtocol
|
||||||
/// Whether the screen is presented modally or within a navigation stack.
|
/// Whether the screen is presented modally or within a navigation stack.
|
||||||
let hasModalPresentation: Bool
|
let hasModalPresentation: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerSelectionCoordinatorResult {
|
enum ServerSelectionCoordinatorAction {
|
||||||
case selected(LoginHomeserver)
|
case updated
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ final class ServerSelectionCoordinator: Coordinator, Presentable {
|
|||||||
private let serverSelectionHostingController: UIViewController
|
private let serverSelectionHostingController: UIViewController
|
||||||
private var serverSelectionViewModel: ServerSelectionViewModelProtocol
|
private var serverSelectionViewModel: ServerSelectionViewModelProtocol
|
||||||
|
|
||||||
|
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService }
|
||||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||||
private var loadingIndicator: UserIndicator?
|
private var loadingIndicator: UserIndicator?
|
||||||
|
|
||||||
@@ -45,14 +46,14 @@ final class ServerSelectionCoordinator: Coordinator, Presentable {
|
|||||||
|
|
||||||
// Must be used only internally
|
// Must be used only internally
|
||||||
var childCoordinators: [Coordinator] = []
|
var childCoordinators: [Coordinator] = []
|
||||||
var callback: (@MainActor (ServerSelectionCoordinatorResult) -> Void)?
|
var callback: (@MainActor (ServerSelectionCoordinatorAction) -> Void)?
|
||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
init(parameters: ServerSelectionCoordinatorParameters) {
|
init(parameters: ServerSelectionCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
let viewModel = ServerSelectionViewModel(homeserverAddress: parameters.homeserver.address,
|
let viewModel = ServerSelectionViewModel(homeserverAddress: parameters.authenticationService.homeserver.address,
|
||||||
hasModalPresentation: parameters.hasModalPresentation)
|
hasModalPresentation: parameters.hasModalPresentation)
|
||||||
let view = ServerSelectionScreen(context: viewModel.context)
|
let view = ServerSelectionScreen(context: viewModel.context)
|
||||||
serverSelectionViewModel = viewModel
|
serverSelectionViewModel = viewModel
|
||||||
@@ -66,11 +67,11 @@ final class ServerSelectionCoordinator: Coordinator, Presentable {
|
|||||||
func start() {
|
func start() {
|
||||||
MXLog.debug("[ServerSelectionCoordinator] did start.")
|
MXLog.debug("[ServerSelectionCoordinator] did start.")
|
||||||
|
|
||||||
serverSelectionViewModel.callback = { [weak self] result in
|
serverSelectionViewModel.callback = { [weak self] action in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
MXLog.debug("[ServerSelectionCoordinator] ServerSelectionViewModel did complete with result: \(result).")
|
MXLog.debug("[ServerSelectionCoordinator] ServerSelectionViewModel did callback with action: \(action).")
|
||||||
|
|
||||||
switch result {
|
switch action {
|
||||||
case .confirm(let homeserverAddress):
|
case .confirm(let homeserverAddress):
|
||||||
self.useHomeserver(homeserverAddress)
|
self.useHomeserver(homeserverAddress)
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
@@ -102,9 +103,25 @@ final class ServerSelectionCoordinator: Coordinator, Presentable {
|
|||||||
private func useHomeserver(_ homeserverAddress: String) {
|
private func useHomeserver(_ homeserverAddress: String) {
|
||||||
startLoading()
|
startLoading()
|
||||||
|
|
||||||
let homeserverAddress = LoginHomeserver.sanitized(homeserverAddress)
|
Task {
|
||||||
|
switch await authenticationService.startLogin(for: homeserverAddress) {
|
||||||
stopLoading()
|
case .success:
|
||||||
callback?(.selected(LoginHomeserver(address: homeserverAddress)))
|
callback?(.updated)
|
||||||
|
stopLoading()
|
||||||
|
case .failure(let error):
|
||||||
|
stopLoading()
|
||||||
|
handleError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes an error to either update the flow or display it to the user.
|
||||||
|
private func handleError(_ error: AuthenticationServiceError) {
|
||||||
|
switch error {
|
||||||
|
case .invalidServer:
|
||||||
|
serverSelectionViewModel.displayError(.footerMessage(ElementL10n.loginErrorHomeserverNotFound))
|
||||||
|
default:
|
||||||
|
serverSelectionViewModel.displayError(.footerMessage(ElementL10n.unknownError))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import Foundation
|
|||||||
|
|
||||||
// MARK: View model
|
// MARK: View model
|
||||||
|
|
||||||
enum ServerSelectionViewModelResult {
|
enum ServerSelectionViewModelAction {
|
||||||
/// The user would like to use the homeserver at the given address.
|
/// The user would like to use the homeserver at the given address.
|
||||||
case confirm(homeserverAddress: String)
|
case confirm(homeserverAddress: String)
|
||||||
/// Dismiss the view without using the entered address.
|
/// Dismiss the view without using the entered address.
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
|
|||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
var callback: (@MainActor (ServerSelectionViewModelResult) -> Void)?
|
var callback: (@MainActor (ServerSelectionViewModelAction) -> Void)?
|
||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ import Foundation
|
|||||||
@MainActor
|
@MainActor
|
||||||
protocol ServerSelectionViewModelProtocol {
|
protocol ServerSelectionViewModelProtocol {
|
||||||
|
|
||||||
var callback: (@MainActor (ServerSelectionViewModelResult) -> Void)? { get set }
|
var callback: (@MainActor (ServerSelectionViewModelAction) -> Void)? { get set }
|
||||||
var context: ServerSelectionViewModelType.Context { get }
|
var context: ServerSelectionViewModelType.Context { get }
|
||||||
|
|
||||||
/// Displays an error to the user.
|
/// Displays an error to the user.
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// AuthenticationService.swift
|
||||||
|
// ElementX
|
||||||
|
//
|
||||||
|
// Created by Doug on 29/06/2022.
|
||||||
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
class AuthenticationService: AuthenticationServiceProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
private(set) var homeserver: LoginHomeserver = LoginHomeserver(address: BuildSettings.defaultHomeserverURLString)
|
||||||
|
private let userSessionStore: UserSessionStoreProtocol
|
||||||
|
|
||||||
|
// MARK: - Setup
|
||||||
|
|
||||||
|
init(userSessionStore: UserSessionStoreProtocol) {
|
||||||
|
self.userSessionStore = userSessionStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
func startLogin(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
|
||||||
|
homeserver = LoginHomeserver(address: homeserverAddress)
|
||||||
|
return .success(())
|
||||||
|
}
|
||||||
|
|
||||||
|
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||||
|
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")
|
||||||
|
|
||||||
|
// Workaround whilst the SDK requires a full MXID.
|
||||||
|
let username = username.isMatrixUserID ? username : "@\(username):\(homeserver.address)"
|
||||||
|
|
||||||
|
let basePath = userSessionStore.baseDirectoryPath(for: username)
|
||||||
|
let builder = ClientBuilder()
|
||||||
|
.basePath(path: basePath)
|
||||||
|
.username(username: username)
|
||||||
|
|
||||||
|
let loginTask: Task<Client, Error> = Task.detached {
|
||||||
|
let client = try builder.build()
|
||||||
|
try client.login(username: username, password: password)
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await loginTask.result {
|
||||||
|
case .success(let client):
|
||||||
|
Benchmark.endTrackingForIdentifier("Login", message: "Finished login")
|
||||||
|
return await userSession(for: client)
|
||||||
|
case .failure(let error):
|
||||||
|
Benchmark.endTrackingForIdentifier("Login", message: "Login failed")
|
||||||
|
|
||||||
|
MXLog.error("Failed logging in with error: \(error)")
|
||||||
|
guard let error = error as? ClientError else { return .failure(.failedLoggingIn) }
|
||||||
|
|
||||||
|
switch error.code {
|
||||||
|
case .forbidden:
|
||||||
|
return .failure(.invalidCredentials)
|
||||||
|
case .userDeactivated:
|
||||||
|
return .failure(.accountDeactivated)
|
||||||
|
default:
|
||||||
|
return .failure(.failedLoggingIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func userSession(for client: Client) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||||
|
switch await userSessionStore.userSession(for: client) {
|
||||||
|
case .success(let clientProxy):
|
||||||
|
return .success(clientProxy)
|
||||||
|
case .failure:
|
||||||
|
return .failure(.failedLoggingIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// AuthenticationServiceProtocol.swift
|
||||||
|
// ElementX
|
||||||
|
//
|
||||||
|
// Created by Doug on 29/06/2022.
|
||||||
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum AuthenticationServiceError: Error {
|
||||||
|
case invalidServer
|
||||||
|
case invalidCredentials
|
||||||
|
case accountDeactivated
|
||||||
|
case failedLoggingIn
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
protocol AuthenticationServiceProtocol {
|
||||||
|
var homeserver: LoginHomeserver { get }
|
||||||
|
|
||||||
|
/// Sets up the service for login on the specified homeserver address.
|
||||||
|
func startLogin(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError>
|
||||||
|
/// Performs a password login using the current homeserver.
|
||||||
|
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError>
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// MockAuthenticationService.swift
|
||||||
|
// ElementX
|
||||||
|
//
|
||||||
|
// Created by Doug on 29/06/2022.
|
||||||
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class MockAuthenticationService: AuthenticationServiceProtocol {
|
||||||
|
let validCredentials = (username: "alice", password: "12345678")
|
||||||
|
private(set) var homeserver: LoginHomeserver = .mockMatrixDotOrg
|
||||||
|
|
||||||
|
func startLogin(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
|
||||||
|
// Map the address to the mock homeservers
|
||||||
|
if LoginHomeserver.mockMatrixDotOrg.address.contains(homeserverAddress) {
|
||||||
|
homeserver = .mockMatrixDotOrg
|
||||||
|
return .success(())
|
||||||
|
} else if LoginHomeserver.mockOIDC.address.contains(homeserverAddress) {
|
||||||
|
homeserver = .mockOIDC
|
||||||
|
return .success(())
|
||||||
|
} else if LoginHomeserver.mockBasicServer.address.contains(homeserverAddress) {
|
||||||
|
homeserver = .mockBasicServer
|
||||||
|
return .success(())
|
||||||
|
} else if LoginHomeserver.mockUnsupported.address.contains(homeserverAddress) {
|
||||||
|
homeserver = .mockUnsupported
|
||||||
|
return .success(())
|
||||||
|
} else {
|
||||||
|
// Otherwise fail with an invalid server.
|
||||||
|
return .failure(.invalidServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||||
|
// Login only succeeds if the username and password match the valid credentials property
|
||||||
|
guard username == validCredentials.username, password == validCredentials.password else {
|
||||||
|
return .failure(.invalidCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
let userSession = MockUserSession(clientProxy: MockClientProxy(userIdentifier: username),
|
||||||
|
mediaProvider: MockMediaProvider())
|
||||||
|
return .success(userSession)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// ClientError.swift
|
||||||
|
// ElementX
|
||||||
|
//
|
||||||
|
// Created by Doug on 30/06/2022.
|
||||||
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
enum MatrixErrorCode: String, CaseIterable {
|
||||||
|
case unknown = "M_UNKNOWN"
|
||||||
|
case userDeactivated = "M_USER_DEACTIVATED"
|
||||||
|
case forbidden = "M_FORBIDDEN"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ClientError {
|
||||||
|
var code: MatrixErrorCode {
|
||||||
|
guard case let .Generic(message) = self else { return .unknown }
|
||||||
|
|
||||||
|
for code in MatrixErrorCode.allCases {
|
||||||
|
if message.contains(code.rawValue) {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ enum ClientProxyError: Error {
|
|||||||
case failedRetrievingAvatarURL
|
case failedRetrievingAvatarURL
|
||||||
case failedRetrievingDisplayName
|
case failedRetrievingDisplayName
|
||||||
case failedRetrievingSessionVerificationController
|
case failedRetrievingSessionVerificationController
|
||||||
|
case failedLoadingMedia
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol ClientProxyProtocol {
|
protocol ClientProxyProtocol {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// MockClientProxy.swift
|
||||||
|
// ElementX
|
||||||
|
//
|
||||||
|
// Created by Doug on 29/06/2022.
|
||||||
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
struct MockClientProxy: ClientProxyProtocol {
|
||||||
|
|
||||||
|
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
|
||||||
|
|
||||||
|
let userIdentifier: String
|
||||||
|
|
||||||
|
let rooms = [RoomProxy]()
|
||||||
|
|
||||||
|
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
|
||||||
|
.failure(.failedRetrievingDisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadUserAvatarURLString() async -> Result<String, ClientProxyError> {
|
||||||
|
.failure(.failedRetrievingAvatarURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mediaSourceForURLString(_ urlString: String) -> MatrixRustSDK.MediaSource {
|
||||||
|
MatrixRustSDK.mediaSourceFromUrl(url: urlString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) throws -> Data {
|
||||||
|
throw ClientProxyError.failedLoadingMedia
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
||||||
|
.failure(.failedRetrievingSessionVerificationController)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// MockUserSession.swift
|
||||||
|
// ElementX
|
||||||
|
//
|
||||||
|
// Created by Doug on 29/06/2022.
|
||||||
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
struct MockUserSession: UserSessionProtocol {
|
||||||
|
let callbacks = PassthroughSubject<UserSessionCallback, Never>()
|
||||||
|
let sessionVerificationController: SessionVerificationControllerProxyProtocol? = nil
|
||||||
|
|
||||||
|
let clientProxy: ClientProxyProtocol
|
||||||
|
let mediaProvider: MediaProviderProtocol
|
||||||
|
}
|
||||||
@@ -98,8 +98,6 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupProxyForClient(_ client: Client) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
|
private func setupProxyForClient(_ client: Client) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
|
||||||
Benchmark.endTrackingForIdentifier("Login", message: "Finished login")
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let accessToken = try client.restoreToken()
|
let accessToken = try client.restoreToken()
|
||||||
let userId = try client.userId()
|
let userId = try client.userId()
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import Foundation
|
|||||||
|
|
||||||
enum UITestScreenIdentifier: String {
|
enum UITestScreenIdentifier: String {
|
||||||
case login
|
case login
|
||||||
case loginOIDC
|
case serverSelection
|
||||||
case loginUnsupported
|
case serverSelectionNonModal
|
||||||
|
case authenticationFlow
|
||||||
case simpleRegular
|
case simpleRegular
|
||||||
case simpleUpgrade
|
case simpleUpgrade
|
||||||
case settings
|
case settings
|
||||||
|
|||||||
@@ -11,28 +11,37 @@ import SwiftUI
|
|||||||
|
|
||||||
class UITestsAppCoordinator: Coordinator {
|
class UITestsAppCoordinator: Coordinator {
|
||||||
private let window: UIWindow
|
private let window: UIWindow
|
||||||
private let mainNavigationController: UINavigationController
|
private let mainNavigationController: ElementNavigationController
|
||||||
|
private let navigationRouter: NavigationRouter
|
||||||
|
private var hostingController: UIViewController?
|
||||||
|
|
||||||
var childCoordinators: [Coordinator] = []
|
var childCoordinators: [Coordinator] = []
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
mainNavigationController = UINavigationController()
|
mainNavigationController = ElementNavigationController()
|
||||||
|
navigationRouter = NavigationRouter(navigationController: mainNavigationController)
|
||||||
|
|
||||||
window = UIWindow(frame: UIScreen.main.bounds)
|
window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
window.rootViewController = mainNavigationController
|
window.rootViewController = mainNavigationController
|
||||||
window.tintColor = .element.accent
|
window.tintColor = .element.accent
|
||||||
|
|
||||||
|
UIView.setAnimationsEnabled(false)
|
||||||
|
|
||||||
let screens = mockScreens()
|
let screens = mockScreens()
|
||||||
|
|
||||||
let rootView = UITestsRootView(mockScreens: screens) { id in
|
let rootView = UITestsRootView(mockScreens: screens) { id in
|
||||||
guard let screen = screens.first(where: { $0.id == id }) else {
|
guard let screen = screens.first(where: { $0.id == id }) else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
screen.coordinator.start()
|
screen.coordinator.start()
|
||||||
|
self.navigationRouter.setRootModule(screen.coordinator)
|
||||||
self.mainNavigationController.pushViewController(screen.coordinator.toPresentable(), animated: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mainNavigationController.setViewControllers([UIHostingController(rootView: rootView)], animated: false)
|
let hostingController = UIHostingController(rootView: rootView)
|
||||||
|
self.hostingController = hostingController
|
||||||
|
|
||||||
|
mainNavigationController.setViewControllers([hostingController], animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
@@ -40,34 +49,34 @@ class UITestsAppCoordinator: Coordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func mockScreens() -> [MockScreen] {
|
private func mockScreens() -> [MockScreen] {
|
||||||
UITestScreenIdentifier.allCases.map { MockScreen(id: $0) }
|
UITestScreenIdentifier.allCases.map { MockScreen(id: $0, navigationRouter: navigationRouter) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class MockScreen: Identifiable {
|
class MockScreen: Identifiable {
|
||||||
let id: UITestScreenIdentifier
|
let id: UITestScreenIdentifier
|
||||||
|
let navigationRouter: NavigationRouter
|
||||||
lazy var coordinator: Coordinator & Presentable = {
|
lazy var coordinator: Coordinator & Presentable = {
|
||||||
switch id {
|
switch id {
|
||||||
case .login:
|
case .login:
|
||||||
let router = NavigationRouter(navigationController: ElementNavigationController())
|
return LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationService(),
|
||||||
return LoginCoordinator(parameters: .init(navigationRouter: router,
|
navigationRouter: navigationRouter))
|
||||||
homeserver: .mockMatrixDotOrg))
|
case .serverSelection:
|
||||||
case .loginOIDC:
|
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationService(),
|
||||||
let router = NavigationRouter(navigationController: ElementNavigationController())
|
hasModalPresentation: true))
|
||||||
return LoginCoordinator(parameters: .init(navigationRouter: router,
|
case .serverSelectionNonModal:
|
||||||
homeserver: .mockOIDC))
|
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationService(),
|
||||||
case .loginUnsupported:
|
hasModalPresentation: false))
|
||||||
let router = NavigationRouter(navigationController: ElementNavigationController())
|
case .authenticationFlow:
|
||||||
return LoginCoordinator(parameters: .init(navigationRouter: router,
|
return AuthenticationCoordinator(authenticationService: MockAuthenticationService(),
|
||||||
homeserver: .mockUnsupported))
|
navigationRouter: navigationRouter)
|
||||||
case .simpleRegular:
|
case .simpleRegular:
|
||||||
return TemplateCoordinator(parameters: .init(promptType: .regular))
|
return TemplateCoordinator(parameters: .init(promptType: .regular))
|
||||||
case .simpleUpgrade:
|
case .simpleUpgrade:
|
||||||
return TemplateCoordinator(parameters: .init(promptType: .upgrade))
|
return TemplateCoordinator(parameters: .init(promptType: .upgrade))
|
||||||
case .settings:
|
case .settings:
|
||||||
let router = NavigationRouter(navigationController: ElementNavigationController())
|
return SettingsCoordinator(parameters: .init(navigationRouter: navigationRouter,
|
||||||
return SettingsCoordinator(parameters: .init(navigationRouter: router,
|
|
||||||
bugReportService: MockBugReportService()))
|
bugReportService: MockBugReportService()))
|
||||||
case .bugReport:
|
case .bugReport:
|
||||||
return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
|
return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
|
||||||
@@ -95,7 +104,8 @@ class MockScreen: Identifiable {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(id: UITestScreenIdentifier) {
|
init(id: UITestScreenIdentifier, navigationRouter: NavigationRouter) {
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.navigationRouter = navigationRouter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// AuthenticationCoordinatorUITests.swift
|
||||||
|
// UITests
|
||||||
|
//
|
||||||
|
// Created by Doug on 30/06/2022.
|
||||||
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import ElementX
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class AuthenticationCoordinatorUITests: XCTestCase {
|
||||||
|
func testLoginWithPassword() {
|
||||||
|
// Given the authentication flow.
|
||||||
|
let app = Application.launch()
|
||||||
|
app.goToScreenWithIdentifier(.authenticationFlow)
|
||||||
|
|
||||||
|
// Splash Screen: Tap get started button
|
||||||
|
app.buttons["getStartedButton"].tap()
|
||||||
|
|
||||||
|
// Login Screen: Enter valid credentials
|
||||||
|
app.textFields["usernameTextField"].tap()
|
||||||
|
app.typeText("alice\n")
|
||||||
|
app.secureTextFields["passwordTextField"].tap()
|
||||||
|
app.typeText("12345678")
|
||||||
|
|
||||||
|
// Login Screen: Tap next
|
||||||
|
app.buttons["nextButton"].tap()
|
||||||
|
|
||||||
|
// Then login should succeed.
|
||||||
|
XCTAssertFalse(app.alerts.element.exists, "No alert should be shown when logging in with valid credentials.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLoginWithIncorrectPassword() {
|
||||||
|
// Given the authentication flow.
|
||||||
|
let app = Application.launch()
|
||||||
|
app.goToScreenWithIdentifier(.authenticationFlow)
|
||||||
|
|
||||||
|
// Splash Screen: Tap get started button
|
||||||
|
app.buttons["getStartedButton"].tap()
|
||||||
|
|
||||||
|
// Login Screen: Enter invalid credentials
|
||||||
|
app.textFields["usernameTextField"].tap()
|
||||||
|
app.typeText("alice\n")
|
||||||
|
app.typeText("87654321\n")
|
||||||
|
|
||||||
|
// Then login should fail.
|
||||||
|
XCTAssertTrue(app.alerts.element.exists, "An error alert should be shown when attempting login with invalid credentials.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSelectingOIDCServer() {
|
||||||
|
// Given the authentication flow.
|
||||||
|
let app = Application.launch()
|
||||||
|
app.goToScreenWithIdentifier(.authenticationFlow)
|
||||||
|
|
||||||
|
// Splash Screen: Tap get started button
|
||||||
|
app.buttons["getStartedButton"].tap()
|
||||||
|
|
||||||
|
// Login Screen: Tap edit server button.
|
||||||
|
XCTAssertFalse(app.buttons["oidcButton"].exists, "The OIDC button shouldn't be shown before entering a supported homeserver.")
|
||||||
|
app.buttons["editServerButton"].tap()
|
||||||
|
|
||||||
|
// Server Selection: Clear the default and enter OIDC server.
|
||||||
|
app.textFields["addressTextField"].tap()
|
||||||
|
app.textFields["addressTextField"].buttons.element.tap()
|
||||||
|
app.typeText("company.com")
|
||||||
|
|
||||||
|
// Dismiss server screen.
|
||||||
|
app.buttons["confirmButton"].tap()
|
||||||
|
|
||||||
|
// Then the login form should be updated for OIDC.
|
||||||
|
XCTAssertTrue(app.buttons["oidcButton"].waitForExistence(timeout: 1), "The OIDC button should be shown after selecting a homeserver with OIDC.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ class LoginScreenUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testMatrixDotOrg() {
|
func testMatrixDotOrg() {
|
||||||
|
// Given the initial login screen which defaults to matrix.org.
|
||||||
app = Application.launch()
|
app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.login)
|
app.goToScreenWithIdentifier(.login)
|
||||||
|
|
||||||
@@ -37,35 +38,49 @@ class LoginScreenUITests: XCTestCase {
|
|||||||
validateNextButtonIsDisabled(for: state)
|
validateNextButtonIsDisabled(for: state)
|
||||||
validateUnsupportedServerTextIsHidden(for: state)
|
validateUnsupportedServerTextIsHidden(for: state)
|
||||||
|
|
||||||
|
// When typing in a username and password.
|
||||||
app.textFields.element.tap()
|
app.textFields.element.tap()
|
||||||
app.typeText("@test:server.com")
|
app.typeText("@test:matrix.org")
|
||||||
|
|
||||||
app.secureTextFields.element.tap()
|
app.secureTextFields.element.tap()
|
||||||
app.typeText("12345678")
|
app.typeText("12345678")
|
||||||
|
|
||||||
|
// Then the form should be ready to submit.
|
||||||
validateNextButtonIsEnabled(for: "matrix.org with credentials entered")
|
validateNextButtonIsEnabled(for: "matrix.org with credentials entered")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testOIDC() {
|
func testOIDC() {
|
||||||
|
// Given the initial login screen.
|
||||||
app = Application.launch()
|
app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.loginOIDC)
|
app.goToScreenWithIdentifier(.login)
|
||||||
|
|
||||||
|
// When entering a username on a homeserver that only supports OIDC.
|
||||||
|
app.textFields.element.tap()
|
||||||
|
app.typeText("@test:company.com\n")
|
||||||
|
|
||||||
|
// Then the screen should be configured for OIDC.
|
||||||
let state = "an OIDC only server"
|
let state = "an OIDC only server"
|
||||||
|
validateOIDCButtonIsShown(for: state)
|
||||||
validateServerDescriptionIsHidden(for: state)
|
validateServerDescriptionIsHidden(for: state)
|
||||||
validateLoginFormIsHidden(for: state)
|
validateLoginFormIsHidden(for: state)
|
||||||
validateOIDCButtonIsShown(for: state)
|
|
||||||
validateUnsupportedServerTextIsHidden(for: state)
|
validateUnsupportedServerTextIsHidden(for: state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUnsupported() {
|
func testUnsupported() {
|
||||||
|
// Given the initial login screen.
|
||||||
app = Application.launch()
|
app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.loginUnsupported)
|
app.goToScreenWithIdentifier(.login)
|
||||||
|
|
||||||
|
// When entering a username on a homeserver with an unsupported flow.
|
||||||
|
app.textFields.element.tap()
|
||||||
|
app.typeText("@test:server.net\n")
|
||||||
|
|
||||||
|
// Then the screen should not allow login to continue.
|
||||||
let state = "an unsupported server"
|
let state = "an unsupported server"
|
||||||
|
validateUnsupportedServerTextIsShown(for: state)
|
||||||
validateServerDescriptionIsHidden(for: state)
|
validateServerDescriptionIsHidden(for: state)
|
||||||
validateLoginFormIsHidden(for: state)
|
validateLoginFormIsHidden(for: state)
|
||||||
validateOIDCButtonIsHidden(for: state)
|
validateOIDCButtonIsHidden(for: state)
|
||||||
validateUnsupportedServerTextIsShown(for: state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that the server description label is shown.
|
/// Checks that the server description label is shown.
|
||||||
@@ -78,7 +93,7 @@ class LoginScreenUITests: XCTestCase {
|
|||||||
/// Checks that the server description label is hidden.
|
/// Checks that the server description label is hidden.
|
||||||
func validateServerDescriptionIsHidden(for state: String) {
|
func validateServerDescriptionIsHidden(for state: String) {
|
||||||
let descriptionLabel = app.staticTexts["serverDescriptionText"]
|
let descriptionLabel = app.staticTexts["serverDescriptionText"]
|
||||||
XCTAssertFalse(descriptionLabel.exists, "The server description should be shown for \(state).")
|
XCTAssertFalse(descriptionLabel.exists, "The server description should be hidden for \(state).")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that the username and password text fields are shown along with the next button.
|
/// Checks that the username and password text fields are shown along with the next button.
|
||||||
@@ -123,7 +138,7 @@ class LoginScreenUITests: XCTestCase {
|
|||||||
/// Checks that the OIDC button is shown on the screen.
|
/// Checks that the OIDC button is shown on the screen.
|
||||||
func validateOIDCButtonIsShown(for state: String) {
|
func validateOIDCButtonIsShown(for state: String) {
|
||||||
let oidcButton = app.buttons["oidcButton"]
|
let oidcButton = app.buttons["oidcButton"]
|
||||||
XCTAssertTrue(oidcButton.exists, "The OIDC button should be shown for \(state).")
|
XCTAssertTrue(oidcButton.waitForExistence(timeout: 1), "The OIDC button should be shown for \(state).")
|
||||||
XCTAssertEqual(oidcButton.label, ElementL10n.loginContinue)
|
XCTAssertEqual(oidcButton.label, ElementL10n.loginContinue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +151,7 @@ class LoginScreenUITests: XCTestCase {
|
|||||||
/// Checks that the unsupported homeserver text is shown on the screen.
|
/// Checks that the unsupported homeserver text is shown on the screen.
|
||||||
func validateUnsupportedServerTextIsShown(for state: String) {
|
func validateUnsupportedServerTextIsShown(for state: String) {
|
||||||
let unsupportedText = app.staticTexts["unsupportedServerText"]
|
let unsupportedText = app.staticTexts["unsupportedServerText"]
|
||||||
XCTAssertTrue(unsupportedText.exists, "The unsupported homeserver text should be shown for \(state).")
|
XCTAssertTrue(unsupportedText.waitForExistence(timeout: 1), "The unsupported homeserver text should be shown for \(state).")
|
||||||
XCTAssertEqual(unsupportedText.label, ElementL10n.autodiscoverWellKnownError)
|
XCTAssertEqual(unsupportedText.label, ElementL10n.autodiscoverWellKnownError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,23 +20,19 @@ import ElementX
|
|||||||
@MainActor
|
@MainActor
|
||||||
class RoomScreenUITests: XCTestCase {
|
class RoomScreenUITests: XCTestCase {
|
||||||
|
|
||||||
func testPlainNoAvatar() async throws {
|
func testPlainNoAvatar() {
|
||||||
let app = Application.launch()
|
let app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.roomPlainNoAvatar)
|
app.goToScreenWithIdentifier(.roomPlainNoAvatar)
|
||||||
|
|
||||||
try await Task.sleep(nanoseconds: 400_000_000)
|
|
||||||
|
|
||||||
XCTAssert(app.staticTexts["roomNameLabel"].exists)
|
XCTAssert(app.staticTexts["roomNameLabel"].exists)
|
||||||
XCTAssert(app.staticTexts["roomAvatarPlaceholderImage"].exists)
|
XCTAssert(app.staticTexts["roomAvatarPlaceholderImage"].exists)
|
||||||
XCTAssertFalse(app.images["encryptionBadgeIcon"].exists)
|
XCTAssertFalse(app.images["encryptionBadgeIcon"].exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEncryptedWithAvatar() async throws {
|
func testEncryptedWithAvatar() {
|
||||||
let app = Application.launch()
|
let app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.roomEncryptedWithAvatar)
|
app.goToScreenWithIdentifier(.roomEncryptedWithAvatar)
|
||||||
|
|
||||||
try await Task.sleep(nanoseconds: 400_000_000)
|
|
||||||
|
|
||||||
XCTAssert(app.staticTexts["roomNameLabel"].exists)
|
XCTAssert(app.staticTexts["roomNameLabel"].exists)
|
||||||
XCTAssert(app.images["roomAvatarImage"].exists)
|
XCTAssert(app.images["roomAvatarImage"].exists)
|
||||||
XCTAssert(app.images["encryptionBadgeIcon"].exists)
|
XCTAssert(app.images["encryptionBadgeIcon"].exists)
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class ServerSelectionUITests: XCTestCase {
|
||||||
|
|
||||||
|
let textFieldIdentifier = "addressTextField"
|
||||||
|
|
||||||
|
func testNormalState() async {
|
||||||
|
// Given the initial server selection screen as a modal.
|
||||||
|
let app = Application.launch()
|
||||||
|
app.goToScreenWithIdentifier(.serverSelection)
|
||||||
|
|
||||||
|
// Then it should be configured for matrix.org and with a cancel button
|
||||||
|
let serverTextField = app.textFields.element
|
||||||
|
XCTAssertEqual(serverTextField.value as? String, "matrix.org", "The server shown should be matrix.org with the https scheme hidden.")
|
||||||
|
|
||||||
|
let confirmButton = app.buttons["confirmButton"]
|
||||||
|
XCTAssertEqual(confirmButton.label, ElementL10n.actionConfirm, "The confirm button should say Confirm when in modal presentation.")
|
||||||
|
XCTAssertTrue(confirmButton.exists, "The confirm button should always be shown.")
|
||||||
|
XCTAssertTrue(confirmButton.isEnabled, "The confirm button should be enabled when there is an address.")
|
||||||
|
|
||||||
|
let textFieldFooter = app.staticTexts[textFieldIdentifier]
|
||||||
|
XCTAssertTrue(textFieldFooter.exists)
|
||||||
|
XCTAssertEqual(textFieldFooter.label, ElementL10n.serverSelectionServerFooter)
|
||||||
|
|
||||||
|
let dismissButton = app.buttons["dismissButton"]
|
||||||
|
XCTAssertTrue(dismissButton.exists, "The dismiss button should be shown during modal presentation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEmptyAddress() async {
|
||||||
|
// Given the initial server selection screen as a modal.
|
||||||
|
let app = Application.launch()
|
||||||
|
app.goToScreenWithIdentifier(.serverSelection)
|
||||||
|
|
||||||
|
// When clearing the server address text field.
|
||||||
|
app.textFields.element.tap()
|
||||||
|
app.textFields.element.buttons.element.tap()
|
||||||
|
|
||||||
|
// Then the screen should not allow the user to continue.
|
||||||
|
let serverTextField = app.textFields.element
|
||||||
|
XCTAssertEqual(serverTextField.value as? String, ElementL10n.serverSelectionServerUrl, "The text field should show placeholder text in this state.")
|
||||||
|
|
||||||
|
let confirmButton = app.buttons["confirmButton"]
|
||||||
|
XCTAssertTrue(confirmButton.exists, "The confirm button should always be shown.")
|
||||||
|
XCTAssertFalse(confirmButton.isEnabled, "The confirm button should be disabled when the address is empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidAddress() {
|
||||||
|
// Given the initial server selection screen as a modal.
|
||||||
|
let app = Application.launch()
|
||||||
|
app.goToScreenWithIdentifier(.serverSelection)
|
||||||
|
|
||||||
|
// When typing in an invalid homeserver
|
||||||
|
app.textFields.element.tap()
|
||||||
|
app.textFields.element.buttons.element.tap()
|
||||||
|
app.typeText("thisisbad\n") // The tests only accept an address from LoginHomeserver.mockXYZ
|
||||||
|
|
||||||
|
// Then an error should be shown and the confirmation button disabled.
|
||||||
|
let serverTextField = app.textFields.element
|
||||||
|
XCTAssertEqual(serverTextField.value as? String, "thisisbad", "The text field should show the entered server.")
|
||||||
|
|
||||||
|
let confirmButton = app.buttons["confirmButton"]
|
||||||
|
XCTAssertTrue(confirmButton.exists, "The confirm button should always be shown.")
|
||||||
|
XCTAssertFalse(confirmButton.isEnabled, "The confirm button should be disabled when there is an error.")
|
||||||
|
|
||||||
|
let textFieldFooter = app.staticTexts[textFieldIdentifier]
|
||||||
|
XCTAssertTrue(textFieldFooter.exists)
|
||||||
|
XCTAssertEqual(textFieldFooter.label, ElementL10n.loginErrorHomeserverNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNonModalPresentation() {
|
||||||
|
// Given the initial server selection screen pushed onto the stack.
|
||||||
|
let app = Application.launch()
|
||||||
|
app.goToScreenWithIdentifier(.serverSelectionNonModal)
|
||||||
|
|
||||||
|
// Then the screen should be tweaked slightly to reflect the change of navigation.
|
||||||
|
let dismissButton = app.buttons["dismissButton"]
|
||||||
|
XCTAssertFalse(dismissButton.exists, "The dismiss button should be hidden when not in modal presentation.")
|
||||||
|
|
||||||
|
let confirmButton = app.buttons["confirmButton"]
|
||||||
|
XCTAssertEqual(confirmButton.label, ElementL10n.actionNext, "The confirm button should say Next when not in modal presentation.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ class SplashScreenUITests: XCTestCase {
|
|||||||
XCTAssertEqual(getStartedButton.label, ElementL10n.loginSplashSubmit)
|
XCTAssertEqual(getStartedButton.label, ElementL10n.loginSplashSubmit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSwipingBetweenPages() async throws {
|
func testSwipingBetweenPages() {
|
||||||
let app = Application.launch()
|
let app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.splash)
|
app.goToScreenWithIdentifier(.splash)
|
||||||
|
|
||||||
@@ -42,7 +42,6 @@ class SplashScreenUITests: XCTestCase {
|
|||||||
|
|
||||||
// When swiping to the next screen.
|
// When swiping to the next screen.
|
||||||
page1TitleText.swipeLeft()
|
page1TitleText.swipeLeft()
|
||||||
try await Task.sleep(nanoseconds: 200_000_000) // Wait for the animation.
|
|
||||||
|
|
||||||
// Then the second screen should be shown.
|
// Then the second screen should be shown.
|
||||||
XCTAssertFalse(page1TitleText.isHittable, "The title from the first page of the carousel should be offscreen.")
|
XCTAssertFalse(page1TitleText.isHittable, "The title from the first page of the carousel should be offscreen.")
|
||||||
@@ -50,7 +49,6 @@ class SplashScreenUITests: XCTestCase {
|
|||||||
|
|
||||||
// When swiping back to the previous screen.
|
// When swiping back to the previous screen.
|
||||||
page2TitleText.swipeRight()
|
page2TitleText.swipeRight()
|
||||||
try await Task.sleep(nanoseconds: 200_000_000) // Wait for the animation.
|
|
||||||
|
|
||||||
// Then the first screen should be shown again.
|
// Then the first screen should be shown again.
|
||||||
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.")
|
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.")
|
||||||
@@ -58,7 +56,6 @@ class SplashScreenUITests: XCTestCase {
|
|||||||
|
|
||||||
// When swiping back to the previous screen.
|
// When swiping back to the previous screen.
|
||||||
page1TitleText.swipeRight()
|
page1TitleText.swipeRight()
|
||||||
try await Task.sleep(nanoseconds: 200_000_000) // Wait for the animation.
|
|
||||||
|
|
||||||
// Then the screen shouldn't change and the hidden screen should be ignored.
|
// Then the screen shouldn't change and the hidden screen should be ignored.
|
||||||
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be still be onscreen.")
|
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be still be onscreen.")
|
||||||
|
|||||||
@@ -136,14 +136,11 @@ class LoginViewModelTests: XCTestCase {
|
|||||||
// Given the coordinator and view model results that contain passwords.
|
// Given the coordinator and view model results that contain passwords.
|
||||||
let password = "supersecretpassword"
|
let password = "supersecretpassword"
|
||||||
let viewModelAction: LoginViewModelAction = .login(username: "Alice", password: password)
|
let viewModelAction: LoginViewModelAction = .login(username: "Alice", password: password)
|
||||||
let coordinatorAction: LoginCoordinatorAction = .login(username: "Alice", password: password)
|
|
||||||
|
|
||||||
// When creating a string representation of those results (e.g. for logging).
|
// When creating a string representation of those results (e.g. for logging).
|
||||||
let viewModelActionString = "\(viewModelAction)"
|
let viewModelActionString = "\(viewModelAction)"
|
||||||
let coordinatorActionString = "\(coordinatorAction)"
|
|
||||||
|
|
||||||
// Then the password should not be included in that string.
|
// Then the password should not be included in that string.
|
||||||
XCTAssertFalse("\(viewModelActionString)".contains(password), "The password must not be included in any strings.")
|
XCTAssertFalse("\(viewModelActionString)".contains(password), "The password must not be included in any strings.")
|
||||||
XCTAssertFalse("\(coordinatorActionString)".contains(password), "The password must not be included in any strings.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Add AuthenticationService and missing UI tests on the flow.
|
||||||
Reference in New Issue
Block a user