diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index ac49fde69..79afba68f 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; }; 07240B7159A3990C4C2E8FFC /* LoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D256FEE2F1AF1E51D39B622 /* LoginTests.swift */; }; 072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; }; + 08248D02BACA75CDC3B39A96 /* UserNotificationCenterSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69219A908D7C22E6EE6689AE /* UserNotificationCenterSpy.swift */; }; 086C2FA7750378EB2BFD0BEE /* UITestsRootCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D751BB69BB7C38FD247517B4 /* UITestsRootCoordinator.swift */; }; 095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; }; 09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; }; @@ -38,6 +39,7 @@ 0EE5EBA18BA1FE10254BB489 /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; }; 0F3F2FDD4021A25A0D57F801 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; }; 0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */; }; + 1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0376C429FAB1687C3D905F3E /* MockCoder.swift */; }; 1281625B25371BE53D36CB3A /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */; }; 12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; }; 132D241B09F9044711FD70A5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; }; @@ -62,6 +64,7 @@ 1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; }; 1A8BDEB96C3B2F033FA563F8 /* EmojiPickerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB785716B9212C093704E767 /* EmojiPickerHeaderView.swift */; }; 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; + 1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */; }; 1B4B3E847BF944DB2C1C217F /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; }; 1CF18DE71D5D23C61BD88852 /* DebugScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9238D3A3A00F45E841FE4EFF /* DebugScreen.swift */; }; 1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */; }; @@ -267,6 +270,7 @@ 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; }; 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */; }; + 8E650379587C31D7912ED67B /* UNNotification+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */; }; 8F2FAA98457750D9D664136F /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; }; 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; }; 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; }; @@ -333,9 +337,11 @@ ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; }; AC5CC8250CEAE57B73900C57 /* UserNotificationModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD80F22830C2360F3F39DDCE /* UserNotificationModalView.swift */; }; AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */; }; + ACAB49022C2EBD00AFC67729 /* ClientProxySpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1924EF73801E03610E0F2AB /* ClientProxySpy.swift */; }; ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */; }; AD2A81B65A9F6163012086F1 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B698739E3410E2CDB7144 /* MXLog.swift */; }; AEE3981A0F090208E4445808 /* MockNotificationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B37DCC9025452F46F91340E /* MockNotificationServiceProxy.swift */; }; + AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */; }; B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; }; B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; }; B09514A0A3EB3C19A4FD0B71 /* SoftLogoutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCBDE671A613B3EB70794C4 /* SoftLogoutScreen.swift */; }; @@ -418,6 +424,7 @@ E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */; }; E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; E290C78E7F09F47FD2662986 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40C19719687984FD9478FBE /* Task.swift */; }; + E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */; }; E3C328EF20C4B3326D263BCD /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; }; E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */; }; E47CD939D8480657D4B706C6 /* AnalyticsPromptCheckmarkItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7B2E9CC5DC3B76ADC35A43 /* AnalyticsPromptCheckmarkItem.swift */; }; @@ -516,6 +523,7 @@ 01C4C7DB37597D7D8379511A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 02A07FF019724B6ACEA73076 /* szl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = szl; path = szl.lproj/Localizable.strings; 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 = ""; }; 04BBC9E08250EF92ADE89CFD /* sr-Latn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sr-Latn"; path = "sr-Latn.lproj/Localizable.strings"; sourceTree = ""; }; 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; 054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionUITests.swift; sourceTree = ""; }; @@ -556,6 +564,8 @@ 1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 124D85E85505B6B81845235F /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fy; path = fy.lproj/Localizable.stringsdict; sourceTree = ""; }; + 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizer.swift; sourceTree = ""; }; + 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryDetails.swift; sourceTree = ""; }; @@ -616,6 +626,7 @@ 2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertInfo.swift; sourceTree = ""; }; 2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsViewModelTests.swift; sourceTree = ""; }; 2F1B28C596DE541DA0AFD16C /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lo; path = lo.lproj/Localizable.stringsdict; sourceTree = ""; }; + 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = ""; }; 31B01468022EC826CB2FD2C0 /* LoginModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginModels.swift; sourceTree = ""; }; 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = ""; }; 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutViewModelTests.swift; sourceTree = ""; }; @@ -657,6 +668,7 @@ 44D8C8431416EB8DFEC7E235 /* ApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationTests.swift; sourceTree = ""; }; 453E722A43D092C06FB8E3FA /* tzm */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tzm; path = tzm.lproj/Localizable.strings; sourceTree = ""; }; 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreen.swift; sourceTree = ""; }; + 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterProtocol.swift; sourceTree = ""; }; 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = ""; }; 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; @@ -721,6 +733,7 @@ 667DD3A9D932D7D9EB380CAA /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sk; path = sk.lproj/Localizable.stringsdict; sourceTree = ""; }; 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProvider.swift; sourceTree = ""; }; 6920A4869821BF72FFC58842 /* MockMediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaProvider.swift; sourceTree = ""; }; + 69219A908D7C22E6EE6689AE /* UserNotificationCenterSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterSpy.swift; sourceTree = ""; }; 6A1AAC8EB2992918D01874AC /* rue */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = rue; path = rue.lproj/Localizable.strings; sourceTree = ""; }; 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoPlistReader.swift; sourceTree = ""; }; 6A6C4BE591FE5C38CE9C7EF3 /* UserProperties+Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserProperties+Element.swift"; sourceTree = ""; }; @@ -951,6 +964,7 @@ DA6B262D7584C65BC5B79A0E /* UserNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotification.swift; sourceTree = ""; }; DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateCoordinator.swift; sourceTree = ""; }; DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = ""; }; + DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = ""; }; DC77FC5C4F2000133047AA27 /* SoftLogoutModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutModels.swift; sourceTree = ""; }; DCD5FEE195446A9E458DDDAF /* NotificationServiceProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceProxyProtocol.swift; sourceTree = ""; }; DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = ""; }; @@ -1003,6 +1017,7 @@ F118CF7C5548099AACF7E90C /* RoomMembersCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersCoordinator.swift; sourceTree = ""; }; F15BE37BE2FB86E00C8D150A /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = ""; }; F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; + F1924EF73801E03610E0F2AB /* ClientProxySpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxySpy.swift; sourceTree = ""; }; F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = ""; }; F2D58333B377888012740101 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; F31A4E5941ACBA4BB9FEF94C /* UserNotificationToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationToastView.swift; sourceTree = ""; }; @@ -1617,6 +1632,7 @@ 96561CC53F7C1E24D4C292E4 /* MockNotificationManager.swift */, 1423AB065857FA546444DB15 /* NotificationManager.swift */, A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */, + 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */, ); path = Manager; sourceTree = ""; @@ -1690,6 +1706,7 @@ EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */, 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */, A3004DFA1B10951962787D90 /* VideoPlayerViewModelTests.swift */, + 7DBC911559934065993A5FF4 /* NotificationManager */, ); path = Sources; sourceTree = ""; @@ -1785,6 +1802,19 @@ path = View; sourceTree = ""; }; + 7DBC911559934065993A5FF4 /* NotificationManager */ = { + isa = PBXGroup; + children = ( + F1924EF73801E03610E0F2AB /* ClientProxySpy.swift */, + 0376C429FAB1687C3D905F3E /* MockCoder.swift */, + 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */, + DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */, + 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */, + 69219A908D7C22E6EE6689AE /* UserNotificationCenterSpy.swift */, + ); + path = NotificationManager; + sourceTree = ""; + }; 7B29CA1D663299262BEADF24 /* RoomDetails */ = { isa = PBXGroup; children = ( @@ -2265,6 +2295,7 @@ 4009BE2E791C16AC6EE39A7E /* BugReport */, F5A65D1D3B83593598DC278D /* EmojiPickerScreen */, B442FCF47E0A6F28D7D50A4D /* FilePreview */, + FC26FB522EDED4965C5325F0 /* Folder */, B53CA9BECD3F97805E1432D0 /* HomeScreen */, 3F38EAC92E2281990E65DAF2 /* OnboardingScreen */, A448A3A8F764174C60CD0CA1 /* Other */, @@ -2348,6 +2379,13 @@ path = EmojiPickerScreen; sourceTree = ""; }; + FC26FB522EDED4965C5325F0 /* Folder */ = { + isa = PBXGroup; + children = ( + ); + path = Folder; + sourceTree = ""; + }; FCDF06BDB123505F0334B4F9 /* Timeline */ = { isa = PBXGroup; children = ( @@ -2816,6 +2854,7 @@ 0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */, 7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */, C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */, + ACAB49022C2EBD00AFC67729 /* ClientProxySpy.swift in Sources */, 9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */, 501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */, 25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */, @@ -2828,9 +2867,11 @@ 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */, 2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */, + 1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */, 981853650217B6C8ECDD998C /* NavigationRootCoordinatorTests.swift in Sources */, FCB9B475F908765531335859 /* NavigationSplitCoordinator.swift in Sources */, 4BB282209EA82015D0DF8F89 /* NavigationStackCoordinatorTests.swift in Sources */, + 1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */, F9F6D2883BBEBB9A3789A137 /* OnboardingViewModelTests.swift in Sources */, 27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */, EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */, @@ -2845,7 +2886,10 @@ 1FEC0A4EC6E6DF693C16B32A /* StringTests.swift in Sources */, 7AE1FFB132F2B84EB8A2AEBC /* TemplateViewModelTests.swift in Sources */, 282A5F3375DDC774AE09B0C3 /* TracingConfigurationTests.swift in Sources */, + 8E650379587C31D7912ED67B /* UNNotification+Creator.swift in Sources */, + AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */, 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */, + 08248D02BACA75CDC3B39A96 /* UserNotificationCenterSpy.swift in Sources */, 8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */, 1504CE9A609A348D90B69E47 /* VideoPlayerViewModelTests.swift in Sources */, ); @@ -3126,6 +3170,7 @@ 071A017E415AD378F2961B11 /* URL.swift in Sources */, 7A71AEF419904209BB8C2833 /* UserAgentBuilder.swift in Sources */, 87BD4F95F9D603C309837378 /* UserNotification.swift in Sources */, + E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */, 5D9F0695DC6C0057F85C12B6 /* UserNotificationController.swift in Sources */, D79F0F852C6A4255D5E616D2 /* UserNotificationControllerProtocol.swift in Sources */, AC5CC8250CEAE57B73900C57 /* UserNotificationModalView.swift in Sources */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 874cb502c..c1319dda0 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -302,7 +302,7 @@ class AppCoordinator: AppCoordinatorProtocol { .sink { [weak self] callback in switch callback { case .registeredNotifications(let deviceToken): - self?.notificationManager?.register(with: deviceToken) + self?.notificationManager?.register(with: deviceToken, completion: nil) case .failedToRegisteredNotifications(let error): self?.notificationManager?.registrationFailed(with: error) } diff --git a/ElementX/Sources/Services/Notification/Manager/MockNotificationManager.swift b/ElementX/Sources/Services/Notification/Manager/MockNotificationManager.swift index fc35c1f7f..fc37f5ad2 100644 --- a/ElementX/Sources/Services/Notification/Manager/MockNotificationManager.swift +++ b/ElementX/Sources/Services/Notification/Manager/MockNotificationManager.swift @@ -29,7 +29,7 @@ class MockNotificationManager: NotificationManagerProtocol { delegate?.authorizationStatusUpdated(self, granted: false) } - func register(with deviceToken: Data) { } + func register(with deviceToken: Data, completion: ((Bool) -> Void)? = nil) { } func registrationFailed(with error: Error) { } diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift index 3378124f2..bc7cfedf1 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift @@ -19,11 +19,12 @@ import UIKit import UserNotifications class NotificationManager: NSObject, NotificationManagerProtocol { - private let notificationCenter = UNUserNotificationCenter.current() + private let notificationCenter: UserNotificationCenterProtocol private let clientProxy: ClientProxyProtocol - init(clientProxy: ClientProxyProtocol) { + init(clientProxy: ClientProxyProtocol, notificationCenter: UserNotificationCenterProtocol = UNUserNotificationCenter.current()) { self.clientProxy = clientProxy + self.notificationCenter = notificationCenter super.init() } @@ -45,22 +46,25 @@ class NotificationManager: NSObject, NotificationManagerProtocol { options: []) notificationCenter.setNotificationCategories([replyCategory]) notificationCenter.delegate = self - Task { - do { - let granted = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) - MXLog.debug("[NotificationManager] permission granted: \(granted)") - - await MainActor.run { - delegate?.authorizationStatusUpdated(self, granted: granted) - } - } catch { + notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, error in + guard let self else { + return + } + if let error { MXLog.debug("[NotificationManager] request authorization failed: \(error)") + } else { + MXLog.debug("[NotificationManager] permission granted: \(granted)") + DispatchQueue.main.async { + self.delegate?.authorizationStatusUpdated(self, granted: granted) + } } } } - func register(with deviceToken: Data) { - setPusher(with: deviceToken, clientProxy: clientProxy) + func register(with deviceToken: Data, completion: ((Bool) -> Void)? = nil) { + setPusher(with: deviceToken, + clientProxy: clientProxy, + completion: completion) } func registrationFailed(with error: Error) { } @@ -74,17 +78,18 @@ class NotificationManager: NSObject, NotificationManagerProtocol { let request = UNNotificationRequest(identifier: ProcessInfo.processInfo.globallyUniqueString, content: content, trigger: nil) - Task { - do { - try await notificationCenter.add(request) - MXLog.debug("[NotificationManager] show local notification succeeded") - } catch { + notificationCenter.add(request) { error in + if let error { MXLog.debug("[NotificationManager] show local notification failed: \(error)") + } else { + MXLog.debug("[NotificationManager] show local notification succeeded") } } } - private func setPusher(with deviceToken: Data, clientProxy: ClientProxyProtocol) { + private func setPusher(with deviceToken: Data, + clientProxy: ClientProxyProtocol, + completion: ((Bool) -> Void)?) { Task { do { try await clientProxy.setPusher(pushkey: deviceToken.base64EncodedString(), @@ -106,8 +111,10 @@ class NotificationManager: NSObject, NotificationManagerProtocol { ] ]) MXLog.debug("[NotificationManager] set pusher succeeded") + completion?(true) } catch { MXLog.debug("[NotificationManager] set pusher failed: \(error)") + completion?(false) } } } @@ -164,3 +171,5 @@ extension NotificationManager: UNUserNotificationCenterDelegate { } } } + +extension UNUserNotificationCenter: UserNotificationCenterProtocol { } diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift index 452367a74..602c37fd8 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift @@ -36,7 +36,7 @@ protocol NotificationManagerProtocol { var delegate: NotificationManagerDelegate? { get set } func start() - func register(with deviceToken: Data) + func register(with deviceToken: Data, completion: ((Bool) -> Void)?) func registrationFailed(with error: Error) func showLocalNotification(with title: String, subtitle: String?) } diff --git a/ElementX/Sources/Services/Notification/Manager/UserNotificationCenterProtocol.swift b/ElementX/Sources/Services/Notification/Manager/UserNotificationCenterProtocol.swift new file mode 100644 index 000000000..e32266c67 --- /dev/null +++ b/ElementX/Sources/Services/Notification/Manager/UserNotificationCenterProtocol.swift @@ -0,0 +1,25 @@ +// +// 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 UserNotifications + +protocol UserNotificationCenterProtocol: AnyObject { + var delegate: UNUserNotificationCenterDelegate? { get set } + func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)?) + func requestAuthorization(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void) + func setNotificationCategories(_ categories: Set) +} diff --git a/UnitTests/Sources/NotificationManager/ClientProxySpy.swift b/UnitTests/Sources/NotificationManager/ClientProxySpy.swift new file mode 100644 index 000000000..350a4ef4f --- /dev/null +++ b/UnitTests/Sources/NotificationManager/ClientProxySpy.swift @@ -0,0 +1,116 @@ +// +// 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 +@testable import ElementX +import Foundation + +class ClientProxySpy: ClientProxyProtocol { + var visibleRoomsSummaryProvider: ElementX.RoomSummaryProviderProtocol? + + var allRoomsSummaryProvider: ElementX.RoomSummaryProviderProtocol? + + let callbacks = PassthroughSubject() + + var userIdentifier = "" + + var isSoftLogout = false + + var deviceId: String? = "" + + var homeserver = "" + + var restorationToken: ElementX.RestorationToken? + + var roomSummaryProvider: ElementX.RoomSummaryProviderProtocol? + + internal init() { } + + func startSync() { } + + func stopSync() { } + + func restartSync() { } + + func roomForIdentifier(_ identifier: String) async -> ElementX.RoomProxyProtocol? { + nil + } + + func loadUserDisplayName() async -> Result { + .failure(.failedLoadingMedia) + } + + func loadUserAvatarURLString() async -> Result { + .failure(.failedLoadingMedia) + } + + func accountDataEvent(type: String) async -> Result where Content: Decodable { + .failure(.failedLoadingMedia) + } + + func setAccountData(content: Content, type: String) async -> Result where Content: Encodable { + .failure(.failedLoadingMedia) + } + + func sessionVerificationControllerProxy() async -> Result { + .failure(.failedLoadingMedia) + } + + func logout() async { } + + var setPusherCalled = false + var setPusherErrorToThrow: Error? + var setPusherPushkey: String? + var setPusherKind: PusherKind? + var setPusherAppId: String? + var setPusherAppDisplayName: String? + var setPusherDeviceDisplayName: String? + var setPusherProfileTag: String? + var setPusherLang: String? + var setPusherUrl: String? + var setPusherFormat: PushFormat? + var setPusherDefaultPayload: [AnyHashable: Any]? + + // swiftlint:disable:next function_parameter_count + func setPusher(pushkey: String, kind: PusherKind?, appId: String, appDisplayName: String, deviceDisplayName: String, profileTag: String?, lang: String, url: String?, format: PushFormat?, defaultPayload: [AnyHashable: Any]?) async throws { + if let setPusherErrorToThrow { + throw setPusherErrorToThrow + } + setPusherCalled = true + setPusherPushkey = pushkey + setPusherKind = kind + setPusherAppId = appId + setPusherAppDisplayName = appDisplayName + setPusherDeviceDisplayName = deviceDisplayName + setPusherProfileTag = profileTag + setPusherLang = lang + setPusherUrl = url + setPusherFormat = format + setPusherDefaultPayload = defaultPayload + } + + func mediaSourceForURLString(_ urlString: String) -> ElementX.MediaSourceProxy { + MediaSourceProxy(urlString: "") + } + + func loadMediaContentForSource(_ source: ElementX.MediaSourceProxy) async throws -> Data { + Data() + } + + func loadMediaThumbnailForSource(_ source: ElementX.MediaSourceProxy, width: UInt, height: UInt) async throws -> Data { + Data() + } +} diff --git a/UnitTests/Sources/NotificationManager/MockCoder.swift b/UnitTests/Sources/NotificationManager/MockCoder.swift new file mode 100644 index 000000000..174196778 --- /dev/null +++ b/UnitTests/Sources/NotificationManager/MockCoder.swift @@ -0,0 +1,22 @@ +// +// 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 + +final class MockCoder: NSKeyedArchiver { + override func decodeObject(forKey _: String) -> Any { "" } + override func decodeInt64(forKey key: String) -> Int64 { 0 } +} diff --git a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift new file mode 100644 index 000000000..faf12246b --- /dev/null +++ b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift @@ -0,0 +1,225 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest + +import Combine +@testable import ElementX + +final class NotificationManagerTests: XCTestCase { + var sut: NotificationManager! + private let clientProxySpy = ClientProxySpy() + private let notificationCenter = UserNotificationCenterSpy() + private var authorizationStatusWasGranted = false + private var shouldDisplayInAppNotificationReturnValue = false + private var handleInlineReplyDelegateCalled = false + private var notificationTappedDelegateCalled = false + private let settings = ServiceLocator.shared.settings + + override func setUp() { + sut = NotificationManager(clientProxy: clientProxySpy, notificationCenter: notificationCenter) + } + + func test_whenRegistered_pusherIsCalled() throws { + let expectation = expectation(description: "Callback happened") + sut.register(with: Data(), completion: { _ in + expectation.fulfill() + }) + waitForExpectations(timeout: 0.5) + XCTAssertTrue(clientProxySpy.setPusherCalled) + } + + func test_whenRegisteredSuccess_completionSuccessIsCalled() throws { + let expectation = expectation(description: "Callback happened") + var success = false + sut.register(with: Data(), completion: { s in + success = s + expectation.fulfill() + }) + waitForExpectations(timeout: 0.5) + XCTAssertTrue(success) + } + + func test_whenRegisteredAndPusherThrowsError_completionFalseIsCalled() throws { + let expectation = expectation(description: "Callback happened") + var success = true + enum TestError: Error { + case someError + } + clientProxySpy.setPusherErrorToThrow = TestError.someError + sut.register(with: Data(), completion: { s in + success = s + expectation.fulfill() + }) + waitForExpectations(timeout: 0.5) + XCTAssertFalse(success) + } + + func test_whenRegistered_pusherIsCalledWithCorrectValues() throws { + let expectation = expectation(description: "Callback happened") + let pushkeyData = Data("1234".utf8) + sut.register(with: pushkeyData, completion: { _ in + expectation.fulfill() + }) + waitForExpectations(timeout: 0.5) + XCTAssertEqual(clientProxySpy.setPusherPushkey, pushkeyData.base64EncodedString()) + XCTAssertEqual(clientProxySpy.setPusherAppId, settings?.pusherAppId) + XCTAssertEqual(clientProxySpy.setPusherKind, .http) + XCTAssertEqual(clientProxySpy.setPusherAppId, settings?.pusherAppId) + XCTAssertEqual(clientProxySpy.setPusherAppDisplayName, "\(InfoPlistReader.target.bundleDisplayName) (iOS)") + XCTAssertEqual(clientProxySpy.setPusherDeviceDisplayName, UIDevice.current.name) + XCTAssertNotNil(clientProxySpy.setPusherProfileTag) + XCTAssertEqual(clientProxySpy.setPusherLang, Bundle.preferredLanguages.first) + XCTAssertEqual(clientProxySpy.setPusherUrl, settings?.pushGatewayBaseURL.absoluteString) + XCTAssertEqual(clientProxySpy.setPusherFormat, .eventIdOnly) + let defaultPayload: [AnyHashable: Any] = [ + "aps": [ + "mutable-content": 1, + "alert": [ + "loc-key": "Notification", + "loc-args": [] + ] + ] + ] + let actualPayload = NSDictionary(dictionary: clientProxySpy.setPusherDefaultPayload ?? [:]) + XCTAssertTrue(actualPayload.isEqual(to: defaultPayload)) + } + + func test_whenRegisteredAndPusherTagNotSetInSettings_tagGeneratedAndSavedInSettings() throws { + let expectation = expectation(description: "Callback happened") + settings?.pusherProfileTag = nil + sut.register(with: Data(), completion: { _ in + expectation.fulfill() + }) + waitForExpectations(timeout: 0.5) + XCTAssertNotNil(settings?.pusherProfileTag) + } + + func test_whenRegisteredAndPusherTagIsSetInSettings_tagNotGenerated() throws { + let expectation = expectation(description: "Callback happened") + settings?.pusherProfileTag = "12345" + sut.register(with: Data(), completion: { _ in + expectation.fulfill() + }) + waitForExpectations(timeout: 0.5) + XCTAssertEqual(settings?.pusherProfileTag, "12345") + } + + func test_whenShowLocalNotification_notificationRequestGetsAdded() throws { + sut.showLocalNotification(with: "Title", subtitle: "Subtitle") + let request = try XCTUnwrap(notificationCenter.addRequest) + XCTAssertEqual(request.content.title, "Title") + XCTAssertEqual(request.content.subtitle, "Subtitle") + } + + func test_whenStart_notificationCategoriesAreSet() throws { + sut.start() + let replyAction = UNTextInputNotificationAction(identifier: NotificationConstants.Action.inlineReply, + title: ElementL10n.actionQuickReply, + options: []) + let replyCategory = UNNotificationCategory(identifier: NotificationConstants.Category.reply, + actions: [replyAction], + intentIdentifiers: [], + options: []) + XCTAssertEqual(notificationCenter.notificationCategoriesValue, [replyCategory]) + } + + func test_whenStart_delegateIsSet() throws { + sut.start() + let delegate = try XCTUnwrap(notificationCenter.delegate) + XCTAssertTrue(delegate.isEqual(sut)) + } + + func test_whenStart_requestAuthorizationCalledWithCorrectParams() throws { + sut.start() + XCTAssertEqual(notificationCenter.requestAuthorizationOptions, [.alert, .sound, .badge]) + } + + func test_whenStartAndAuthorizationGranted_delegateCalled() throws { + authorizationStatusWasGranted = false + notificationCenter.requestAuthorizationGrantedReturnValue = true + sut.delegate = self + sut.start() + let expectation = expectation(description: "Wait for main thread") + DispatchQueue.main.async { + expectation.fulfill() + } + waitForExpectations(timeout: 1, handler: nil) + XCTAssertTrue(authorizationStatusWasGranted) + } + + func test_whenWillPresentNotificationsDelegateNotSet_CorrectPresentationOptionsReturned() async throws { + sut.start() + let archiver = MockCoder(requiringSecureCoding: false) + let notification = try XCTUnwrap(UNNotification(coder: archiver)) + let options = await sut.userNotificationCenter(UNUserNotificationCenter.current(), willPresent: notification) + XCTAssertEqual(options, [.badge, .sound, .list, .banner]) + } + + func test_whenWillPresentNotificationsDelegateSetAndNotificationsShoudNotBeDisplayed_CorrectPresentationOptionsReturned() async throws { + shouldDisplayInAppNotificationReturnValue = false + sut.delegate = self + sut.start() + let notification = try UNNotification.with(userInfo: [AnyHashable: Any]()) + let options = await sut.userNotificationCenter(UNUserNotificationCenter.current(), willPresent: notification) + XCTAssertEqual(options, []) + } + + func test_whenWillPresentNotificationsDelegateSetAndNotificationsShoudBeDisplayed_CorrectPresentationOptionsReturned() async throws { + shouldDisplayInAppNotificationReturnValue = true + sut.delegate = self + sut.start() + let notification = try UNNotification.with(userInfo: [AnyHashable: Any]()) + let options = await sut.userNotificationCenter(UNUserNotificationCenter.current(), willPresent: notification) + XCTAssertEqual(options, [.badge, .sound, .list, .banner]) + } + + func test_whenNotificationCenterReceivedResponseInLineReply_delegateIsCalled() async throws { + handleInlineReplyDelegateCalled = false + sut.delegate = self + sut.start() + let response = try UNTextInputNotificationResponse.with(userInfo: [AnyHashable: Any](), actionIdentifier: NotificationConstants.Action.inlineReply) + await sut.userNotificationCenter(UNUserNotificationCenter.current(), didReceive: response) + XCTAssertTrue(handleInlineReplyDelegateCalled) + } + + func test_whenNotificationCenterReceivedResponseWithActionIdentifier_delegateIsCalled() async throws { + notificationTappedDelegateCalled = false + sut.delegate = self + sut.start() + let response = try UNTextInputNotificationResponse.with(userInfo: [AnyHashable: Any](), actionIdentifier: UNNotificationDefaultActionIdentifier) + await sut.userNotificationCenter(UNUserNotificationCenter.current(), didReceive: response) + XCTAssertTrue(notificationTappedDelegateCalled) + } +} + +extension NotificationManagerTests: NotificationManagerDelegate { + func authorizationStatusUpdated(_ service: ElementX.NotificationManagerProtocol, granted: Bool) { + authorizationStatusWasGranted = granted + } + + func shouldDisplayInAppNotification(_ service: ElementX.NotificationManagerProtocol, content: UNNotificationContent) -> Bool { + shouldDisplayInAppNotificationReturnValue + } + + func notificationTapped(_ service: ElementX.NotificationManagerProtocol, content: UNNotificationContent) async { + notificationTappedDelegateCalled = true + } + + func handleInlineReply(_ service: ElementX.NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async { + handleInlineReplyDelegateCalled = true + } +} diff --git a/UnitTests/Sources/NotificationManager/UNNotification+Creator.swift b/UnitTests/Sources/NotificationManager/UNNotification+Creator.swift new file mode 100644 index 000000000..f9d5b5a3f --- /dev/null +++ b/UnitTests/Sources/NotificationManager/UNNotification+Creator.swift @@ -0,0 +1,35 @@ +// +// 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 UserNotifications +import XCTest + +extension UNNotification { + static func with(userInfo: [AnyHashable: Any], actionIdentifier: String = UNNotificationDefaultActionIdentifier) throws -> UNNotification { + let content = UNMutableNotificationContent() + content.userInfo = userInfo + let request = UNNotificationRequest( + identifier: "", + content: content, + trigger: nil + ) + let archiver = MockCoder(requiringSecureCoding: false) + let notification = try XCTUnwrap(UNNotification(coder: archiver)) + notification.setValue(request, forKey: "request") + return notification + } +} diff --git a/UnitTests/Sources/NotificationManager/UNTextInputNotificationResponse+Creator.swift b/UnitTests/Sources/NotificationManager/UNTextInputNotificationResponse+Creator.swift new file mode 100644 index 000000000..4bf0b100a --- /dev/null +++ b/UnitTests/Sources/NotificationManager/UNTextInputNotificationResponse+Creator.swift @@ -0,0 +1,38 @@ +// +// 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 UserNotifications +import XCTest + +extension UNTextInputNotificationResponse { + static func with(userInfo: [AnyHashable: Any], actionIdentifier: String = UNNotificationDefaultActionIdentifier) throws -> UNNotificationResponse { + let content = UNMutableNotificationContent() + content.userInfo = userInfo + let request = UNNotificationRequest( + identifier: "", + content: content, + trigger: nil + ) + let archiver = MockCoder(requiringSecureCoding: false) + let notification = try XCTUnwrap(UNNotification(coder: archiver)) + notification.setValue(request, forKey: "request") + + let response = try XCTUnwrap(UNTextInputNotificationResponse(coder: archiver)) + response.setValue(notification, forKey: "notification") + response.setValue(actionIdentifier, forKey: "actionIdentifier") + return response + } +} diff --git a/UnitTests/Sources/NotificationManager/UserNotificationCenterSpy.swift b/UnitTests/Sources/NotificationManager/UserNotificationCenterSpy.swift new file mode 100644 index 000000000..f4e972c64 --- /dev/null +++ b/UnitTests/Sources/NotificationManager/UserNotificationCenterSpy.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. +// +@testable import ElementX +import Foundation +import UserNotifications + +class UserNotificationCenterSpy: UserNotificationCenterProtocol { + weak var delegate: UNUserNotificationCenterDelegate? + + var addRequest: UNNotificationRequest? + func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)?) { + addRequest = request + completionHandler?(nil) + } + + var requestAuthorizationOptions: UNAuthorizationOptions? + var requestAuthorizationGrantedReturnValue = false + func requestAuthorization(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void) { + requestAuthorizationOptions = options + completionHandler(requestAuthorizationGrantedReturnValue, nil) + } + + var notificationCategoriesValue: Set? + func setNotificationCategories(_ categories: Set) { + notificationCategoriesValue = categories + } +}