New notifications section in settings (#1273)

This commit is contained in:
Nicolas Mauri
2023-07-10 19:26:49 +02:00
committed by GitHub
parent 2cec1858ff
commit 5ae227179c
26 changed files with 639 additions and 14 deletions
+56
View File
@@ -61,6 +61,7 @@
167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */; }; 167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */; };
1702981A8085BE4FB0EC001B /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33116993D54FADC0C721C1F /* Application.swift */; }; 1702981A8085BE4FB0EC001B /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33116993D54FADC0C721C1F /* Application.swift */; };
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */; }; 172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */; };
17780569FB41E9BAC60D4710 /* UNUserNotificationCenter+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E685274772980BDEFF6691E /* UNUserNotificationCenter+Settings.swift */; };
18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; }; 18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */; }; 1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */; };
19FE025AE9BA2959B6589B0D /* RoomMemberDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC575D1895FA62591451A93 /* RoomMemberDetailsScreen.swift */; }; 19FE025AE9BA2959B6589B0D /* RoomMemberDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC575D1895FA62591451A93 /* RoomMemberDetailsScreen.swift */; };
@@ -269,6 +270,7 @@
664F77F02A57617A00FB9B24 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664F77EF2A57617A00FB9B24 /* XCTestCase.swift */; }; 664F77F02A57617A00FB9B24 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664F77EF2A57617A00FB9B24 /* XCTestCase.swift */; };
6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */; }; 6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */; };
67160204A8D362BB7D4AD259 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693E16574C6F7F9FA1015A8C /* Search.swift */; }; 67160204A8D362BB7D4AD259 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693E16574C6F7F9FA1015A8C /* Search.swift */; };
6786C4B0936AC84D993B20BF /* NotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */; };
67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; }; 67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; };
67D6E0700A9C1E676F6231F8 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = AD544C0FA48DFFB080920061 /* Collections */; }; 67D6E0700A9C1E676F6231F8 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = AD544C0FA48DFFB080920061 /* Collections */; };
68184EF36396424FE19A727D /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */; }; 68184EF36396424FE19A727D /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */; };
@@ -337,6 +339,7 @@
7F08F4BC1312075E2B5EAEFA /* AuthenticationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */; }; 7F08F4BC1312075E2B5EAEFA /* AuthenticationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */; };
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */; }; 7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */; };
7F64FA937B95924B3A44EC12 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB8E75B9CB6C78BE8D09B1AF /* OnboardingScreen.swift */; }; 7F64FA937B95924B3A44EC12 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB8E75B9CB6C78BE8D09B1AF /* OnboardingScreen.swift */; };
7F7EA51A9A43125A8CB6AC90 /* NotificationSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D560DDA3B20C82766ACFAD /* NotificationSettingsScreenViewModel.swift */; };
7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */; }; 7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */; };
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; }; 8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; };
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; }; 804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; };
@@ -492,6 +495,7 @@
AF19D65A9C60C6B2646F3210 /* RedactedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E6BDF9D26DB05C88901416 /* RedactedRoomTimelineItem.swift */; }; AF19D65A9C60C6B2646F3210 /* RedactedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E6BDF9D26DB05C88901416 /* RedactedRoomTimelineItem.swift */; };
AF2ABA2794E376B64104C964 /* MockSoftLogoutScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5644919DB2022397D9D5825A /* MockSoftLogoutScreenState.swift */; }; AF2ABA2794E376B64104C964 /* MockSoftLogoutScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5644919DB2022397D9D5825A /* MockSoftLogoutScreenState.swift */; };
AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */; }; AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */; };
AF4232E6F08C3DB86FFA9BBD /* NotificationSettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83BC0DC9A2DF2DD60F9B6E9 /* NotificationSettingsScreenUITests.swift */; };
AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */; }; AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */; };
B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; }; B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; };
B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; }; B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; };
@@ -584,6 +588,7 @@
CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */; }; CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */; };
CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; }; CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
CBA9EDF305036039166E76FF /* StartChatScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2AEC1AB349A341FE13DEC1 /* StartChatScreenUITests.swift */; }; CBA9EDF305036039166E76FF /* StartChatScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2AEC1AB349A341FE13DEC1 /* StartChatScreenUITests.swift */; };
CBD2ABE4C1A47ECD99E1488E /* NotificationSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421FA93BCC2840E66E4F306F /* NotificationSettingsScreenViewModelProtocol.swift */; };
CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */; }; CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */; };
CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */; }; CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */; };
CCAA0671B46EAFD0BB528E2C /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 8FC26871038FB0E4AAE22605 /* apple_emojis_data.json */; }; CCAA0671B46EAFD0BB528E2C /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 8FC26871038FB0E4AAE22605 /* apple_emojis_data.json */; };
@@ -598,6 +603,7 @@
CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */; }; CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */; };
CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; }; CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; };
D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; }; D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; };
D12F440F7973F1489F61389D /* NotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */; };
D181AC8FF236B7F91C0A8C28 /* MapTiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AA3F4B285570805CB0CCDD /* MapTiler.swift */; }; D181AC8FF236B7F91C0A8C28 /* MapTiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AA3F4B285570805CB0CCDD /* MapTiler.swift */; };
D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */; }; D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */; };
D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; }; D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; };
@@ -648,6 +654,7 @@
E2DDA49BD62F03F180A42E30 /* MapLibreStaticMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */; }; E2DDA49BD62F03F180A42E30 /* MapLibreStaticMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */; };
E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0287793F11C480E242B03DF5 /* UserDiscoveryServiceTest.swift */; }; E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0287793F11C480E242B03DF5 /* UserDiscoveryServiceTest.swift */; };
E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */; }; E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */; };
E3AC72E3E58F364EF15C1CC7 /* NotificationSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */; };
E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */; }; E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */; };
E3E1E255DC8CB34BD8573E0D /* UserIndicatorControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */; }; E3E1E255DC8CB34BD8573E0D /* UserIndicatorControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */; };
E45C9FA22BC13B477FD3B4AC /* EmojiDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */; }; E45C9FA22BC13B477FD3B4AC /* EmojiDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */; };
@@ -705,6 +712,7 @@
F656F92A63D3DC1978D79427 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 07FEEEDB11543A7DED420F04 /* Compound */; }; F656F92A63D3DC1978D79427 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 07FEEEDB11543A7DED420F04 /* Compound */; };
F692D4AF571333C0D785725A /* MigrationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBC5E7C0F8337D2A46EB2DD /* MigrationScreenViewModelProtocol.swift */; }; F692D4AF571333C0D785725A /* MigrationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBC5E7C0F8337D2A46EB2DD /* MigrationScreenViewModelProtocol.swift */; };
F697284B9B5F2C00CFEA3B12 /* EmojiDetectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */; }; F697284B9B5F2C00CFEA3B12 /* EmojiDetectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */; };
F6DFA23885980118AD7359C5 /* NotificationSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2389732B0E115A999A069083 /* NotificationSettingsScreenCoordinator.swift */; };
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; }; F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; };
F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; }; F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; };
F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */; }; F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */; };
@@ -822,6 +830,7 @@
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; }; 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; };
0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenUITests.swift; sourceTree = "<group>"; }; 0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenUITests.swift; sourceTree = "<group>"; };
0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = "<group>"; }; 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = "<group>"; };
0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenModels.swift; sourceTree = "<group>"; };
0FA60F848D1C14F873F9621A /* RoomMemberDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenCoordinator.swift; sourceTree = "<group>"; }; 0FA60F848D1C14F873F9621A /* RoomMemberDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenCoordinator.swift; sourceTree = "<group>"; };
0FB782CE6176A5D2C082EC5D /* WelcomeScreenScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenScreenModels.swift; sourceTree = "<group>"; }; 0FB782CE6176A5D2C082EC5D /* WelcomeScreenScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenScreenModels.swift; sourceTree = "<group>"; };
1059E2AE7878CF7820592637 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; 1059E2AE7878CF7820592637 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@@ -885,6 +894,7 @@
225EFCA26877E75CDFE7F48D /* MapTilerStyleBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStyleBuilderProtocol.swift; sourceTree = "<group>"; }; 225EFCA26877E75CDFE7F48D /* MapTilerStyleBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStyleBuilderProtocol.swift; sourceTree = "<group>"; };
22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; };
227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; }; 227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
2389732B0E115A999A069083 /* NotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = "<group>"; }; 23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = "<group>"; };
24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = "<group>"; }; 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = "<group>"; };
2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerTests.swift; sourceTree = "<group>"; }; 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerTests.swift; sourceTree = "<group>"; };
@@ -956,6 +966,7 @@
4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenUITests.swift; sourceTree = "<group>"; }; 4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenUITests.swift; sourceTree = "<group>"; };
4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreen.swift; sourceTree = "<group>"; }; 4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreen.swift; sourceTree = "<group>"; };
421E716C521F96D24ECE69B3 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = "<group>"; }; 421E716C521F96D24ECE69B3 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = "<group>"; };
421FA93BCC2840E66E4F306F /* NotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
422724361B6555364C43281E /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = "<group>"; }; 422724361B6555364C43281E /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = "<group>"; };
42ADEA322D2089391E049535 /* InvitesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreen.swift; sourceTree = "<group>"; }; 42ADEA322D2089391E049535 /* InvitesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreen.swift; sourceTree = "<group>"; };
42C64A14EE89928207E3B42B /* AnalyticsSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenModels.swift; sourceTree = "<group>"; }; 42C64A14EE89928207E3B42B /* AnalyticsSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenModels.swift; sourceTree = "<group>"; };
@@ -970,6 +981,7 @@
466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenModels.swift; sourceTree = "<group>"; }; 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenModels.swift; sourceTree = "<group>"; };
46B59EC4B0C93254089EAACB /* MigrationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModelTests.swift; sourceTree = "<group>"; }; 46B59EC4B0C93254089EAACB /* MigrationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModelTests.swift; sourceTree = "<group>"; };
46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = "<group>"; }; 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = "<group>"; };
46D560DDA3B20C82766ACFAD /* NotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModel.swift; sourceTree = "<group>"; };
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; }; 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
@@ -998,6 +1010,7 @@
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = "<group>"; }; 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = "<group>"; };
5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = "<group>"; }; 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = "<group>"; };
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = "<group>"; }; 50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = "<group>"; };
514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
51C2BCE0BC1FC69C1B36E688 /* BugReportScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenModels.swift; sourceTree = "<group>"; }; 51C2BCE0BC1FC69C1B36E688 /* BugReportScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenModels.swift; sourceTree = "<group>"; };
51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileProxy.swift; sourceTree = "<group>"; }; 51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileProxy.swift; sourceTree = "<group>"; };
52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenModels.swift; sourceTree = "<group>"; }; 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenModels.swift; sourceTree = "<group>"; };
@@ -1189,6 +1202,7 @@
9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackCoordinatorTests.swift; sourceTree = "<group>"; }; 9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackCoordinatorTests.swift; sourceTree = "<group>"; };
9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = "<group>"; }; 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = "<group>"; };
9E685274772980BDEFF6691E /* UNUserNotificationCenter+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNUserNotificationCenter+Settings.swift"; sourceTree = "<group>"; };
9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = "<group>"; }; 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = "<group>"; };
9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = "<group>"; }; 9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = "<group>"; };
A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = "<group>"; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@@ -1262,6 +1276,7 @@
B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsSignalling.swift; sourceTree = "<group>"; }; B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsSignalling.swift; sourceTree = "<group>"; };
B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenCoordinator.swift; sourceTree = "<group>"; }; B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenCoordinator.swift; sourceTree = "<group>"; };
B81B6170DB690013CEB646F4 /* MapLibreModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreModels.swift; sourceTree = "<group>"; }; B81B6170DB690013CEB646F4 /* MapLibreModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreModels.swift; sourceTree = "<group>"; };
B83BC0DC9A2DF2DD60F9B6E9 /* NotificationSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenUITests.swift; sourceTree = "<group>"; };
B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; }; B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; };
B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; }; B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; };
B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = "<group>"; }; B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = "<group>"; };
@@ -1301,6 +1316,7 @@
C54464351F170D570110AFCA /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; }; C54464351F170D570110AFCA /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; }; C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenUITests.swift; sourceTree = "<group>"; }; C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenUITests.swift; sourceTree = "<group>"; };
C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreen.swift; sourceTree = "<group>"; };
C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelProtocol.swift; sourceTree = "<group>"; }; C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomCoordinator.swift; sourceTree = "<group>"; }; C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomCoordinator.swift; sourceTree = "<group>"; };
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; }; C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
@@ -2326,6 +2342,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C830A64609CBD152F06E0457 /* NotificationConstants.swift */, C830A64609CBD152F06E0457 /* NotificationConstants.swift */,
A9E072BD31B744FC34F4D368 /* Helper */,
6EE5E2BBFBC7947CFE789B4D /* Manager */, 6EE5E2BBFBC7947CFE789B4D /* Manager */,
832FC81F760220239E285294 /* Proxy */, 832FC81F760220239E285294 /* Proxy */,
); );
@@ -2349,6 +2366,7 @@
09C599CB430ABF160C1EE55C /* AnalyticsSettingsScreen */, 09C599CB430ABF160C1EE55C /* AnalyticsSettingsScreen */,
1CA6CD0DE6F0445156361B6D /* DeveloperOptionsScreen */, 1CA6CD0DE6F0445156361B6D /* DeveloperOptionsScreen */,
38A1C74493B816B8753F5BC2 /* LegalInformationScreen */, 38A1C74493B816B8753F5BC2 /* LegalInformationScreen */,
7B91CB64534AD870924CCFEF /* NotificationSettingsScreen */,
B364E08924AD15820350CDD9 /* SettingsScreen */, B364E08924AD15820350CDD9 /* SettingsScreen */,
); );
path = Settings; path = Settings;
@@ -2420,6 +2438,7 @@
F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */, F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */,
78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */, 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */,
9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */, 9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */,
514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */,
00A941F289F6AB876BA3361A /* OnboardingViewModelTests.swift */, 00A941F289F6AB876BA3361A /* OnboardingViewModelTests.swift */,
6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */, 6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */,
086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */, 086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */,
@@ -2568,6 +2587,18 @@
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7B91CB64534AD870924CCFEF /* NotificationSettingsScreen */ = {
isa = PBXGroup;
children = (
2389732B0E115A999A069083 /* NotificationSettingsScreenCoordinator.swift */,
0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */,
46D560DDA3B20C82766ACFAD /* NotificationSettingsScreenViewModel.swift */,
421FA93BCC2840E66E4F306F /* NotificationSettingsScreenViewModelProtocol.swift */,
BE7641A284D3E81DC96943E3 /* View */,
);
path = NotificationSettingsScreen;
sourceTree = "<group>";
};
7DBC911559934065993A5FF4 /* NotificationManager */ = { 7DBC911559934065993A5FF4 /* NotificationManager */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -2792,6 +2823,7 @@
39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */, 39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */,
59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */, 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */,
75910F5A36EA8FF9BAD08D18 /* MigrationScreenUITests.swift */, 75910F5A36EA8FF9BAD08D18 /* MigrationScreenUITests.swift */,
B83BC0DC9A2DF2DD60F9B6E9 /* NotificationSettingsScreenUITests.swift */,
0C88046D6A070D9827181C4D /* OnboardingUITests.swift */, 0C88046D6A070D9827181C4D /* OnboardingUITests.swift */,
4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */, 4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */,
122186B7CD1BC46A9C629DD9 /* RoomDetailsEditScreenUITests.swift */, 122186B7CD1BC46A9C629DD9 /* RoomDetailsEditScreenUITests.swift */,
@@ -2970,6 +3002,14 @@
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A9E072BD31B744FC34F4D368 /* Helper */ = {
isa = PBXGroup;
children = (
9E685274772980BDEFF6691E /* UNUserNotificationCenter+Settings.swift */,
);
path = Helper;
sourceTree = "<group>";
};
AAFDD509929A0CCF8BCE51EB /* Authentication */ = { AAFDD509929A0CCF8BCE51EB /* Authentication */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -3131,6 +3171,14 @@
path = ServerConfirmationScreen; path = ServerConfirmationScreen;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BE7641A284D3E81DC96943E3 /* View */ = {
isa = PBXGroup;
children = (
C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
C0937E3B06A8F0E2DB7C8241 /* Other */ = { C0937E3B06A8F0E2DB7C8241 /* Other */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -4037,6 +4085,7 @@
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */, 69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */,
4BB282209EA82015D0DF8F89 /* NavigationStackCoordinatorTests.swift in Sources */, 4BB282209EA82015D0DF8F89 /* NavigationStackCoordinatorTests.swift in Sources */,
1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */, 1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */,
E3AC72E3E58F364EF15C1CC7 /* NotificationSettingsScreenViewModelTests.swift in Sources */,
F9F6D2883BBEBB9A3789A137 /* OnboardingViewModelTests.swift in Sources */, F9F6D2883BBEBB9A3789A137 /* OnboardingViewModelTests.swift in Sources */,
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */, 27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
D415764645491F10344FC6AC /* Publisher.swift in Sources */, D415764645491F10344FC6AC /* Publisher.swift in Sources */,
@@ -4341,6 +4390,11 @@
CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */, CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */,
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */, 652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */,
06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */, 06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */,
6786C4B0936AC84D993B20BF /* NotificationSettingsScreen.swift in Sources */,
F6DFA23885980118AD7359C5 /* NotificationSettingsScreenCoordinator.swift in Sources */,
D12F440F7973F1489F61389D /* NotificationSettingsScreenModels.swift in Sources */,
7F7EA51A9A43125A8CB6AC90 /* NotificationSettingsScreenViewModel.swift in Sources */,
CBD2ABE4C1A47ECD99E1488E /* NotificationSettingsScreenViewModelProtocol.swift in Sources */,
9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */, 9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */,
329571083B132E4941131835 /* OnboardingBackgroundImage.swift in Sources */, 329571083B132E4941131835 /* OnboardingBackgroundImage.swift in Sources */,
2CB6787E25B11711518E9588 /* OnboardingCoordinator.swift in Sources */, 2CB6787E25B11711518E9588 /* OnboardingCoordinator.swift in Sources */,
@@ -4530,6 +4584,7 @@
22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */, 22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */,
706289B086B0A6B0C211763F /* UITestsSignalling.swift in Sources */, 706289B086B0A6B0C211763F /* UITestsSignalling.swift in Sources */,
D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */, D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */,
17780569FB41E9BAC60D4710 /* UNUserNotificationCenter+Settings.swift in Sources */,
071A017E415AD378F2961B11 /* URL.swift in Sources */, 071A017E415AD378F2961B11 /* URL.swift in Sources */,
90733645AE76FB33DAD28C2B /* URLSession.swift in Sources */, 90733645AE76FB33DAD28C2B /* URLSession.swift in Sources */,
18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */, 18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */,
@@ -4598,6 +4653,7 @@
7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */, 7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */,
6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */, 6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */,
51C240F4660F7269203A9B3A /* MigrationScreenUITests.swift in Sources */, 51C240F4660F7269203A9B3A /* MigrationScreenUITests.swift in Sources */,
AF4232E6F08C3DB86FFA9BBD /* NotificationSettingsScreenUITests.swift in Sources */,
6B15FF984906AAFCF9DC4F58 /* OnboardingUITests.swift in Sources */, 6B15FF984906AAFCF9DC4F58 /* OnboardingUITests.swift in Sources */,
BA0D3DDCEDD97502DAC4B6E9 /* ReportContentScreenUITests.swift in Sources */, BA0D3DDCEDD97502DAC4B6E9 /* ReportContentScreenUITests.swift in Sources */,
F16109A6F6DF03DA26D59233 /* RoomDetailsEditScreenUITests.swift in Sources */, F16109A6F6DF03DA26D59233 /* RoomDetailsEditScreenUITests.swift in Sources */,
@@ -133,12 +133,14 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
// MARK: - NotificationManagerDelegate // MARK: - NotificationManagerDelegate
func authorizationStatusUpdated(_ service: NotificationManagerProtocol, granted: Bool) { func registerForRemoteNotifications() {
if granted { UIApplication.shared.registerForRemoteNotifications()
UIApplication.shared.registerForRemoteNotifications()
}
} }
func unregisterForRemoteNotifications() {
UIApplication.shared.unregisterForRemoteNotifications()
}
func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol, content: UNNotificationContent) -> Bool { func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol, content: UNNotificationContent) -> Bool {
guard let roomId = content.roomID else { guard let roomId = content.roomID else {
return true return true
@@ -26,6 +26,7 @@ final class AppSettings {
case migratedAccounts case migratedAccounts
case timelineStyle case timelineStyle
case analyticsConsentState case analyticsConsentState
case enableNotifications
case enableInAppNotifications case enableInAppNotifications
case pusherProfileTag case pusherProfileTag
case shouldCollapseRoomStateEvents case shouldCollapseRoomStateEvents
@@ -34,6 +35,7 @@ final class AppSettings {
case locationEventsEnabled case locationEventsEnabled
case shareLocationEnabled case shareLocationEnabled
case hasShownWelcomeScreen case hasShownWelcomeScreen
case notificationSettingsEnabled
} }
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@@ -178,6 +180,9 @@ final class AppSettings {
// MARK: - Notifications // MARK: - Notifications
@UserPreference(key: UserDefaultsKeys.enableNotifications, defaultValue: true, storageType: .userDefaults(store))
var enableNotifications
@UserPreference(key: UserDefaultsKeys.enableInAppNotifications, defaultValue: true, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.enableInAppNotifications, defaultValue: true, storageType: .userDefaults(store))
var enableInAppNotifications var enableInAppNotifications
@@ -218,4 +223,7 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.shareLocationEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.shareLocationEnabled, defaultValue: false, storageType: .userDefaults(store))
var shareLocationEnabled var shareLocationEnabled
@UserPreference(key: UserDefaultsKeys.notificationSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
var notificationSettingsEnabled
} }
@@ -1317,4 +1317,86 @@ class UserIndicatorControllerMock: UserIndicatorControllerProtocol {
} }
} }
} }
class UserNotificationCenterMock: UserNotificationCenterProtocol {
var delegate: UNUserNotificationCenterDelegate?
//MARK: - add
var addThrowableError: Error?
var addCallsCount = 0
var addCalled: Bool {
return addCallsCount > 0
}
var addReceivedRequest: UNNotificationRequest?
var addReceivedInvocations: [UNNotificationRequest] = []
var addClosure: ((UNNotificationRequest) async throws -> Void)?
func add(_ request: UNNotificationRequest) async throws {
if let error = addThrowableError {
throw error
}
addCallsCount += 1
addReceivedRequest = request
addReceivedInvocations.append(request)
try await addClosure?(request)
}
//MARK: - requestAuthorization
var requestAuthorizationOptionsThrowableError: Error?
var requestAuthorizationOptionsCallsCount = 0
var requestAuthorizationOptionsCalled: Bool {
return requestAuthorizationOptionsCallsCount > 0
}
var requestAuthorizationOptionsReceivedOptions: UNAuthorizationOptions?
var requestAuthorizationOptionsReceivedInvocations: [UNAuthorizationOptions] = []
var requestAuthorizationOptionsReturnValue: Bool!
var requestAuthorizationOptionsClosure: ((UNAuthorizationOptions) async throws -> Bool)?
func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool {
if let error = requestAuthorizationOptionsThrowableError {
throw error
}
requestAuthorizationOptionsCallsCount += 1
requestAuthorizationOptionsReceivedOptions = options
requestAuthorizationOptionsReceivedInvocations.append(options)
if let requestAuthorizationOptionsClosure = requestAuthorizationOptionsClosure {
return try await requestAuthorizationOptionsClosure(options)
} else {
return requestAuthorizationOptionsReturnValue
}
}
//MARK: - setNotificationCategories
var setNotificationCategoriesCallsCount = 0
var setNotificationCategoriesCalled: Bool {
return setNotificationCategoriesCallsCount > 0
}
var setNotificationCategoriesReceivedCategories: Set<UNNotificationCategory>?
var setNotificationCategoriesReceivedInvocations: [Set<UNNotificationCategory>] = []
var setNotificationCategoriesClosure: ((Set<UNNotificationCategory>) -> Void)?
func setNotificationCategories(_ categories: Set<UNNotificationCategory>) {
setNotificationCategoriesCallsCount += 1
setNotificationCategoriesReceivedCategories = categories
setNotificationCategoriesReceivedInvocations.append(categories)
setNotificationCategoriesClosure?(categories)
}
//MARK: - authorizationStatus
var authorizationStatusCallsCount = 0
var authorizationStatusCalled: Bool {
return authorizationStatusCallsCount > 0
}
var authorizationStatusReturnValue: UNAuthorizationStatus!
var authorizationStatusClosure: (() async -> UNAuthorizationStatus)?
func authorizationStatus() async -> UNAuthorizationStatus {
authorizationStatusCallsCount += 1
if let authorizationStatusClosure = authorizationStatusClosure {
return await authorizationStatusClosure()
} else {
return authorizationStatusReturnValue
}
}
}
// swiftlint:enable all // swiftlint:enable all
@@ -31,6 +31,7 @@ struct DeveloperOptionsScreenViewStateBindings {
var isEncryptionSyncEnabled: Bool var isEncryptionSyncEnabled: Bool
var locationEventsEnabled: Bool var locationEventsEnabled: Bool
var shareLocationEnabled: Bool var shareLocationEnabled: Bool
var notificationSettingsEnabled: Bool
} }
enum DeveloperOptionsScreenViewAction { enum DeveloperOptionsScreenViewAction {
@@ -40,5 +41,6 @@ enum DeveloperOptionsScreenViewAction {
case changedIsEncryptionSyncEnabled case changedIsEncryptionSyncEnabled
case changedLocationEventsEnabled case changedLocationEventsEnabled
case changedShareLocationEnabled case changedShareLocationEnabled
case changedNotificationSettingsEnabled
case clearCache case clearCache
} }
@@ -31,7 +31,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
readReceiptsEnabled: appSettings.readReceiptsEnabled, readReceiptsEnabled: appSettings.readReceiptsEnabled,
isEncryptionSyncEnabled: appSettings.isEncryptionSyncEnabled, isEncryptionSyncEnabled: appSettings.isEncryptionSyncEnabled,
locationEventsEnabled: appSettings.locationEventsEnabled, locationEventsEnabled: appSettings.locationEventsEnabled,
shareLocationEnabled: appSettings.shareLocationEnabled) shareLocationEnabled: appSettings.shareLocationEnabled,
notificationSettingsEnabled: appSettings.notificationSettingsEnabled)
let state = DeveloperOptionsScreenViewState(bindings: bindings) let state = DeveloperOptionsScreenViewState(bindings: bindings)
super.init(initialViewState: state) super.init(initialViewState: state)
@@ -55,6 +56,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
appSettings.locationEventsEnabled = state.bindings.locationEventsEnabled appSettings.locationEventsEnabled = state.bindings.locationEventsEnabled
case .changedShareLocationEnabled: case .changedShareLocationEnabled:
appSettings.shareLocationEnabled = state.bindings.shareLocationEnabled appSettings.shareLocationEnabled = state.bindings.shareLocationEnabled
case .changedNotificationSettingsEnabled:
appSettings.notificationSettingsEnabled = state.bindings.notificationSettingsEnabled
case .clearCache: case .clearCache:
callback?(.clearCache) callback?(.clearCache)
} }
@@ -66,6 +66,13 @@ struct DeveloperOptionsScreen: View {
.onChange(of: context.shareLocationEnabled) { _ in .onChange(of: context.shareLocationEnabled) { _ in
context.send(viewAction: .changedShareLocationEnabled) context.send(viewAction: .changedShareLocationEnabled)
} }
Toggle(isOn: $context.notificationSettingsEnabled) {
Text("Show notification settings")
}
.onChange(of: context.notificationSettingsEnabled) { _ in
context.send(viewAction: .changedNotificationSettingsEnabled)
}
} }
Section { Section {
@@ -0,0 +1,48 @@
//
// 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 NotificationSettingsScreenCoordinatorParameters {
let userNotificationCenter: UserNotificationCenterProtocol
}
enum NotificationSettingsScreenCoordinatorAction { }
final class NotificationSettingsScreenCoordinator: CoordinatorProtocol {
private let parameters: NotificationSettingsScreenCoordinatorParameters
private var viewModel: NotificationSettingsScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<NotificationSettingsScreenCoordinatorAction, Never> = .init()
private var cancellables: Set<AnyCancellable> = .init()
var actions: AnyPublisher<NotificationSettingsScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: NotificationSettingsScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = NotificationSettingsScreenViewModel(appSettings: ServiceLocator.shared.settings,
userNotificationCenter: parameters.userNotificationCenter)
}
func start() { }
func toPresentable() -> AnyView {
AnyView(NotificationSettingsScreen(context: viewModel.context))
}
}
@@ -0,0 +1,53 @@
//
// 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
import UIKit
enum NotificationSettingsScreenViewModelAction { }
struct NotificationSettingsScreenViewState: BindableState {
var bindings: NotificationSettingsScreenViewStateBindings
var strings = NotificationSettingsScreenStrings()
var isUserPermissionGranted: Bool?
var showSystemNotificationsAlert: Bool {
bindings.enableNotifications && isUserPermissionGranted == false
}
}
struct NotificationSettingsScreenViewStateBindings {
var enableNotifications = false
}
struct NotificationSettingsScreenStrings {
let changeYourSystemSettings: AttributedString = {
let linkPlaceholder = "{link}"
var text = AttributedString(L10n.screenNotificationSettingsSystemNotificationsActionRequired(linkPlaceholder))
var linkString = AttributedString(L10n.screenNotificationSettingsSystemNotificationsActionRequiredContentLink)
// Note: On the simulator, `UIApplication.openNotificationSettingsURLString` opens the `Settings` application instead of the application's notification settings screen.
linkString.link = URL(string: UIApplication.openNotificationSettingsURLString)
linkString.bold()
text.replace(linkPlaceholder, with: linkString)
return text
}()
}
enum NotificationSettingsScreenViewAction {
case linkClicked(url: URL)
case changedEnableNotifications
}
@@ -0,0 +1,76 @@
//
// 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 NotificationSettingsScreenViewModelType = StateStoreViewModel<NotificationSettingsScreenViewState, NotificationSettingsScreenViewAction>
class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelType, NotificationSettingsScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<NotificationSettingsScreenViewModelAction, Never> = .init()
private let appSettings: AppSettings
private let userNotificationCenter: UserNotificationCenterProtocol
var actions: AnyPublisher<NotificationSettingsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(appSettings: AppSettings, userNotificationCenter: UserNotificationCenterProtocol) {
self.appSettings = appSettings
self.userNotificationCenter = userNotificationCenter
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: appSettings.enableNotifications)
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings))
// Listen for changes to AppSettings.enableNotifications
appSettings.$enableNotifications
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
.store(in: &cancellables)
// Refresh authorization status uppon UIApplication.didBecomeActiveNotification notification
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { [weak self] _ in
Task {
await self?.readSystemAuthorizationStatus()
}
}
.store(in: &cancellables)
Task {
await readSystemAuthorizationStatus()
}
}
// MARK: - Public
override func process(viewAction: NotificationSettingsScreenViewAction) {
switch viewAction {
case .linkClicked(let url):
MXLog.warning("Link clicked: \(url)")
case .changedEnableNotifications:
toggleNotifications()
}
}
// MARK: - Private
func readSystemAuthorizationStatus() async {
state.isUserPermissionGranted = await userNotificationCenter.authorizationStatus() == .authorized
}
func toggleNotifications() {
appSettings.enableNotifications.toggle()
}
}
@@ -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 NotificationSettingsScreenViewModelProtocol {
var actions: AnyPublisher<NotificationSettingsScreenViewModelAction, Never> { get }
var context: NotificationSettingsScreenViewModelType.Context { get }
}
@@ -0,0 +1,88 @@
//
// 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 NotificationSettingsScreen: View {
@ObservedObject var context: NotificationSettingsScreenViewModel.Context
var body: some View {
Form {
if context.viewState.showSystemNotificationsAlert {
userPermissionSection
}
enableNotificationSection
}
.compoundForm()
.navigationTitle(L10n.screenNotificationSettingsTitle)
.track(screen: .settingsNotifications)
}
// MARK: - Private
private var userPermissionSection: some View {
Section {
HStack(alignment: .firstTextBaseline, spacing: 13) {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(.compound.iconTertiaryAlpha)
VStack(alignment: .leading, spacing: 2) {
Text(L10n.screenNotificationSettingsSystemNotificationsTurnedOff)
.font(.compound.bodyLG)
.foregroundColor(.compound.textPrimary)
Text(context.viewState.strings.changeYourSystemSettings)
.font(.compound.bodySM)
.foregroundColor(.compound.textSecondary)
.tint(.compound.textPrimary)
.environment(\.openURL, OpenURLAction { url in
context.send(viewAction: .linkClicked(url: url))
return .systemAction
})
}
.padding(.vertical, 5)
}
}
.compoundFormSection()
}
private var enableNotificationSection: some View {
Section {
Toggle(isOn: $context.enableNotifications) {
Text(L10n.screenNotificationSettingsEnableNotifications)
}
.toggleStyle(.compoundForm)
.onChange(of: context.enableNotifications) { _ in
context.send(viewAction: .changedEnableNotifications)
}
}
.compoundFormSection()
}
}
// MARK: - Previews
struct NotificationSettingsScreen_Previews: PreviewProvider {
static let viewModel: NotificationSettingsScreenViewModel = {
let appSettings = AppSettings()
let notificationCenter = UserNotificationCenterMock()
notificationCenter.authorizationStatusReturnValue = .notDetermined
var viewModel = NotificationSettingsScreenViewModel(appSettings: appSettings, userNotificationCenter: notificationCenter)
return viewModel
}()
static var previews: some View {
NotificationSettingsScreen(context: viewModel.context)
}
}
@@ -59,6 +59,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
self.presentDeveloperOptions() self.presentDeveloperOptions()
case .logout: case .logout:
self.callback?(.logout) self.callback?(.logout)
case .notifications:
self.presentNotificationSettings()
} }
} }
} }
@@ -136,4 +138,12 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
private func showSuccess(label: String) { private func showSuccess(label: String) {
parameters.userIndicatorController?.submitIndicator(UserIndicator(title: label)) parameters.userIndicatorController?.submitIndicator(UserIndicator(title: label))
} }
private func presentNotificationSettings() {
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(
userNotificationCenter: UNUserNotificationCenter.current()
)
let coordinator = NotificationSettingsScreenCoordinator(parameters: notificationParameters)
parameters.navigationStackCoordinator?.push(coordinator)
}
} }
@@ -24,6 +24,7 @@ enum SettingsScreenViewModelAction {
case about case about
case sessionVerification case sessionVerification
case developerOptions case developerOptions
case notifications
case logout case logout
} }
@@ -34,6 +35,7 @@ struct SettingsScreenViewState: BindableState {
var userAvatarURL: URL? var userAvatarURL: URL?
var userDisplayName: String? var userDisplayName: String?
var showSessionVerificationSection: Bool var showSessionVerificationSection: Bool
var showNotificationSettings: Bool
var showDeveloperOptions: Bool var showDeveloperOptions: Bool
} }
@@ -50,4 +52,5 @@ enum SettingsScreenViewAction {
case logout case logout
case changedTimelineStyle case changedTimelineStyle
case developerOptions case developerOptions
case notifications
} }
@@ -40,6 +40,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
deviceID: userSession.deviceID, deviceID: userSession.deviceID,
userID: userSession.userID, userID: userSession.userID,
showSessionVerificationSection: showSessionVerificationSection, showSessionVerificationSection: showSessionVerificationSection,
showNotificationSettings: appSettings.notificationSettingsEnabled,
showDeveloperOptions: appSettings.canShowDeveloperOptions), showDeveloperOptions: appSettings.canShowDeveloperOptions),
imageProvider: userSession.mediaProvider) imageProvider: userSession.mediaProvider)
@@ -51,6 +52,10 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
.weakAssign(to: \.state.userAvatarURL, on: self) .weakAssign(to: \.state.userAvatarURL, on: self)
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$notificationSettingsEnabled
.weakAssign(to: \.state.showNotificationSettings, on: self)
.store(in: &cancellables)
Task { Task {
await userSession.clientProxy.loadUserAvatarURL() await userSession.clientProxy.loadUserAvatarURL()
} }
@@ -92,6 +97,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
callback?(.sessionVerification) callback?(.sessionVerification)
case .changedTimelineStyle: case .changedTimelineStyle:
appSettings.timelineStyle = state.bindings.timelineStyle appSettings.timelineStyle = state.bindings.timelineStyle
case .notifications:
callback?(.notifications)
case .developerOptions: case .developerOptions:
callback?(.developerOptions) callback?(.developerOptions)
} }
@@ -113,6 +113,15 @@ struct SettingsScreen: View {
context.send(viewAction: .changedTimelineStyle) context.send(viewAction: .changedTimelineStyle)
} }
// Notifications
if context.viewState.showNotificationSettings {
Button { context.send(viewAction: .notifications) } label: {
Label(L10n.screenNotificationSettingsTitle, systemImage: "bell")
}
.buttonStyle(.compoundForm(accessory: .navigationLink))
.accessibilityIdentifier("notificationsButton")
}
// Analytics // Analytics
Button { context.send(viewAction: .analytics) } label: { Button { context.send(viewAction: .analytics) } label: {
Label(L10n.commonAnalytics, systemImage: "chart.bar") Label(L10n.commonAnalytics, systemImage: "chart.bar")
@@ -194,6 +203,7 @@ struct SettingsScreen_Previews: PreviewProvider {
static let viewModel = { static let viewModel = {
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"),
mediaProvider: MockMediaProvider()) mediaProvider: MockMediaProvider())
ServiceLocator.shared.settings.notificationSettingsEnabled = true
return SettingsScreenViewModel(userSession: userSession, return SettingsScreenViewModel(userSession: userSession,
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings)
}() }()
@@ -0,0 +1,24 @@
//
// 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 UserNotifications
extension UNUserNotificationCenter {
func authorizationStatus() async -> UNAuthorizationStatus {
await notificationSettings().authorizationStatus
}
}
@@ -24,7 +24,9 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
private let appSettings: AppSettings private let appSettings: AppSettings
private var userSession: UserSessionProtocol? private var userSession: UserSessionProtocol?
private var cancellables = Set<AnyCancellable>()
private var notificationsEnabled = false
init(notificationCenter: UserNotificationCenterProtocol, init(notificationCenter: UserNotificationCenterProtocol,
appSettings: AppSettings) { appSettings: AppSettings) {
self.notificationCenter = notificationCenter self.notificationCenter = notificationCenter
@@ -50,15 +52,35 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
options: []) options: [])
notificationCenter.setNotificationCategories([messageCategory, inviteCategory]) notificationCenter.setNotificationCategories([messageCategory, inviteCategory])
notificationCenter.delegate = self notificationCenter.delegate = self
notificationsEnabled = appSettings.enableNotifications
MXLog.info("[NotificationManager] app setting 'enableNotifications' is '\(notificationsEnabled)'")
// Listen for changes to AppSettings.enableNotifications
appSettings.$enableNotifications
.sink { [weak self] newValue in
self?.enableNotifications(newValue)
}
.store(in: &cancellables)
// Request authorization uppon UIApplication.didBecomeActiveNotification notification
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { [weak self] _ in
self?.requestAuthorization()
}
.store(in: &cancellables)
} }
func requestAuthorization() { func requestAuthorization() {
guard appSettings.enableNotifications, !userSession.isNil else { return }
Task { Task {
do { do {
let granted = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) let permissionGranted = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge])
MXLog.info("[NotificationManager] permission granted: \(granted)") MXLog.info("[NotificationManager] permission granted: \(permissionGranted)")
await MainActor.run { await MainActor.run {
self.delegate?.authorizationStatusUpdated(self, granted: granted) if permissionGranted {
self.delegate?.registerForRemoteNotifications()
}
} }
} catch { } catch {
MXLog.error("[NotificationManager] request authorization failed: \(error)") MXLog.error("[NotificationManager] request authorization failed: \(error)")
@@ -136,6 +158,17 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
appSettings.pusherProfileTag = newTag appSettings.pusherProfileTag = newTag
return newTag return newTag
} }
private func enableNotifications(_ enable: Bool) {
guard notificationsEnabled != enable else { return }
notificationsEnabled = enable
MXLog.info("[NotificationManager] app setting 'enableNotifications' changed to '\(enable)'")
if enable {
requestAuthorization()
} else {
delegate?.unregisterForRemoteNotifications()
}
}
} }
// MARK: - UNUserNotificationCenterDelegate // MARK: - UNUserNotificationCenterDelegate
@@ -18,8 +18,6 @@ import Foundation
import UserNotifications import UserNotifications
protocol NotificationManagerDelegate: AnyObject { protocol NotificationManagerDelegate: AnyObject {
func authorizationStatusUpdated(_ service: NotificationManagerProtocol,
granted: Bool)
func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol, func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol,
content: UNNotificationContent) -> Bool content: UNNotificationContent) -> Bool
func notificationTapped(_ service: NotificationManagerProtocol, func notificationTapped(_ service: NotificationManagerProtocol,
@@ -27,6 +25,8 @@ protocol NotificationManagerDelegate: AnyObject {
func handleInlineReply(_ service: NotificationManagerProtocol, func handleInlineReply(_ service: NotificationManagerProtocol,
content: UNNotificationContent, content: UNNotificationContent,
replyText: String) async replyText: String) async
func registerForRemoteNotifications()
func unregisterForRemoteNotifications()
} }
// MARK: - NotificationManagerProtocol // MARK: - NotificationManagerProtocol
@@ -22,4 +22,8 @@ protocol UserNotificationCenterProtocol: AnyObject {
func add(_ request: UNNotificationRequest) async throws func add(_ request: UNNotificationRequest) async throws
func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool
func setNotificationCategories(_ categories: Set<UNNotificationCategory>) func setNotificationCategories(_ categories: Set<UNNotificationCategory>)
func authorizationStatus() async -> UNAuthorizationStatus
} }
// sourcery: AutoMockable
extension UserNotificationCenterProtocol { }
@@ -191,6 +191,11 @@ class MockScreen: Identifiable {
isModallyPresented: false)) isModallyPresented: false))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .notificationSettingsScreen:
let userNotificationCenter = UserNotificationCenterMock()
userNotificationCenter.authorizationStatusReturnValue = .denied
let parameters = NotificationSettingsScreenCoordinatorParameters(userNotificationCenter: userNotificationCenter)
return NotificationSettingsScreenCoordinator(parameters: parameters)
case .onboarding: case .onboarding:
return OnboardingCoordinator() return OnboardingCoordinator()
case .roomPlainNoAvatar: case .roomPlainNoAvatar:
@@ -34,6 +34,7 @@ enum UITestsScreenIdentifier: String {
case settings case settings
case bugReport case bugReport
case bugReportWithScreenshot case bugReportWithScreenshot
case notificationSettingsScreen
case onboarding case onboarding
case roomPlainNoAvatar case roomPlainNoAvatar
case roomEncryptedWithAvatar case roomEncryptedWithAvatar
@@ -0,0 +1,21 @@
//
// 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 ElementX
import XCTest
@MainActor
class NotificationSettingsScreenUITests: XCTestCase { }
@@ -179,8 +179,12 @@ final class NotificationManagerTests: XCTestCase {
} }
extension NotificationManagerTests: NotificationManagerDelegate { extension NotificationManagerTests: NotificationManagerDelegate {
func authorizationStatusUpdated(_ service: ElementX.NotificationManagerProtocol, granted: Bool) { func registerForRemoteNotifications() {
authorizationStatusWasGranted = granted authorizationStatusWasGranted = true
}
func unregisterForRemoteNotifications() {
authorizationStatusWasGranted = false
} }
func shouldDisplayInAppNotification(_ service: ElementX.NotificationManagerProtocol, content: UNNotificationContent) -> Bool { func shouldDisplayInAppNotification(_ service: ElementX.NotificationManagerProtocol, content: UNNotificationContent) -> Bool {
@@ -36,4 +36,9 @@ class UserNotificationCenterSpy: UserNotificationCenterProtocol {
func setNotificationCategories(_ categories: Set<UNNotificationCategory>) { func setNotificationCategories(_ categories: Set<UNNotificationCategory>) {
notificationCategoriesValue = categories notificationCategoriesValue = categories
} }
var authorizationStatusValue: UNAuthorizationStatus?
func authorizationStatus() async -> UNAuthorizationStatus {
authorizationStatusValue ?? UNAuthorizationStatus.denied
}
} }
@@ -0,0 +1,50 @@
//
// 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 XCTest
@testable import ElementX
@MainActor
class NotificationSettingsScreenViewModelTests: XCTestCase {
private var viewModel: NotificationSettingsScreenViewModelProtocol!
private var context: NotificationSettingsScreenViewModelType.Context!
private var appSettings: AppSettings!
private var userNotificationCenter: UserNotificationCenterMock!
@MainActor override func setUpWithError() throws {
AppSettings.reset()
userNotificationCenter = UserNotificationCenterMock()
userNotificationCenter.authorizationStatusReturnValue = .authorized
appSettings = AppSettings()
viewModel = NotificationSettingsScreenViewModel(appSettings: appSettings,
userNotificationCenter: userNotificationCenter)
context = viewModel.context
}
func testEnableNotifications() {
appSettings.enableNotifications = false
context.send(viewAction: .changedEnableNotifications)
XCTAssertTrue(appSettings.enableNotifications)
}
func testOptOut() {
appSettings.enableNotifications = true
context.send(viewAction: .changedEnableNotifications)
XCTAssertFalse(appSettings.enableNotifications)
}
}