mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Initial implementation for AppLockSetupFlowCoordinator. (#1949)
Rename AppLockSettingsScreen to AppLockSetupSettingsScreen. Implement forced logout and persist PIN entry attempts.
This commit is contained in:
@@ -10,7 +10,6 @@
|
|||||||
000765812BABB81F5174C601 /* AppLockSetupPINScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEDC38E389B89BCF5C1AFD4A /* AppLockSetupPINScreenUITests.swift */; };
|
000765812BABB81F5174C601 /* AppLockSetupPINScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEDC38E389B89BCF5C1AFD4A /* AppLockSetupPINScreenUITests.swift */; };
|
||||||
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; };
|
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; };
|
||||||
0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B746EFA112532A7B701FB914 /* RoomNotificationSettingsCustomSectionView.swift */; };
|
0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B746EFA112532A7B701FB914 /* RoomNotificationSettingsCustomSectionView.swift */; };
|
||||||
0206016CCEF6EF9365916768 /* AppLockSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33284693F54382F46CFD2EDD /* AppLockSettingsScreenViewModelProtocol.swift */; };
|
|
||||||
020C530986D7B97631877FEF /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */; };
|
020C530986D7B97631877FEF /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */; };
|
||||||
020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D7F513A36C9C1951DB44C /* AnalyticsSettingsScreen.swift */; };
|
020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D7F513A36C9C1951DB44C /* AnalyticsSettingsScreen.swift */; };
|
||||||
024E70451A7CD9E4E034D8A9 /* VoiceMessageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D529B976F8B2AA654D923422 /* VoiceMessageRoomTimelineItem.swift */; };
|
024E70451A7CD9E4E034D8A9 /* VoiceMessageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D529B976F8B2AA654D923422 /* VoiceMessageRoomTimelineItem.swift */; };
|
||||||
@@ -45,7 +44,6 @@
|
|||||||
0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; };
|
0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; };
|
||||||
0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */; };
|
0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */; };
|
||||||
0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; };
|
0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; };
|
||||||
0AD81E04A8C024C09B7AEAC5 /* AppLockSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86F43BF1C26CB31B4BFA610 /* AppLockSettingsScreenModels.swift */; };
|
|
||||||
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
||||||
0B57C2399B9E1CE5CE0D8005 /* ComposerToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D121B4FCFC38DBCC17BCC6D6 /* ComposerToolbar.swift */; };
|
0B57C2399B9E1CE5CE0D8005 /* ComposerToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D121B4FCFC38DBCC17BCC6D6 /* ComposerToolbar.swift */; };
|
||||||
0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; };
|
0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; };
|
||||||
@@ -197,7 +195,6 @@
|
|||||||
355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */; };
|
355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */; };
|
||||||
3582056513A384F110EC8274 /* MediaPlayerProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7A2C4A3A74F0D2FFE9356A /* MediaPlayerProviderTests.swift */; };
|
3582056513A384F110EC8274 /* MediaPlayerProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7A2C4A3A74F0D2FFE9356A /* MediaPlayerProviderTests.swift */; };
|
||||||
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
|
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
|
||||||
3627DFEE96824E0E2EA69B88 /* AppLockSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6615CBDE154455007F456DBB /* AppLockSettingsScreen.swift */; };
|
|
||||||
366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */; };
|
366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */; };
|
||||||
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
|
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
|
||||||
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; };
|
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; };
|
||||||
@@ -265,6 +262,7 @@
|
|||||||
4714991754A08B58B4D7ED85 /* OnboardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F27BAB69EB568369F1F6B3 /* OnboardingScreenViewModelProtocol.swift */; };
|
4714991754A08B58B4D7ED85 /* OnboardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F27BAB69EB568369F1F6B3 /* OnboardingScreenViewModelProtocol.swift */; };
|
||||||
47305C0911C9E1AA774A4000 /* TemplateScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */; };
|
47305C0911C9E1AA774A4000 /* TemplateScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */; };
|
||||||
4799A852132F1744E2825994 /* CreateRoomViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */; };
|
4799A852132F1744E2825994 /* CreateRoomViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */; };
|
||||||
|
4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */; };
|
||||||
484202C5D50983442D24D061 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
|
484202C5D50983442D24D061 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
|
||||||
491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */; };
|
491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */; };
|
||||||
492274DA6691EE985C2FCCAA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
|
492274DA6691EE985C2FCCAA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
|
||||||
@@ -282,6 +280,7 @@
|
|||||||
4C356F5CCB4CDC99BFA45185 /* AppLockSetupPINScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7884BD256C091EB511B2EDF /* AppLockSetupPINScreenViewModelProtocol.swift */; };
|
4C356F5CCB4CDC99BFA45185 /* AppLockSetupPINScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7884BD256C091EB511B2EDF /* AppLockSetupPINScreenViewModelProtocol.swift */; };
|
||||||
4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5351EBD7A0B9610548E4B7B2 /* EncryptedRoomTimelineItem.swift */; };
|
4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5351EBD7A0B9610548E4B7B2 /* EncryptedRoomTimelineItem.swift */; };
|
||||||
4E0D9E09B52CEC4C0E6211A8 /* MediaPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */; };
|
4E0D9E09B52CEC4C0E6211A8 /* MediaPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */; };
|
||||||
|
4E8A2A2CFEB212F14E49E1A1 /* AppLockSetupSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */; };
|
||||||
4E8F17EBA24FBBA6ABB62ECB /* MockBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948D16F021DFDB2CD26EAA8 /* MockBackgroundTaskService.swift */; };
|
4E8F17EBA24FBBA6ABB62ECB /* MockBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948D16F021DFDB2CD26EAA8 /* MockBackgroundTaskService.swift */; };
|
||||||
4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */; };
|
4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */; };
|
||||||
4EA1CE0E88EA68E862FF0EA2 /* NotificationSettingsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */; };
|
4EA1CE0E88EA68E862FF0EA2 /* NotificationSettingsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */; };
|
||||||
@@ -352,7 +351,6 @@
|
|||||||
6189B4ABD535CE526FA1107B /* StartChatViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF438EAFC732D2D95D34BF6 /* StartChatViewModelTests.swift */; };
|
6189B4ABD535CE526FA1107B /* StartChatViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF438EAFC732D2D95D34BF6 /* StartChatViewModelTests.swift */; };
|
||||||
61941DEE5F3834765770BE01 /* InviteUsersScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F32E0B4B83D2A11EE8D011 /* InviteUsersScreenSelectedItem.swift */; };
|
61941DEE5F3834765770BE01 /* InviteUsersScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F32E0B4B83D2A11EE8D011 /* InviteUsersScreenSelectedItem.swift */; };
|
||||||
61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; };
|
61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; };
|
||||||
61C345258DD392477E79A3B5 /* AppLockSettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F088B61525099A48909743B /* AppLockSettingsScreenUITests.swift */; };
|
|
||||||
6213C897001F953E21D3CC16 /* CompletionSuggestionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF54536699ACEE3DB6BA3CB /* CompletionSuggestionService.swift */; };
|
6213C897001F953E21D3CC16 /* CompletionSuggestionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF54536699ACEE3DB6BA3CB /* CompletionSuggestionService.swift */; };
|
||||||
62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */; };
|
62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */; };
|
||||||
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */; };
|
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */; };
|
||||||
@@ -400,6 +398,7 @@
|
|||||||
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; };
|
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; };
|
||||||
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
|
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
|
||||||
6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */; };
|
6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */; };
|
||||||
|
6E4E401BE97AC241DA7C7716 /* AppLockSetupSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502F986D57158674172C58E3 /* AppLockSetupSettingsScreenModels.swift */; };
|
||||||
6E63704717F17593A475D152 /* RoomNotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */; };
|
6E63704717F17593A475D152 /* RoomNotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */; };
|
||||||
6EC7A40A537CFB3D526A111C /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
6EC7A40A537CFB3D526A111C /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
||||||
6F26CBC84AE87EB4068D398B /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 78B28D75FF7AF8E6146DEE2A /* LRUCache */; };
|
6F26CBC84AE87EB4068D398B /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 78B28D75FF7AF8E6146DEE2A /* LRUCache */; };
|
||||||
@@ -480,7 +479,6 @@
|
|||||||
829062DD3C3F7016FE1A6476 /* RoomDetailsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */; };
|
829062DD3C3F7016FE1A6476 /* RoomDetailsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */; };
|
||||||
8317E1314C00DCCC99D30DA8 /* TextBasedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9227F7495DA43324050A863 /* TextBasedRoomTimelineItem.swift */; };
|
8317E1314C00DCCC99D30DA8 /* TextBasedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9227F7495DA43324050A863 /* TextBasedRoomTimelineItem.swift */; };
|
||||||
83A4DAB181C56987C3E804FF /* MapTilerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */; };
|
83A4DAB181C56987C3E804FF /* MapTilerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */; };
|
||||||
84215E902C62E9B8E8AB79F0 /* AppLockSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 267C0279BB8D907E2C40DDCA /* AppLockSettingsScreenCoordinator.swift */; };
|
|
||||||
84226AD2E1F1FBC965F3B09E /* UnitTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E19C4645D3F5F9FB02355 /* UnitTestsAppCoordinator.swift */; };
|
84226AD2E1F1FBC965F3B09E /* UnitTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E19C4645D3F5F9FB02355 /* UnitTestsAppCoordinator.swift */; };
|
||||||
8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */; };
|
8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */; };
|
||||||
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */; };
|
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */; };
|
||||||
@@ -532,6 +530,7 @@
|
|||||||
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
|
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
|
||||||
8D605456793F243649EC96AA /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = CD6B0C4639E066915B5E6463 /* target.yml */; };
|
8D605456793F243649EC96AA /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = CD6B0C4639E066915B5E6463 /* target.yml */; };
|
||||||
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */; };
|
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */; };
|
||||||
|
8DCD9CC5361FF22A5B2C20F1 /* AppLockSetupSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9FCE4D1E3A81AC1CC5CB91 /* AppLockSetupSettingsScreenCoordinator.swift */; };
|
||||||
8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */; };
|
8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */; };
|
||||||
8E650379587C31D7912ED67B /* UNNotification+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */; };
|
8E650379587C31D7912ED67B /* UNNotification+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */; };
|
||||||
8EF63DDDC1B54F122070B04D /* ReadMarkerRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */; };
|
8EF63DDDC1B54F122070B04D /* ReadMarkerRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */; };
|
||||||
@@ -635,7 +634,6 @@
|
|||||||
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
||||||
A743841F91B62B0E56217B04 /* SecureBackupKeyBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DCB219D7B7B0299358FF81 /* SecureBackupKeyBackupScreenUITests.swift */; };
|
A743841F91B62B0E56217B04 /* SecureBackupKeyBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DCB219D7B7B0299358FF81 /* SecureBackupKeyBackupScreenUITests.swift */; };
|
||||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||||
A7BEE8216B4B12BE4C0F2C3F /* AppLockSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892EF45CCC5D2BF0FD1F770C /* AppLockSettingsScreenViewModel.swift */; };
|
|
||||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||||
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
||||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
||||||
@@ -673,6 +671,7 @@
|
|||||||
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; };
|
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; };
|
||||||
B1387648C6F71F1B98244803 /* SecureBackupRecoveryKeyScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596AA8843AC1A234F3387767 /* SecureBackupRecoveryKeyScreenCoordinator.swift */; };
|
B1387648C6F71F1B98244803 /* SecureBackupRecoveryKeyScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596AA8843AC1A234F3387767 /* SecureBackupRecoveryKeyScreenCoordinator.swift */; };
|
||||||
B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */; };
|
B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */; };
|
||||||
|
B188D0907A4D38AAAF6FEFA8 /* AppLockSetupFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */; };
|
||||||
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; };
|
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; };
|
||||||
B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; };
|
B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; };
|
||||||
B27D3190784F85916DA1C394 /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */; };
|
B27D3190784F85916DA1C394 /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */; };
|
||||||
@@ -766,6 +765,7 @@
|
|||||||
C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */; };
|
C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */; };
|
||||||
C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; };
|
C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; };
|
||||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; };
|
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; };
|
||||||
|
C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */; };
|
||||||
C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; };
|
C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; };
|
||||||
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; };
|
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; };
|
||||||
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
|
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
|
||||||
@@ -1066,6 +1066,7 @@
|
|||||||
0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; };
|
0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; };
|
||||||
0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||||
0DF5CBAF69BDF5DF31C661E1 /* IntentionalMentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentionalMentions.swift; sourceTree = "<group>"; };
|
0DF5CBAF69BDF5DF31C661E1 /* IntentionalMentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentionalMentions.swift; sourceTree = "<group>"; };
|
||||||
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; };
|
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; };
|
||||||
0EA689E792E679F5E3956F21 /* UITimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITimelineView.swift; sourceTree = "<group>"; };
|
0EA689E792E679F5E3956F21 /* UITimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITimelineView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1131,6 +1132,7 @@
|
|||||||
1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyProtocol.swift; sourceTree = "<group>"; };
|
1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
|
1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
|
||||||
1D8866FE1CCCF10305FCACBC /* CallScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenUITests.swift; sourceTree = "<group>"; };
|
1D8866FE1CCCF10305FCACBC /* CallScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
|
1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = "<group>"; };
|
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
1DFE0E493FB55E5A62E7852A /* ProposedViewSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProposedViewSize.swift; sourceTree = "<group>"; };
|
1DFE0E493FB55E5A62E7852A /* ProposedViewSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProposedViewSize.swift; sourceTree = "<group>"; };
|
||||||
@@ -1159,7 +1161,6 @@
|
|||||||
24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = "<group>"; };
|
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = "<group>"; };
|
||||||
260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = "<group>"; };
|
260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = "<group>"; };
|
||||||
267C0279BB8D907E2C40DDCA /* AppLockSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
|
|
||||||
26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorder.swift; sourceTree = "<group>"; };
|
26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorder.swift; sourceTree = "<group>"; };
|
||||||
26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceProtocol.swift; sourceTree = "<group>"; };
|
26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceProtocol.swift; sourceTree = "<group>"; };
|
||||||
2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverterProtocol.swift; sourceTree = "<group>"; };
|
2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverterProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -1200,7 +1201,6 @@
|
|||||||
3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsUserDefinedScreen.swift; sourceTree = "<group>"; };
|
3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsUserDefinedScreen.swift; sourceTree = "<group>"; };
|
||||||
32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyProtocol.swift; sourceTree = "<group>"; };
|
32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutViewModelTests.swift; sourceTree = "<group>"; };
|
32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
33284693F54382F46CFD2EDD /* AppLockSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
|
||||||
33649299575BADC34924ABC6 /* InvitesScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenCoordinator.swift; sourceTree = "<group>"; };
|
33649299575BADC34924ABC6 /* InvitesScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
33720F7AD25E85E4A84669E8 /* MapTilerGeocoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerGeocoding.swift; sourceTree = "<group>"; };
|
33720F7AD25E85E4A84669E8 /* MapTilerGeocoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerGeocoding.swift; sourceTree = "<group>"; };
|
||||||
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
|
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -1237,6 +1237,7 @@
|
|||||||
3D1D4A6D451F43A03CACD01D /* PINTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextField.swift; sourceTree = "<group>"; };
|
3D1D4A6D451F43A03CACD01D /* PINTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextField.swift; sourceTree = "<group>"; };
|
||||||
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = "<group>"; };
|
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
3D4DD336905C72F95EAF34B7 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = "<group>"; };
|
3D4DD336905C72F95EAF34B7 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
3D9FCE4D1E3A81AC1CC5CB91 /* AppLockSetupSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
|
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
|
||||||
3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskService.swift; sourceTree = "<group>"; };
|
3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskService.swift; sourceTree = "<group>"; };
|
||||||
3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = "<group>"; };
|
3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
@@ -1305,6 +1306,7 @@
|
|||||||
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinatorTests.swift; sourceTree = "<group>"; };
|
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||||
4FD6E621CC5E6D4830D96D2D /* MockMediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaProvider.swift; sourceTree = "<group>"; };
|
4FD6E621CC5E6D4830D96D2D /* MockMediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaProvider.swift; sourceTree = "<group>"; };
|
||||||
4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
|
4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
502F986D57158674172C58E3 /* AppLockSetupSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenModels.swift; sourceTree = "<group>"; };
|
||||||
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = "<group>"; };
|
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = "<group>"; };
|
5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = "<group>"; };
|
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = "<group>"; };
|
||||||
@@ -1323,6 +1325,7 @@
|
|||||||
536E72DCBEEC4A1FE66CFDCE /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
536E72DCBEEC4A1FE66CFDCE /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||||
542D4F49FABA056DEEEB3400 /* RustTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustTracing.swift; sourceTree = "<group>"; };
|
542D4F49FABA056DEEEB3400 /* RustTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustTracing.swift; sourceTree = "<group>"; };
|
||||||
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
|
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
|
||||||
|
5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreen.swift; sourceTree = "<group>"; };
|
||||||
54C4E7B46099462F12000C91 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
54C4E7B46099462F12000C91 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSender.swift; sourceTree = "<group>"; };
|
55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSender.swift; sourceTree = "<group>"; };
|
||||||
55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = "<group>"; };
|
55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = "<group>"; };
|
||||||
@@ -1355,7 +1358,6 @@
|
|||||||
5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetection.swift; sourceTree = "<group>"; };
|
5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetection.swift; sourceTree = "<group>"; };
|
||||||
5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProperties.swift; sourceTree = "<group>"; };
|
5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProperties.swift; sourceTree = "<group>"; };
|
||||||
5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenCoordinator.swift; sourceTree = "<group>"; };
|
5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
5F088B61525099A48909743B /* AppLockSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreenUITests.swift; 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>"; };
|
||||||
5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = "<group>"; };
|
5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = "<group>"; };
|
||||||
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
|
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -1375,7 +1377,6 @@
|
|||||||
6569593FA36B22259E806A67 /* AudioRecorderState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderState.swift; sourceTree = "<group>"; };
|
6569593FA36B22259E806A67 /* AudioRecorderState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderState.swift; sourceTree = "<group>"; };
|
||||||
65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoveryService.swift; sourceTree = "<group>"; };
|
65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoveryService.swift; sourceTree = "<group>"; };
|
||||||
65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
||||||
6615CBDE154455007F456DBB /* AppLockSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreen.swift; sourceTree = "<group>"; };
|
|
||||||
6654859746B0BE9611459391 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
6654859746B0BE9611459391 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
667DD3A9D932D7D9EB380CAA /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sk; path = sk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
667DD3A9D932D7D9EB380CAA /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sk; path = sk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenUITests.swift; sourceTree = "<group>"; };
|
66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
@@ -1481,6 +1482,7 @@
|
|||||||
8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenCoordinator.swift; sourceTree = "<group>"; };
|
854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = "<group>"; };
|
85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
86376BEE425704AEE197CA54 /* PillContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContext.swift; sourceTree = "<group>"; };
|
86376BEE425704AEE197CA54 /* PillContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContext.swift; sourceTree = "<group>"; };
|
||||||
86873A768B13069BB5CAECF6 /* InvitesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
86873A768B13069BB5CAECF6 /* InvitesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModel.swift; sourceTree = "<group>"; };
|
86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -1491,7 +1493,6 @@
|
|||||||
889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerProtocol.swift; sourceTree = "<group>"; };
|
889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerProtocol.swift; sourceTree = "<group>"; };
|
||||||
89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerStateTests.swift; sourceTree = "<group>"; };
|
89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerStateTests.swift; sourceTree = "<group>"; };
|
||||||
892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = "<group>"; };
|
892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = "<group>"; };
|
||||||
892EF45CCC5D2BF0FD1F770C /* AppLockSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreenViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
893777A4997BBDB68079D4F5 /* ArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = "<group>"; };
|
893777A4997BBDB68079D4F5 /* ArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = "<group>"; };
|
||||||
8977176AB534AA41630395BC /* LegalInformationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
8977176AB534AA41630395BC /* LegalInformationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
897DF5E9A70CE05A632FC8AF /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = "<group>"; };
|
897DF5E9A70CE05A632FC8AF /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = "<group>"; };
|
||||||
@@ -1655,7 +1656,6 @@
|
|||||||
B81B6170DB690013CEB646F4 /* MapLibreModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreModels.swift; sourceTree = "<group>"; };
|
B81B6170DB690013CEB646F4 /* MapLibreModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreModels.swift; sourceTree = "<group>"; };
|
||||||
B83BC0DC9A2DF2DD60F9B6E9 /* NotificationSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenUITests.swift; sourceTree = "<group>"; };
|
B83BC0DC9A2DF2DD60F9B6E9 /* NotificationSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; };
|
B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; };
|
||||||
B86F43BF1C26CB31B4BFA610 /* AppLockSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreenModels.swift; sourceTree = "<group>"; };
|
|
||||||
B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; };
|
B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; };
|
||||||
B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = "<group>"; };
|
B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = "<group>"; };
|
||||||
B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; };
|
B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; };
|
||||||
@@ -2022,14 +2022,6 @@
|
|||||||
path = VoiceMessage;
|
path = VoiceMessage;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
01FACE97F4C9E9F522D401EB /* View */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
6615CBDE154455007F456DBB /* AppLockSettingsScreen.swift */,
|
|
||||||
);
|
|
||||||
path = View;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
0210F4932B59277E2EEEF7BC /* RoomNotificationSettingsScreen */ = {
|
0210F4932B59277E2EEEF7BC /* RoomNotificationSettingsScreen */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2211,9 +2203,9 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3AD37D7DDF9904587601239D /* AppLockScreen */,
|
3AD37D7DDF9904587601239D /* AppLockScreen */,
|
||||||
CE39C9B97963CC30AB0859E5 /* AppLockSettingsScreen */,
|
|
||||||
4570BFC8DD6665A91381F400 /* AppLockSetupBiometricsScreen */,
|
4570BFC8DD6665A91381F400 /* AppLockSetupBiometricsScreen */,
|
||||||
570026F1BA71A2D167652E48 /* AppLockSetupPINScreen */,
|
570026F1BA71A2D167652E48 /* AppLockSetupPINScreen */,
|
||||||
|
2AC6FD695C6F79F67C056463 /* AppLockSetupSettingsScreen */,
|
||||||
FB039572AA54E0690B4051AD /* Common */,
|
FB039572AA54E0690B4051AD /* Common */,
|
||||||
);
|
);
|
||||||
path = AppLock;
|
path = AppLock;
|
||||||
@@ -2347,6 +2339,18 @@
|
|||||||
path = ComposerToolbar;
|
path = ComposerToolbar;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
2AC6FD695C6F79F67C056463 /* AppLockSetupSettingsScreen */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3D9FCE4D1E3A81AC1CC5CB91 /* AppLockSetupSettingsScreenCoordinator.swift */,
|
||||||
|
502F986D57158674172C58E3 /* AppLockSetupSettingsScreenModels.swift */,
|
||||||
|
1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */,
|
||||||
|
8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */,
|
||||||
|
5C3CC0EA3D5B1E74EC71A463 /* View */,
|
||||||
|
);
|
||||||
|
path = AppLockSetupSettingsScreen;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
2C0F49BD446849654C0D24E0 /* RoomMember */ = {
|
2C0F49BD446849654C0D24E0 /* RoomMember */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2928,6 +2932,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */,
|
FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */,
|
||||||
|
0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */,
|
||||||
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
|
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
|
||||||
C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */,
|
C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */,
|
||||||
E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */,
|
E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */,
|
||||||
@@ -2961,6 +2966,14 @@
|
|||||||
path = SoftLogoutScreen;
|
path = SoftLogoutScreen;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5C3CC0EA3D5B1E74EC71A463 /* View */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */,
|
||||||
|
);
|
||||||
|
path = View;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
5DC032EC5F19009AA7CADB41 /* View */ = {
|
5DC032EC5F19009AA7CADB41 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -3662,7 +3675,6 @@
|
|||||||
16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */,
|
16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */,
|
||||||
7D0CBC76C80E04345E11F2DB /* Application.swift */,
|
7D0CBC76C80E04345E11F2DB /* Application.swift */,
|
||||||
349C633291427A0F29C28C54 /* AppLockScreenUITests.swift */,
|
349C633291427A0F29C28C54 /* AppLockScreenUITests.swift */,
|
||||||
5F088B61525099A48909743B /* AppLockSettingsScreenUITests.swift */,
|
|
||||||
AEDC38E389B89BCF5C1AFD4A /* AppLockSetupPINScreenUITests.swift */,
|
AEDC38E389B89BCF5C1AFD4A /* AppLockSetupPINScreenUITests.swift */,
|
||||||
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */,
|
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */,
|
||||||
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
|
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
|
||||||
@@ -4280,18 +4292,6 @@
|
|||||||
path = Layout;
|
path = Layout;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
CE39C9B97963CC30AB0859E5 /* AppLockSettingsScreen */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
267C0279BB8D907E2C40DDCA /* AppLockSettingsScreenCoordinator.swift */,
|
|
||||||
B86F43BF1C26CB31B4BFA610 /* AppLockSettingsScreenModels.swift */,
|
|
||||||
892EF45CCC5D2BF0FD1F770C /* AppLockSettingsScreenViewModel.swift */,
|
|
||||||
33284693F54382F46CFD2EDD /* AppLockSettingsScreenViewModelProtocol.swift */,
|
|
||||||
01FACE97F4C9E9F522D401EB /* View */,
|
|
||||||
);
|
|
||||||
path = AppLockSettingsScreen;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D4DB8163C10389C069458252 /* RoomMemberListScreen */ = {
|
D4DB8163C10389C069458252 /* RoomMemberListScreen */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -5277,21 +5277,22 @@
|
|||||||
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */,
|
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */,
|
||||||
1D623953F970D11F6F38499C /* AppLockService.swift in Sources */,
|
1D623953F970D11F6F38499C /* AppLockService.swift in Sources */,
|
||||||
D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */,
|
D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */,
|
||||||
3627DFEE96824E0E2EA69B88 /* AppLockSettingsScreen.swift in Sources */,
|
|
||||||
84215E902C62E9B8E8AB79F0 /* AppLockSettingsScreenCoordinator.swift in Sources */,
|
|
||||||
0AD81E04A8C024C09B7AEAC5 /* AppLockSettingsScreenModels.swift in Sources */,
|
|
||||||
A7BEE8216B4B12BE4C0F2C3F /* AppLockSettingsScreenViewModel.swift in Sources */,
|
|
||||||
0206016CCEF6EF9365916768 /* AppLockSettingsScreenViewModelProtocol.swift in Sources */,
|
|
||||||
BB9B800C6094E34860E89DC5 /* AppLockSetupBiometricsScreen.swift in Sources */,
|
BB9B800C6094E34860E89DC5 /* AppLockSetupBiometricsScreen.swift in Sources */,
|
||||||
8015842CB4DE1BE414D2CDED /* AppLockSetupBiometricsScreenCoordinator.swift in Sources */,
|
8015842CB4DE1BE414D2CDED /* AppLockSetupBiometricsScreenCoordinator.swift in Sources */,
|
||||||
21813AF91CFC6F3E3896DB53 /* AppLockSetupBiometricsScreenModels.swift in Sources */,
|
21813AF91CFC6F3E3896DB53 /* AppLockSetupBiometricsScreenModels.swift in Sources */,
|
||||||
37906355E207DB5703754675 /* AppLockSetupBiometricsScreenViewModel.swift in Sources */,
|
37906355E207DB5703754675 /* AppLockSetupBiometricsScreenViewModel.swift in Sources */,
|
||||||
9FB41B0E8B2AA9B404E52C8B /* AppLockSetupBiometricsScreenViewModelProtocol.swift in Sources */,
|
9FB41B0E8B2AA9B404E52C8B /* AppLockSetupBiometricsScreenViewModelProtocol.swift in Sources */,
|
||||||
|
B188D0907A4D38AAAF6FEFA8 /* AppLockSetupFlowCoordinator.swift in Sources */,
|
||||||
733E2B19AB1FDA3B93293A28 /* AppLockSetupPINScreen.swift in Sources */,
|
733E2B19AB1FDA3B93293A28 /* AppLockSetupPINScreen.swift in Sources */,
|
||||||
9696ECAFB4F0C079C5C2A526 /* AppLockSetupPINScreenCoordinator.swift in Sources */,
|
9696ECAFB4F0C079C5C2A526 /* AppLockSetupPINScreenCoordinator.swift in Sources */,
|
||||||
E4B07FF075C99D04D9AF792D /* AppLockSetupPINScreenModels.swift in Sources */,
|
E4B07FF075C99D04D9AF792D /* AppLockSetupPINScreenModels.swift in Sources */,
|
||||||
0F6C8033FA60CFD36F7CA205 /* AppLockSetupPINScreenViewModel.swift in Sources */,
|
0F6C8033FA60CFD36F7CA205 /* AppLockSetupPINScreenViewModel.swift in Sources */,
|
||||||
4C356F5CCB4CDC99BFA45185 /* AppLockSetupPINScreenViewModelProtocol.swift in Sources */,
|
4C356F5CCB4CDC99BFA45185 /* AppLockSetupPINScreenViewModelProtocol.swift in Sources */,
|
||||||
|
4E8A2A2CFEB212F14E49E1A1 /* AppLockSetupSettingsScreen.swift in Sources */,
|
||||||
|
8DCD9CC5361FF22A5B2C20F1 /* AppLockSetupSettingsScreenCoordinator.swift in Sources */,
|
||||||
|
6E4E401BE97AC241DA7C7716 /* AppLockSetupSettingsScreenModels.swift in Sources */,
|
||||||
|
4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */,
|
||||||
|
C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */,
|
||||||
EF890DEF0479E66548F2BA23 /* AppLockTimer.swift in Sources */,
|
EF890DEF0479E66548F2BA23 /* AppLockTimer.swift in Sources */,
|
||||||
355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */,
|
355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */,
|
||||||
12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */,
|
12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */,
|
||||||
@@ -5908,7 +5909,6 @@
|
|||||||
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */,
|
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */,
|
||||||
BF675964C9159F718589C36A /* AnalyticsSettingsScreenUITests.swift in Sources */,
|
BF675964C9159F718589C36A /* AnalyticsSettingsScreenUITests.swift in Sources */,
|
||||||
F05516474DB42369FD976CEF /* AppLockScreenUITests.swift in Sources */,
|
F05516474DB42369FD976CEF /* AppLockScreenUITests.swift in Sources */,
|
||||||
61C345258DD392477E79A3B5 /* AppLockSettingsScreenUITests.swift in Sources */,
|
|
||||||
000765812BABB81F5174C601 /* AppLockSetupPINScreenUITests.swift in Sources */,
|
000765812BABB81F5174C601 /* AppLockSetupPINScreenUITests.swift in Sources */,
|
||||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */,
|
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */,
|
||||||
ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */,
|
ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */,
|
||||||
|
|||||||
@@ -144,6 +144,7 @@
|
|||||||
"common_server_url" = "Server URL";
|
"common_server_url" = "Server URL";
|
||||||
"common_settings" = "Settings";
|
"common_settings" = "Settings";
|
||||||
"common_shared_location" = "Shared location";
|
"common_shared_location" = "Shared location";
|
||||||
|
"common_signing_out" = "Signing out";
|
||||||
"common_starting_chat" = "Starting chat…";
|
"common_starting_chat" = "Starting chat…";
|
||||||
"common_sticker" = "Sticker";
|
"common_sticker" = "Sticker";
|
||||||
"common_success" = "Success";
|
"common_success" = "Success";
|
||||||
@@ -297,7 +298,6 @@
|
|||||||
"screen_app_lock_setup_pin_mismatch_dialog_title" = "PINs don't match";
|
"screen_app_lock_setup_pin_mismatch_dialog_title" = "PINs don't match";
|
||||||
"screen_app_lock_signout_alert_message" = "You’ll need to re-login and create a new PIN to proceed";
|
"screen_app_lock_signout_alert_message" = "You’ll need to re-login and create a new PIN to proceed";
|
||||||
"screen_app_lock_signout_alert_title" = "You are being signed out";
|
"screen_app_lock_signout_alert_title" = "You are being signed out";
|
||||||
"screen_app_lock_subtitle" = "You have 3 attempts to unlock";
|
|
||||||
"screen_bug_report_attach_screenshot" = "Attach screenshot";
|
"screen_bug_report_attach_screenshot" = "Attach screenshot";
|
||||||
"screen_bug_report_contact_me" = "You may contact me if you have any follow up questions.";
|
"screen_bug_report_contact_me" = "You may contact me if you have any follow up questions.";
|
||||||
"screen_bug_report_contact_me_title" = "Contact me";
|
"screen_bug_report_contact_me_title" = "Contact me";
|
||||||
|
|||||||
@@ -162,6 +162,22 @@
|
|||||||
<string>%1$d room changes</string>
|
<string>%1$d room changes</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>screen_app_lock_subtitle</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@COUNT@</string>
|
||||||
|
<key>COUNT</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>You have %1$d attempt to unlock</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>You have %1$d attempts to unlock</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>screen_app_lock_subtitle_wrong_pin</key>
|
<key>screen_app_lock_subtitle_wrong_pin</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|||||||
@@ -102,8 +102,11 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
backgroundTaskService: backgroundTaskService)
|
backgroundTaskService: backgroundTaskService)
|
||||||
|
|
||||||
let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
||||||
|
let appLockNavigationCoordinator = NavigationRootCoordinator()
|
||||||
|
let appLockFlowUserIndicatorController = UserIndicatorController(rootCoordinator: appLockNavigationCoordinator)
|
||||||
appLockFlowCoordinator = AppLockFlowCoordinator(appLockService: appLockService,
|
appLockFlowCoordinator = AppLockFlowCoordinator(appLockService: appLockService,
|
||||||
navigationCoordinator: NavigationRootCoordinator())
|
userIndicatorController: appLockFlowUserIndicatorController,
|
||||||
|
navigationCoordinator: appLockNavigationCoordinator)
|
||||||
|
|
||||||
notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(),
|
notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(),
|
||||||
appSettings: appSettings)
|
appSettings: appSettings)
|
||||||
@@ -329,12 +332,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
// We can ignore signOut when already in the process of signing out,
|
// We can ignore signOut when already in the process of signing out,
|
||||||
// such as the SDK sending an authError due to token invalidation.
|
// such as the SDK sending an authError due to token invalidation.
|
||||||
break
|
break
|
||||||
case (_, .signOut(let isSoft), .signingOut):
|
case (_, .signOut(let isSoft, _), .signingOut):
|
||||||
self.logout(isSoft: isSoft)
|
self.logout(isSoft: isSoft)
|
||||||
case (.signingOut, .completedSigningOut, .signedOut):
|
case (.signingOut(_, let disableAppLock), .completedSigningOut, .signedOut):
|
||||||
self.presentSplashScreen(isSoftLogout: false)
|
self.presentSplashScreen(isSoftLogout: false, disableAppLock: disableAppLock)
|
||||||
case (.signingOut, .showSoftLogout, .softLogout):
|
case (.signingOut(_, let disableAppLock), .showSoftLogout, .softLogout):
|
||||||
self.presentSplashScreen(isSoftLogout: true)
|
self.presentSplashScreen(isSoftLogout: true, disableAppLock: disableAppLock)
|
||||||
case (.signedIn, .clearCache, .initial):
|
case (.signedIn, .clearCache, .initial):
|
||||||
self.clearCache()
|
self.clearCache()
|
||||||
default:
|
default:
|
||||||
@@ -371,7 +374,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
navigationStackCoordinator: authenticationNavigationStackCoordinator,
|
navigationStackCoordinator: authenticationNavigationStackCoordinator,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
|
appLockService: appLockFlowCoordinator.appLockService)
|
||||||
authenticationCoordinator?.delegate = self
|
authenticationCoordinator?.delegate = self
|
||||||
|
|
||||||
authenticationCoordinator?.start()
|
authenticationCoordinator?.start()
|
||||||
@@ -410,7 +414,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
stateMachine.processEvent(.createdUserSession)
|
stateMachine.processEvent(.createdUserSession)
|
||||||
case .clearAllData:
|
case .clearAllData:
|
||||||
self.softLogoutCoordinator = nil
|
self.softLogoutCoordinator = nil
|
||||||
stateMachine.processEvent(.signOut(isSoft: false))
|
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -439,7 +443,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .logout:
|
case .logout:
|
||||||
stateMachine.processEvent(.signOut(isSoft: false))
|
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: false))
|
||||||
case .clearCache:
|
case .clearCache:
|
||||||
stateMachine.processEvent(.clearCache)
|
stateMachine.processEvent(.clearCache)
|
||||||
}
|
}
|
||||||
@@ -512,7 +516,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
notificationManager.setUserSession(nil)
|
notificationManager.setUserSession(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentSplashScreen(isSoftLogout: Bool = false) {
|
private func presentSplashScreen(isSoftLogout: Bool = false, disableAppLock: Bool = false) {
|
||||||
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
|
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
|
||||||
|
|
||||||
if isSoftLogout {
|
if isSoftLogout {
|
||||||
@@ -520,6 +524,15 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
} else {
|
} else {
|
||||||
startAuthentication()
|
startAuthentication()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if disableAppLock {
|
||||||
|
Task {
|
||||||
|
// Ensure the navigation stack has settled.
|
||||||
|
try? await Task.sleep(for: .milliseconds(500))
|
||||||
|
appLockFlowCoordinator.appLockService.disable()
|
||||||
|
windowManager.switchToMain()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureNotificationManager() {
|
private func configureNotificationManager() {
|
||||||
@@ -549,7 +562,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch callback {
|
switch callback {
|
||||||
case .didReceiveAuthError(let isSoftLogout):
|
case .didReceiveAuthError(let isSoftLogout):
|
||||||
stateMachine.processEvent(.signOut(isSoft: isSoftLogout))
|
stateMachine.processEvent(.signOut(isSoft: isSoftLogout, disableAppLock: false))
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -583,6 +596,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
windowManager.switchToAlternate()
|
windowManager.switchToAlternate()
|
||||||
case .unlockApp:
|
case .unlockApp:
|
||||||
windowManager.switchToMain()
|
windowManager.switchToMain()
|
||||||
|
case .forceLogout:
|
||||||
|
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class AppCoordinatorStateMachine {
|
|||||||
case signedIn
|
case signedIn
|
||||||
|
|
||||||
/// Processing a sign out request
|
/// Processing a sign out request
|
||||||
case signingOut(isSoft: Bool)
|
case signingOut(isSoft: Bool, disableAppLock: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Events that can be triggered on the AppCoordinator state machine
|
/// Events that can be triggered on the AppCoordinator state machine
|
||||||
@@ -51,7 +51,7 @@ class AppCoordinatorStateMachine {
|
|||||||
case createdUserSession
|
case createdUserSession
|
||||||
|
|
||||||
/// Request sign out.
|
/// Request sign out.
|
||||||
case signOut(isSoft: Bool)
|
case signOut(isSoft: Bool, disableAppLock: Bool)
|
||||||
/// Request the soft logout screen.
|
/// Request the soft logout screen.
|
||||||
case showSoftLogout
|
case showSoftLogout
|
||||||
/// Signing out completed.
|
/// Signing out completed.
|
||||||
@@ -80,16 +80,17 @@ class AppCoordinatorStateMachine {
|
|||||||
stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn])
|
stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn])
|
||||||
stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
|
stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
|
||||||
|
|
||||||
stateMachine.addRoutes(event: .completedSigningOut, transitions: [.signingOut(isSoft: false) => .signedOut])
|
stateMachine.addRoutes(event: .completedSigningOut, transitions: [.signingOut(isSoft: false, disableAppLock: false) => .signedOut,
|
||||||
stateMachine.addRoutes(event: .showSoftLogout, transitions: [.signingOut(isSoft: true) => .softLogout])
|
.signingOut(isSoft: false, disableAppLock: true) => .signedOut])
|
||||||
|
stateMachine.addRoutes(event: .showSoftLogout, transitions: [.signingOut(isSoft: true, disableAppLock: false) => .softLogout])
|
||||||
|
|
||||||
stateMachine.addRoutes(event: .clearCache, transitions: [.signedIn => .initial])
|
stateMachine.addRoutes(event: .clearCache, transitions: [.signedIn => .initial])
|
||||||
|
|
||||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||||
stateMachine.addRouteMapping { event, fromState, _ in
|
stateMachine.addRouteMapping { event, fromState, _ in
|
||||||
switch (event, fromState) {
|
switch (event, fromState) {
|
||||||
case (.signOut(let isSoft), _):
|
case (.signOut(let isSoft, let disableAppLock), _):
|
||||||
return .signingOut(isSoft: isSoft)
|
return .signingOut(isSoft: isSoft, disableAppLock: disableAppLock)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ final class AppSettings {
|
|||||||
private enum UserDefaultsKeys: String {
|
private enum UserDefaultsKeys: String {
|
||||||
case lastVersionLaunched
|
case lastVersionLaunched
|
||||||
case seenInvites
|
case seenInvites
|
||||||
|
case hasShownWelcomeScreen
|
||||||
|
case appLockNumberOfPINAttempts
|
||||||
|
case appLockNumberOfBiometricAttempts
|
||||||
case lastLoginDate
|
case lastLoginDate
|
||||||
case migratedAccounts
|
case migratedAccounts
|
||||||
case timelineStyle
|
case timelineStyle
|
||||||
@@ -38,7 +41,6 @@ final class AppSettings {
|
|||||||
case shouldCollapseRoomStateEvents
|
case shouldCollapseRoomStateEvents
|
||||||
case userSuggestionsEnabled
|
case userSuggestionsEnabled
|
||||||
case readReceiptsEnabled
|
case readReceiptsEnabled
|
||||||
case hasShownWelcomeScreen
|
|
||||||
case swiftUITimelineEnabled
|
case swiftUITimelineEnabled
|
||||||
case voiceMessageEnabled
|
case voiceMessageEnabled
|
||||||
case mentionsEnabled
|
case mentionsEnabled
|
||||||
@@ -122,10 +124,18 @@ final class AppSettings {
|
|||||||
|
|
||||||
// MARK: - Security
|
// MARK: - Security
|
||||||
|
|
||||||
|
/// The app must be locked with a PIN code as part of the authentication flow.
|
||||||
|
let appLockIsMandatory = false
|
||||||
/// The amount of time the app can remain in the background for without requesting the PIN/TouchID/FaceID.
|
/// The amount of time the app can remain in the background for without requesting the PIN/TouchID/FaceID.
|
||||||
let appLockGracePeriod: TimeInterval = 180
|
let appLockGracePeriod: TimeInterval = 180
|
||||||
/// Any codes that the user isn't allowed to use for their PIN.
|
/// Any codes that the user isn't allowed to use for their PIN.
|
||||||
let appLockPINCodeBlockList = ["0000", "1234"]
|
let appLockPINCodeBlockList = ["0000", "1234"]
|
||||||
|
/// The number of attempts the user has made to unlock the app with a PIN code (resets when unlocked).
|
||||||
|
@UserPreference(key: UserDefaultsKeys.appLockNumberOfPINAttempts, defaultValue: 0, storageType: .userDefaults(store))
|
||||||
|
var appLockNumberOfPINAttempts: Int
|
||||||
|
/// The number of attempts the user has made to unlock the app with Touch/Face ID (resets when unlocked).
|
||||||
|
@UserPreference(key: UserDefaultsKeys.appLockNumberOfBiometricAttempts, defaultValue: 0, storageType: .userDefaults(store))
|
||||||
|
var appLockNumberOfBiometricAttempts: Int
|
||||||
|
|
||||||
// MARK: - Authentication
|
// MARK: - Authentication
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ enum AppLockFlowCoordinatorAction: Equatable {
|
|||||||
case lockApp
|
case lockApp
|
||||||
/// Hide the unlock flow.
|
/// Hide the unlock flow.
|
||||||
case unlockApp
|
case unlockApp
|
||||||
|
/// Forces a logout of the user.
|
||||||
|
case forceLogout
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Coordinates the display of any screens shown when the app is locked.
|
/// Coordinates the display of any screens shown when the app is locked.
|
||||||
class AppLockFlowCoordinator: CoordinatorProtocol {
|
class AppLockFlowCoordinator: CoordinatorProtocol {
|
||||||
let appLockService: AppLockServiceProtocol
|
let appLockService: AppLockServiceProtocol
|
||||||
|
let userIndicatorController: UserIndicatorController
|
||||||
let navigationCoordinator: NavigationRootCoordinator
|
let navigationCoordinator: NavigationRootCoordinator
|
||||||
|
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
@@ -36,10 +39,18 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(appLockService: AppLockServiceProtocol, navigationCoordinator: NavigationRootCoordinator) {
|
init(appLockService: AppLockServiceProtocol, userIndicatorController: UserIndicatorController, navigationCoordinator: NavigationRootCoordinator) {
|
||||||
self.appLockService = appLockService
|
self.appLockService = appLockService
|
||||||
|
self.userIndicatorController = userIndicatorController
|
||||||
self.navigationCoordinator = navigationCoordinator
|
self.navigationCoordinator = navigationCoordinator
|
||||||
|
|
||||||
|
appLockService.disabledPublisher
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
// When the service is disabled via a force logout, we need to remove the activity indicator.
|
||||||
|
self?.userIndicatorController.retractAllIndicators()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
|
NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.applicationDidEnterBackground()
|
self?.applicationDidEnterBackground()
|
||||||
@@ -54,7 +65,7 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(navigationCoordinator.toPresentable())
|
AnyView(userIndicatorController.toPresentable())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - App unlock
|
// MARK: - App unlock
|
||||||
@@ -91,6 +102,9 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .appUnlocked:
|
case .appUnlocked:
|
||||||
actionsSubject.send(.unlockApp)
|
actionsSubject.send(.unlockApp)
|
||||||
|
case .forceLogout:
|
||||||
|
userIndicatorController.submitIndicator(UserIndicator(type: .modal, title: L10n.commonSigningOut, persistent: true))
|
||||||
|
actionsSubject.send(.forceLogout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import SwiftState
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
enum AppLockSetupFlowCoordinatorAction: Equatable {
|
||||||
|
case complete
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coordinates the display of any screens used to configure the App Lock feature.
|
||||||
|
class AppLockSetupFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
|
private let presentingFlow: PresentationFlow
|
||||||
|
private let appLockService: AppLockServiceProtocol
|
||||||
|
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||||
|
private let modalNavigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
|
/// The presentation context of the flow.
|
||||||
|
enum PresentationFlow {
|
||||||
|
/// The flow is shown as for mandatory PIN creation in the authentication flow
|
||||||
|
case authentication
|
||||||
|
/// The flow is shown from the Settings screen.
|
||||||
|
case settings
|
||||||
|
}
|
||||||
|
|
||||||
|
/// States the flow can find itself in
|
||||||
|
enum State: StateType {
|
||||||
|
/// The initial state, used before the flow starts
|
||||||
|
case initial
|
||||||
|
/// The unlock screen.
|
||||||
|
case unlock
|
||||||
|
/// The create PIN screen.
|
||||||
|
case createPIN
|
||||||
|
/// The allow biometrics screen.
|
||||||
|
case biometricsPrompt
|
||||||
|
/// The settings screen.
|
||||||
|
case settings
|
||||||
|
/// The flow is finished.
|
||||||
|
case complete
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Events that can be triggered on the flow state machine
|
||||||
|
enum Event: EventType {
|
||||||
|
/// Start the flow.
|
||||||
|
case start
|
||||||
|
/// The user entered a PIN.
|
||||||
|
case pinEntered
|
||||||
|
/// The user completed the biometrics prompt.
|
||||||
|
case biometricsSet
|
||||||
|
/// The user wants to change their PIN.
|
||||||
|
case changePIN
|
||||||
|
/// The user wants to dismiss the flow.
|
||||||
|
case dismiss
|
||||||
|
}
|
||||||
|
|
||||||
|
private let stateMachine: StateMachine<State, Event>
|
||||||
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
private let actionsSubject: PassthroughSubject<AppLockSetupFlowCoordinatorAction, Never> = .init()
|
||||||
|
var actions: AnyPublisher<AppLockSetupFlowCoordinatorAction, Never> {
|
||||||
|
actionsSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(presentingFlow: PresentationFlow, appLockService: AppLockServiceProtocol, navigationStackCoordinator: NavigationStackCoordinator) {
|
||||||
|
self.presentingFlow = presentingFlow
|
||||||
|
self.appLockService = appLockService
|
||||||
|
self.navigationStackCoordinator = navigationStackCoordinator
|
||||||
|
|
||||||
|
stateMachine = .init(state: .initial)
|
||||||
|
configureStateMachine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
stateMachine.tryEvent(.start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||||
|
// Deep links not supported.
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearRoute(animated: Bool) {
|
||||||
|
// Deep links not supported.
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func configureStateMachine() {
|
||||||
|
stateMachine.addRouteMapping { [weak self] event, fromState, _ in
|
||||||
|
guard let self else { return nil }
|
||||||
|
|
||||||
|
switch (event, fromState) {
|
||||||
|
case (.start, .initial):
|
||||||
|
if presentingFlow == .authentication { return .createPIN }
|
||||||
|
return appLockService.isEnabled ? .unlock : .createPIN
|
||||||
|
case (.pinEntered, .unlock):
|
||||||
|
return .settings
|
||||||
|
case (.pinEntered, .createPIN):
|
||||||
|
if presentingFlow == .authentication {
|
||||||
|
return appLockService.biometryType != .none ? .biometricsPrompt : .complete
|
||||||
|
} else {
|
||||||
|
return appLockService.biometricUnlockEnabled || appLockService.biometryType == .none ? .settings : .biometricsPrompt
|
||||||
|
}
|
||||||
|
case (.biometricsSet, .biometricsPrompt):
|
||||||
|
return presentingFlow == .settings ? .settings : .complete
|
||||||
|
case (.changePIN, .settings):
|
||||||
|
return .createPIN
|
||||||
|
case (.dismiss, _):
|
||||||
|
return .complete
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMachine.addAnyHandler(.any => .any) { [weak self] context in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
MXLog.info("Transitioning from `\(context.fromState)` to `\(context.toState)` with event `\(String(describing: context.event))`.")
|
||||||
|
switch (context.fromState, context.toState) {
|
||||||
|
case (.initial, .unlock):
|
||||||
|
showPINUnlock()
|
||||||
|
case (.initial, .createPIN):
|
||||||
|
showCreatePIN()
|
||||||
|
case (.unlock, .settings):
|
||||||
|
showSettings()
|
||||||
|
case (.createPIN, .biometricsPrompt):
|
||||||
|
showBiometricsPrompt()
|
||||||
|
case (.createPIN, .settings):
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
case (.biometricsPrompt, .settings):
|
||||||
|
showSettings()
|
||||||
|
case (.settings, .createPIN):
|
||||||
|
showCreatePIN()
|
||||||
|
case (_, .complete):
|
||||||
|
complete(from: context.fromState)
|
||||||
|
default:
|
||||||
|
fatalError("Unhandled transition.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMachine.addErrorHandler { context in
|
||||||
|
fatalError("Unexpected transition from `\(context.fromState)` to `\(context.toState)` with event `\(String(describing: context.event))`.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showCreatePIN() {
|
||||||
|
// Despite appLockService.isMandatory existing, we don't use that here,
|
||||||
|
// to allow for cancellation when changing the PIN code within settings.
|
||||||
|
let isMandatory = presentingFlow == .authentication
|
||||||
|
|
||||||
|
let coordinator = AppLockSetupPINScreenCoordinator(parameters: .init(initialMode: .create,
|
||||||
|
isMandatory: isMandatory,
|
||||||
|
appLockService: appLockService))
|
||||||
|
coordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .cancel:
|
||||||
|
stateMachine.tryEvent(.dismiss)
|
||||||
|
case .complete:
|
||||||
|
stateMachine.tryEvent(.pinEntered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
if presentingFlow == .authentication {
|
||||||
|
navigationStackCoordinator.push(coordinator)
|
||||||
|
} else {
|
||||||
|
modalNavigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(modalNavigationStackCoordinator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showBiometricsPrompt() {
|
||||||
|
let coordinator = AppLockSetupBiometricsScreenCoordinator(parameters: .init(appLockService: appLockService))
|
||||||
|
coordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .continue:
|
||||||
|
stateMachine.tryEvent(.biometricsSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
if presentingFlow == .authentication {
|
||||||
|
navigationStackCoordinator.push(coordinator)
|
||||||
|
} else {
|
||||||
|
modalNavigationStackCoordinator.push(coordinator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showPINUnlock() {
|
||||||
|
let coordinator = AppLockSetupPINScreenCoordinator(parameters: .init(initialMode: .unlock,
|
||||||
|
isMandatory: false,
|
||||||
|
appLockService: appLockService))
|
||||||
|
coordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .cancel:
|
||||||
|
stateMachine.tryEvent(.dismiss)
|
||||||
|
case .complete:
|
||||||
|
stateMachine.tryEvent(.pinEntered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
modalNavigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(modalNavigationStackCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showSettings() {
|
||||||
|
let coordinator = AppLockSetupSettingsScreenCoordinator(parameters: .init(isMandatory: appLockService.isMandatory,
|
||||||
|
appLockService: appLockService))
|
||||||
|
coordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .changePINCode:
|
||||||
|
stateMachine.tryEvent(.changePIN)
|
||||||
|
case .appLockDisabled:
|
||||||
|
stateMachine.tryEvent(.dismiss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
navigationStackCoordinator.push(coordinator, animated: false) { [weak self] in
|
||||||
|
self?.actionsSubject.send(.complete)
|
||||||
|
}
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tear down the flow for completion.
|
||||||
|
private func complete(from state: State) {
|
||||||
|
switch state {
|
||||||
|
case .initial, .complete: fatalError()
|
||||||
|
case .unlock:
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
actionsSubject.send(.complete)
|
||||||
|
case .createPIN:
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
actionsSubject.send(.complete)
|
||||||
|
case .biometricsPrompt:
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
actionsSubject.send(.complete)
|
||||||
|
case .settings:
|
||||||
|
navigationStackCoordinator.pop()
|
||||||
|
actionsSubject.send(.complete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -328,6 +328,8 @@ public enum L10n {
|
|||||||
public static var commonSettings: String { return L10n.tr("Localizable", "common_settings") }
|
public static var commonSettings: String { return L10n.tr("Localizable", "common_settings") }
|
||||||
/// Shared location
|
/// Shared location
|
||||||
public static var commonSharedLocation: String { return L10n.tr("Localizable", "common_shared_location") }
|
public static var commonSharedLocation: String { return L10n.tr("Localizable", "common_shared_location") }
|
||||||
|
/// Signing out
|
||||||
|
public static var commonSigningOut: String { return L10n.tr("Localizable", "common_signing_out") }
|
||||||
/// Starting chat…
|
/// Starting chat…
|
||||||
public static var commonStartingChat: String { return L10n.tr("Localizable", "common_starting_chat") }
|
public static var commonStartingChat: String { return L10n.tr("Localizable", "common_starting_chat") }
|
||||||
/// Sticker
|
/// Sticker
|
||||||
@@ -724,8 +726,10 @@ public enum L10n {
|
|||||||
public static var screenAppLockSignoutAlertMessage: String { return L10n.tr("Localizable", "screen_app_lock_signout_alert_message") }
|
public static var screenAppLockSignoutAlertMessage: String { return L10n.tr("Localizable", "screen_app_lock_signout_alert_message") }
|
||||||
/// You are being signed out
|
/// You are being signed out
|
||||||
public static var screenAppLockSignoutAlertTitle: String { return L10n.tr("Localizable", "screen_app_lock_signout_alert_title") }
|
public static var screenAppLockSignoutAlertTitle: String { return L10n.tr("Localizable", "screen_app_lock_signout_alert_title") }
|
||||||
/// You have 3 attempts to unlock
|
/// Plural format key: "%#@COUNT@"
|
||||||
public static var screenAppLockSubtitle: String { return L10n.tr("Localizable", "screen_app_lock_subtitle") }
|
public static func screenAppLockSubtitle(_ p1: Int) -> String {
|
||||||
|
return L10n.tr("Localizable", "screen_app_lock_subtitle", p1)
|
||||||
|
}
|
||||||
/// Plural format key: "%#@COUNT@"
|
/// Plural format key: "%#@COUNT@"
|
||||||
public static func screenAppLockSubtitleWrongPin(_ p1: Int) -> String {
|
public static func screenAppLockSubtitleWrongPin(_ p1: Int) -> String {
|
||||||
return L10n.tr("Localizable", "screen_app_lock_subtitle_wrong_pin", p1)
|
return L10n.tr("Localizable", "screen_app_lock_subtitle_wrong_pin", p1)
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ class AnalyticsClientMock: AnalyticsClientProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class AppLockServiceMock: AppLockServiceProtocol {
|
class AppLockServiceMock: AppLockServiceProtocol {
|
||||||
|
var isMandatory: Bool {
|
||||||
|
get { return underlyingIsMandatory }
|
||||||
|
set(value) { underlyingIsMandatory = value }
|
||||||
|
}
|
||||||
|
var underlyingIsMandatory: Bool!
|
||||||
var isEnabled: Bool {
|
var isEnabled: Bool {
|
||||||
get { return underlyingIsEnabled }
|
get { return underlyingIsEnabled }
|
||||||
set(value) { underlyingIsEnabled = value }
|
set(value) { underlyingIsEnabled = value }
|
||||||
@@ -132,6 +137,21 @@ class AppLockServiceMock: AppLockServiceProtocol {
|
|||||||
set(value) { underlyingBiometricUnlockEnabled = value }
|
set(value) { underlyingBiometricUnlockEnabled = value }
|
||||||
}
|
}
|
||||||
var underlyingBiometricUnlockEnabled: Bool!
|
var underlyingBiometricUnlockEnabled: Bool!
|
||||||
|
var disabledPublisher: AnyPublisher<Void, Never> {
|
||||||
|
get { return underlyingDisabledPublisher }
|
||||||
|
set(value) { underlyingDisabledPublisher = value }
|
||||||
|
}
|
||||||
|
var underlyingDisabledPublisher: AnyPublisher<Void, Never>!
|
||||||
|
var numberOfPINAttempts: AnyPublisher<Int, Never> {
|
||||||
|
get { return underlyingNumberOfPINAttempts }
|
||||||
|
set(value) { underlyingNumberOfPINAttempts = value }
|
||||||
|
}
|
||||||
|
var underlyingNumberOfPINAttempts: AnyPublisher<Int, Never>!
|
||||||
|
var numberOfBiometricAttempts: AnyPublisher<Int, Never> {
|
||||||
|
get { return underlyingNumberOfBiometricAttempts }
|
||||||
|
set(value) { underlyingNumberOfBiometricAttempts = value }
|
||||||
|
}
|
||||||
|
var underlyingNumberOfBiometricAttempts: AnyPublisher<Int, Never>!
|
||||||
|
|
||||||
//MARK: - setupPINCode
|
//MARK: - setupPINCode
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ struct AppLockScreenCoordinatorParameters {
|
|||||||
enum AppLockScreenCoordinatorAction {
|
enum AppLockScreenCoordinatorAction {
|
||||||
/// The user has successfully unlocked the app.
|
/// The user has successfully unlocked the app.
|
||||||
case appUnlocked
|
case appUnlocked
|
||||||
|
/// The user failed to unlock the app (or forgot their PIN).
|
||||||
|
case forceLogout
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AppLockScreenCoordinator: CoordinatorProtocol {
|
final class AppLockScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -51,6 +53,8 @@ final class AppLockScreenCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .appUnlocked:
|
case .appUnlocked:
|
||||||
self.actionsSubject.send(.appUnlocked)
|
self.actionsSubject.send(.appUnlocked)
|
||||||
|
case .forceLogout:
|
||||||
|
self.actionsSubject.send(.forceLogout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import Foundation
|
|||||||
enum AppLockScreenViewModelAction {
|
enum AppLockScreenViewModelAction {
|
||||||
/// The user has successfully unlocked the app.
|
/// The user has successfully unlocked the app.
|
||||||
case appUnlocked
|
case appUnlocked
|
||||||
|
/// The user failed to unlock the app (or forgot their PIN).
|
||||||
|
case forceLogout
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppLockScreenViewState: BindableState {
|
struct AppLockScreenViewState: BindableState {
|
||||||
@@ -36,7 +38,7 @@ struct AppLockScreenViewState: BindableState {
|
|||||||
/// The string shown in the screen's subtitle.
|
/// The string shown in the screen's subtitle.
|
||||||
var subtitle: String {
|
var subtitle: String {
|
||||||
if !isSubtitleWarning {
|
if !isSubtitleWarning {
|
||||||
return L10n.screenAppLockSubtitle
|
return L10n.screenAppLockSubtitle(maximumAttempts)
|
||||||
} else {
|
} else {
|
||||||
return L10n.screenAppLockSubtitleWrongPin(maximumAttempts - numberOfPINAttempts)
|
return L10n.screenAppLockSubtitleWrongPin(maximumAttempts - numberOfPINAttempts)
|
||||||
}
|
}
|
||||||
@@ -50,8 +52,8 @@ struct AppLockScreenViewStateBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum AppLockScreenAlertType {
|
enum AppLockScreenAlertType {
|
||||||
/// The user has failed too many times, they're being signed out.
|
/// The user has failed too many times, they're being logged out.
|
||||||
case forceSignOut
|
case forcedLogout
|
||||||
/// The user has forgotten their PIN, confirm they're happy to sign out.
|
/// The user has forgotten their PIN, confirm they're happy to sign out.
|
||||||
case confirmResetPIN
|
case confirmResetPIN
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ class AppLockScreenViewModel: AppLockScreenViewModelType, AppLockScreenViewModel
|
|||||||
self.appLockService = appLockService
|
self.appLockService = appLockService
|
||||||
|
|
||||||
super.init(initialViewState: AppLockScreenViewState(bindings: .init()))
|
super.init(initialViewState: AppLockScreenViewState(bindings: .init()))
|
||||||
|
|
||||||
|
appLockService.numberOfPINAttempts
|
||||||
|
.weakAssign(to: \.state.numberOfPINAttempts, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
showForceLogoutAlertIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -58,24 +64,21 @@ class AppLockScreenViewModel: AppLockScreenViewModelType, AppLockScreenViewModel
|
|||||||
state.bindings.alertInfo = .init(id: .confirmResetPIN,
|
state.bindings.alertInfo = .init(id: .confirmResetPIN,
|
||||||
title: L10n.screenAppLockSignoutAlertTitle,
|
title: L10n.screenAppLockSignoutAlertTitle,
|
||||||
message: L10n.screenAppLockSignoutAlertMessage,
|
message: L10n.screenAppLockSignoutAlertMessage,
|
||||||
primaryButton: .init(title: L10n.actionOk, action: forceSignOut),
|
primaryButton: .init(title: L10n.actionOk) { self.actionsSubject.send(.forceLogout) },
|
||||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleInvalidPIN() {
|
private func handleInvalidPIN() {
|
||||||
MXLog.warning("Invalid PIN code entered.")
|
MXLog.warning("Invalid PIN code entered.")
|
||||||
state.numberOfPINAttempts += 1
|
showForceLogoutAlertIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
if state.numberOfPINAttempts == 3 {
|
private func showForceLogoutAlertIfNeeded() {
|
||||||
state.bindings.alertInfo = .init(id: .forceSignOut,
|
if state.numberOfPINAttempts >= 3 {
|
||||||
|
state.bindings.alertInfo = .init(id: .forcedLogout,
|
||||||
title: L10n.screenAppLockSignoutAlertTitle,
|
title: L10n.screenAppLockSignoutAlertTitle,
|
||||||
message: L10n.screenAppLockSignoutAlertMessage,
|
message: L10n.screenAppLockSignoutAlertMessage,
|
||||||
primaryButton: .init(title: L10n.actionOk, action: nil))
|
primaryButton: .init(title: L10n.actionOk) { self.actionsSubject.send(.forceLogout) })
|
||||||
forceSignOut()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func forceSignOut() {
|
|
||||||
// To be implemented.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -31,8 +31,10 @@ struct AppLockSetupBiometricsScreen: View {
|
|||||||
.padding(.top, UIConstants.iconTopPaddingToNavigationBar)
|
.padding(.top, UIConstants.iconTopPaddingToNavigationBar)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.toolbar(.visible, for: .navigationBar)
|
|
||||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||||
|
.toolbar(.visible, for: .navigationBar)
|
||||||
|
.navigationBarBackButtonHidden()
|
||||||
|
.interactiveDismissDisabled()
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
buttons
|
buttons
|
||||||
.padding(.top, 16)
|
.padding(.top, 16)
|
||||||
|
|||||||
+5
-2
@@ -21,6 +21,8 @@ struct AppLockSetupPINScreenCoordinatorParameters {
|
|||||||
/// Whether the screen should start in create or unlock mode.
|
/// Whether the screen should start in create or unlock mode.
|
||||||
/// Specifying confirm here will raise a fatal error.
|
/// Specifying confirm here will raise a fatal error.
|
||||||
let initialMode: AppLockSetupPINScreenMode
|
let initialMode: AppLockSetupPINScreenMode
|
||||||
|
/// Whether the screen is mandatory or can be cancelled by the user.
|
||||||
|
let isMandatory: Bool
|
||||||
let appLockService: AppLockServiceProtocol
|
let appLockService: AppLockServiceProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ final class AppLockSetupPINScreenCoordinator: CoordinatorProtocol {
|
|||||||
|
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: parameters.initialMode,
|
viewModel = AppLockSetupPINScreenViewModel(initialMode: parameters.initialMode,
|
||||||
|
isMandatory: parameters.isMandatory,
|
||||||
appLockService: parameters.appLockService)
|
appLockService: parameters.appLockService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +59,9 @@ final class AppLockSetupPINScreenCoordinator: CoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .complete:
|
case .complete:
|
||||||
break
|
actionsSubject.send(.complete)
|
||||||
case .cancel:
|
case .cancel:
|
||||||
break
|
actionsSubject.send(.cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
+2
@@ -35,6 +35,8 @@ enum AppLockSetupPINScreenMode {
|
|||||||
struct AppLockSetupPINScreenViewState: BindableState {
|
struct AppLockSetupPINScreenViewState: BindableState {
|
||||||
/// The current mode that the screen is in.
|
/// The current mode that the screen is in.
|
||||||
var mode: AppLockSetupPINScreenMode
|
var mode: AppLockSetupPINScreenMode
|
||||||
|
/// Whether the screen is mandatory or can be cancelled.
|
||||||
|
let isMandatory: Bool
|
||||||
|
|
||||||
var title: String {
|
var title: String {
|
||||||
switch mode {
|
switch mode {
|
||||||
|
|||||||
+2
-2
@@ -30,9 +30,9 @@ class AppLockSetupPINScreenViewModel: AppLockSetupPINScreenViewModelType, AppLoc
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(initialMode: AppLockSetupPINScreenMode, appLockService: AppLockServiceProtocol) {
|
init(initialMode: AppLockSetupPINScreenMode, isMandatory: Bool, appLockService: AppLockServiceProtocol) {
|
||||||
self.appLockService = appLockService
|
self.appLockService = appLockService
|
||||||
super.init(initialViewState: AppLockSetupPINScreenViewState(mode: initialMode, bindings: .init(pinCode: "")))
|
super.init(initialViewState: AppLockSetupPINScreenViewState(mode: initialMode, isMandatory: isMandatory, bindings: .init(pinCode: "")))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|||||||
+29
-3
@@ -21,6 +21,8 @@ import SwiftUI
|
|||||||
struct AppLockSetupPINScreen: View {
|
struct AppLockSetupPINScreen: View {
|
||||||
@ObservedObject var context: AppLockSetupPINScreenViewModel.Context
|
@ObservedObject var context: AppLockSetupPINScreenViewModel.Context
|
||||||
|
|
||||||
|
@FocusState private var textFieldFocus
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 48) {
|
VStack(spacing: 48) {
|
||||||
@@ -28,6 +30,7 @@ struct AppLockSetupPINScreen: View {
|
|||||||
|
|
||||||
PINTextField(pinCode: $context.pinCode,
|
PINTextField(pinCode: $context.pinCode,
|
||||||
isSecure: context.viewState.mode == .unlock)
|
isSecure: context.viewState.mode == .unlock)
|
||||||
|
.focused($textFieldFocus)
|
||||||
.onChange(of: context.pinCode) { newValue in
|
.onChange(of: context.pinCode) { newValue in
|
||||||
guard newValue.count == 4 else { return }
|
guard newValue.count == 4 else { return }
|
||||||
context.send(viewAction: .submitPINCode)
|
context.send(viewAction: .submitPINCode)
|
||||||
@@ -38,6 +41,12 @@ struct AppLockSetupPINScreen: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||||
|
.toolbar { toolbar }
|
||||||
|
.toolbar(.visible, for: .navigationBar)
|
||||||
|
.navigationBarBackButtonHidden()
|
||||||
|
.interactiveDismissDisabled(context.viewState.isMandatory)
|
||||||
|
.alert(item: $context.alertInfo)
|
||||||
|
.onAppear { textFieldFocus = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
var header: some View {
|
var header: some View {
|
||||||
@@ -59,6 +68,17 @@ struct AppLockSetupPINScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ToolbarContentBuilder
|
||||||
|
var toolbar: some ToolbarContent {
|
||||||
|
if !context.viewState.isMandatory {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button(L10n.actionCancel) {
|
||||||
|
context.send(viewAction: .cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
@@ -66,9 +86,15 @@ struct AppLockSetupPINScreen: View {
|
|||||||
struct AppLockSetupPINScreen_Previews: PreviewProvider, TestablePreview {
|
struct AppLockSetupPINScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let service = AppLockService(keychainController: KeychainControllerMock(),
|
static let service = AppLockService(keychainController: KeychainControllerMock(),
|
||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
static let createViewModel = AppLockSetupPINScreenViewModel(initialMode: .create, appLockService: service)
|
static let createViewModel = AppLockSetupPINScreenViewModel(initialMode: .create,
|
||||||
static let confirmViewModel = AppLockSetupPINScreenViewModel(initialMode: .confirm, appLockService: service)
|
isMandatory: false,
|
||||||
static let unlockViewModel = AppLockSetupPINScreenViewModel(initialMode: .unlock, appLockService: service)
|
appLockService: service)
|
||||||
|
static let confirmViewModel = AppLockSetupPINScreenViewModel(initialMode: .confirm,
|
||||||
|
isMandatory: false,
|
||||||
|
appLockService: service)
|
||||||
|
static let unlockViewModel = AppLockSetupPINScreenViewModel(initialMode: .unlock,
|
||||||
|
isMandatory: false,
|
||||||
|
appLockService: service)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
|||||||
+17
-14
@@ -17,28 +17,31 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AppLockSettingsScreenCoordinatorParameters {
|
struct AppLockSetupSettingsScreenCoordinatorParameters {
|
||||||
|
/// Whether App Lock is mandatory and can be disabled by the user.
|
||||||
|
let isMandatory: Bool
|
||||||
let appLockService: AppLockServiceProtocol
|
let appLockService: AppLockServiceProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AppLockSettingsScreenCoordinatorAction {
|
enum AppLockSetupSettingsScreenCoordinatorAction {
|
||||||
case done
|
case changePINCode
|
||||||
|
case appLockDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AppLockSettingsScreenCoordinator: CoordinatorProtocol {
|
final class AppLockSetupSettingsScreenCoordinator: CoordinatorProtocol {
|
||||||
private let parameters: AppLockSettingsScreenCoordinatorParameters
|
private let parameters: AppLockSetupSettingsScreenCoordinatorParameters
|
||||||
private var viewModel: AppLockSettingsScreenViewModelProtocol
|
private var viewModel: AppLockSetupSettingsScreenViewModelProtocol
|
||||||
private let actionsSubject: PassthroughSubject<AppLockSettingsScreenCoordinatorAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<AppLockSetupSettingsScreenCoordinatorAction, Never> = .init()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
var actions: AnyPublisher<AppLockSettingsScreenCoordinatorAction, Never> {
|
var actions: AnyPublisher<AppLockSetupSettingsScreenCoordinatorAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(parameters: AppLockSettingsScreenCoordinatorParameters) {
|
init(parameters: AppLockSetupSettingsScreenCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
viewModel = AppLockSettingsScreenViewModel(appLockService: parameters.appLockService)
|
viewModel = AppLockSetupSettingsScreenViewModel(appLockService: parameters.appLockService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
@@ -48,15 +51,15 @@ final class AppLockSettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .changePINCode:
|
case .changePINCode:
|
||||||
break
|
actionsSubject.send(.changePINCode)
|
||||||
case .done:
|
case .appLockDisabled:
|
||||||
self.actionsSubject.send(.done)
|
actionsSubject.send(.appLockDisabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(AppLockSettingsScreen(context: viewModel.context))
|
AnyView(AppLockSetupSettingsScreen(context: viewModel.context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+11
-9
@@ -17,36 +17,38 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
|
|
||||||
enum AppLockSettingsScreenViewModelAction {
|
enum AppLockSetupSettingsScreenViewModelAction {
|
||||||
/// The user would like to enter new PIN code.
|
/// The user would like to enter new PIN code.
|
||||||
case changePINCode
|
case changePINCode
|
||||||
case done
|
/// The user has disabled the App Lock feature.
|
||||||
|
case appLockDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppLockSettingsScreenViewState: BindableState {
|
struct AppLockSetupSettingsScreenViewState: BindableState {
|
||||||
|
/// Whether App Lock is mandatory and can be disabled by the user.
|
||||||
|
let isMandatory: Bool
|
||||||
let biometryType: LABiometryType
|
let biometryType: LABiometryType
|
||||||
var bindings: AppLockSettingsScreenViewStateBindings
|
var bindings: AppLockSetupSettingsScreenViewStateBindings
|
||||||
|
|
||||||
var supportsBiometry: Bool { biometryType != .none }
|
var supportsBiometry: Bool { biometryType != .none }
|
||||||
var enableBiometryTitle: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) }
|
var enableBiometryTitle: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppLockSettingsScreenViewStateBindings {
|
struct AppLockSetupSettingsScreenViewStateBindings {
|
||||||
var enableBiometrics: Bool
|
var enableBiometrics: Bool
|
||||||
var alertInfo: AlertInfo<AppLockSettingsScreenAlertType>?
|
var alertInfo: AlertInfo<AppLockSetupSettingsScreenAlertType>?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AppLockSettingsScreenAlertType {
|
enum AppLockSetupSettingsScreenAlertType {
|
||||||
/// The alert shown to confirm the user would like to remove their PIN.
|
/// The alert shown to confirm the user would like to remove their PIN.
|
||||||
case confirmRemovePINCode
|
case confirmRemovePINCode
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AppLockSettingsScreenViewAction {
|
enum AppLockSetupSettingsScreenViewAction {
|
||||||
/// The user would like to enter a new PIN code.
|
/// The user would like to enter a new PIN code.
|
||||||
case changePINCode
|
case changePINCode
|
||||||
/// The user would like to disable the App Lock feature.
|
/// The user would like to disable the App Lock feature.
|
||||||
case disable
|
case disable
|
||||||
/// The user has toggled the biometrics setting.
|
/// The user has toggled the biometrics setting.
|
||||||
case enableBiometricsChanged
|
case enableBiometricsChanged
|
||||||
case done
|
|
||||||
}
|
}
|
||||||
+9
-10
@@ -17,25 +17,26 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias AppLockSettingsScreenViewModelType = StateStoreViewModel<AppLockSettingsScreenViewState, AppLockSettingsScreenViewAction>
|
typealias AppLockSetupSettingsScreenViewModelType = StateStoreViewModel<AppLockSetupSettingsScreenViewState, AppLockSetupSettingsScreenViewAction>
|
||||||
|
|
||||||
class AppLockSettingsScreenViewModel: AppLockSettingsScreenViewModelType, AppLockSettingsScreenViewModelProtocol {
|
class AppLockSetupSettingsScreenViewModel: AppLockSetupSettingsScreenViewModelType, AppLockSetupSettingsScreenViewModelProtocol {
|
||||||
private let appLockService: AppLockServiceProtocol
|
private let appLockService: AppLockServiceProtocol
|
||||||
private var actionsSubject: PassthroughSubject<AppLockSettingsScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<AppLockSetupSettingsScreenViewModelAction, Never> = .init()
|
||||||
|
|
||||||
var actions: AnyPublisher<AppLockSettingsScreenViewModelAction, Never> {
|
var actions: AnyPublisher<AppLockSetupSettingsScreenViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(appLockService: AppLockServiceProtocol) {
|
init(appLockService: AppLockServiceProtocol) {
|
||||||
self.appLockService = appLockService
|
self.appLockService = appLockService
|
||||||
super.init(initialViewState: AppLockSettingsScreenViewState(biometryType: appLockService.biometryType,
|
super.init(initialViewState: AppLockSetupSettingsScreenViewState(isMandatory: appLockService.isMandatory,
|
||||||
bindings: .init(enableBiometrics: appLockService.biometricUnlockEnabled)))
|
biometryType: appLockService.biometryType,
|
||||||
|
bindings: .init(enableBiometrics: appLockService.biometricUnlockEnabled)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: AppLockSettingsScreenViewAction) {
|
override func process(viewAction: AppLockSetupSettingsScreenViewAction) {
|
||||||
MXLog.info("View model: received view action: \(viewAction)")
|
MXLog.info("View model: received view action: \(viewAction)")
|
||||||
|
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
@@ -45,8 +46,6 @@ class AppLockSettingsScreenViewModel: AppLockSettingsScreenViewModelType, AppLoc
|
|||||||
showRemovePINAlert()
|
showRemovePINAlert()
|
||||||
case .enableBiometricsChanged:
|
case .enableBiometricsChanged:
|
||||||
appLockService.biometricUnlockEnabled = state.bindings.enableBiometrics
|
appLockService.biometricUnlockEnabled = state.bindings.enableBiometrics
|
||||||
case .done:
|
|
||||||
actionsSubject.send(.done)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +63,6 @@ class AppLockSettingsScreenViewModel: AppLockSettingsScreenViewModelType, AppLoc
|
|||||||
/// Removes the user's PIN code, disabling the App Lock feature.
|
/// Removes the user's PIN code, disabling the App Lock feature.
|
||||||
private func completeRemovePIN() {
|
private func completeRemovePIN() {
|
||||||
appLockService.disable()
|
appLockService.disable()
|
||||||
actionsSubject.send(.done)
|
actionsSubject.send(.appLockDisabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-3
@@ -17,7 +17,7 @@
|
|||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol AppLockSettingsScreenViewModelProtocol {
|
protocol AppLockSetupSettingsScreenViewModelProtocol {
|
||||||
var actions: AnyPublisher<AppLockSettingsScreenViewModelAction, Never> { get }
|
var actions: AnyPublisher<AppLockSetupSettingsScreenViewModelAction, Never> { get }
|
||||||
var context: AppLockSettingsScreenViewModelType.Context { get }
|
var context: AppLockSetupSettingsScreenViewModelType.Context { get }
|
||||||
}
|
}
|
||||||
+16
-13
@@ -17,16 +17,19 @@
|
|||||||
import Compound
|
import Compound
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AppLockSettingsScreen: View {
|
struct AppLockSetupSettingsScreen: View {
|
||||||
@ObservedObject var context: AppLockSettingsScreenViewModel.Context
|
@ObservedObject var context: AppLockSetupSettingsScreenViewModel.Context
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
ListRow(label: .plain(title: L10n.screenAppLockSettingsChangePin),
|
ListRow(label: .plain(title: L10n.screenAppLockSettingsChangePin),
|
||||||
kind: .button { context.send(viewAction: .changePINCode) })
|
kind: .button { context.send(viewAction: .changePINCode) })
|
||||||
ListRow(label: .plain(title: L10n.screenAppLockSettingsRemovePin, role: .destructive),
|
|
||||||
kind: .button { context.send(viewAction: .disable) })
|
if !context.viewState.isMandatory {
|
||||||
|
ListRow(label: .plain(title: L10n.screenAppLockSettingsRemovePin, role: .destructive),
|
||||||
|
kind: .button { context.send(viewAction: .disable) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.viewState.supportsBiometry {
|
if context.viewState.supportsBiometry {
|
||||||
@@ -48,25 +51,25 @@ struct AppLockSettingsScreen: View {
|
|||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct AppLockSettingsScreen_Previews: PreviewProvider, TestablePreview {
|
struct AppLockSetupSettingsScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let faceIDViewModel = AppLockSettingsScreenViewModel(appLockService: AppLockServiceMock.mock(biometryType: .faceID))
|
static let faceIDViewModel = AppLockSetupSettingsScreenViewModel(appLockService: AppLockServiceMock.mock(biometryType: .faceID))
|
||||||
static let touchIDViewModel = AppLockSettingsScreenViewModel(appLockService: AppLockServiceMock.mock(biometryType: .touchID))
|
static let touchIDViewModel = AppLockSetupSettingsScreenViewModel(appLockService: AppLockServiceMock.mock(isMandatory: true, biometryType: .touchID))
|
||||||
static let biometricsUnavailableViewModel = AppLockSettingsScreenViewModel(appLockService: AppLockServiceMock.mock(biometryType: .none))
|
static let biometricsUnavailableViewModel = AppLockSetupSettingsScreenViewModel(appLockService: AppLockServiceMock.mock(biometryType: .none))
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
AppLockSettingsScreen(context: faceIDViewModel.context)
|
AppLockSetupSettingsScreen(context: faceIDViewModel.context)
|
||||||
}
|
}
|
||||||
.previewDisplayName("Face ID")
|
.previewDisplayName("Face ID")
|
||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
AppLockSettingsScreen(context: touchIDViewModel.context)
|
AppLockSetupSettingsScreen(context: touchIDViewModel.context)
|
||||||
}
|
}
|
||||||
.previewDisplayName("Touch ID")
|
.previewDisplayName("Touch ID (Mandatory)")
|
||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
AppLockSettingsScreen(context: biometricsUnavailableViewModel.context)
|
AppLockSetupSettingsScreen(context: biometricsUnavailableViewModel.context)
|
||||||
}
|
}
|
||||||
.previewDisplayName("No Biometrics")
|
.previewDisplayName("PIN only")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,10 +29,12 @@ class AuthenticationCoordinator: CoordinatorProtocol {
|
|||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
private let analytics: AnalyticsService
|
private let analytics: AnalyticsService
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
private let appLockService: AppLockServiceProtocol
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
private var oidcPresenter: OIDCAuthenticationPresenter?
|
private var oidcPresenter: OIDCAuthenticationPresenter?
|
||||||
|
private var appLockFlowCoordinator: AppLockSetupFlowCoordinator?
|
||||||
|
|
||||||
weak var delegate: AuthenticationCoordinatorDelegate?
|
weak var delegate: AuthenticationCoordinatorDelegate?
|
||||||
|
|
||||||
@@ -40,12 +42,14 @@ class AuthenticationCoordinator: CoordinatorProtocol {
|
|||||||
navigationStackCoordinator: NavigationStackCoordinator,
|
navigationStackCoordinator: NavigationStackCoordinator,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
analytics: AnalyticsService,
|
analytics: AnalyticsService,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
|
appLockService: AppLockServiceProtocol) {
|
||||||
self.authenticationService = authenticationService
|
self.authenticationService = authenticationService
|
||||||
self.navigationStackCoordinator = navigationStackCoordinator
|
self.navigationStackCoordinator = navigationStackCoordinator
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
|
self.appLockService = appLockService
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
@@ -237,25 +241,46 @@ class AuthenticationCoordinator: CoordinatorProtocol {
|
|||||||
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
||||||
appSettings.lastLoginDate = .now
|
appSettings.lastLoginDate = .now
|
||||||
|
|
||||||
showAnalyticsPromptIfNeeded { [weak self] in
|
if appSettings.appLockIsMandatory, !appLockService.isEnabled {
|
||||||
guard let self else { return }
|
showAppLockSetupFlow(userSession: userSession)
|
||||||
self.delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
|
} else if analytics.shouldShowAnalyticsPrompt {
|
||||||
|
showAnalyticsPrompt(userSession: userSession)
|
||||||
|
} else {
|
||||||
|
delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showAnalyticsPromptIfNeeded(completion: @escaping () -> Void) {
|
private func showAppLockSetupFlow(userSession: UserSessionProtocol) {
|
||||||
guard analytics.shouldShowAnalyticsPrompt else {
|
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .authentication,
|
||||||
completion()
|
appLockService: appLockService,
|
||||||
return
|
navigationStackCoordinator: navigationStackCoordinator)
|
||||||
|
coordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .complete:
|
||||||
|
appLockFlowCoordinator = nil
|
||||||
|
if analytics.shouldShowAnalyticsPrompt {
|
||||||
|
showAnalyticsPrompt(userSession: userSession)
|
||||||
|
} else {
|
||||||
|
delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
appLockFlowCoordinator = coordinator
|
||||||
|
coordinator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showAnalyticsPrompt(userSession: UserSessionProtocol) {
|
||||||
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analytics, termsURL: appSettings.analyticsConfiguration.termsURL)
|
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analytics, termsURL: appSettings.analyticsConfiguration.termsURL)
|
||||||
|
|
||||||
coordinator.actions
|
coordinator.actions
|
||||||
.sink { action in
|
.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .done:
|
case .done:
|
||||||
completion()
|
delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
private let parameters: SettingsScreenCoordinatorParameters
|
private let parameters: SettingsScreenCoordinatorParameters
|
||||||
private var viewModel: SettingsScreenViewModelProtocol
|
private var viewModel: SettingsScreenViewModelProtocol
|
||||||
|
|
||||||
|
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
|
||||||
|
|
||||||
private let actionsSubject: PassthroughSubject<SettingsScreenCoordinatorAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<SettingsScreenCoordinatorAction, Never> = .init()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
case .analytics:
|
case .analytics:
|
||||||
presentAnalyticsScreen()
|
presentAnalyticsScreen()
|
||||||
case .appLock:
|
case .appLock:
|
||||||
presentAppLockSettingsScreen()
|
presentAppLockSetupFlow()
|
||||||
case .reportBug:
|
case .reportBug:
|
||||||
presentBugReportScreen()
|
presentBugReportScreen()
|
||||||
case .about:
|
case .about:
|
||||||
@@ -146,9 +148,27 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
parameters.navigationStackCoordinator?.push(coordinator)
|
parameters.navigationStackCoordinator?.push(coordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentAppLockSettingsScreen() {
|
private func presentAppLockSetupFlow() {
|
||||||
let coordinator = AppLockSettingsScreenCoordinator(parameters: .init(appLockService: parameters.appLockService))
|
guard let navigationStackCoordinator = parameters.navigationStackCoordinator else {
|
||||||
parameters.navigationStackCoordinator?.push(coordinator)
|
MXLog.error("The navigation stack has gone! 🌫️")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .settings,
|
||||||
|
appLockService: parameters.appLockService,
|
||||||
|
navigationStackCoordinator: navigationStackCoordinator)
|
||||||
|
coordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .complete:
|
||||||
|
// The flow coordinator tidies up the stack, no need to do anything.
|
||||||
|
appLockSetupFlowCoordinator = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
appLockSetupFlowCoordinator = coordinator
|
||||||
|
coordinator.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentBugReportScreen() {
|
private func presentBugReportScreen() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
|
|
||||||
/// The service responsible for locking and unlocking the app.
|
/// The service responsible for locking and unlocking the app.
|
||||||
@@ -24,6 +25,8 @@ class AppLockService: AppLockServiceProtocol {
|
|||||||
|
|
||||||
private let timer: AppLockTimer
|
private let timer: AppLockTimer
|
||||||
|
|
||||||
|
var isMandatory: Bool { appSettings.appLockIsMandatory }
|
||||||
|
|
||||||
var isEnabled: Bool {
|
var isEnabled: Bool {
|
||||||
do {
|
do {
|
||||||
guard appSettings.appLockFlowEnabled else { return false }
|
guard appSettings.appLockFlowEnabled else { return false }
|
||||||
@@ -38,6 +41,12 @@ class AppLockService: AppLockServiceProtocol {
|
|||||||
var biometryType: LABiometryType { context.biometryType }
|
var biometryType: LABiometryType { context.biometryType }
|
||||||
var biometricUnlockEnabled = false // Needs to be stored, not sure if in the keychain or defaults yet.
|
var biometricUnlockEnabled = false // Needs to be stored, not sure if in the keychain or defaults yet.
|
||||||
|
|
||||||
|
var numberOfPINAttempts: AnyPublisher<Int, Never> { appSettings.$appLockNumberOfPINAttempts }
|
||||||
|
var numberOfBiometricAttempts: AnyPublisher<Int, Never> { appSettings.$appLockNumberOfBiometricAttempts }
|
||||||
|
|
||||||
|
private var disabledSubject: PassthroughSubject<Void, Never> = .init()
|
||||||
|
var disabledPublisher: AnyPublisher<Void, Never> { disabledSubject.eraseToAnyPublisher() }
|
||||||
|
|
||||||
init(keychainController: KeychainControllerProtocol, appSettings: AppSettings) {
|
init(keychainController: KeychainControllerProtocol, appSettings: AppSettings) {
|
||||||
self.keychainController = keychainController
|
self.keychainController = keychainController
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
@@ -68,6 +77,9 @@ class AppLockService: AppLockServiceProtocol {
|
|||||||
func disable() {
|
func disable() {
|
||||||
biometricUnlockEnabled = false
|
biometricUnlockEnabled = false
|
||||||
keychainController.removePINCode()
|
keychainController.removePINCode()
|
||||||
|
appSettings.appLockNumberOfPINAttempts = 0
|
||||||
|
appSettings.appLockNumberOfBiometricAttempts = 0
|
||||||
|
disabledSubject.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidEnterBackground() {
|
func applicationDidEnterBackground() {
|
||||||
@@ -79,12 +91,20 @@ class AppLockService: AppLockServiceProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func unlock(with pinCode: String) -> Bool {
|
func unlock(with pinCode: String) -> Bool {
|
||||||
guard pinCode == keychainController.pinCode() else { return false }
|
guard pinCode == keychainController.pinCode() else {
|
||||||
|
MXLog.warning("Wrong PIN entered.")
|
||||||
|
appSettings.appLockNumberOfPINAttempts += 1
|
||||||
|
return false
|
||||||
|
}
|
||||||
return completeUnlock()
|
return completeUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlockWithBiometrics() -> Bool {
|
func unlockWithBiometrics() -> Bool {
|
||||||
guard biometryType != .none, biometricUnlockEnabled else { return false }
|
guard biometryType != .none, biometricUnlockEnabled else {
|
||||||
|
MXLog.warning("\(biometryType) failed.")
|
||||||
|
appSettings.appLockNumberOfBiometricAttempts += 1
|
||||||
|
return false
|
||||||
|
}
|
||||||
return completeUnlock()
|
return completeUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +123,8 @@ class AppLockService: AppLockServiceProtocol {
|
|||||||
/// Shared logic for completing an unlock via a PIN or biometry.
|
/// Shared logic for completing an unlock via a PIN or biometry.
|
||||||
private func completeUnlock() -> Bool {
|
private func completeUnlock() -> Bool {
|
||||||
timer.registerUnlock()
|
timer.registerUnlock()
|
||||||
|
appSettings.appLockNumberOfPINAttempts = 0
|
||||||
|
appSettings.appLockNumberOfBiometricAttempts = 0
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
|
|
||||||
enum AppLockServiceError: Error {
|
enum AppLockServiceError: Error {
|
||||||
@@ -27,13 +28,19 @@ enum AppLockServiceError: Error {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol AppLockServiceProtocol: AnyObject {
|
protocol AppLockServiceProtocol: AnyObject {
|
||||||
|
/// The use of a PIN code is mandatory for this device.
|
||||||
|
var isMandatory: Bool { get }
|
||||||
/// The app has been configured to automatically lock with a PIN code.
|
/// The app has been configured to automatically lock with a PIN code.
|
||||||
var isEnabled: Bool { get }
|
var isEnabled: Bool { get }
|
||||||
|
|
||||||
/// The type of biometric authentication supported by the device.
|
/// The type of biometric authentication supported by the device.
|
||||||
var biometryType: LABiometryType { get }
|
var biometryType: LABiometryType { get }
|
||||||
/// Whether or not the user has enabled unlock via TouchID, FaceID or (possibly) OpticID.
|
/// Whether or not the user has enabled unlock via TouchID, FaceID or (possibly) OpticID.
|
||||||
var biometricUnlockEnabled: Bool { get set }
|
var biometricUnlockEnabled: Bool { get set }
|
||||||
|
|
||||||
|
/// A publisher that advertises when the service has been disabled.
|
||||||
|
var disabledPublisher: AnyPublisher<Void, Never> { get }
|
||||||
|
|
||||||
/// Sets the user's PIN code used to unlock the app.
|
/// Sets the user's PIN code used to unlock the app.
|
||||||
func setupPINCode(_ pinCode: String) -> Result<Void, AppLockServiceError>
|
func setupPINCode(_ pinCode: String) -> Result<Void, AppLockServiceError>
|
||||||
/// Validates the supplied PIN code is long enough, only contains digits and isn't a weak choice.
|
/// Validates the supplied PIN code is long enough, only contains digits and isn't a weak choice.
|
||||||
@@ -50,15 +57,23 @@ protocol AppLockServiceProtocol: AnyObject {
|
|||||||
func unlock(with pinCode: String) -> Bool
|
func unlock(with pinCode: String) -> Bool
|
||||||
/// Attempt to unlock the app using FaceID or TouchID.
|
/// Attempt to unlock the app using FaceID or TouchID.
|
||||||
func unlockWithBiometrics() -> Bool
|
func unlockWithBiometrics() -> Bool
|
||||||
|
|
||||||
|
/// The number of attempts the user had made to unlock with a PIN code.
|
||||||
|
var numberOfPINAttempts: AnyPublisher<Int, Never> { get }
|
||||||
|
/// The number of attempts the user has made to unlock with Touch/Face ID.
|
||||||
|
var numberOfBiometricAttempts: AnyPublisher<Int, Never> { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
extension AppLockServiceProtocol { }
|
extension AppLockServiceProtocol { }
|
||||||
|
|
||||||
extension AppLockServiceMock {
|
extension AppLockServiceMock {
|
||||||
static func mock(pinCode: String? = "2023", biometryType: LABiometryType = .faceID) -> AppLockServiceMock {
|
static func mock(pinCode: String? = "2023", isMandatory: Bool = false, biometryType: LABiometryType = .faceID) -> AppLockServiceMock {
|
||||||
let mock = AppLockServiceMock()
|
let mock = AppLockServiceMock()
|
||||||
mock.isEnabled = pinCode != nil
|
mock.isEnabled = pinCode != nil
|
||||||
|
mock.isMandatory = isMandatory
|
||||||
|
mock.numberOfPINAttempts = PassthroughSubject<Int, Never>().eraseToAnyPublisher()
|
||||||
|
mock.numberOfBiometricAttempts = PassthroughSubject<Int, Never>().eraseToAnyPublisher()
|
||||||
mock.underlyingBiometryType = biometryType
|
mock.underlyingBiometryType = biometryType
|
||||||
mock.underlyingBiometricUnlockEnabled = biometryType != .none
|
mock.underlyingBiometricUnlockEnabled = biometryType != .none
|
||||||
mock.unlockWithClosure = { $0 == pinCode }
|
mock.unlockWithClosure = { $0 == pinCode }
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ class MockScreen: Identifiable {
|
|||||||
navigationStackCoordinator: navigationStackCoordinator,
|
navigationStackCoordinator: navigationStackCoordinator,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
|
appLockService: AppLockServiceMock())
|
||||||
retainedState.append(coordinator)
|
retainedState.append(coordinator)
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
@@ -162,17 +163,12 @@ class MockScreen: Identifiable {
|
|||||||
let coordinator = AppLockScreenCoordinator(parameters: .init(appLockService: appLockService))
|
let coordinator = AppLockScreenCoordinator(parameters: .init(appLockService: appLockService))
|
||||||
return coordinator
|
return coordinator
|
||||||
case .appLockSetupFlow:
|
case .appLockSetupFlow:
|
||||||
// Use the flow coordinator once more screens are added and remove the settings screen below.
|
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let appLockService = AppLockService(keychainController: KeychainControllerMock(), appSettings: ServiceLocator.shared.settings)
|
let appLockService = AppLockService(keychainController: KeychainControllerMock(), appSettings: ServiceLocator.shared.settings)
|
||||||
let coordinator = AppLockSetupPINScreenCoordinator(parameters: .init(initialMode: .create, appLockService: appLockService))
|
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .settings,
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
appLockService: appLockService,
|
||||||
return navigationStackCoordinator
|
navigationStackCoordinator: navigationStackCoordinator)
|
||||||
case .appLockSettingsScreen:
|
coordinator.start()
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
|
||||||
let appLockService = AppLockServiceMock.mock(biometryType: .faceID)
|
|
||||||
let coordinator = AppLockSettingsScreenCoordinator(parameters: .init(appLockService: appLockService))
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
case .home:
|
case .home:
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ enum UITestsScreenIdentifier: String {
|
|||||||
case templateScreen
|
case templateScreen
|
||||||
case appLockScreen
|
case appLockScreen
|
||||||
case appLockSetupFlow
|
case appLockSetupFlow
|
||||||
case appLockSettingsScreen
|
|
||||||
case home
|
case home
|
||||||
case settings
|
case settings
|
||||||
case bugReport
|
case bugReport
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import ElementX
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
class AppLockSettingsScreenUITests: XCTestCase {
|
|
||||||
func testScreen() async throws {
|
|
||||||
let app = Application.launch(.appLockSettingsScreen)
|
|
||||||
try await app.assertScreenshot(.appLockSettingsScreen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e09c1a718424e75c120fc2f2038f786388fcb2542231bc737c332e0b23004b33
|
oid sha256:02d0e19e329769fd507b7e02cbb95c55f36db403ba221b1362fea51e8e37a026
|
||||||
size 67281
|
size 100756
|
||||||
|
|||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:3ebc5ae6771cf79d67f658af15893c271ac8181a4cc893ccac2c9ed96ff29d62
|
|
||||||
size 72971
|
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e4af88936877c1eb32530fe4eaaf0ec228f055a3c080766a15b81473752cdd53
|
oid sha256:bf66d4809566af646252cf16b7582ef3bbb56b55308041d264902de44ea91463
|
||||||
size 84142
|
size 164655
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:abb591b36f490253c34fd00092e45ba35961ba0aab4d6fcb8efb036ac3c2575e
|
oid sha256:326cb365efab4ca083b39104402e1056ae915e4a230dacd07f494343e427cef7
|
||||||
size 67921
|
size 117772
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:908a80ad0ec73c875b8d581202903865d257ec23582fa3f3674c528569735aec
|
|
||||||
size 81618
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:47ed1ba1d4a0f067dc463b8e122c81e910b57ce580c6d66b5aa66b9ec5239452
|
oid sha256:1b0e2fe4e868bce1a4c758c8cc7026c17604bfe666c284ef83859ad0ac0aa001
|
||||||
size 91739
|
size 138452
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:89df727aac25c1d26ec86fe564ae97ca1687ba9d710dd5fc234f0052676c0a19
|
oid sha256:c5b462d8c9213706f06d4dab74a2b51bd29a5b961748ad3fd8156e48d407d832
|
||||||
size 69836
|
size 108960
|
||||||
|
|||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:8eb2d44feaca1201363407063fcfd5356ed200030670533f43d4568da1d121b0
|
|
||||||
size 76181
|
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cb43f034b7fb94655b025e4ae9e6b652c27ebb6b4ab04fdae61c5958ab3d16c2
|
oid sha256:fee0ecd3529882e1799122602775c15cc0eb1f766885998b35cf078a06396640
|
||||||
size 101999
|
size 184338
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d08f08f1d3e22048432ec4733cd0192e763325760d338864e9395cb24e124f21
|
oid sha256:2d439b5c3e450115d6e46ae632ab3486a54763478db5e2111a763b9ab71da0cb
|
||||||
size 72314
|
size 127567
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:61fdc7d6975047d7c10592b8771484066e96705f28be0e1ec74ca7025e387fe7
|
|
||||||
size 83815
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d507ae7b47328770e3a82a730f00d9735a8d37da3df91818bcc1d76542b59325
|
oid sha256:01d584c4c83f2abded3b6d929cf4ef3781c18c1ee2a1d6d88174458a1b117e12
|
||||||
size 123595
|
size 168409
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import XCTest
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class AppLockScreenViewModelTests: XCTestCase {
|
class AppLockScreenViewModelTests: XCTestCase {
|
||||||
|
var appSettings: AppSettings!
|
||||||
var appLockService: AppLockService!
|
var appLockService: AppLockService!
|
||||||
var keychainController: KeychainControllerMock!
|
var keychainController: KeychainControllerMock!
|
||||||
var viewModel: AppLockScreenViewModelProtocol!
|
var viewModel: AppLockScreenViewModelProtocol!
|
||||||
@@ -28,8 +29,9 @@ class AppLockScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
AppSettings.reset()
|
AppSettings.reset()
|
||||||
|
appSettings = AppSettings()
|
||||||
keychainController = KeychainControllerMock()
|
keychainController = KeychainControllerMock()
|
||||||
appLockService = AppLockService(keychainController: keychainController, appSettings: AppSettings())
|
appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
||||||
viewModel = AppLockScreenViewModel(appLockService: appLockService)
|
viewModel = AppLockScreenViewModel(appLockService: appLockService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,4 +53,60 @@ class AppLockScreenViewModelTests: XCTestCase {
|
|||||||
// The app should become unlocked.
|
// The app should become unlocked.
|
||||||
XCTAssertEqual(result, .appUnlocked)
|
XCTAssertEqual(result, .appUnlocked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testForgotPIN() {
|
||||||
|
// Given a fresh launch of the app.
|
||||||
|
XCTAssertNil(context.alertInfo, "No alert should be shown initially.")
|
||||||
|
|
||||||
|
// When the user has forgotten their PIN.
|
||||||
|
context.send(viewAction: .forgotPIN)
|
||||||
|
|
||||||
|
// Then an alert should be shown before logging out.
|
||||||
|
XCTAssertEqual(context.alertInfo?.id, .confirmResetPIN, "An alert should be shown before logging out.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnlockFailure() async throws {
|
||||||
|
// Given an invalid PIN code.
|
||||||
|
let pinCode = "2024"
|
||||||
|
keychainController.pinCodeReturnValue = "2023"
|
||||||
|
XCTAssertEqual(context.viewState.numberOfPINAttempts, 0, "The shouldn't be any attempts yet.")
|
||||||
|
XCTAssertFalse(context.viewState.isSubtitleWarning, "No warning should be shown yet.")
|
||||||
|
XCTAssertNil(context.alertInfo, "No alert should be shown yet.")
|
||||||
|
|
||||||
|
// When entering it on the lock screen.
|
||||||
|
viewModel.context.pinCode = pinCode
|
||||||
|
context.send(viewAction: .submitPINCode)
|
||||||
|
|
||||||
|
// Then a failed attempt should be shown.
|
||||||
|
XCTAssertEqual(context.viewState.numberOfPINAttempts, 1, "A failed attempt should have been recorded.")
|
||||||
|
XCTAssertTrue(context.viewState.isSubtitleWarning, "A warning should now be shown.")
|
||||||
|
XCTAssertNil(context.alertInfo, "No alert should be shown yet.")
|
||||||
|
|
||||||
|
// When entering twice more
|
||||||
|
context.send(viewAction: .submitPINCode)
|
||||||
|
context.send(viewAction: .submitPINCode)
|
||||||
|
|
||||||
|
// Then an alert should be shown
|
||||||
|
XCTAssertEqual(context.viewState.numberOfPINAttempts, 3, "All the attempts should have been recorded.")
|
||||||
|
XCTAssertTrue(context.viewState.isSubtitleWarning, "The warning should still be shown.")
|
||||||
|
XCTAssertEqual(context.alertInfo?.id, .forcedLogout, "An alert should now be shown.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testForceQuitRequiresLogout() {
|
||||||
|
// Given an app with a PIN set where the user attempted to unlock 3 times.
|
||||||
|
keychainController.pinCodeReturnValue = "2023"
|
||||||
|
appSettings.appLockNumberOfPINAttempts = 2
|
||||||
|
XCTAssertNil(context.alertInfo)
|
||||||
|
viewModel.context.pinCode = "0000"
|
||||||
|
context.send(viewAction: .submitPINCode)
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 3, "The app should have 3 failed attempts before the force quit.")
|
||||||
|
XCTAssertEqual(context.alertInfo?.id, .forcedLogout, "The app should be showing the alert before the force quit.")
|
||||||
|
|
||||||
|
// When force quitting the app and relaunching.
|
||||||
|
viewModel = nil
|
||||||
|
let freshViewModel = AppLockScreenViewModel(appLockService: appLockService)
|
||||||
|
|
||||||
|
// Then the alert should remain in place
|
||||||
|
XCTAssertEqual(freshViewModel.context.alertInfo?.id, .forcedLogout, "The new view model from the fresh launch should also show the alert")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,4 +175,47 @@ class AppLockServiceTests: XCTestCase {
|
|||||||
XCTAssertFalse(service.isEnabled, "The service should no longer be enabled.")
|
XCTAssertFalse(service.isEnabled, "The service should no longer be enabled.")
|
||||||
XCTAssertFalse(service.unlock(with: pinCode), "The initial PIN shouldn't work any more.")
|
XCTAssertFalse(service.unlock(with: pinCode), "The initial PIN shouldn't work any more.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testResetAttemptsOnUnlock() {
|
||||||
|
// Given a service that is enabled and has failed unlock attempts.
|
||||||
|
let pinCode = "2023"
|
||||||
|
guard case .success = service.setupPINCode(pinCode) else {
|
||||||
|
XCTFail("The PIN should be valid.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appSettings.appLockNumberOfPINAttempts = 2
|
||||||
|
appSettings.appLockNumberOfBiometricAttempts = 2
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 2, "The initial conditions should be stored.")
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 2, "The initial conditions should be stored.")
|
||||||
|
XCTAssertTrue(service.isEnabled, "The service should be enabled.")
|
||||||
|
|
||||||
|
// When unlocking the service
|
||||||
|
XCTAssertTrue(service.unlock(with: pinCode), "The PIN should work.")
|
||||||
|
|
||||||
|
// Then the attempts counts should both be reset.
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 0, "The PIN attempts should be reset.")
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 0, "The biometric attempts should be reset.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResetAttemptsOnDisable() {
|
||||||
|
// Given a service that is enabled and has failed unlock attempts.
|
||||||
|
let pinCode = "2023"
|
||||||
|
guard case .success = service.setupPINCode(pinCode) else {
|
||||||
|
XCTFail("The PIN should be valid.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appSettings.appLockNumberOfPINAttempts = 2
|
||||||
|
appSettings.appLockNumberOfBiometricAttempts = 2
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 2, "The initial conditions should be stored.")
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 2, "The initial conditions should be stored.")
|
||||||
|
XCTAssertTrue(service.isEnabled, "The service should be enabled.")
|
||||||
|
|
||||||
|
// When disabling the service
|
||||||
|
service.disable()
|
||||||
|
XCTAssertFalse(service.isEnabled, "The service should be disabled.")
|
||||||
|
|
||||||
|
// Then the attempts counts should both be reset.
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 0, "The PIN attempts should be reset.")
|
||||||
|
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 0, "The biometric attempts should be reset.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import XCTest
|
|||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class AppLockSettingsScreenViewModelTests: XCTestCase {
|
class AppLockSetupSettingsScreenViewModelTests: XCTestCase {
|
||||||
var appLockService: AppLockServiceProtocol!
|
var appLockService: AppLockServiceProtocol!
|
||||||
var keychainController: KeychainControllerMock!
|
var keychainController: KeychainControllerMock!
|
||||||
var viewModel: AppLockSettingsScreenViewModelProtocol!
|
var viewModel: AppLockSetupSettingsScreenViewModelProtocol!
|
||||||
|
|
||||||
var context: AppLockSettingsScreenViewModelType.Context {
|
var context: AppLockSetupSettingsScreenViewModelType.Context {
|
||||||
viewModel.context
|
viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class AppLockSettingsScreenViewModelTests: XCTestCase {
|
|||||||
keychainController = KeychainControllerMock()
|
keychainController = KeychainControllerMock()
|
||||||
appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
||||||
|
|
||||||
viewModel = AppLockSettingsScreenViewModel(appLockService: AppLockServiceMock.mock())
|
viewModel = AppLockSetupSettingsScreenViewModel(appLockService: AppLockServiceMock.mock())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCreatePIN() async throws {
|
func testCreatePIN() async throws {
|
||||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, appLockService: appLockService)
|
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, isMandatory: false, appLockService: appLockService)
|
||||||
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
||||||
|
|
||||||
let createDeferred = deferFulfillment(context.$viewState, message: "A valid PIN needs confirming.") { $0.mode == .confirm }
|
let createDeferred = deferFulfillment(context.$viewState, message: "A valid PIN needs confirming.") { $0.mode == .confirm }
|
||||||
@@ -53,7 +53,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCreateWeakPIN() async throws {
|
func testCreateWeakPIN() async throws {
|
||||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, appLockService: appLockService)
|
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, isMandatory: false, appLockService: appLockService)
|
||||||
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
||||||
XCTAssertNil(context.alertInfo, "There shouldn't be an alert to begin with.")
|
XCTAssertNil(context.alertInfo, "There shouldn't be an alert to begin with.")
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCreatePINMismatch() async throws {
|
func testCreatePINMismatch() async throws {
|
||||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, appLockService: appLockService)
|
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, isMandatory: false, appLockService: appLockService)
|
||||||
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
||||||
XCTAssertNil(context.alertInfo, "There shouldn't be an alert to begin with.")
|
XCTAssertNil(context.alertInfo, "There shouldn't be an alert to begin with.")
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testUnlock() async throws {
|
func testUnlock() async throws {
|
||||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .unlock, appLockService: appLockService)
|
viewModel = AppLockSetupPINScreenViewModel(initialMode: .unlock, isMandatory: false, appLockService: appLockService)
|
||||||
let pinCode = "2023"
|
let pinCode = "2023"
|
||||||
keychainController.pinCodeReturnValue = pinCode
|
keychainController.pinCodeReturnValue = pinCode
|
||||||
keychainController.containsPINCodeReturnValue = true
|
keychainController.containsPINCodeReturnValue = true
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c78cada10d7c15137a635b0e841fe20e124814921d953296fca5ed16dea93fef
|
oid sha256:3f1fa2ac91c3c5966db4e7cf34e8f4d724547d583147ac23bd392c536b0357bb
|
||||||
size 98997
|
size 105733
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f2aa8303cb959ebeae8a6b6bced04f2c3ecdfe429c97f153a38a2d650dddadf4
|
oid sha256:a3de8520c297d0c53f500a5cbe1322d50ae1f76d7c4678ab7c7e1d243c6a8657
|
||||||
size 99386
|
size 106000
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e53aa446554e19bc47b31f59038ec3caeeaba8c4c15a1884f5e510f30eac4c61
|
oid sha256:641c74d77ed0b43113fda06653a3a7eda92906a81a682cbd6b781aabcce51ab0
|
||||||
size 72867
|
size 76727
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:905d8d16cfce1540a91a3c8ef7d4295b844d2aab4c3ed3035230277acb56e228
|
||||||
|
size 94357
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f918886d8da2987f6ae26762267557ce2f7d7f0b02ce985de56caa3fb7ee305c
|
||||||
|
size 83101
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:08af67d397dafe03f60b36e2d1650a9031c12041ce5e015f6c2f81af4e9731fa
|
||||||
|
size 88510
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Add an AppLockSetupFlowCoordinator for creating a PIN with both mandatory and optional flows.
|
||||||
Reference in New Issue
Block a user