Initial setup ready for PIN/Biometric app lock. (#1876)

* Add AppLockCoordinator and WindowManager.
This commit is contained in:
Doug 2023-10-11 13:59:47 +01:00 committed by GitHub
parent b35bee5d8f
commit b6470c9651
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 838 additions and 15 deletions

View File

@ -28,6 +28,7 @@
06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */; };
071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; };
07756D532EFE33DD1FA258E5 /* GeoURITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */; };
08CB4BD12CEEDE6AAE4A18DD /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035177BCD8E8308B098AC3C2 /* WindowManager.swift */; };
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */; };
09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; };
@ -52,6 +53,7 @@
0E08BB72B2258652CF501A8B /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 78B28D75FF7AF8E6146DEE2A /* LRUCache */; };
0E8C480700870BB34A2A360F /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4003BC24B24C9E63D3304177 /* DeviceKit */; };
0EA6537A07E2DC882AEA5962 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 187853A7E643995EE49FAD43 /* Localizable.stringsdict */; };
0EAEA507586717B055441970 /* AppLockScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80AD634BF0A1767FE8941C5 /* AppLockScreenCoordinator.swift */; };
0ED691ADC9C2EA457E7A9427 /* FormattingToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE449DFBA7CC863EEB2FD2A /* FormattingToolbar.swift */; };
0EE5EBA18BA1FE10254BB489 /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; };
0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */; };
@ -94,6 +96,7 @@
1C8BC70A18060677E295A846 /* ShareToMapsAppActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */; };
1C9BB74711E5F24C77B7FED0 /* RoomMembersListScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */; };
1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
1D623953F970D11F6F38499C /* AppLockService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851B95BB98649B8E773D6790 /* AppLockService.swift */; };
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */; };
1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; };
1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */; };
@ -157,6 +160,7 @@
3113065AABBC14CEAE6843FA /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */; };
3116693C5EB476E028990416 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74611A4182DCF5F4D42696EC /* XCTestCase.swift */; };
32B7891D937377A59606EDFC /* UserFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */; };
33094DB91C3A4131E76B2C07 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5574FD6FC3C2DC0DF160A85 /* AppLockScreenViewModelProtocol.swift */; };
339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; };
33CAC1226DFB8B5D8447D286 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
340D39DB87F3800D53A6A621 /* EmojiPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */; };
@ -236,6 +240,7 @@
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; };
4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; };
4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */; };
4A945B96B87D61F873F48933 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC930E5F7F138112CAE5AC63 /* AppLockScreenModels.swift */; };
4AAA8606FBA290E23D15422E /* AvatarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */; };
4B978C09567387EF4366BD7A /* MediaLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF1AC723C2609C7705569CA /* MediaLoaderTests.swift */; };
4BAB8222DBA0B4207D1223E0 /* NotificationSettingsProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */; };
@ -317,6 +322,7 @@
64AB99285DC4437C0DDE9585 /* MenuSheetLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ABAB186CF00B15C5521D04 /* MenuSheetLabelStyle.swift */; };
64C373ACCFA26D42BA45CFAD /* HomeScreenInvitesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */; };
64D05250CEDE8B604119F6E6 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981663D961C94270FA035FD0 /* Alert.swift */; };
64DD8AB9CA0405A43043BDF8 /* AppLockScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891D46CF94626F05614829A2 /* AppLockScreen.swift */; };
64F43D7390DA2A0AFD6BA911 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */; };
64FF5CB4E35971255872E1BB /* AuthenticationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */; };
651341E67C3514F9811A1EC1 /* LoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */; };
@ -333,6 +339,7 @@
67D6E0700A9C1E676F6231F8 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = AD544C0FA48DFFB080920061 /* Collections */; };
68184EF36396424FE19A727D /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */; };
6832733838C57A7D3FE8FEB5 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; };
6851B077B4C913CC12DB6E77 /* AppLockFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */; };
6860721DB3091BE08164C132 /* MapAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B48B7AD4908C5C374517B892 /* MapAssets.xcassets */; };
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; };
@ -568,6 +575,7 @@
A9A5801D5EE3D4D91F6DDADB /* AnalyticsSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */; };
A9D349478F7D4A2B1E40CEF9 /* LegalInformationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8977176AB534AA41630395BC /* LegalInformationScreenViewModelProtocol.swift */; };
AA050DF4AEE54A641BA7CA22 /* RoomSummaryProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CC626F97AD70FF0420C115 /* RoomSummaryProviderProtocol.swift */; };
AA64AAE1C4BB96C7F2761CAB /* AppLockScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4BEE95A091150EEBF1C358 /* AppLockScreenViewModelTests.swift */; };
AA93B3F9B5DD097DEF79F981 /* NotificationSettingsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */; };
AAF0BBED840DF4A53EE85E77 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = C2C69B8BA5A9702E7A8BC08F /* MatrixRustSDK */; };
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; };
@ -641,6 +649,7 @@
BD782053BE4C3D2F0BDE5699 /* ServiceLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */; };
BDA68E8D95B2B24B28825B8B /* LoginScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C368CAB3063EF275357ECD4 /* LoginScreenViewModel.swift */; };
BDED6DA7AD1E76018C424143 /* LegalInformationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C34667458773B02AB5FB0B2 /* LegalInformationScreenViewModel.swift */; };
BE641CE5F9036B9AD7367DF1 /* AppLockScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1CBFD0D6D5AA0C8DCA0DA6 /* AppLockScreenViewModel.swift */; };
BEA646DF302711A753F0D420 /* MapTilerStyleBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225EFCA26877E75CDFE7F48D /* MapTilerStyleBuilderProtocol.swift */; };
BF675964C9159F718589C36A /* AnalyticsSettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */; };
C0090506A52A1991BAF4BA68 /* NotificationSettingsChatType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07579F9C29001E40715F3014 /* NotificationSettingsChatType.swift */; };
@ -809,6 +818,7 @@
EF0D0155DD104C7A41A2EB0E /* PlainMentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE78FA0011E07920AE83135 /* PlainMentionBuilder.swift */; };
EF5009AC03212227131C8AF2 /* RoomNotificationSettingsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */; };
EF7924005216B8189898F370 /* BackgroundTaskProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */; };
F05516474DB42369FD976CEF /* AppLockScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C633291427A0F29C28C54 /* AppLockScreenUITests.swift */; };
F06CE9132855E81EBB6DDC32 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 940C605265DD82DA0C655E23 /* Kingfisher */; };
F07D88421A9BC4D03D4A5055 /* VideoRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */; };
F0A26CD502C3A5868353B0FA /* ServerConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24DEE0682C95F897B6C7CB0D /* ServerConfirmationScreenViewModel.swift */; };
@ -849,12 +859,14 @@
FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; };
FB0A9D06FC9122E37992D962 /* LayoutDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */; };
FB53CD9B74A15B3B94F9F788 /* CreateRoomModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B849D2FF2CC12BA411A1651 /* CreateRoomModels.swift */; };
FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */; };
FB9A1DD83EF641A75ABBCE69 /* WaitlistScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C796FC1DFDBCDD5573D0360F /* WaitlistScreenViewModelTests.swift */; };
FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */; };
FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */; };
FC10228E73323BDC09526F97 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; };
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; };
FCDA202B246F75BA28E10C5F /* MapTilerAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */; };
FD29471C72872F8B7580E3E1 /* KeychainControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */; };
FD4C21F8DA1E273DE94FCD1A /* NotificationItemProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B927CF5EF7FCCDA5EDC474B /* NotificationItemProxyProtocol.swift */; };
FD4DEC88210F35C35B2FB386 /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A1398EFF65090FDA1CB639 /* ProcessInfo.swift */; };
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; };
@ -918,6 +930,7 @@
02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModel.swift; sourceTree = "<group>"; };
03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
035177BCD8E8308B098AC3C2 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = "<group>"; };
0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = "<group>"; };
03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = "<group>"; };
@ -1070,6 +1083,7 @@
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModelProtocol.swift; sourceTree = "<group>"; };
342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelProtocol.swift; sourceTree = "<group>"; };
349C633291427A0F29C28C54 /* AppLockScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenUITests.swift; sourceTree = "<group>"; };
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReactionsView.swift; sourceTree = "<group>"; };
3558A15CFB934F9229301527 /* RestorationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationToken.swift; sourceTree = "<group>"; };
35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -1084,6 +1098,7 @@
3948D16F021DFDB2CD26EAA8 /* MockBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBackgroundTaskService.swift; sourceTree = "<group>"; };
398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableFrameModifier.swift; sourceTree = "<group>"; };
39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenUITests.swift; sourceTree = "<group>"; };
39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerMock.swift; sourceTree = "<group>"; };
3A328F9E556F5CFA89332017 /* CreatePollScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollScreenViewModel.swift; sourceTree = "<group>"; };
3B5E97E9615A158C76B2AB77 /* DateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTests.swift; sourceTree = "<group>"; };
3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenUITests.swift; sourceTree = "<group>"; };
@ -1154,6 +1169,7 @@
4E47F18A9A077E351CEA10D4 /* TextBasedRoomTimelineViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewProtocol.swift; sourceTree = "<group>"; };
4E625B0EB2F86B37C14EF7E6 /* SettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModel.swift; sourceTree = "<group>"; };
4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxyProtocol.swift; sourceTree = "<group>"; };
4F4BEE95A091150EEBF1C358 /* AppLockScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModelTests.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>"; };
4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
@ -1190,6 +1206,7 @@
58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = "<group>"; };
59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenUITests.swift; sourceTree = "<group>"; };
5A37E2FACFD041CE466223CD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = "<group>"; };
5B0D7955FFB19B584594844B /* OnboardingLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingLogo.swift; sourceTree = "<group>"; };
5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = "<group>"; };
@ -1306,6 +1323,7 @@
84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = "<group>"; };
84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunnerTests.swift; sourceTree = "<group>"; };
85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
851B95BB98649B8E773D6790 /* AppLockService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockService.swift; sourceTree = "<group>"; };
851EF6258DF8B7EF129DC3AC /* WelcomeScreenScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenScreenViewModelTests.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>"; };
@ -1318,6 +1336,7 @@
8872E9C5E91E9F2BFC4EBCCA /* AlignedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlignedScrollView.swift; sourceTree = "<group>"; };
8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreen.swift; sourceTree = "<group>"; };
889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerProtocol.swift; sourceTree = "<group>"; };
891D46CF94626F05614829A2 /* AppLockScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreen.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>"; };
893777A4997BBDB68079D4F5 /* ArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = "<group>"; };
@ -1475,7 +1494,9 @@
BB23BEAF8831DC6A57E39F52 /* CreatePollScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollScreenCoordinator.swift; sourceTree = "<group>"; };
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = "<group>"; };
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
BC930E5F7F138112CAE5AC63 /* AppLockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenModels.swift; sourceTree = "<group>"; };
BCF54536699ACEE3DB6BA3CB /* CompletionSuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionService.swift; sourceTree = "<group>"; };
BD1CBFD0D6D5AA0C8DCA0DA6 /* AppLockScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModel.swift; sourceTree = "<group>"; };
BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = "<group>"; };
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = "<group>"; };
BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenCoordinator.swift; sourceTree = "<group>"; };
@ -1516,6 +1537,7 @@
C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomList.swift; sourceTree = "<group>"; };
C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
C796FC1DFDBCDD5573D0360F /* WaitlistScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelTests.swift; sourceTree = "<group>"; };
C80AD634BF0A1767FE8941C5 /* AppLockScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenCoordinator.swift; sourceTree = "<group>"; };
C830A64609CBD152F06E0457 /* NotificationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationConstants.swift; sourceTree = "<group>"; };
C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelTests.swift; sourceTree = "<group>"; };
C8F2A7A4E3F5060F52ACFFB0 /* RedactedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineView.swift; sourceTree = "<group>"; };
@ -1609,6 +1631,7 @@
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteURLParserTests.swift; sourceTree = "<group>"; };
E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = "<group>"; };
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
E5574FD6FC3C2DC0DF160A85 /* AppLockScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModelProtocol.swift; sourceTree = "<group>"; };
E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -1679,6 +1702,7 @@
FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = "<group>"; };
FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorProtocol.swift; sourceTree = "<group>"; };
FC2D505742FDA21FCDC4C18A /* AudioRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineView.swift; sourceTree = "<group>"; };
FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockFlowCoordinator.swift; sourceTree = "<group>"; };
FD1275D9CE0FFBA6E8E85426 /* UserIndicatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorController.swift; sourceTree = "<group>"; };
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerTests.swift; sourceTree = "<group>"; };
FE87C931165F5E201CACBB87 /* MediaPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProtocol.swift; sourceTree = "<group>"; };
@ -1858,6 +1882,7 @@
isa = PBXGroup;
children = (
4BF8D11D9ED15CFC373D0119 /* Analytics */,
7803E03F759061C948D66B7E /* AppLock */,
984A887BA0294FE3B00CE9B1 /* AudioPlayer */,
AAFDD509929A0CCF8BCE51EB /* Authentication */,
EBBEB5471737E9D116DF4738 /* Background */,
@ -2427,6 +2452,14 @@
path = View;
sourceTree = "<group>";
};
4AEBBDDC75A39318FFF01EBF /* View */ = {
isa = PBXGroup;
children = (
891D46CF94626F05614829A2 /* AppLockScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
4B5DC42A1DB20ECEB0FF67CB /* Tests */ = {
isa = PBXGroup;
children = (
@ -2552,6 +2585,7 @@
593C7129C5927E25AD8B688F /* FlowCoordinators */ = {
isa = PBXGroup;
children = (
FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */,
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */,
E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */,
@ -2707,6 +2741,15 @@
path = Manager;
sourceTree = "<group>";
};
703929219780FFABAC6380AA /* Windowing */ = {
isa = PBXGroup;
children = (
5A37E2FACFD041CE466223CD /* SceneDelegate.swift */,
035177BCD8E8308B098AC3C2 /* WindowManager.swift */,
);
path = Windowing;
sourceTree = "<group>";
};
70B74A432C241E56A7ACE610 /* Settings */ = {
isa = PBXGroup;
children = (
@ -2758,6 +2801,7 @@
children = (
58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */,
C687844F60BFF532D49A994C /* AnalyticsTests.swift */,
4F4BEE95A091150EEBF1C358 /* AppLockScreenViewModelTests.swift */,
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */,
893777A4997BBDB68079D4F5 /* ArrayTests.swift */,
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */,
@ -2878,6 +2922,26 @@
path = Items;
sourceTree = "<group>";
};
77566988A0A4F94744C3818B /* AppLockScreen */ = {
isa = PBXGroup;
children = (
C80AD634BF0A1767FE8941C5 /* AppLockScreenCoordinator.swift */,
BC930E5F7F138112CAE5AC63 /* AppLockScreenModels.swift */,
BD1CBFD0D6D5AA0C8DCA0DA6 /* AppLockScreenViewModel.swift */,
E5574FD6FC3C2DC0DF160A85 /* AppLockScreenViewModelProtocol.swift */,
4AEBBDDC75A39318FFF01EBF /* View */,
);
path = AppLockScreen;
sourceTree = "<group>";
};
7803E03F759061C948D66B7E /* AppLock */ = {
isa = PBXGroup;
children = (
851B95BB98649B8E773D6790 /* AppLockService.swift */,
);
path = AppLock;
sourceTree = "<group>";
};
780F74C73E826685A9DB289B /* Navigation */ = {
isa = PBXGroup;
children = (
@ -3212,6 +3276,7 @@
AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */,
16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */,
7D0CBC76C80E04345E11F2DB /* Application.swift */,
349C633291427A0F29C28C54 /* AppLockScreenUITests.swift */,
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */,
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
A5DDF245FA51CF75F89E58A4 /* CreatePollScreenUITests.swift */,
@ -3423,6 +3488,7 @@
7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */,
57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */,
780F74C73E826685A9DB289B /* Navigation */,
703929219780FFABAC6380AA /* Windowing */,
);
path = Application;
sourceTree = "<group>";
@ -3723,6 +3789,7 @@
isa = PBXGroup;
children = (
E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */,
39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */,
E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */,
);
path = Keychain;
@ -3867,6 +3934,7 @@
isa = PBXGroup;
children = (
669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */,
77566988A0A4F94744C3818B /* AppLockScreen */,
E74CD7681375AD2EAA34D66B /* Authentication */,
53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */,
27F2500AC8736AAE774520C0 /* ComposerToolbar */,
@ -4549,6 +4617,7 @@
files = (
A9A5801D5EE3D4D91F6DDADB /* AnalyticsSettingsScreenViewModelTests.swift in Sources */,
890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */,
AA64AAE1C4BB96C7F2761CAB /* AppLockScreenViewModelTests.swift in Sources */,
EA78A7512AFB1E5451744EB1 /* AppRouteURLParserTests.swift in Sources */,
3EC698F80DDEEFA273857841 /* ArrayTests.swift in Sources */,
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */,
@ -4682,6 +4751,13 @@
A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */,
4FF90E2242DBD596E1ED2E27 /* AppCoordinatorStateMachine.swift in Sources */,
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */,
6851B077B4C913CC12DB6E77 /* AppLockFlowCoordinator.swift in Sources */,
64DD8AB9CA0405A43043BDF8 /* AppLockScreen.swift in Sources */,
0EAEA507586717B055441970 /* AppLockScreenCoordinator.swift in Sources */,
4A945B96B87D61F873F48933 /* AppLockScreenModels.swift in Sources */,
BE641CE5F9036B9AD7367DF1 /* AppLockScreenViewModel.swift in Sources */,
33094DB91C3A4131E76B2C07 /* AppLockScreenViewModelProtocol.swift in Sources */,
1D623953F970D11F6F38499C /* AppLockService.swift in Sources */,
355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */,
12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */,
9462C62798F47E39DCC182D2 /* Application.swift in Sources */,
@ -4840,6 +4916,7 @@
BD6D98676111DA8FC2BE4908 /* InvitesScreenViewModelProtocol.swift in Sources */,
E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */,
1FE593ECEC40A43789105D80 /* KeychainController.swift in Sources */,
FD29471C72872F8B7580E3E1 /* KeychainControllerMock.swift in Sources */,
CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */,
D5681C80D8281560AACE0035 /* Label.swift in Sources */,
EEAE954289DE813A61656AE0 /* LayoutDirection.swift in Sources */,
@ -5065,6 +5142,7 @@
D8385A51A3D0FA9283556281 /* RoundedLabelItem.swift in Sources */,
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */,
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */,
FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */,
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */,
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */,
67160204A8D362BB7D4AD259 /* Search.swift in Sources */,
@ -5227,6 +5305,7 @@
BD2BF1EC73FFB0C01552ECDA /* WelcomeScreenScreenModels.swift in Sources */,
DC1BB5EE5F4D9B6A1F98A77A /* WelcomeScreenScreenViewModel.swift in Sources */,
94CEF587A3994A36A46D8334 /* WelcomeScreenScreenViewModelProtocol.swift in Sources */,
08CB4BD12CEEDE6AAE4A18DD /* WindowManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -5237,6 +5316,7 @@
795A854F63301DC6B46217B9 /* AccessibilityIdentifiers.swift in Sources */,
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */,
BF675964C9159F718589C36A /* AnalyticsSettingsScreenUITests.swift in Sources */,
F05516474DB42369FD976CEF /* AppLockScreenUITests.swift in Sources */,
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */,
ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */,
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */,

View File

@ -4,6 +4,9 @@
/* Used for testing */
"untranslated" = "Untranslated";
"screen_app_lock_title" = "%@ is locked";
"common_unlock" = "Unlock";
// MARK: - Soft logout
"soft_logout_signin_title" = "Sign in";

View File

@ -20,7 +20,7 @@ import MatrixRustSDK
import SwiftUI
import Version
class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, NotificationManagerDelegate {
class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, NotificationManagerDelegate, WindowManagerDelegate {
private let stateMachine: AppCoordinatorStateMachine
private let navigationRootCoordinator: NavigationRootCoordinator
private let userSessionStore: UserSessionStoreProtocol
@ -43,8 +43,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
}
}
private var userSessionFlowCoordinator: UserSessionFlowCoordinator?
private var authenticationCoordinator: AuthenticationCoordinator?
private let appLockFlowCoordinator: AppLockFlowCoordinator
private var userSessionFlowCoordinator: UserSessionFlowCoordinator?
private var softLogoutCoordinator: SoftLogoutScreenCoordinator?
private let backgroundTaskService: BackgroundTaskServiceProtocol
@ -54,6 +55,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
private var clientProxyObserver: AnyCancellable?
private var cancellables = Set<AnyCancellable>()
let windowManager = WindowManager()
let notificationManager: NotificationManagerProtocol
private let appRouteURLParser: AppRouteURLParser
@ -94,10 +96,20 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
UIApplication.shared
}
userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService)
let keychainController = KeychainController(service: .sessions,
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
userSessionStore = UserSessionStore(keychainController: keychainController,
backgroundTaskService: backgroundTaskService)
let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
appLockFlowCoordinator = AppLockFlowCoordinator(appLockService: appLockService,
navigationCoordinator: NavigationRootCoordinator())
notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(),
appSettings: appSettings)
windowManager.delegate = self
notificationManager.delegate = self
notificationManager.start()
@ -117,6 +129,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
observeApplicationState()
observeNetworkState()
observeAppLockChanges()
registerBackgroundAppRefresh()
}
@ -178,6 +191,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
stateMachine.processEvent(.createdUserSession)
}
// MARK: - WindowManagerDelegate
func windowManagerDidConfigureWindows(_ windowManager: WindowManager) {
windowManager.alternateWindow.rootViewController = UIHostingController(rootView: appLockFlowCoordinator.toPresentable())
}
// MARK: - NotificationManagerDelegate
func registerForRemoteNotifications() {
@ -553,6 +572,19 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
.store(in: &cancellables)
}
private func observeAppLockChanges() {
appLockFlowCoordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .lockApp:
windowManager.switchToAlternate()
case .unlockApp:
windowManager.switchToMain()
}
}
.store(in: &cancellables)
}
private func handleAppRoute(_ appRoute: AppRoute) {
if let userSessionFlowCoordinator {
userSessionFlowCoordinator.handleAppRoute(appRoute, animated: UIApplication.shared.applicationState == .active)

View File

@ -25,6 +25,13 @@ enum AppDelegateCallback {
class AppDelegate: NSObject, UIApplicationDelegate {
let callbacks = PassthroughSubject<AppDelegateCallback, Never>()
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Add a SceneDelegate to the SwiftUI scene so that we can connect up the WindowManager.
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
configuration.delegateClass = SceneDelegate.self
return configuration
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
NSTextAttachment.registerViewProviderClass(PillAttachmentViewProvider.self, forFileType: InfoPlistReader.main.pillsUTType)

View File

@ -42,6 +42,7 @@ final class AppSettings {
case swiftUITimelineEnabled
case voiceMessageEnabled
case mentionsEnabled
case appLockFlowEnabled
}
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@ -257,4 +258,7 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.mentionsEnabled, defaultValue: false, storageType: .userDefaults(store))
var mentionsEnabled
@UserPreference(key: UserDefaultsKeys.appLockFlowEnabled, defaultValue: false, storageType: .volatile)
var appLockFlowEnabled
}

View File

@ -18,8 +18,9 @@ import SwiftUI
@main
struct Application: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var applicationDelegate
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
@Environment(\.openURL) private var openURL
private var appCoordinator: AppCoordinatorProtocol!
init() {
@ -28,7 +29,9 @@ struct Application: App {
} else if ProcessInfo.isRunningUnitTests {
appCoordinator = UnitTestsAppCoordinator()
} else {
appCoordinator = AppCoordinator(appDelegate: applicationDelegate)
let coordinator = AppCoordinator(appDelegate: appDelegate)
SceneDelegate.windowManager = coordinator.windowManager
appCoordinator = coordinator
}
}

View File

@ -57,13 +57,18 @@ class NavigationRootCoordinator: ObservableObject, CoordinatorProtocol, CustomSt
/// Sets or replaces the presented coordinator
/// - Parameter coordinator: the coordinator to display
func setRootCoordinator(_ coordinator: (any CoordinatorProtocol)?, dismissalCallback: (() -> Void)? = nil) {
guard let coordinator else {
rootModule = nil
return
}
func setRootCoordinator(_ coordinator: (any CoordinatorProtocol)?, animated: Bool = true, dismissalCallback: (() -> Void)? = nil) {
var transaction = Transaction()
transaction.disablesAnimations = !animated
rootModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
withTransaction(transaction) {
guard let coordinator else {
rootModule = nil
return
}
rootModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}
}
/// - dismissalCallback: called when the sheet has been dismissed, programatically or otherwise

View File

@ -0,0 +1,29 @@
//
// 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 SwiftUI
/// A basic window scene delegate used to configure the `WindowManager`.
///
/// We don't support multiple scenes right now, so the implementation is pretty basic.
class SceneDelegate: NSObject, UIWindowSceneDelegate {
weak static var windowManager: WindowManager!
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene, !ProcessInfo.isRunningTests else { return }
Self.windowManager.configure(with: windowScene)
}
}

View File

@ -0,0 +1,67 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
protocol WindowManagerDelegate: AnyObject {
/// The window manager has configured its windows.
func windowManagerDidConfigureWindows(_ windowManager: WindowManager)
}
@MainActor
/// A window manager that supports switching between a main app window with an overlay and
/// an alternate window to switch contexts whilst also preserving the main view hierarchy.
class WindowManager {
weak var delegate: WindowManagerDelegate?
/// The app's main window (we only support a single scene).
private(set) var mainWindow: UIWindow!
/// Presented on top of the main window, to display e.g. user indicators.
private(set) var overlayWindow: UIWindow!
/// A secondary window that can be presented instead of the main/overlay window combo.
private(set) var alternateWindow: UIWindow!
/// Configures the window manager to operate on the supplied scene.
func configure(with windowScene: UIWindowScene) {
mainWindow = windowScene.keyWindow
overlayWindow = UIWindow(windowScene: windowScene)
overlayWindow.backgroundColor = .clear
// We don't support user interaction on our indicators so disable interaction, to pass
// touches through to the main window. If this changes, there's another solution here:
// https://www.fivestars.blog/articles/swiftui-windows/
overlayWindow.isUserInteractionEnabled = false
alternateWindow = UIWindow(windowScene: windowScene)
delegate?.windowManagerDidConfigureWindows(self)
}
/// Shows the main and overlay window combo, hiding the alternate window.
func switchToMain() {
mainWindow.isHidden = false
overlayWindow.isHidden = false
alternateWindow.isHidden = true
}
/// Shows the alternate window, hiding the main and overlay combo.
func switchToAlternate() {
alternateWindow.isHidden = false
overlayWindow.isHidden = true
mainWindow.isHidden = true
}
}

View File

@ -0,0 +1,87 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
enum AppLockFlowCoordinatorAction: Equatable {
/// Display the unlock flow.
case lockApp
/// Hide the unlock flow.
case unlockApp
}
/// Coordinates the display of any screens shown when the app is locked.
class AppLockFlowCoordinator: CoordinatorProtocol {
let appLockService: AppLockServiceProtocol
let navigationCoordinator: NavigationRootCoordinator
private var cancellables: Set<AnyCancellable> = []
private let actionsSubject: PassthroughSubject<AppLockFlowCoordinatorAction, Never> = .init()
var actions: AnyPublisher<AppLockFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(appLockService: AppLockServiceProtocol, navigationCoordinator: NavigationRootCoordinator) {
self.appLockService = appLockService
self.navigationCoordinator = navigationCoordinator
NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
.sink { [weak self] _ in
self?.showPlaceholderIfNeeded()
}
.store(in: &cancellables)
NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.showUnlockScreenIfNeeded()
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(navigationCoordinator.toPresentable())
}
// MARK: - App unlock
/// Displays the unlock flow with the app's placeholder view to hide obscure the view hierarchy in the app switcher.
private func showPlaceholderIfNeeded() {
guard appLockService.isEnabled else { return }
navigationCoordinator.setRootCoordinator(PlaceholderScreenCoordinator(), animated: false)
actionsSubject.send(.lockApp)
}
/// Displays the unlock flow with the main unlock screen.
private func showUnlockScreenIfNeeded() {
guard appLockService.isEnabled, appLockService.needsUnlock else { return }
let coordinator = AppLockScreenCoordinator(parameters: .init(appLockService: appLockService))
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .appUnlocked:
actionsSubject.send(.unlockApp)
}
}
.store(in: &cancellables)
navigationCoordinator.setRootCoordinator(coordinator, animated: false)
actionsSubject.send(.lockApp)
}
}

View File

@ -10,6 +10,12 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
public enum UntranslatedL10n {
/// Unlock
public static var commonUnlock: String { return UntranslatedL10n.tr("Untranslated", "common_unlock") }
/// %@ is locked
public static func screenAppLockTitle(_ p1: Any) -> String {
return UntranslatedL10n.tr("Untranslated", "screen_app_lock_title", String(describing: p1))
}
/// Clear all data currently stored on this device?
/// Sign in again to access your account data and messages.
public static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") }

View File

@ -314,6 +314,91 @@ class CompletionSuggestionServiceMock: CompletionSuggestionServiceProtocol {
setSuggestionTriggerClosure?(suggestionTrigger)
}
}
class KeychainControllerMock: KeychainControllerProtocol {
//MARK: - setRestorationToken
var setRestorationTokenForUsernameCallsCount = 0
var setRestorationTokenForUsernameCalled: Bool {
return setRestorationTokenForUsernameCallsCount > 0
}
var setRestorationTokenForUsernameReceivedArguments: (restorationToken: RestorationToken, forUsername: String)?
var setRestorationTokenForUsernameReceivedInvocations: [(restorationToken: RestorationToken, forUsername: String)] = []
var setRestorationTokenForUsernameClosure: ((RestorationToken, String) -> Void)?
func setRestorationToken(_ restorationToken: RestorationToken, forUsername: String) {
setRestorationTokenForUsernameCallsCount += 1
setRestorationTokenForUsernameReceivedArguments = (restorationToken: restorationToken, forUsername: forUsername)
setRestorationTokenForUsernameReceivedInvocations.append((restorationToken: restorationToken, forUsername: forUsername))
setRestorationTokenForUsernameClosure?(restorationToken, forUsername)
}
//MARK: - restorationTokenForUsername
var restorationTokenForUsernameCallsCount = 0
var restorationTokenForUsernameCalled: Bool {
return restorationTokenForUsernameCallsCount > 0
}
var restorationTokenForUsernameReceivedUsername: String?
var restorationTokenForUsernameReceivedInvocations: [String] = []
var restorationTokenForUsernameReturnValue: RestorationToken?
var restorationTokenForUsernameClosure: ((String) -> RestorationToken?)?
func restorationTokenForUsername(_ username: String) -> RestorationToken? {
restorationTokenForUsernameCallsCount += 1
restorationTokenForUsernameReceivedUsername = username
restorationTokenForUsernameReceivedInvocations.append(username)
if let restorationTokenForUsernameClosure = restorationTokenForUsernameClosure {
return restorationTokenForUsernameClosure(username)
} else {
return restorationTokenForUsernameReturnValue
}
}
//MARK: - restorationTokens
var restorationTokensCallsCount = 0
var restorationTokensCalled: Bool {
return restorationTokensCallsCount > 0
}
var restorationTokensReturnValue: [KeychainCredentials]!
var restorationTokensClosure: (() -> [KeychainCredentials])?
func restorationTokens() -> [KeychainCredentials] {
restorationTokensCallsCount += 1
if let restorationTokensClosure = restorationTokensClosure {
return restorationTokensClosure()
} else {
return restorationTokensReturnValue
}
}
//MARK: - removeRestorationTokenForUsername
var removeRestorationTokenForUsernameCallsCount = 0
var removeRestorationTokenForUsernameCalled: Bool {
return removeRestorationTokenForUsernameCallsCount > 0
}
var removeRestorationTokenForUsernameReceivedUsername: String?
var removeRestorationTokenForUsernameReceivedInvocations: [String] = []
var removeRestorationTokenForUsernameClosure: ((String) -> Void)?
func removeRestorationTokenForUsername(_ username: String) {
removeRestorationTokenForUsernameCallsCount += 1
removeRestorationTokenForUsernameReceivedUsername = username
removeRestorationTokenForUsernameReceivedInvocations.append(username)
removeRestorationTokenForUsernameClosure?(username)
}
//MARK: - removeAllRestorationTokens
var removeAllRestorationTokensCallsCount = 0
var removeAllRestorationTokensCalled: Bool {
return removeAllRestorationTokensCallsCount > 0
}
var removeAllRestorationTokensClosure: (() -> Void)?
func removeAllRestorationTokens() {
removeAllRestorationTokensCallsCount += 1
removeAllRestorationTokensClosure?()
}
}
class MediaPlayerMock: MediaPlayerProtocol {
var mediaSource: MediaSourceProxy?
var currentTime: TimeInterval {

View File

@ -0,0 +1,62 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
struct AppLockScreenCoordinatorParameters {
/// The service used to unlock the app.
let appLockService: AppLockServiceProtocol
}
enum AppLockScreenCoordinatorAction {
/// The user has successfully unlocked the app.
case appUnlocked
}
final class AppLockScreenCoordinator: CoordinatorProtocol {
private let parameters: AppLockScreenCoordinatorParameters
private var viewModel: AppLockScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<AppLockScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<AppLockScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: AppLockScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = AppLockScreenViewModel(appLockService: parameters.appLockService)
}
func start() {
viewModel.actions.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return }
switch action {
case .appUnlocked:
self.actionsSubject.send(.appUnlocked)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(AppLockScreen(context: viewModel.context))
}
}

View File

@ -0,0 +1,40 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum AppLockScreenViewModelAction {
/// The user has successfully unlocked the app.
case appUnlocked
}
struct AppLockScreenViewState: BindableState {
var bindings: AppLockScreenViewStateBindings
}
struct AppLockScreenViewStateBindings { }
enum AppLockScreenViewAction: CustomStringConvertible {
/// Attempt to unlock the app with the supplied PIN code.
case submitPINCode(String)
var description: String {
switch self {
case .submitPINCode:
return "submitPINCode"
}
}
}

View File

@ -0,0 +1,51 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias AppLockScreenViewModelType = StateStoreViewModel<AppLockScreenViewState, AppLockScreenViewAction>
class AppLockScreenViewModel: AppLockScreenViewModelType, AppLockScreenViewModelProtocol {
private let appLockService: AppLockServiceProtocol
private var actionsSubject: PassthroughSubject<AppLockScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<AppLockScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(appLockService: AppLockServiceProtocol) {
self.appLockService = appLockService
super.init(initialViewState: AppLockScreenViewState(bindings: .init()))
}
// MARK: - Public
override func process(viewAction: AppLockScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .submitPINCode(let pinCode):
guard appLockService.unlock(with: pinCode) else {
MXLog.warning("Invalid PIN code entered.")
// Indicate failure here.
return
}
actionsSubject.send(.appUnlocked)
}
}
}

View File

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

View File

@ -0,0 +1,63 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
// Move this to Compound.
extension ShapeStyle where Self == Color {
static var compound: CompoundColors { Self.compound }
}
// This implementation is only for development purposes.
struct AppLockScreen: View {
@ObservedObject var context: AppLockScreenViewModel.Context
var body: some View {
FullscreenDialog {
VStack(spacing: 8) {
AuthenticationIconImage(image: Image(systemSymbol: .lock))
.symbolVariant(.fill)
.padding(.bottom, 8)
Text(UntranslatedL10n.screenAppLockTitle(InfoPlistReader.main.bundleDisplayName))
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
}
} bottomContent: {
Button(UntranslatedL10n.commonUnlock) {
context.send(viewAction: .submitPINCode("0000"))
}
.buttonStyle(.elementAction(.xLarge))
}
}
}
// MARK: - Previews
// Add TestablePreview conformance once we have designs.
struct AppLockScreen_Previews: PreviewProvider {
static let viewModel = AppLockScreenViewModel(appLockService: AppLockService(keychainController: KeychainControllerMock(),
appSettings: ServiceLocator.shared.settings))
static var previews: some View {
NavigationStack {
AppLockScreen(context: viewModel.context)
}
}
}

View File

@ -51,6 +51,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
var swiftUITimelineEnabled: Bool { get set }
var voiceMessageEnabled: Bool { get set }
var mentionsEnabled: Bool { get set }
var appLockFlowEnabled: Bool { get set }
}
extension AppSettings: DeveloperOptionsProtocol { }

View File

@ -63,6 +63,13 @@ struct DeveloperOptionsScreen: View {
Text("Enable voice messages")
}
}
Section("Security") {
Toggle(isOn: $context.appLockFlowEnabled) {
Text("PIN/Biometric lock")
Text("Resets on reboot")
}
}
Section {
Button {

View File

@ -0,0 +1,55 @@
//
// 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 LocalAuthentication
@MainActor
protocol AppLockServiceProtocol {
/// The app has been configured to automatically lock with a PIN code.
var isEnabled: Bool { get }
/// The app can additionally be unlocked using FaceID or TouchID.
var supportsBiometrics: Bool { get }
/// The app should be unlocked with a PIN code/biometrics before being presented.
var needsUnlock: Bool { get }
/// Attempt to unlock the app with the supplied PIN code.
func unlock(with pinCode: String) -> Bool
/// Attempt to unlock the app using FaceID or TouchID.
func unlockWithBiometrics() -> Bool
}
class AppLockService: AppLockServiceProtocol {
private let keychainController: KeychainControllerProtocol
private let appSettings: AppSettings
var isEnabled: Bool { appSettings.appLockFlowEnabled }
var supportsBiometrics: Bool { true }
var needsUnlock: Bool { true }
init(keychainController: KeychainControllerProtocol, appSettings: AppSettings) {
self.keychainController = keychainController
self.appSettings = appSettings
}
func unlock(with pinCode: String) -> Bool {
true
}
func unlockWithBiometrics() -> Bool {
guard supportsBiometrics else { return false }
return true
}
}

View File

@ -0,0 +1,23 @@
//
// 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 MatrixRustSDK
/// Adds the missing methods for conformance to the protocol.
extension KeychainControllerMock {
func retrieveSessionFromKeychain(userId: String) throws -> Session { fatalError("Not implemented") }
func saveSessionInKeychain(session: Session) { fatalError("Not implemented") }
}

View File

@ -22,6 +22,7 @@ struct KeychainCredentials {
let restorationToken: RestorationToken
}
// sourcery: AutoMockable
protocol KeychainControllerProtocol: ClientSessionDelegate {
func setRestorationToken(_ restorationToken: RestorationToken, forUsername: String)
func restorationTokenForUsername(_ username: String) -> RestorationToken?

View File

@ -33,9 +33,8 @@ class UserSessionStore: UserSessionStoreProtocol {
var clientSessionDelegate: ClientSessionDelegate { keychainController }
init(backgroundTaskService: BackgroundTaskServiceProtocol) {
keychainController = KeychainController(service: .sessions,
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
init(keychainController: KeychainControllerProtocol, backgroundTaskService: BackgroundTaskServiceProtocol) {
self.keychainController = keychainController
self.backgroundTaskService = backgroundTaskService
baseDirectory = .sessionsBaseDirectory
MXLog.info("Setup base directory at: \(baseDirectory)")

View File

@ -157,6 +157,10 @@ class MockScreen: Identifiable {
let coordinator = TemplateScreenCoordinator(parameters: .init())
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .appLockScreen:
let appLockService = AppLockService(keychainController: KeychainControllerMock(), appSettings: ServiceLocator.shared.settings)
let coordinator = AppLockScreenCoordinator(parameters: .init(appLockService: appLockService))
return coordinator
case .home:
let navigationStackCoordinator = NavigationStackCoordinator()
let session = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:matrix.org"),

View File

@ -29,6 +29,7 @@ enum UITestsScreenIdentifier: String {
case analyticsSettingsScreen
case migration
case templateScreen
case appLockScreen
case home
case settings
case bugReport

View File

@ -0,0 +1,26 @@
//
// 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 AppLockScreenUITests: XCTestCase {
func testScreen() async throws {
let app = Application.launch(.appLockScreen)
try await app.assertScreenshot(.appLockScreen)
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,46 @@
//
// 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 XCTest
@testable import ElementX
@MainActor
class AppLockScreenViewModelTests: XCTestCase {
var appLockService: AppLockService!
var viewModel: AppLockScreenViewModelProtocol!
var context: AppLockScreenViewModelType.Context { viewModel.context }
override func setUp() {
AppSettings.reset()
appLockService = AppLockService(keychainController: KeychainControllerMock(), appSettings: AppSettings())
viewModel = AppLockScreenViewModel(appLockService: appLockService)
}
func testUnlock() async throws {
// Given a valid PIN code.
let pinCode = "0000"
// When entering it on the lock screen.
let deferred = deferFulfillment(viewModel.actions) { $0 == .appUnlocked }
context.send(viewAction: .submitPINCode(pinCode))
let result = try await deferred.fulfill()
// The app should become unlocked.
XCTAssertEqual(result, .appUnlocked)
}
}

1
changelog.d/pr-1876.wip Normal file
View File

@ -0,0 +1 @@
Initial setup for PIN/Biometric app lock.