diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 57fa307a2..43c4fb674 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; }; 05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */; }; 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; }; + 06AA515C7053FD7E17A5CF81 /* RoomNotificationSettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */; }; 06B55882911B4BF5B14E9851 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; }; 06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */; }; 071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; }; @@ -51,6 +52,7 @@ 12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; }; 13853973A5E24374FCEDE8A3 /* RedactedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F2A7A4E3F5060F52ACFFB0 /* RedactedRoomTimelineView.swift */; }; 13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */; }; + 13CBC470FB619A6393A21908 /* RoomNotificationSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */; }; 14343C2F9AD2BFEA92CA28FF /* MapTilerStyleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AE92E7BFF71797BDE1D261 /* MapTilerStyleBuilder.swift */; }; 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */; }; 14E99D27628B1A6F0CB46FEA /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */; }; @@ -291,6 +293,7 @@ 6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; }; 6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; }; 6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */; }; + 6E63704717F17593A475D152 /* RoomNotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */; }; 6EC7A40A537CFB3D526A111C /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; }; 6F2AB43A1EFAD8A97AF41A15 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; }; 6F2D5D4F2590310DFAE973E4 /* WaitingDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6D698BFD68B061350553930 /* WaitingDialog.swift */; }; @@ -546,6 +549,7 @@ BA074E9812F96FFA3200ED1D /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; }; BA0D3DDCEDD97502DAC4B6E9 /* ReportContentScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */; }; BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2245243369B99216C7D84E /* ImageCache.swift */; }; + BA4C9049BC96DED3A2F3B82E /* RoomNotificationSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */; }; BB6BF528BC7F5B87E08C4F18 /* CameraPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */; }; BB784A02BADB03C820617A46 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */; }; BCC864190651B3A3CF51E4DF /* MediaFileHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */; }; @@ -585,10 +589,12 @@ C67FCC854F3A6FC7A2EC04D0 /* MediaUploadPreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */; }; C6C06DDA8881260303FBA3A0 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; }; C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; }; + C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */; }; C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; }; C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; }; C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; }; C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; }; + C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; }; CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */; }; CB137BFB3E083C33E398A6CB /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; }; CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; }; @@ -668,6 +674,7 @@ E3E1E255DC8CB34BD8573E0D /* UserIndicatorControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */; }; E45C9FA22BC13B477FD3B4AC /* EmojiDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */; }; E481C8FDCB6C089963C95344 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = BC01130651CB23340B899032 /* DeviceKit */; }; + E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */; }; E570117376826665640F0CFD /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */; }; E571163060CBE87D82CE24FD /* NSESettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DFC0FBA0FC6FC4DC0FC9FC /* NSESettings.swift */; }; E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; }; @@ -678,6 +685,7 @@ E78D429F18071545BF661A52 /* RoomDetailsEditScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */; }; E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; }; E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0197EAE9D45A662B8847B6 /* RoomTimelineControllerProtocol.swift */; }; + E9347F56CF0683208F4D9249 /* RoomNotificationSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */; }; E9560744F7B0292E20ECE5F2 /* RoomDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */; }; E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; }; E9F148072F9513EC2272AA21 /* SessionVerificationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */; }; @@ -809,6 +817,7 @@ 03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenCoordinator.swift; sourceTree = ""; }; 033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = ""; }; + 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = ""; }; 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; @@ -1050,6 +1059,7 @@ 57FE5EF0AFFE360C66420AAE /* WelcomeScreenScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenScreenCoordinator.swift; sourceTree = ""; }; 584A61D9C459FAFEF038A7C0 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; 58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelTests.swift; sourceTree = ""; }; + 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelTests.swift; sourceTree = ""; }; 592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = ""; }; 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenUITests.swift; sourceTree = ""; }; 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = ""; }; @@ -1080,6 +1090,7 @@ 65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationServiceProxy.swift; sourceTree = ""; }; 6654859746B0BE9611459391 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = ""; }; 667DD3A9D932D7D9EB380CAA /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sk; path = sk.lproj/Localizable.stringsdict; sourceTree = ""; }; + 66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenUITests.swift; sourceTree = ""; }; 669F35C505ACE1110589F875 /* MediaUploadingPreprocessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadingPreprocessor.swift; sourceTree = ""; }; 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProvider.swift; sourceTree = ""; }; 6861FE915C7B5466E6962BBA /* StartChatScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreen.swift; sourceTree = ""; }; @@ -1148,8 +1159,10 @@ 818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineItem.swift; sourceTree = ""; }; 818CBE6249ED6E8FC30E8366 /* ViewModelContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelContext.swift; sourceTree = ""; }; 8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModelProtocol.swift; sourceTree = ""; }; + 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModel.swift; sourceTree = ""; }; 81B17B1F29448D1B9049B11C /* ReportContentScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModel.swift; sourceTree = ""; }; 81B17DB1BC3B0C62AF84D230 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenCoordinator.swift; sourceTree = ""; }; 837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModel.swift; sourceTree = ""; }; 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = ""; }; 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = ""; }; @@ -1164,6 +1177,7 @@ 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModel.swift; sourceTree = ""; }; 874A1842477895F199567BD7 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = ""; }; 8872E9C5E91E9F2BFC4EBCCA /* AlignedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlignedScrollView.swift; sourceTree = ""; }; + 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreen.swift; sourceTree = ""; }; 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = ""; }; 893777A4997BBDB68079D4F5 /* ArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = ""; }; 8977176AB534AA41630395BC /* LegalInformationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1321,6 +1335,7 @@ C0900BBF0A5D5D775E917C70 /* EventBasedMessageTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedMessageTimelineItemProtocol.swift; sourceTree = ""; }; C1198B925F4A88DA74083662 /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = ""; }; C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDirection.swift; sourceTree = ""; }; + C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationModeProxy.swift; sourceTree = ""; }; C15E0017717EAE3A1D02D005 /* StaticLocationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenCoordinator.swift; sourceTree = ""; }; C23B3FAD8B23C421BC0D1B1E /* MapTilerGeoCodingServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerGeoCodingServiceProtocol.swift; sourceTree = ""; }; C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = ""; }; @@ -1391,6 +1406,7 @@ D8E057FB1F07A5C201C89061 /* MockServerSelectionScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServerSelectionScreenState.swift; sourceTree = ""; }; D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModel.swift; sourceTree = ""; }; D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineItem.swift; sourceTree = ""; }; + DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenModels.swift; sourceTree = ""; }; DA2AEC1AB349A341FE13DEC1 /* StartChatScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenUITests.swift; sourceTree = ""; }; DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreen.swift; sourceTree = ""; }; DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedUserDefaultsKeys.swift; sourceTree = ""; }; @@ -1571,6 +1587,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0210F4932B59277E2EEEF7BC /* RoomNotificationSettingsScreen */ = { + isa = PBXGroup; + children = ( + 8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */, + DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */, + 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */, + 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */, + DDF77194AB6E167891D0A8F3 /* View */, + ); + path = RoomNotificationSettingsScreen; + sourceTree = ""; + }; 040A58C2A22F7195740EBF5C /* NCE */ = { isa = PBXGroup; children = ( @@ -1691,6 +1719,7 @@ children = ( E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */, 4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */, + C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */, AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */, E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */, ); @@ -2483,6 +2512,7 @@ 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */, EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */, 69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */, + 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */, 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */, AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */, 277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */, @@ -2864,6 +2894,7 @@ 3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */, 0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */, C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */, + 66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */, 086B997409328F091EBA43CE /* RoomScreenUITests.swift */, DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */, 054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */, @@ -3390,6 +3421,14 @@ path = View; sourceTree = ""; }; + DDF77194AB6E167891D0A8F3 /* View */ = { + isa = PBXGroup; + children = ( + 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */, + ); + path = View; + sourceTree = ""; + }; DE73C35177886818B49EAE42 /* Sources */ = { isa = PBXGroup; children = ( @@ -3462,6 +3501,7 @@ E703BBD16266053B8A193C7B /* RoomDetailsScreen */, B86CF59E083C82C2A842E4AD /* RoomMemberDetailsScreen */, D4DB8163C10389C069458252 /* RoomMemberListScreen */, + 0210F4932B59277E2EEEF7BC /* RoomNotificationSettingsScreen */, 679E9837ECA8D6776079D16E /* RoomScreen */, 3153FCA3F4B0E88B16D99D12 /* SessionVerificationScreen */, 70B74A432C241E56A7ACE610 /* Settings */, @@ -4140,6 +4180,7 @@ 095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */, 6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */, CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */, + E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */, 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */, CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */, 53A55748D5F587C9061F98BF /* ServerConfigurationScreenViewStateTests.swift in Sources */, @@ -4504,9 +4545,15 @@ 8944548A684F1C837CEC47F4 /* RoomMembersListScreenModels.swift in Sources */, F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */, C4078364FD9FA00EA9D00A15 /* RoomMembersListScreenViewModelProtocol.swift in Sources */, + C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */, CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */, 8544657DEEE717ED2E22E382 /* RoomNotificationSettingsProxyMock.swift in Sources */, EF5009AC03212227131C8AF2 /* RoomNotificationSettingsProxyProtocol.swift in Sources */, + C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */, + 13CBC470FB619A6393A21908 /* RoomNotificationSettingsScreenCoordinator.swift in Sources */, + 6E63704717F17593A475D152 /* RoomNotificationSettingsScreenModels.swift in Sources */, + E9347F56CF0683208F4D9249 /* RoomNotificationSettingsScreenViewModel.swift in Sources */, + BA4C9049BC96DED3A2F3B82E /* RoomNotificationSettingsScreenViewModelProtocol.swift in Sources */, 4FC1EFE4968A259CBBACFAFB /* RoomProxy.swift in Sources */, BD203FC6A7AE7637EA003643 /* RoomProxyMock.swift in Sources */, FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */, @@ -4714,6 +4761,7 @@ 829062DD3C3F7016FE1A6476 /* RoomDetailsScreenUITests.swift in Sources */, A8771F5975A82759FA5138AE /* RoomMemberDetailsScreenUITests.swift in Sources */, 44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */, + 06AA515C7053FD7E17A5CF81 /* RoomNotificationSettingsScreenUITests.swift in Sources */, 2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */, A1D4033881320C9EB88196E6 /* ServerConfirmationScreenUITests.swift in Sources */, 77FACC29F98FE2E65BBB6A5F /* ServerSelectionUITests.swift in Sources */, diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index a026d0117..572cd8f4a 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -352,11 +352,11 @@ class NotificationSettingsProxyMock: NotificationSettingsProxyProtocol { var setNotificationModeRoomIdModeCalled: Bool { return setNotificationModeRoomIdModeCallsCount > 0 } - var setNotificationModeRoomIdModeReceivedArguments: (roomId: String, mode: RoomNotificationMode)? - var setNotificationModeRoomIdModeReceivedInvocations: [(roomId: String, mode: RoomNotificationMode)] = [] - var setNotificationModeRoomIdModeClosure: ((String, RoomNotificationMode) async throws -> Void)? + var setNotificationModeRoomIdModeReceivedArguments: (roomId: String, mode: RoomNotificationModeProxy)? + var setNotificationModeRoomIdModeReceivedInvocations: [(roomId: String, mode: RoomNotificationModeProxy)] = [] + var setNotificationModeRoomIdModeClosure: ((String, RoomNotificationModeProxy) async throws -> Void)? - func setNotificationMode(roomId: String, mode: RoomNotificationMode) async throws { + func setNotificationMode(roomId: String, mode: RoomNotificationModeProxy) async throws { if let error = setNotificationModeRoomIdModeThrowableError { throw error } @@ -373,10 +373,10 @@ class NotificationSettingsProxyMock: NotificationSettingsProxyProtocol { } var getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReceivedArguments: (isEncrypted: Bool, activeMembersCount: UInt64)? var getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReceivedInvocations: [(isEncrypted: Bool, activeMembersCount: UInt64)] = [] - var getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReturnValue: RoomNotificationMode! - var getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure: ((Bool, UInt64) async -> RoomNotificationMode)? + var getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReturnValue: RoomNotificationModeProxy! + var getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure: ((Bool, UInt64) async -> RoomNotificationModeProxy)? - func getDefaultNotificationRoomMode(isEncrypted: Bool, activeMembersCount: UInt64) async -> RoomNotificationMode { + func getDefaultNotificationRoomMode(isEncrypted: Bool, activeMembersCount: UInt64) async -> RoomNotificationModeProxy { getDefaultNotificationRoomModeIsEncryptedActiveMembersCountCallsCount += 1 getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReceivedArguments = (isEncrypted: isEncrypted, activeMembersCount: activeMembersCount) getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReceivedInvocations.append((isEncrypted: isEncrypted, activeMembersCount: activeMembersCount)) @@ -668,11 +668,11 @@ class RoomMemberProxyMock: RoomMemberProxyProtocol { } } class RoomNotificationSettingsProxyMock: RoomNotificationSettingsProxyProtocol { - var mode: RoomNotificationMode { + var mode: RoomNotificationModeProxy { get { return underlyingMode } set(value) { underlyingMode = value } } - var underlyingMode: RoomNotificationMode! + var underlyingMode: RoomNotificationModeProxy! var isDefault: Bool { get { return underlyingIsDefault } set(value) { underlyingIsDefault = value } diff --git a/ElementX/Sources/Mocks/NotificationSettingsProxyMock.swift b/ElementX/Sources/Mocks/NotificationSettingsProxyMock.swift index 45bc648a7..fe4c7fd4f 100644 --- a/ElementX/Sources/Mocks/NotificationSettingsProxyMock.swift +++ b/ElementX/Sources/Mocks/NotificationSettingsProxyMock.swift @@ -20,8 +20,13 @@ import MatrixRustSDK struct NotificationSettingsProxyMockConfiguration { var callback = PassthroughSubject() - var defaultRoomMode: RoomNotificationMode = .mentionsAndKeywordsOnly - var roomMode = RoomNotificationSettingsProxyMock(with: RoomNotificationSettingsProxyMockConfiguration(mode: .allMessages, isDefault: true)) + var defaultRoomMode: RoomNotificationModeProxy + var roomMode: RoomNotificationSettingsProxyMock + + init(defaultRoomMode: RoomNotificationModeProxy = .allMessages, roomMode: RoomNotificationModeProxy = .allMessages) { + self.defaultRoomMode = defaultRoomMode + self.roomMode = RoomNotificationSettingsProxyMock(with: RoomNotificationSettingsProxyMockConfiguration(mode: roomMode, isDefault: defaultRoomMode == roomMode)) + } } extension NotificationSettingsProxyMock { @@ -31,5 +36,20 @@ extension NotificationSettingsProxyMock { callbacks = configuration.callback getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = configuration.roomMode getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReturnValue = configuration.defaultRoomMode + + setNotificationModeRoomIdModeClosure = { [weak self] _, mode in + guard let self else { return } + self.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: mode, isDefault: false)) + Task { + self.callbacks.send(.settingsDidChange) + } + } + restoreDefaultNotificationModeRoomIdClosure = { [weak self] _ in + guard let self else { return } + self.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: configuration.defaultRoomMode, isDefault: true)) + Task { + self.callbacks.send(.settingsDidChange) + } + } } } diff --git a/ElementX/Sources/Mocks/RoomNotificationSettingsProxyMock.swift b/ElementX/Sources/Mocks/RoomNotificationSettingsProxyMock.swift index ee4577ef5..b2572e4df 100644 --- a/ElementX/Sources/Mocks/RoomNotificationSettingsProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomNotificationSettingsProxyMock.swift @@ -18,7 +18,7 @@ import Foundation import MatrixRustSDK struct RoomNotificationSettingsProxyMockConfiguration { - var mode: RoomNotificationMode = .allMessages + var mode: RoomNotificationModeProxy = .allMessages var isDefault = true } diff --git a/ElementX/Sources/Other/AccessibilityIdentifiers.swift b/ElementX/Sources/Other/AccessibilityIdentifiers.swift index 5a660a6d5..5d227188b 100644 --- a/ElementX/Sources/Other/AccessibilityIdentifiers.swift +++ b/ElementX/Sources/Other/AccessibilityIdentifiers.swift @@ -26,6 +26,7 @@ struct A11yIdentifiers { static let reportContent = ReportContent() static let roomScreen = RoomScreen() static let roomDetailsScreen = RoomDetailsScreen() + static let roomNotificationSettingsScreen = RoomNotificationSettingsScreen() static let serverConfirmationScreen = ServerConfirmationScreen() static let sessionVerificationScreen = SessionVerificationScreen() static let softLogoutScreen = SoftLogoutScreen() @@ -111,6 +112,10 @@ struct A11yIdentifiers { let unignore = "room_member_details-unignore" } + struct RoomNotificationSettingsScreen { + let allowCustomSetting = "room_notification_settings-allow_custom" + } + struct ServerConfirmationScreen { let `continue` = "server_confirmation-continue" let changeServer = "server_confirmation-change_server" diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift index a277b0203..4173e4872 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift @@ -188,7 +188,12 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { } } - private func presentNotificationSettings() { } + private func presentNotificationSettings() { + let roomNotificationSettingsParameters = RoomNotificationSettingsScreenCoordinatorParameters(notificationSettingsProxy: parameters.notificationSettings, + roomProxy: parameters.roomProxy) + let roomNotificationSettingsCoordinator = RoomNotificationSettingsScreenCoordinator(parameters: roomNotificationSettingsParameters) + navigationStackCoordinator?.push(roomNotificationSettingsCoordinator) + } } private extension Result { diff --git a/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenCoordinator.swift new file mode 100644 index 000000000..ced098959 --- /dev/null +++ b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenCoordinator.swift @@ -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 Combine +import SwiftUI + +struct RoomNotificationSettingsScreenCoordinatorParameters { + let notificationSettingsProxy: NotificationSettingsProxyProtocol + let roomProxy: RoomProxyProtocol +} + +final class RoomNotificationSettingsScreenCoordinator: CoordinatorProtocol { + private let parameters: RoomNotificationSettingsScreenCoordinatorParameters + private var viewModel: RoomNotificationSettingsScreenViewModelProtocol + + init(parameters: RoomNotificationSettingsScreenCoordinatorParameters) { + self.parameters = parameters + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: parameters.notificationSettingsProxy, + roomProxy: parameters.roomProxy) + } + + func start() { } + + func toPresentable() -> AnyView { + AnyView(RoomNotificationSettingsScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenModels.swift b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenModels.swift new file mode 100644 index 000000000..a01e03f11 --- /dev/null +++ b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenModels.swift @@ -0,0 +1,111 @@ +// +// 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 RoomNotificationSettingsScreenViewModelAction { } + +enum RoomNotificationSettingsState { + case loading + case loaded(settings: RoomNotificationSettingsProxyProtocol) + case error +} + +extension RoomNotificationSettingsState { + var isLoading: Bool { + if case .loading = self { + return true + } + return false + } + + var isLoaded: Bool { + if case .loaded = self { + return true + } + return false + } + + var isError: Bool { + if case .error = self { + return true + } + return false + } +} + +struct RoomNotificationSettingsScreenViewState: BindableState { + var bindings: RoomNotificationSettingsScreenViewStateBindings + let strings = RoomNotificationSettingsScreenStrings() + var notificationSettingsState: RoomNotificationSettingsState = .loading + var availableCustomRoomNotificationModes: [RoomNotificationModeProxy] = [.allMessages, .mentionsAndKeywordsOnly, .mute] + var isRestoringDefautSetting = false + var pendingCustomMode: RoomNotificationModeProxy? +} + +struct RoomNotificationSettingsScreenViewStateBindings { + var allowCustomSetting = false + var customMode: RoomNotificationModeProxy = .allMessages + /// Information describing the currently displayed alert. + var alertInfo: AlertInfo? +} + +enum RoomNotificationSettingsScreenViewAction { + case changedAllowCustomSettings + case setCustomMode(RoomNotificationModeProxy) +} + +struct RoomNotificationSettingsScreenStrings { + let customSettingFootnote: AttributedString + + init() { + let linkPlaceholder = "{link}" + var customSettingFootnote = AttributedString(L10n.screenRoomNotificationSettingsDefaultSettingFootnote(linkPlaceholder)) + var linkString = AttributedString(L10n.screenRoomNotificationSettingsDefaultSettingFootnoteContentLink) + linkString.bold() + customSettingFootnote.replace(linkPlaceholder, with: linkString) + + self.customSettingFootnote = customSettingFootnote + } + + func string(for mode: RoomNotificationModeProxy) -> String { + switch mode { + case .allMessages: + return L10n.screenRoomNotificationSettingsModeAllMessages + case .mentionsAndKeywordsOnly: + return L10n.screenRoomNotificationSettingsModeMentionsAndKeywords + case .mute: + return L10n.commonMute + } + } + + func string(for state: RoomNotificationSettingsState) -> String { + switch state { + case .loading: + return L10n.commonLoading + case .loaded(let settings): + return string(for: settings.mode) + case .error: + return L10n.commonError + } + } +} + +enum RoomNotificationSettingsScreenErrorType: Hashable { + case loadingSettingsFailed + case setModeFailed + case restoreDefaultFailed +} diff --git a/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift new file mode 100644 index 000000000..d177f6c18 --- /dev/null +++ b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift @@ -0,0 +1,154 @@ +// +// 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 RoomNotificationSettingsScreenViewModelType = StateStoreViewModel + +class RoomNotificationSettingsScreenViewModel: RoomNotificationSettingsScreenViewModelType, RoomNotificationSettingsScreenViewModelProtocol { + private let actionsSubject: PassthroughSubject = .init() + private let notificationSettingsProxy: NotificationSettingsProxyProtocol + private let roomProxy: RoomProxyProtocol + + @CancellableTask private var fetchNotificationSettingsTask: Task? + + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(notificationSettingsProxy: NotificationSettingsProxyProtocol, roomProxy: RoomProxyProtocol) { + let bindings = RoomNotificationSettingsScreenViewStateBindings() + self.notificationSettingsProxy = notificationSettingsProxy + self.roomProxy = roomProxy + super.init(initialViewState: RoomNotificationSettingsScreenViewState(bindings: bindings)) + + setupNotificationSettingsSubscription() + fetchNotificationSettings() + } + + // MARK: - Public + + override func process(viewAction: RoomNotificationSettingsScreenViewAction) { + switch viewAction { + case .changedAllowCustomSettings: + toogleCustomSetting() + case .setCustomMode(let mode): + setCustomMode(mode) + } + } + + // MARK: - Private + + private func setupNotificationSettingsSubscription() { + notificationSettingsProxy.callbacks + .receive(on: DispatchQueue.main) + .sink { [weak self] callback in + guard let self else { return } + + switch callback { + case .settingsDidChange: + self.fetchNotificationSettings() + } + } + .store(in: &cancellables) + } + + private func fetchNotificationSettings() { + fetchNotificationSettingsTask = Task { + await fetchRoomNotificationSettings() + } + } + + private func fetchRoomNotificationSettings() async { + do { + let settings = try await notificationSettingsProxy.getNotificationSettings(roomId: roomProxy.id, + isEncrypted: roomProxy.isEncrypted, + activeMembersCount: UInt64(roomProxy.activeMembersCount)) + guard !Task.isCancelled else { return } + state.notificationSettingsState = .loaded(settings: settings) + if !state.isRestoringDefautSetting { + state.bindings.allowCustomSetting = !settings.isDefault + } + if state.pendingCustomMode == nil { + state.bindings.customMode = settings.mode + } + } catch { + state.notificationSettingsState = .error + displayError(.loadingSettingsFailed) + } + } + + private func toogleCustomSetting() { + guard case .loaded(let settings) = state.notificationSettingsState else { return } + guard state.bindings.allowCustomSetting == settings.isDefault else { return } + + if state.bindings.allowCustomSetting { + setCustomMode(settings.mode) + } else { + restoreDefaultSetting() + } + } + + private func restoreDefaultSetting() { + state.isRestoringDefautSetting = true + Task { + do { + try await notificationSettingsProxy.restoreDefaultNotificationMode(roomId: roomProxy.id) + } catch { + displayError(.restoreDefaultFailed) + } + state.isRestoringDefautSetting = false + } + } + + private func setCustomMode(_ mode: RoomNotificationModeProxy) { + // Check if the new mode is already the current one + if case .loaded(let currentSettings) = state.notificationSettingsState { + if !currentSettings.isDefault, currentSettings.mode == mode { + return + } + } + + state.pendingCustomMode = mode + Task { + do { + try await notificationSettingsProxy.setNotificationMode(roomId: roomProxy.id, mode: mode) + } catch { + displayError(.setModeFailed) + } + state.pendingCustomMode = nil + } + } + + private func displayError(_ type: RoomNotificationSettingsScreenErrorType) { + switch type { + case .loadingSettingsFailed: + state.bindings.alertInfo = AlertInfo(id: type, + title: L10n.commonError, + message: L10n.screenRoomNotificationSettingsErrorLoadingSettings) + case .setModeFailed: + state.bindings.alertInfo = AlertInfo(id: type, + title: L10n.commonError, + message: L10n.screenRoomNotificationSettingsErrorSettingMode) + + case .restoreDefaultFailed: + state.bindings.alertInfo = AlertInfo(id: type, + title: L10n.commonError, + message: L10n.screenRoomNotificationSettingsErrorRestoringDefault) + } + } +} diff --git a/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModelProtocol.swift b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModelProtocol.swift new file mode 100644 index 000000000..f03791618 --- /dev/null +++ b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModelProtocol.swift @@ -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 RoomNotificationSettingsScreenViewModelProtocol { + var actions: AnyPublisher { get } + var context: RoomNotificationSettingsScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/RoomNotificationSettingsScreen/View/RoomNotificationSettingsScreen.swift b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/View/RoomNotificationSettingsScreen.swift new file mode 100644 index 000000000..faea5df38 --- /dev/null +++ b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/View/RoomNotificationSettingsScreen.swift @@ -0,0 +1,132 @@ +// +// 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 SwiftUI + +struct RoomNotificationSettingsScreen: View { + @ObservedObject var context: RoomNotificationSettingsScreenViewModel.Context + + var body: some View { + Form { + allowCustomSettingSection + + if !context.allowCustomSetting { + defaultSettingSection + } else { + customSettingsSection + } + } + .compoundForm() + .navigationTitle(L10n.screenRoomDetailsNotificationTitle) + .alert(item: $context.alertInfo) + .track(screen: .roomNotifications) + } + + // MARK: - Private + + @ViewBuilder + private var allowCustomSettingSection: some View { + Section { + Toggle(isOn: $context.allowCustomSetting) { + Text(L10n.screenRoomNotificationSettingsAllowCustom) + } + .toggleStyle(.compoundForm) + .accessibilityIdentifier(A11yIdentifiers.roomNotificationSettingsScreen.allowCustomSetting) + .disabled(context.viewState.notificationSettingsState.isLoading) + .onChange(of: context.allowCustomSetting) { _ in + context.send(viewAction: .changedAllowCustomSettings) + } + } footer: { + Text(L10n.screenRoomNotificationSettingsAllowCustomFootnote) + .compoundFormSectionFooter() + } + .compoundFormSection() + } + + @ViewBuilder + private var defaultSettingSection: some View { + Section { + if context.viewState.isRestoringDefautSetting { + Text(L10n.commonLoading) + .foregroundColor(.compound.textPlaceholder) + } else { + Text(context.viewState.strings.string(for: context.viewState.notificationSettingsState)) + .foregroundColor(.compound.textPrimary) + } + } header: { + Text(L10n.screenRoomNotificationSettingsDefaultSettingTitle) + .compoundFormSectionHeader() + } footer: { + Text(context.viewState.strings.customSettingFootnote) + .compoundFormSectionFooter() + } + .compoundFormSection() + } + + @ViewBuilder + private var customSettingsSection: some View { + Section { + Picker("", selection: $context.customMode) { + ForEach(context.viewState.availableCustomRoomNotificationModes, id: \.self) { mode in + Text(context.viewState.strings.string(for: mode)) + .tag(mode) + } + } + .onChange(of: context.customMode) { mode in + context.send(viewAction: .setCustomMode(mode)) + } + .labelsHidden() + .pickerStyle(.inline) + } header: { + Text(L10n.screenRoomNotificationSettingsCustomSettingsTitle) + .compoundFormSectionHeader() + } + .compoundFormSection() + } +} + +// MARK: - Previews + +struct RoomNotificationSettingsScreen_Previews: PreviewProvider { + static let viewModel = { + let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init(defaultRoomMode: .mentionsAndKeywordsOnly, roomMode: .mentionsAndKeywordsOnly)) + + let roomProxy = RoomProxyMock(with: .init(displayName: "Room", isEncrypted: true, joinedMembersCount: 4)) + + let model = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxy, + roomProxy: roomProxy) + + return model + }() + + static let viewModelCustom = { + let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init(defaultRoomMode: .allMessages, roomMode: .mentionsAndKeywordsOnly)) + + let roomProxy = RoomProxyMock(with: .init(displayName: "Room", isEncrypted: true, joinedMembersCount: 4)) + + let model = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxy, + roomProxy: roomProxy) + + return model + }() + + static var previews: some View { + RoomNotificationSettingsScreen(context: viewModel.context) + .previewDisplayName("Default") + RoomNotificationSettingsScreen(context: viewModelCustom.context) + .previewDisplayName("Custom") + } +} diff --git a/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxy.swift b/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxy.swift index af4b1d50e..4504ebdbe 100644 --- a/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxy.swift +++ b/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxy.swift @@ -51,15 +51,26 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol { return RoomNotificationSettingsProxy(roomNotificationSettings: roomMotificationSettings) } - func setNotificationMode(roomId: String, mode: RoomNotificationMode) async throws { + func setNotificationMode(roomId: String, mode: RoomNotificationModeProxy) async throws { let backgroundTask = await backgroundTaskService?.startBackgroundTask(withName: "setNotificationMode") defer { backgroundTask?.stop() } - try await notificationSettings.setRoomNotificationMode(roomId: roomId, mode: mode) + let roomNotificationMode: RoomNotificationMode + switch mode { + case .allMessages: + roomNotificationMode = .allMessages + case .mentionsAndKeywordsOnly: + roomNotificationMode = .mentionsAndKeywordsOnly + case .mute: + roomNotificationMode = .mute + } + try await notificationSettings.setRoomNotificationMode(roomId: roomId, mode: roomNotificationMode) + await updatedSettings() } - func getDefaultNotificationRoomMode(isEncrypted: Bool, activeMembersCount: UInt64) async -> RoomNotificationMode { - await notificationSettings.getDefaultRoomNotificationMode(isEncrypted: isEncrypted, activeMembersCount: activeMembersCount) + func getDefaultNotificationRoomMode(isEncrypted: Bool, activeMembersCount: UInt64) async -> RoomNotificationModeProxy { + let roomNotificationMode = await notificationSettings.getDefaultRoomNotificationMode(isEncrypted: isEncrypted, activeMembersCount: activeMembersCount) + return RoomNotificationModeProxy.from(roomNotificationMode: roomNotificationMode) } func restoreDefaultNotificationMode(roomId: String) async throws { @@ -67,6 +78,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol { defer { backgroundTask?.stop() } try await notificationSettings.restoreDefaultRoomNotificationMode(roomId: roomId) + await updatedSettings() } func containsKeywordsRules() async -> Bool { @@ -78,6 +90,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol { defer { backgroundTask?.stop() } try await notificationSettings.unmuteRoom(roomId: roomId, isEncrypted: isEncrypted, membersCount: activeMembersCount) + await updatedSettings() } func isRoomMentionEnabled() async throws -> Bool { @@ -89,6 +102,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol { defer { backgroundTask?.stop() } try await notificationSettings.setRoomMentionEnabled(enabled: enabled) + await updatedSettings() } func isUserMentionEnabled() async throws -> Bool { @@ -100,6 +114,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol { defer { backgroundTask?.stop() } try await notificationSettings.setUserMentionEnabled(enabled: enabled) + await updatedSettings() } func isCallEnabled() async throws -> Bool { @@ -111,10 +126,15 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol { defer { backgroundTask?.stop() } try await notificationSettings.setCallEnabled(enabled: enabled) + await updatedSettings() } // MARK: - Private + func updatedSettings() async { + _ = await callbacks.values.first(where: { $0 == .settingsDidChange }) + } + @MainActor func settingsDidChange() { callbacks.send(.settingsDidChange) diff --git a/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxyProtocol.swift b/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxyProtocol.swift index f08666866..4a736315b 100644 --- a/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxyProtocol.swift +++ b/ElementX/Sources/Services/NotificationSettings/NotificationSettingsProxyProtocol.swift @@ -27,8 +27,8 @@ protocol NotificationSettingsProxyProtocol { var callbacks: PassthroughSubject { get } func getNotificationSettings(roomId: String, isEncrypted: Bool, activeMembersCount: UInt64) async throws -> RoomNotificationSettingsProxyProtocol - func setNotificationMode(roomId: String, mode: RoomNotificationMode) async throws - func getDefaultNotificationRoomMode(isEncrypted: Bool, activeMembersCount: UInt64) async -> RoomNotificationMode + func setNotificationMode(roomId: String, mode: RoomNotificationModeProxy) async throws + func getDefaultNotificationRoomMode(isEncrypted: Bool, activeMembersCount: UInt64) async -> RoomNotificationModeProxy func restoreDefaultNotificationMode(roomId: String) async throws func containsKeywordsRules() async -> Bool func unmuteRoom(roomId: String, isEncrypted: Bool, activeMembersCount: UInt64) async throws diff --git a/ElementX/Sources/Services/NotificationSettings/RoomNotificationModeProxy.swift b/ElementX/Sources/Services/NotificationSettings/RoomNotificationModeProxy.swift new file mode 100644 index 000000000..6abe1b264 --- /dev/null +++ b/ElementX/Sources/Services/NotificationSettings/RoomNotificationModeProxy.swift @@ -0,0 +1,37 @@ +// +// 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 Foundation +import MatrixRustSDK + +enum RoomNotificationModeProxy { + case allMessages + case mentionsAndKeywordsOnly + case mute +} + +extension RoomNotificationModeProxy { + static func from(roomNotificationMode: RoomNotificationMode) -> Self { + switch roomNotificationMode { + case .allMessages: + return .allMessages + case .mentionsAndKeywordsOnly: + return .mentionsAndKeywordsOnly + case .mute: + return .mute + } + } +} diff --git a/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxy.swift b/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxy.swift index 507291821..e449c4f87 100644 --- a/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxy.swift +++ b/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxy.swift @@ -20,8 +20,8 @@ import MatrixRustSDK struct RoomNotificationSettingsProxy: RoomNotificationSettingsProxyProtocol { private let roomNotificationSettings: RoomNotificationSettings - var mode: RoomNotificationMode { - roomNotificationSettings.mode + var mode: RoomNotificationModeProxy { + RoomNotificationModeProxy.from(roomNotificationMode: roomNotificationSettings.mode) } var isDefault: Bool { diff --git a/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxyProtocol.swift b/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxyProtocol.swift index 79e4b6a3e..cddba1b09 100644 --- a/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxyProtocol.swift +++ b/ElementX/Sources/Services/NotificationSettings/RoomNotificationSettingsProxyProtocol.swift @@ -19,6 +19,6 @@ import MatrixRustSDK // sourcery: AutoMockable protocol RoomNotificationSettingsProxyProtocol { - var mode: RoomNotificationMode { get } + var mode: RoomNotificationModeProxy { get } var isDefault: Bool { get } } diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 0fbbbd66b..c6b7f5a25 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -478,6 +478,20 @@ class MockScreen: Identifiable { roomProxy: RoomProxyMock(with: .init(displayName: "test", members: members)))) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator + case .roomNotificationSettingsDefaultSetting: + let navigationStackCoordinator = NavigationStackCoordinator() + let members: [RoomMemberProxyMock] = [.mockInvitedAlice, .mockBob, .mockCharlie] + let coordinator = RoomNotificationSettingsScreenCoordinator(parameters: .init(notificationSettingsProxy: NotificationSettingsProxyMock(with: .init(defaultRoomMode: .allMessages, roomMode: .allMessages)), + roomProxy: RoomProxyMock(with: .init(displayName: "test", members: members)))) + navigationStackCoordinator.setRootCoordinator(coordinator) + return navigationStackCoordinator + case .roomNotificationSettingsCustomSetting: + let navigationStackCoordinator = NavigationStackCoordinator() + let members: [RoomMemberProxyMock] = [.mockInvitedAlice, .mockBob, .mockCharlie] + let coordinator = RoomNotificationSettingsScreenCoordinator(parameters: .init(notificationSettingsProxy: NotificationSettingsProxyMock(with: .init(defaultRoomMode: .allMessages, roomMode: .mentionsAndKeywordsOnly)), + roomProxy: RoomProxyMock(with: .init(displayName: "test", members: members)))) + navigationStackCoordinator.setRootCoordinator(coordinator) + return navigationStackCoordinator case .reportContent: let navigationStackCoordinator = NavigationStackCoordinator() let coordinator = ReportContentScreenCoordinator(parameters: .init(eventID: "test", diff --git a/ElementX/Sources/UITests/UITestsScreenIdentifier.swift b/ElementX/Sources/UITests/UITestsScreenIdentifier.swift index 4fd9bfbb9..04eb15222 100644 --- a/ElementX/Sources/UITests/UITestsScreenIdentifier.swift +++ b/ElementX/Sources/UITests/UITestsScreenIdentifier.swift @@ -60,6 +60,8 @@ enum UITestsScreenIdentifier: String { case roomMemberDetailsAccountOwner case roomMemberDetails case roomMemberDetailsIgnoredUser + case roomNotificationSettingsDefaultSetting + case roomNotificationSettingsCustomSetting case reportContent case startChat case startChatWithSearchResults diff --git a/UITests/Sources/RoomNotificationSettingsScreenUITests.swift b/UITests/Sources/RoomNotificationSettingsScreenUITests.swift new file mode 100644 index 000000000..15c13489c --- /dev/null +++ b/UITests/Sources/RoomNotificationSettingsScreenUITests.swift @@ -0,0 +1,33 @@ +// +// 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 RoomNotificationSettingsScreenUITests: XCTestCase { + func testDefaultSetting() async throws { + let app = Application.launch(.roomNotificationSettingsDefaultSetting) + XCTAssertFalse(app.switches[A11yIdentifiers.roomNotificationSettingsScreen.allowCustomSetting].isOn) + try await app.assertScreenshot(.roomNotificationSettingsDefaultSetting) + } + + func testCustomSettings() async throws { + let app = Application.launch(.roomNotificationSettingsCustomSetting) + XCTAssert(app.switches[A11yIdentifiers.roomNotificationSettingsScreen.allowCustomSetting].isOn) + try await app.assertScreenshot(.roomNotificationSettingsCustomSetting) + } +} diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomNotificationSettingsCustomSetting.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomNotificationSettingsCustomSetting.png new file mode 100644 index 000000000..7c1be8578 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomNotificationSettingsCustomSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31971d8da2139d035c3dd59c595ea887ecd33a11c496464a4798dabb04503ab1 +size 91579 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomNotificationSettingsDefaultSetting.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomNotificationSettingsDefaultSetting.png new file mode 100644 index 000000000..4a489edef --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomNotificationSettingsDefaultSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6360b213d0c017f9dcf7747e621887c4dab3995e4603e87440bcfa7f1b9661be +size 89003 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomNotificationSettingsCustomSetting.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomNotificationSettingsCustomSetting.png new file mode 100644 index 000000000..15f5f878a --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomNotificationSettingsCustomSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ec5118beb0691f8df57b51c372d0df418c63dc1b73233efea058da41291e9e3 +size 107915 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomNotificationSettingsDefaultSetting.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomNotificationSettingsDefaultSetting.png new file mode 100644 index 000000000..813d28ca0 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomNotificationSettingsDefaultSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aabd8e46d32b82e9a69081de0ba2d6ea505fb1f1c9bd649b87695b33556112dd +size 101243 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomNotificationSettingsCustomSetting.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomNotificationSettingsCustomSetting.png new file mode 100644 index 000000000..da17aab09 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomNotificationSettingsCustomSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f68bab9b84c6747f8ea4610da46c3c79b138bfb95d75216a62e05c28c9ce2373 +size 99103 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomNotificationSettingsDefaultSetting.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomNotificationSettingsDefaultSetting.png new file mode 100644 index 000000000..17fdb2276 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomNotificationSettingsDefaultSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:911c455aadd9bb40c83b7f759cd51f03c2d091855274550de31f35a0a9001d9a +size 99273 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomNotificationSettingsCustomSetting.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomNotificationSettingsCustomSetting.png new file mode 100644 index 000000000..8af3c3b71 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomNotificationSettingsCustomSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bcf4fd1f00368481f8688abdb60bffe8aa65234d64f6bf44d9ff9dfe0421b25 +size 141085 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomNotificationSettingsDefaultSetting.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomNotificationSettingsDefaultSetting.png new file mode 100644 index 000000000..9dec7a0c6 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomNotificationSettingsDefaultSetting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe711a53de7a85957694d9f19ef81f99ec3e51dddcda0f6537d4fcfd7ceb9906 +size 126350 diff --git a/UnitTests/Sources/RoomNotificationSettingsScreenViewModelTests.swift b/UnitTests/Sources/RoomNotificationSettingsScreenViewModelTests.swift new file mode 100644 index 000000000..3c6638f81 --- /dev/null +++ b/UnitTests/Sources/RoomNotificationSettingsScreenViewModelTests.swift @@ -0,0 +1,146 @@ +// +// 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 MatrixRustSDK +import XCTest + +@testable import ElementX + +@MainActor +class RoomNotificationSettingsScreenViewModelTests: XCTestCase { + var viewModel: RoomNotificationSettingsScreenViewModel! + var roomProxyMock: RoomProxyMock! + var notificationSettingsProxyMock: NotificationSettingsProxyMock! + var context: RoomNotificationSettingsScreenViewModelType.Context { viewModel.context } + + override func setUpWithError() throws { + roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", joinedMembersCount: 0)) + notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()) + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock, + roomProxy: roomProxyMock) + } + + func testInitialStateDefaultMode() async throws { + notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: true)) + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock, + roomProxy: roomProxyMock) + let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState) + .first(where: \.isLoaded)) + try await deferred.fulfill() + + XCTAssertFalse(context.allowCustomSetting) + } + + func testInitialStateCustomMode() async throws { + notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false)) + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock, + roomProxy: roomProxyMock) + let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState) + .first(where: \.isLoaded)) + try await deferred.fulfill() + + XCTAssertTrue(context.allowCustomSetting) + } + + func testInitialStateFailure() async throws { + notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountThrowableError = NotificationSettingsError.Generic(message: "error") + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock, + roomProxy: roomProxyMock) + let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState) + .first(where: \.isError)) + try await deferred.fulfill() + + let expectedAlertInfo = AlertInfo(id: RoomNotificationSettingsScreenErrorType.loadingSettingsFailed, + title: L10n.commonError, + message: L10n.screenRoomNotificationSettingsErrorLoadingSettings) + XCTAssertEqual(context.viewState.bindings.alertInfo?.id, expectedAlertInfo.id) + XCTAssertEqual(context.viewState.bindings.alertInfo?.title, expectedAlertInfo.title) + XCTAssertEqual(context.viewState.bindings.alertInfo?.message, expectedAlertInfo.message) + } + + func testToggleAllCustomSettingOff() async throws { + notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false)) + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock, + roomProxy: roomProxyMock) + let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState) + .first(where: \.isLoaded)) + try await deferred.fulfill() + + let deferredIsRestoringDefaultSettings = deferFulfillment(context.$viewState.map(\.isRestoringDefautSetting) + .removeDuplicates() + .collect(3).first()) + viewModel.state.bindings.allowCustomSetting = false + context.send(viewAction: .changedAllowCustomSettings) + let states = try await deferredIsRestoringDefaultSettings.fulfill() + XCTAssertEqual(states, [false, true, false]) + + XCTAssertEqual(notificationSettingsProxyMock.restoreDefaultNotificationModeRoomIdReceivedRoomId, roomProxyMock.id) + XCTAssertEqual(notificationSettingsProxyMock.restoreDefaultNotificationModeRoomIdCallsCount, 1) + } + + func testToggleAllCustomSettingOffOn() async throws { + notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: true)) + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock, + roomProxy: roomProxyMock) + let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState) + .first(where: \.isLoaded)) + try await deferred.fulfill() + + viewModel.state.bindings.allowCustomSetting = true + context.send(viewAction: .changedAllowCustomSettings) + await context.nextViewState() + + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.1, .mentionsAndKeywordsOnly) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeCallsCount, 1) + } + + func testSetCustomMode() async throws { + notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedActiveMembersCountReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false)) + viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock, + roomProxy: roomProxyMock) + let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState) + .first(where: \.isLoaded)) + try await deferred.fulfill() + + do { + context.send(viewAction: .setCustomMode(.allMessages)) + await context.nextViewState() + + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.1, .allMessages) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeCallsCount, 1) + } + + do { + context.send(viewAction: .setCustomMode(.mute)) + await context.nextViewState() + + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.1, .mute) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeCallsCount, 2) + } + + do { + context.send(viewAction: .setCustomMode(.mentionsAndKeywordsOnly)) + await context.nextViewState() + + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.1, .mentionsAndKeywordsOnly) + XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeCallsCount, 3) + } + } +}