mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Initial setup ready for PIN/Biometric app lock. (#1876)
* Add AppLockCoordinator and WindowManager.
This commit is contained in:
parent
b35bee5d8f
commit
b6470c9651
@ -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 */,
|
||||
|
@ -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";
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
29
ElementX/Sources/Application/Windowing/SceneDelegate.swift
Normal file
29
ElementX/Sources/Application/Windowing/SceneDelegate.swift
Normal 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)
|
||||
}
|
||||
}
|
67
ElementX/Sources/Application/Windowing/WindowManager.swift
Normal file
67
ElementX/Sources/Application/Windowing/WindowManager.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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") }
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 { }
|
||||
|
@ -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 {
|
||||
|
55
ElementX/Sources/Services/AppLock/AppLockService.swift
Normal file
55
ElementX/Sources/Services/AppLock/AppLockService.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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") }
|
||||
}
|
@ -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?
|
||||
|
@ -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)")
|
||||
|
@ -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"),
|
||||
|
@ -29,6 +29,7 @@ enum UITestsScreenIdentifier: String {
|
||||
case analyticsSettingsScreen
|
||||
case migration
|
||||
case templateScreen
|
||||
case appLockScreen
|
||||
case home
|
||||
case settings
|
||||
case bugReport
|
||||
|
26
UITests/Sources/AppLockScreenUITests.swift
Normal file
26
UITests/Sources/AppLockScreenUITests.swift
Normal 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)
|
||||
}
|
||||
}
|
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockScreen.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.appLockScreen.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockScreen.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.appLockScreen.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockScreen.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.appLockScreen.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockScreen.png
(Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.appLockScreen.png
(Stored with Git LFS)
Normal file
Binary file not shown.
46
UnitTests/Sources/AppLockScreenViewModelTests.swift
Normal file
46
UnitTests/Sources/AppLockScreenViewModelTests.swift
Normal 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
1
changelog.d/pr-1876.wip
Normal file
@ -0,0 +1 @@
|
||||
Initial setup for PIN/Biometric app lock.
|
Loading…
x
Reference in New Issue
Block a user