diff --git a/Podfile b/Podfile index 4ce2c868c..b10731683 100644 --- a/Podfile +++ b/Podfile @@ -93,6 +93,10 @@ abstract_target 'RiotPods' do import_MatrixKitAppExtension end + target "RiotNSE" do + import_MatrixKitAppExtension + end + end diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 3b145e393..96712dc9b 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -684,6 +684,7 @@ B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E5368C21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift */; }; B1E5368F21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1E5368E21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard */; }; B1FDF56021F5FE5500BA3834 /* KeyBackupSetupPassphraseViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1FDF55F21F5FE5500BA3834 /* KeyBackupSetupPassphraseViewAction.swift */; }; + DB1392A2332C3CAF6C9962EF /* Pods_RiotPods_RiotNSE.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4D418D054E4032F2CFA8B51 /* Pods_RiotPods_RiotNSE.framework */; }; EC85D6AE2477DC89002C44C9 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D6AD2477DC89002C44C9 /* RoundedButton.swift */; }; EC85D7142477DCD7002C44C9 /* KeyVerificationScanConfirmationViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D70C2477DCD7002C44C9 /* KeyVerificationScanConfirmationViewState.swift */; }; EC85D7152477DCD7002C44C9 /* KeyVerificationScanConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D70D2477DCD7002C44C9 /* KeyVerificationScanConfirmationViewController.swift */; }; @@ -706,6 +707,14 @@ EC85D7372477DD97002C44C9 /* LocalContactsSectionHeaderContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = EC85D7352477DD97002C44C9 /* LocalContactsSectionHeaderContainerView.m */; }; EC85D73A2477DDB8002C44C9 /* DirectorySectionHeaderContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = EC85D7392477DDB8002C44C9 /* DirectorySectionHeaderContainerView.m */; }; EC85D73E2477DDD0002C44C9 /* PushNotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = EC85D73D2477DDD0002C44C9 /* PushNotificationService.m */; }; + EC85D7462477E5F7002C44C9 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D7452477E5F7002C44C9 /* NotificationService.swift */; }; + EC85D74A2477E5F7002C44C9 /* RiotNSE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = EC85D7432477E5F7002C44C9 /* RiotNSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + EC85D74F2477E8EB002C44C9 /* RiotSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B5597F20EFC3DF00210D55 /* RiotSettings.swift */; }; + EC85D751247C0E8F002C44C9 /* UNUserNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */; }; + EC85D752247C0F52002C44C9 /* UNUserNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */; }; + EC85D754247C0F5B002C44C9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D753247C0F5B002C44C9 /* Constants.swift */; }; + EC85D755247C0F84002C44C9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D753247C0F5B002C44C9 /* Constants.swift */; }; + EC85D757247E700F002C44C9 /* NSEMemoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D756247E700F002C44C9 /* NSEMemoryStore.swift */; }; ECB101302477CFDB00CF8C11 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB1012C2477CFDB00CF8C11 /* UITableView.swift */; }; ECB101312477CFDB00CF8C11 /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB1012D2477CFDB00CF8C11 /* UILabel.swift */; }; ECB101322477CFDB00CF8C11 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB1012E2477CFDB00CF8C11 /* UIDevice.swift */; }; @@ -751,6 +760,13 @@ remoteGlobalIDString = 92726A421F58737A004AD26F; remoteInfo = SiriIntents; }; + EC85D7482477E5F7002C44C9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F094A99A1B78D8F000B1FBBF /* Project object */; + proxyType = 1; + remoteGlobalIDString = EC85D7422477E5F7002C44C9; + remoteInfo = RiotNSE; + }; F094A9BF1B78D8F000B1FBBF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F094A99A1B78D8F000B1FBBF /* Project object */; @@ -769,6 +785,7 @@ files = ( 24CBEC591F0EAD310093EABB /* RiotShareExtension.appex in Embed App Extensions */, 92726A4B1F58737A004AD26F /* SiriIntents.appex in Embed App Extensions */, + EC85D74A2477E5F7002C44C9 /* RiotNSE.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -779,6 +796,7 @@ 129EB7E27E7E4AC3F5F098F5 /* Pods_RiotTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1ACF09217ADF1D7E7A35BC02 /* Pods_RiotPods_Riot.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_Riot.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 24CBEC4E1F0EAD310093EABB /* RiotShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = RiotShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 2B582BE9B2A98BCF5F740873 /* Pods-RiotPods-RiotNSE.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-RiotNSE.debug.xcconfig"; path = "Target Support Files/Pods-RiotPods-RiotNSE/Pods-RiotPods-RiotNSE.debug.xcconfig"; sourceTree = ""; }; 3209451121F1C1430088CAA2 /* BlackTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlackTheme.swift; sourceTree = ""; }; 32242F0821E8B05F00725742 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 32242F0C21E8FBA900725742 /* ThemeService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThemeService.m; sourceTree = ""; }; @@ -912,6 +930,7 @@ 43C2962BE367F59220F517FA /* Pods-RiotPods-Riot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-Riot.debug.xcconfig"; path = "Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot.debug.xcconfig"; sourceTree = ""; }; 4FC6A5D63FAD1B27C2F57AFA /* Pods-RiotPods-RiotShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-RiotShareExtension.release.xcconfig"; path = "Target Support Files/Pods-RiotPods-RiotShareExtension/Pods-RiotPods-RiotShareExtension.release.xcconfig"; sourceTree = ""; }; 51187E952D5CECF6D6F5A28E /* Pods_RiotPods_SiriIntents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_SiriIntents.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8A61E94F88EA96AFE1CFD9D3 /* Pods-RiotPods-RiotNSE.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-RiotNSE.release.xcconfig"; path = "Target Support Files/Pods-RiotPods-RiotNSE/Pods-RiotPods-RiotNSE.release.xcconfig"; sourceTree = ""; }; 926FA53D1F4C132000F826C2 /* MXSession+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXSession+Riot.h"; sourceTree = ""; }; 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXSession+Riot.m"; sourceTree = ""; }; 92726A431F58737A004AD26F /* SiriIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SiriIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1673,6 +1692,7 @@ B43DC75D1590BB8A4243BD4D /* Pods-RiotPods-Riot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-Riot.release.xcconfig"; path = "Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot.release.xcconfig"; sourceTree = ""; }; BABB6681FBD79219B1213D6C /* Pods-RiotTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotTests.debug.xcconfig"; path = "Target Support Files/Pods-RiotTests/Pods-RiotTests.debug.xcconfig"; sourceTree = ""; }; E2599D0ECB8DD206624E450B /* Pods-RiotPods-SiriIntents.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-SiriIntents.release.xcconfig"; path = "Target Support Files/Pods-RiotPods-SiriIntents/Pods-RiotPods-SiriIntents.release.xcconfig"; sourceTree = ""; }; + E4D418D054E4032F2CFA8B51 /* Pods_RiotPods_RiotNSE.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_RiotNSE.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC85D6AD2477DC89002C44C9 /* RoundedButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; EC85D70C2477DCD7002C44C9 /* KeyVerificationScanConfirmationViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationViewState.swift; sourceTree = ""; }; EC85D70D2477DCD7002C44C9 /* KeyVerificationScanConfirmationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationViewController.swift; sourceTree = ""; }; @@ -1700,6 +1720,13 @@ EC85D7392477DDB8002C44C9 /* DirectorySectionHeaderContainerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DirectorySectionHeaderContainerView.m; sourceTree = ""; }; EC85D73C2477DDD0002C44C9 /* PushNotificationService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PushNotificationService.h; sourceTree = ""; }; EC85D73D2477DDD0002C44C9 /* PushNotificationService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PushNotificationService.m; sourceTree = ""; }; + EC85D7432477E5F7002C44C9 /* RiotNSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = RiotNSE.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + EC85D7452477E5F7002C44C9 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + EC85D7472477E5F7002C44C9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EC85D74E2477E614002C44C9 /* RiotNSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RiotNSE.entitlements; sourceTree = ""; }; + EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNUserNotificationCenter.swift; sourceTree = ""; }; + EC85D753247C0F5B002C44C9 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + EC85D756247E700F002C44C9 /* NSEMemoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEMemoryStore.swift; sourceTree = ""; }; ECB1012C2477CFDB00CF8C11 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; ECB1012D2477CFDB00CF8C11 /* UILabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILabel.swift; sourceTree = ""; }; ECB1012E2477CFDB00CF8C11 /* UIDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; @@ -1763,6 +1790,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC85D7402477E5F7002C44C9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DB1392A2332C3CAF6C9962EF /* Pods_RiotPods_RiotNSE.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F094A99F1B78D8F000B1FBBF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2111,6 +2146,8 @@ E2599D0ECB8DD206624E450B /* Pods-RiotPods-SiriIntents.release.xcconfig */, BABB6681FBD79219B1213D6C /* Pods-RiotTests.debug.xcconfig */, AC34BF67FD21A9D01C16AE5D /* Pods-RiotTests.release.xcconfig */, + 2B582BE9B2A98BCF5F740873 /* Pods-RiotPods-RiotNSE.debug.xcconfig */, + 8A61E94F88EA96AFE1CFD9D3 /* Pods-RiotPods-RiotNSE.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -2123,6 +2160,7 @@ 97151D7F0F892081250D50A3 /* Pods_RiotPods_RiotShareExtension.framework */, 51187E952D5CECF6D6F5A28E /* Pods_RiotPods_SiriIntents.framework */, 129EB7E27E7E4AC3F5F098F5 /* Pods_RiotTests.framework */, + E4D418D054E4032F2CFA8B51 /* Pods_RiotPods_RiotNSE.framework */, ); name = Frameworks; sourceTree = ""; @@ -4247,6 +4285,17 @@ path = PushNotification; sourceTree = ""; }; + EC85D7442477E5F7002C44C9 /* RiotNSE */ = { + isa = PBXGroup; + children = ( + EC85D74E2477E614002C44C9 /* RiotNSE.entitlements */, + EC85D7452477E5F7002C44C9 /* NotificationService.swift */, + EC85D756247E700F002C44C9 /* NSEMemoryStore.swift */, + EC85D7472477E5F7002C44C9 /* Info.plist */, + ); + path = RiotNSE; + sourceTree = ""; + }; F083BB021E7005FD00A9B29C /* RiotTests */ = { isa = PBXGroup; children = ( @@ -4338,6 +4387,7 @@ 926FA53D1F4C132000F826C2 /* MXSession+Riot.h */, 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */, 32242F0821E8B05F00725742 /* UIColor.swift */, + EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */, B1CA3A2621EF6913000D1D89 /* UIViewController.swift */, B1CA3A2821EF692B000D1D89 /* UIView.swift */, B140B4A121F87F7100E3F5FE /* OperationQueue.swift */, @@ -4380,6 +4430,7 @@ F083BC0F1E7009EC00A9B29C /* Utils */ = { isa = PBXGroup; children = ( + EC85D753247C0F5B002C44C9 /* Constants.swift */, ECB101342477D00700CF8C11 /* UniversalLink.h */, ECB101352477D00700CF8C11 /* UniversalLink.m */, F083BC101E7009EC00A9B29C /* AvatarGenerator.h */, @@ -4407,6 +4458,7 @@ F083BB021E7005FD00A9B29C /* RiotTests */, 24CBEC4F1F0EAD310093EABB /* RiotShareExtension */, 92726A441F58737A004AD26F /* SiriIntents */, + EC85D7442477E5F7002C44C9 /* RiotNSE */, F094A9A31B78D8F000B1FBBF /* Products */, 5FC42FA41F5186AFFB6A2404 /* Frameworks */, 3232AAFE22564D9100AD6A5C /* Tools */, @@ -4422,6 +4474,7 @@ F094A9BE1B78D8F000B1FBBF /* RiotTests.xctest */, 24CBEC4E1F0EAD310093EABB /* RiotShareExtension.appex */, 92726A431F58737A004AD26F /* SiriIntents.appex */, + EC85D7432477E5F7002C44C9 /* RiotNSE.appex */, ); name = Products; sourceTree = ""; @@ -4465,6 +4518,24 @@ productReference = 92726A431F58737A004AD26F /* SiriIntents.appex */; productType = "com.apple.product-type.app-extension"; }; + EC85D7422477E5F7002C44C9 /* RiotNSE */ = { + isa = PBXNativeTarget; + buildConfigurationList = EC85D74D2477E5F7002C44C9 /* Build configuration list for PBXNativeTarget "RiotNSE" */; + buildPhases = ( + E96A5016582B740FB3EABBB3 /* [CP] Check Pods Manifest.lock */, + EC85D73F2477E5F7002C44C9 /* Sources */, + EC85D7402477E5F7002C44C9 /* Frameworks */, + EC85D7412477E5F7002C44C9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RiotNSE; + productName = RiotNSE; + productReference = EC85D7432477E5F7002C44C9 /* RiotNSE.appex */; + productType = "com.apple.product-type.app-extension"; + }; F094A9A11B78D8F000B1FBBF /* Riot */ = { isa = PBXNativeTarget; buildConfigurationList = F094A9C81B78D8F000B1FBBF /* Build configuration list for PBXNativeTarget "Riot" */; @@ -4483,6 +4554,7 @@ dependencies = ( 242661F61F12B1BA00D3FC08 /* PBXTargetDependency */, 92726A4A1F58737A004AD26F /* PBXTargetDependency */, + EC85D7492477E5F7002C44C9 /* PBXTargetDependency */, ); name = Riot; productName = Vector; @@ -4514,6 +4586,8 @@ F094A99A1B78D8F000B1FBBF /* Project object */ = { isa = PBXProject; attributes = { + DefaultBuildSystemTypeForWorkspace = Original; + LastSwiftUpdateCheck = 1130; LastUpgradeCheck = 0940; ORGANIZATIONNAME = matrix.org; TargetAttributes = { @@ -4538,6 +4612,9 @@ }; }; }; + EC85D7422477E5F7002C44C9 = { + CreatedOnToolsVersion = 11.3.1; + }; F094A9A11B78D8F000B1FBBF = { CreatedOnToolsVersion = 6.2; DevelopmentTeam = 7J4U792NQT; @@ -4599,6 +4676,7 @@ F094A9BD1B78D8F000B1FBBF /* RiotTests */, 24CBEC4D1F0EAD310093EABB /* RiotShareExtension */, 92726A421F58737A004AD26F /* SiriIntents */, + EC85D7422477E5F7002C44C9 /* RiotNSE */, ); }; /* End PBXProject section */ @@ -4625,6 +4703,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC85D7412477E5F7002C44C9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; F094A9A01B78D8F000B1FBBF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4934,6 +5019,28 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + E96A5016582B740FB3EABBB3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RiotPods-RiotNSE-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; FF06981FDA0DB688B8C52A41 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -4997,6 +5104,18 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC85D73F2477E5F7002C44C9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC85D74F2477E8EB002C44C9 /* RiotSettings.swift in Sources */, + EC85D757247E700F002C44C9 /* NSEMemoryStore.swift in Sources */, + EC85D7462477E5F7002C44C9 /* NotificationService.swift in Sources */, + EC85D752247C0F52002C44C9 /* UNUserNotificationCenter.swift in Sources */, + EC85D755247C0F84002C44C9 /* Constants.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F094A99E1B78D8F000B1FBBF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5292,6 +5411,7 @@ 32A6001D22C661100042C1D9 /* EditHistoryCoordinatorType.swift in Sources */, F083BDFA1E7009ED00A9B29C /* RoomPreviewData.m in Sources */, B1B557B420EF5AEF00210D55 /* EventDetailsView.m in Sources */, + EC85D751247C0E8F002C44C9 /* UNUserNotificationCenter.swift in Sources */, B1BEE73823DF44A60003A4CB /* UserVerificationSessionsStatusViewAction.swift in Sources */, B1B5577E20EE84BF00210D55 /* IncomingCallView.m in Sources */, B1CE83E22422817200D07506 /* KeyVerificationVerifyBySASViewState.swift in Sources */, @@ -5395,6 +5515,7 @@ B1B558C820EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.m in Sources */, B1B557C620EF5CD400210D55 /* DirectoryServerDetailTableViewCell.m in Sources */, EC85D71A2477DCD7002C44C9 /* KeyVerificationScanConfirmationCoordinator.swift in Sources */, + EC85D754247C0F5B002C44C9 /* Constants.swift in Sources */, B1B336C4242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewAction.swift in Sources */, B1B5590920EF768F00210D55 /* RoomEmptyBubbleCell.m in Sources */, 324A2054225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift in Sources */, @@ -5540,6 +5661,11 @@ target = 92726A421F58737A004AD26F /* SiriIntents */; targetProxy = 92726A491F58737A004AD26F /* PBXContainerItemProxy */; }; + EC85D7492477E5F7002C44C9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = EC85D7422477E5F7002C44C9 /* RiotNSE */; + targetProxy = EC85D7482477E5F7002C44C9 /* PBXContainerItemProxy */; + }; F094A9C01B78D8F000B1FBBF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F094A9A11B78D8F000B1FBBF /* Riot */; @@ -5791,6 +5917,83 @@ }; name = Release; }; + EC85D74B2477E5F7002C44C9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2B582BE9B2A98BCF5F740873 /* Pods-RiotPods-RiotNSE.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = RiotNSE/RiotNSE.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 0.11.5; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 7J4U792NQT; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = RiotNSE/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 0.11.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = im.vector.app.nse; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + EC85D74C2477E5F7002C44C9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8A61E94F88EA96AFE1CFD9D3 /* Pods-RiotPods-RiotNSE.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = RiotNSE/RiotNSE.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0.11.5; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 7J4U792NQT; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = RiotNSE/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 0.11.5; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = im.vector.app.nse; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; F094A9C61B78D8F000B1FBBF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6041,6 +6244,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + EC85D74D2477E5F7002C44C9 /* Build configuration list for PBXNativeTarget "RiotNSE" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EC85D74B2477E5F7002C44C9 /* Debug */, + EC85D74C2477E5F7002C44C9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F094A99D1B78D8F000B1FBBF /* Build configuration list for PBXProject "Riot" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index a4e2473d1..571a834b6 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -273,6 +273,28 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self.pushNotificationService registerForRemoteNotificationsWithCompletion:completion]; } +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + [self.pushNotificationService didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + + NSString * deviceTokenString = [[[[deviceToken description] + stringByReplacingOccurrencesOfString: @"<" withString: @""] + stringByReplacingOccurrencesOfString: @">" withString: @""] + stringByReplacingOccurrencesOfString: @" " withString: @""]; + + NSLog(@"The generated device token string is : %@",deviceTokenString); +} + +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error +{ + [self.pushNotificationService didFailToRegisterForRemoteNotificationsWithError:error]; +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + [self.pushNotificationService didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + #pragma mark - - (NSString*)appVersion @@ -484,6 +506,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [MXSDKOptions sharedInstance].analyticsDelegate = [Analytics sharedInstance]; [DecryptionFailureTracker sharedInstance].delegate = [Analytics sharedInstance]; [[Analytics sharedInstance] start]; + + // Disable CallKit + [MXKAppSettings standardAppSettings].enableCallKit = NO; self.pushNotificationService = [PushNotificationService new]; self.pushNotificationService.delegate = self; @@ -1953,7 +1978,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if (isPushRegistered) { // Enable push notifications by default on new added account - [account enablePushKitNotifications:YES success:nil failure:nil]; + [account enablePushNotifications:YES success:nil failure:nil]; } else { @@ -2034,13 +2059,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni accountManager.storeClass = [MXFileStore class]; // Disable APNS use. - if (accountManager.apnsDeviceToken) - { - // We use now Pushkit, unregister for all remote notifications received via Apple Push Notification service. - [[UIApplication sharedApplication] unregisterForRemoteNotifications]; - [accountManager setApnsDeviceToken:nil]; - } - +// if (accountManager.apnsDeviceToken) +// { +// // We use now Pushkit, unregister for all remote notifications received via Apple Push Notification service. +// [[UIApplication sharedApplication] unregisterForRemoteNotifications]; +// [accountManager setApnsDeviceToken:nil]; +// } + // Observers have been defined, we can start a matrix session for each enabled accounts. NSLog(@"[AppDelegate] initMatrixSessions: prepareSessionForActiveAccounts (app state: %tu)", [[UIApplication sharedApplication] applicationState]); [accountManager prepareSessionForActiveAccounts]; @@ -4569,6 +4594,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni NSDictionary *defaults = [NSDictionary dictionaryWithContentsOfFile:defaultsPathFromApp]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; + if (!RiotSettings.shared.isUserDefaultsMigrated) + { + [RiotSettings.shared migrate]; + } + // Now use RiotSettings and NSUserDefaults to store `showDecryptedContentInNotifications` setting option // Migrate this information from main MXKAccount to RiotSettings, if value is not in UserDefaults diff --git a/Riot/Assets/Riot-Defaults.plist b/Riot/Assets/Riot-Defaults.plist index 7bc0ce4f7..c7f887560 100644 --- a/Riot/Assets/Riot-Defaults.plist +++ b/Riot/Assets/Riot-Defaults.plist @@ -12,6 +12,10 @@ im.vector.app.ios.voip.prod pushKitAppIdDev im.vector.app.ios.voip.dev + pusherAppIdDev + im.vector.app.ios.dev + pusherAppIdProd + im.vector.app.ios.prod identityserverurl https://vector.im homeserverurl diff --git a/Riot/Categories/UNUserNotificationCenter.swift b/Riot/Categories/UNUserNotificationCenter.swift new file mode 100644 index 000000000..7dbd0f43c --- /dev/null +++ b/Riot/Categories/UNUserNotificationCenter.swift @@ -0,0 +1,32 @@ +/* + Copyright 2020 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 + +@objc extension UNUserNotificationCenter { + + func removeUnwantedNotifications() { + UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in + // get identifiers of notifications whose category identifiers are "TO_BE_REMOVED" + let identifiersToBeRemoved = notifications.compactMap({ $0.request.content.categoryIdentifier == Constants.toBeRemovedNotificationCategoryIdentifier ? $0.request.identifier : nil }) + + // remove the notifications with these id's + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiersToBeRemoved) + } + } + +} diff --git a/Riot/Generated/RiotDefaults.swift b/Riot/Generated/RiotDefaults.swift index 6d6431104..3cb6f869a 100644 --- a/Riot/Generated/RiotDefaults.swift +++ b/Riot/Generated/RiotDefaults.swift @@ -34,6 +34,8 @@ internal enum RiotDefaults { internal static let pushGatewayURL: String = _document["pushGatewayURL"] internal static let pushKitAppIdDev: String = _document["pushKitAppIdDev"] internal static let pushKitAppIdProd: String = _document["pushKitAppIdProd"] + internal static let pusherAppIdDev: String = _document["pusherAppIdDev"] + internal static let pusherAppIdProd: String = _document["pusherAppIdProd"] internal static let roomDirectoryServers: [String: Any] = _document["roomDirectoryServers"] internal static let showAllEventsInRoomHistory: Bool = _document["showAllEventsInRoomHistory"] internal static let showLeftMembersInRoomMemberList: Bool = _document["showLeftMembersInRoomMemberList"] diff --git a/Riot/Managers/PushNotification/PushNotificationService.h b/Riot/Managers/PushNotification/PushNotificationService.h index c91c4544e..fe76e70b3 100644 --- a/Riot/Managers/PushNotification/PushNotificationService.h +++ b/Riot/Managers/PushNotification/PushNotificationService.h @@ -15,16 +15,19 @@ limitations under the License. */ +#import #import -#import #import @class MXSession; +@class MXEvent; +@class MXPushRule; +@class MXKAccount; @protocol PushNotificationServiceDelegate; NS_ASSUME_NONNULL_BEGIN -@interface PushNotificationService : NSObject +@interface PushNotificationService : NSObject /** Is push really registered. @@ -48,6 +51,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)registerForRemoteNotificationsWithCompletion:(nullable void (^)(NSError *))completion; +- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; + +- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; + +- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; + /** Perform deregistration for remote notifications. */ diff --git a/Riot/Managers/PushNotification/PushNotificationService.m b/Riot/Managers/PushNotification/PushNotificationService.m index 7948a03d4..85e97ece8 100644 --- a/Riot/Managers/PushNotification/PushNotificationService.m +++ b/Riot/Managers/PushNotification/PushNotificationService.m @@ -46,7 +46,7 @@ NSMutableDictionary *notificationListenerBlocks; } -@property (nonatomic, strong) PKPushRegistry *pushRegistry; +//@property (nonatomic, strong) PKPushRegistry *pushRegistry; @property (nonatomic) NSMutableDictionary *> *incomingPushEventIds; @@ -110,16 +110,56 @@ - (void)registerForRemoteNotificationsWithCompletion:(nullable void (^)(NSError *))completion { - self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:nil]; - self.pushRegistry.delegate = self; - self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; - self.registrationForRemoteNotificationsCompletion = completion; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] registerForRemoteNotifications]; + }); +} + +- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; + [accountManager setApnsDeviceToken:deviceToken]; + // remove PushKit pusher if exists + if (accountManager.pushDeviceToken) + { + [accountManager setPushDeviceToken:nil withPushOptions:nil]; + } + // Sanity check: Make sure the Pushkit push token is deleted + NSParameterAssert(!accountManager.isPushAvailable); + NSParameterAssert(!accountManager.pushDeviceToken); + + _isPushRegistered = YES; + + if (self.registrationForRemoteNotificationsCompletion) + { + self.registrationForRemoteNotificationsCompletion(nil); + self.registrationForRemoteNotificationsCompletion = nil; + } +} + +- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error +{ + [self clearPushNotificationToken]; + + if (self.registrationForRemoteNotificationsCompletion) + { + self.registrationForRemoteNotificationsCompletion(error); + self.registrationForRemoteNotificationsCompletion = nil; + } +} + +- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + NSLog(@"[PushNotificationService][Push] didReceiveRemoteNotification: applicationState: %tu - payload: %@", [UIApplication sharedApplication].applicationState, userInfo); + + completionHandler(UIBackgroundFetchResultNewData); } - (void)deregisterRemoteNotifications { - self.pushRegistry = nil; _isPushRegistered = NO; } @@ -131,6 +171,8 @@ [array removeAllObjects]; } [incomingPushPayloads removeAllObjects]; + + [[UNUserNotificationCenter currentNotificationCenter] removeUnwantedNotifications]; } - (void)addMatrixSession:(MXSession *)mxSession @@ -259,65 +301,6 @@ } } -#pragma mark - PKPushRegistryDelegate - -- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type -{ - NSData *token = credentials.token; - - NSLog(@"[PushNotificationService][Push] didUpdatePushCredentials: Got Push token: %@. Type: %@", [MXKTools logForPushToken:token], type); - - MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; - [accountManager setPushDeviceToken:token withPushOptions:@{@"format": @"event_id_only"}]; - - _isPushRegistered = YES; - - if (self.registrationForRemoteNotificationsCompletion) - { - self.registrationForRemoteNotificationsCompletion(nil); - self.registrationForRemoteNotificationsCompletion = nil; - } -} - -- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type -{ - NSLog(@"[PushNotificationService][Push] didInvalidatePushTokenForType: Type: %@", type); - - [self clearPushNotificationToken]; -} - -- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type -{ - NSLog(@"[PushNotificationService][Push] didReceiveIncomingPushWithPayload: applicationState: %tu - type: %@ - payload: %@", [UIApplication sharedApplication].applicationState, payload.type, payload.dictionaryPayload); - - // Display local notifications only when the app is running in background. - if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) - { - NSLog(@"[PushNotificationService][Push] didReceiveIncomingPushWithPayload while app is in background"); - - // Check whether an event id is provided. - NSString *eventId = payload.dictionaryPayload[@"event_id"]; - if (eventId) - { - // Add this event identifier in the pending push array for each session. - for (NSMutableArray *array in self.incomingPushEventIds.allValues) - { - [array addObject:eventId]; - } - - // Cache payload for further usage - incomingPushPayloads[eventId] = payload.dictionaryPayload; - } - else - { - NSLog(@"[PushNotificationService][Push] didReceiveIncomingPushWithPayload - Unexpected payload %@", payload.dictionaryPayload); - } - - // Trigger a background sync to handle notifications. - [self launchBackgroundSync]; - } -} - #pragma mark - UNUserNotificationCenterDelegate // iOS 10+, see application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler: @@ -373,14 +356,6 @@ } } -// iOS 10+, this is called when a notification is about to display in foreground. -- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler -{ - NSLog(@"[PushNotificationService][Push] willPresentNotification: applicationState: %@", @([UIApplication sharedApplication].applicationState)); - - completionHandler(UNNotificationPresentationOptionNone); -} - #pragma mark - Other Methods - (void)launchBackgroundSync @@ -607,7 +582,7 @@ } // iOS 10+, does the same thing as notificationBodyForEvent:pushRule:inAccount:onComplete:, except with more features -- (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule inAccount:(MXKAccount *)account onComplete:(void (^)(UNNotificationContent * _Nullable notificationContent))onComplete; +- (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule inAccount:(MXKAccount *)account onComplete:(void (^)(UNNotificationContent * _Nullable notificationContent))onComplete { if (!event.content || !event.content.count) { diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index b1ab98997..bc9322c31 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -38,39 +38,61 @@ final class RiotSettings: NSObject { static let shared = RiotSettings() + /// UserDefaults to be used on reads and writes. + private lazy var defaults: UserDefaults = { + return UserDefaults(suiteName: "group.im.vector")! + }() + // MARK: - Public // MARK: Notifications /// Indicate if `showDecryptedContentInNotifications` settings has been set once. var isShowDecryptedContentInNotificationsHasBeenSetOnce: Bool { - return UserDefaults.standard.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil + return defaults.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil + } + + /// Indicate if UserDefaults suite has been migrated once. + var isUserDefaultsMigrated: Bool { + return defaults.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil + } + + func migrate() { + // read all values from standard + let dictionary = UserDefaults.standard.dictionaryRepresentation() + + // write values to suite + // remove redundant values from standard + for (key, value) in dictionary { + defaults.set(value, forKey: key) + UserDefaults.standard.removeObject(forKey: key) + } } /// Indicate if encrypted messages content should be displayed in notifications. var showDecryptedContentInNotifications: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) + return defaults.bool(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.notificationsShowDecryptedContent) + defaults.set(newValue, forKey: UserDefaultsKeys.notificationsShowDecryptedContent) } } /// Indicate if rooms with missed notifications should be displayed first on home screen. var pinRoomsWithMissedNotificationsOnHome: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) + return defaults.bool(forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) + defaults.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) } } /// Indicate if rooms with unread messages should be displayed first on home screen. var pinRoomsWithUnreadMessagesOnHome: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) + return defaults.bool(forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) + defaults.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) } } @@ -78,9 +100,9 @@ final class RiotSettings: NSObject { var userInterfaceTheme: String? { get { - return UserDefaults.standard.string(forKey: UserDefaultsKeys.userInterfaceTheme) + return defaults.string(forKey: UserDefaultsKeys.userInterfaceTheme) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.userInterfaceTheme) + defaults.set(newValue, forKey: UserDefaultsKeys.userInterfaceTheme) } } @@ -88,22 +110,22 @@ final class RiotSettings: NSObject { /// Indicate if `enableCrashReport` settings has been set once. var isEnableCrashReportHasBeenSetOnce: Bool { - return UserDefaults.standard.object(forKey: UserDefaultsKeys.enableCrashReport) != nil + return defaults.object(forKey: UserDefaultsKeys.enableCrashReport) != nil } var enableCrashReport: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableCrashReport) + return defaults.bool(forKey: UserDefaultsKeys.enableCrashReport) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableCrashReport) + defaults.set(newValue, forKey: UserDefaultsKeys.enableCrashReport) } } var enableRageShake: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableRageShake) + return defaults.bool(forKey: UserDefaultsKeys.enableRageShake) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableRageShake) + defaults.set(newValue, forKey: UserDefaultsKeys.enableRageShake) } } @@ -111,9 +133,9 @@ final class RiotSettings: NSObject { var createConferenceCallsWithJitsi: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) + return defaults.bool(forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) + defaults.set(newValue, forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) } } @@ -121,36 +143,36 @@ final class RiotSettings: NSObject { /// Indicate if `allowStunServerFallback` settings has been set once. var isAllowStunServerFallbackHasBeenSetOnce: Bool { - return UserDefaults.standard.object(forKey: UserDefaultsKeys.allowStunServerFallback) != nil + return defaults.object(forKey: UserDefaultsKeys.allowStunServerFallback) != nil } var allowStunServerFallback: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.allowStunServerFallback) + return defaults.bool(forKey: UserDefaultsKeys.allowStunServerFallback) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.allowStunServerFallback) + defaults.set(newValue, forKey: UserDefaultsKeys.allowStunServerFallback) } } var stunServerFallback: String? { - return UserDefaults.standard.string(forKey: UserDefaultsKeys.stunServerFallback) + return defaults.string(forKey: UserDefaultsKeys.stunServerFallback) } // MARK: Key verification var hideVerifyThisSessionAlert: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.hideVerifyThisSessionAlert) + return defaults.bool(forKey: UserDefaultsKeys.hideVerifyThisSessionAlert) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.hideVerifyThisSessionAlert) + defaults.set(newValue, forKey: UserDefaultsKeys.hideVerifyThisSessionAlert) } } var hideReviewSessionsAlert: Bool { get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.hideReviewSessionsAlert) + return defaults.bool(forKey: UserDefaultsKeys.hideReviewSessionsAlert) } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.hideReviewSessionsAlert) + defaults.set(newValue, forKey: UserDefaultsKeys.hideReviewSessionsAlert) } } } diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index b59da61c1..8dfc81ad7 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -87,9 +87,7 @@ enum enum { - CALLS_ENABLE_CALLKIT_INDEX = 0, - CALLS_CALLKIT_DESCRIPTION_INDEX, - CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX, + CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX=0, CALLS_STUN_SERVER_FALLBACK_DESCRIPTION_INDEX, CALLS_COUNT }; @@ -1732,39 +1730,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } else if (section == SETTINGS_SECTION_CALLS_INDEX) { - if (row == CALLS_ENABLE_CALLKIT_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_callkit", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].isCallKitEnabled; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleCallKit:) forControlEvents:UIControlEventTouchUpInside]; - - if (![MXCallKitAdapter callKitAvailable]) - { - labelAndSwitchCell.mxkSwitch.on = NO; - labelAndSwitchCell.mxkSwitch.enabled = NO; - labelAndSwitchCell.mxkLabel.enabled = NO; - } - - cell = labelAndSwitchCell; - } - else if (row == CALLS_CALLKIT_DESCRIPTION_INDEX) - { - MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; - globalInfoCell.textLabel.text = NSLocalizedStringFromTable(@"settings_callkit_info", @"Vector", nil); - globalInfoCell.textLabel.numberOfLines = 0; - globalInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; - - if (![MXCallKitAdapter callKitAvailable]) - { - globalInfoCell.textLabel.enabled = NO; - } - - cell = globalInfoCell; - } - else if (row == CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX) + if (row == CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_calls_stun_server_fallback_button", @"Vector", nil); @@ -2849,10 +2815,10 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; MXKAccount* account = accountManager.activeAccounts.firstObject; - - if (accountManager.pushDeviceToken) + + if (accountManager.apnsDeviceToken) { - [account enablePushKitNotifications:!account.isPushKitNotificationActive success:^{ + [account enablePushNotifications:!account.pushNotificationServiceIsActive success:^{ [self stopActivityIndicator]; } failure:^(NSError *error) { [self stopActivityIndicator]; @@ -2869,7 +2835,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } else { - [account enablePushKitNotifications:YES success:^{ + [account enablePushNotifications:YES success:^{ [self stopActivityIndicator]; } failure:^(NSError *error) { [self stopActivityIndicator]; diff --git a/Riot/Utils/Constants.swift b/Riot/Utils/Constants.swift new file mode 100644 index 000000000..200c52cdd --- /dev/null +++ b/Riot/Utils/Constants.swift @@ -0,0 +1,23 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum Constants { + + static let toBeRemovedNotificationCategoryIdentifier = "TO_BE_REMOVED" + +} diff --git a/RiotNSE/Info.plist b/RiotNSE/Info.plist new file mode 100644 index 000000000..5ce763e05 --- /dev/null +++ b/RiotNSE/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + RiotNSE + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/RiotNSE/NSEMemoryStore.swift b/RiotNSE/NSEMemoryStore.swift new file mode 100644 index 000000000..8bbe923dc --- /dev/null +++ b/RiotNSE/NSEMemoryStore.swift @@ -0,0 +1,90 @@ +/* + Copyright 2020 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 MatrixSDK + +/// Fake memory store implementation. Uses some real values from an MXFileStore instance. +class NSEMemoryStore: MXMemoryStore { + + private var credentials: MXCredentials + // real store + private var fileStore: MXFileStore + + init(withCredentials credentials: MXCredentials) { + self.credentials = credentials + fileStore = MXFileStore(credentials: credentials) + // load real eventStreamToken + fileStore.loadMetaData() + } + + // Return real eventStreamToken, to be able to launch a meaningful background sync + override var eventStreamToken: String? { + get { + return fileStore.eventStreamToken + } set { + // no-op + } + } + + // Return real userAccountData, to be able to use push rules + override var userAccountData: [AnyHashable : Any]? { + get { + return fileStore.userAccountData + } set { + // no-op + } + } + + // This store should act like as a permanent one + override var isPermanent: Bool { + return true + } + + // Some mandatory methods to implement to be permanent + override func storeState(forRoom roomId: String, stateEvents: [MXEvent]) { + // no-op + } + + // Fetch real room state + override func state(ofRoom roomId: String, success: @escaping ([MXEvent]) -> Void, failure: ((Error) -> Void)? = nil) { + fileStore.state(ofRoom: roomId, success: success, failure: failure) + } + + // Fetch real soom summary + override func summary(ofRoom roomId: String) -> MXRoomSummary? { + return fileStore.summary(ofRoom: roomId) + } + + // Fetch real room account data + override func accountData(ofRoom roomId: String) -> MXRoomAccountData? { + return fileStore.accountData(ofRoom: roomId) + } + + // Override and return a user to be stored on session.myUser + override func user(withUserId userId: String) -> MXUser? { + if userId == credentials.userId { + return MXMyUser(userId: userId) + } + return MXUser(userId: userId) + } + + override func close() { + // close real store + fileStore.close() + } + +} diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift new file mode 100644 index 000000000..e70fd3192 --- /dev/null +++ b/RiotNSE/NotificationService.swift @@ -0,0 +1,520 @@ +/* + Copyright 2020 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 MatrixKit + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var originalContent: UNMutableNotificationContent? + + var mxSession: MXSession? + var store: NSEMemoryStore! + var showDecryptedContentInNotifications: Bool { + return RiotSettings.shared.showDecryptedContentInNotifications + } + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + // save this content as fallback content + originalContent = request.content.mutableCopy() as? UNMutableNotificationContent + + UNUserNotificationCenter.current().removeUnwantedNotifications() + + // check if this is a Matrix notification + guard let content = originalContent else { + return + } + + let userInfo = content.userInfo + NSLog("[NotificationService] Payload came: \(userInfo)") + + guard let roomId = userInfo["room_id"] as? String, let _ = userInfo["event_id"] as? String else { + // it's not a Matrix notification, do not change the content + NSLog("[NotificationService] didReceiveRequest: This is not a Matrix notification.") + contentHandler(content) + return + } + + // setup user account + setup(withRoomId: roomId) { + // fetch the event first + self.fetchEvent() + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + NSLog("[NotificationService] serviceExtensionTimeWillExpire") + fallbackToOriginalContent() + } + + func setup(withRoomId roomId: String, completion: @escaping () -> Void) { + let sdkOptions = MXSDKOptions.sharedInstance() + sdkOptions.applicationGroupIdentifier = "group.im.vector" + sdkOptions.disableIdenticonUseForUserAvatar = true + sdkOptions.enableCryptoWhenStartingMXSession = true + sdkOptions.backgroundModeHandler = MXUIKitBackgroundModeHandler() + Bundle.mxk_customizeLocalizedStringTableName("Vector") + + if isatty(STDERR_FILENO) == 0 { + MXLogger.setSubLogName("nse") + MXLogger.redirectNSLog(toFiles: true) + } + + if let userAccount = MXKAccountManager.shared()?.activeAccounts.first { + store = NSEMemoryStore(withCredentials: userAccount.mxCredentials) + // Fake roomStores in memory store. This is for both -[MXMemoryStore rooms] and -[MXSession rooms] to return some rooms. + // Also roomsSummaries will be filled with the room summary for this roomId. + // They all will be used afterwards. + store.getOrCreateRoomStore(roomId) + + mxSession = MXSession(matrixRestClient: MXRestClient(credentials: userAccount.mxCredentials, unrecognizedCertificateHandler: nil)) + mxSession?.setStore(store, completion: { (response) in + switch response { + case .success: + completion() + break + case .failure(let error): + NSLog("[NotificationService] setup: MXSession.setStore method returned error: \(String(describing: error))") + self.fallbackToOriginalContent() + break + } + }) + } + } + + func fetchEvent() { + guard let content = originalContent, let mxSession = mxSession else { + // there is something wrong, do not change the content + NSLog("[NotificationService] fetchEvent: Either originalContent or mxSession is missing.") + fallbackToOriginalContent() + return + } + let userInfo = content.userInfo + + guard let roomId = userInfo["room_id"] as? String, let eventId = userInfo["event_id"] as? String else { + // it's not a Matrix notification, do not change the content + NSLog("[NotificationService] fetchEvent: This is not a Matrix notification.") + contentHandler?(content) + return + } + + mxSession.event(withEventId: eventId, inRoom: roomId, success: { [weak self] (event) in + guard let self = self else { + NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late successfully.") + return + } + + guard let event = event else { + self.fallbackToOriginalContent() + return + } + + if !event.isEncrypted { + // not encrypted, go on processing + self.processEvent(event) + return + } + + // encrypted + if !self.showDecryptedContentInNotifications { + // do not show decrypted content in notification + self.fallbackToOriginalContent() + return + } + + // should show decrypted content in notification + if event.clear != nil { + // already decrypted + self.processEvent(event) + return + } + + // should decrypt it first + if mxSession.decryptEvent(event, inTimeline: nil) { + // decryption succeeded + self.processEvent(event) + } else { + // decryption failed + NSLog("[NotificationService] fetchEvent: Event needs to be decrpyted, but we don't have the keys to decrypt it. Launching a background sync.") + self.launchBackgroundSync() + } + }) { [weak self] (error) in + guard let self = self else { + NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late with error: \(String(describing: error))") + return + } + NSLog("[NotificationService] fetchEvent: MXSession.event method returned error: \(String(describing: error))") + self.fallbackToOriginalContent() + } + } + + func launchBackgroundSync() { + guard let mxSession = mxSession else { + NSLog("[NotificationService] launchBackgroundSync: mxSession is missing.") + self.fallbackToOriginalContent() + return + } + + // launch an initial background sync + mxSession.backgroundSync(withTimeout: 20, ignoreSessionState: true) { [weak self] (response) in + switch response { + case .success: + guard let self = self else { + NSLog("[NotificationService] launchBackgroundSync: MXSession.initialBackgroundSync returned too late successfully") + return + } + self.fetchEvent() + break + case .failure(let error): + guard let self = self else { + NSLog("[NotificationService] launchBackgroundSync: MXSession.initialBackgroundSync returned too late with error: \(String(describing: error))") + return + } + NSLog("[NotificationService] launchBackgroundSync: MXSession.initialBackgroundSync returned with error: \(String(describing: error))") + self.fallbackToOriginalContent() + break + } + } + } + + func processEvent(_ event: MXEvent) { + guard let content = originalContent, let mxSession = mxSession else { + self.fallbackToOriginalContent() + return + } + + self.notificationContent(forEvent: event, inSession: mxSession) { (notificationContent) in + // close store + self.store.close() + + // Modify the notification content here... + if let newContent = notificationContent { + content.title = newContent.title + content.subtitle = newContent.subtitle + content.body = newContent.body + content.threadIdentifier = newContent.threadIdentifier + content.categoryIdentifier = newContent.categoryIdentifier + content.userInfo = newContent.userInfo + content.sound = newContent.sound + } else { + // this is an unwanted notification, mark as to be deleted when app is foregrounded again OR a new push came + content.categoryIdentifier = Constants.toBeRemovedNotificationCategoryIdentifier + } + + self.contentHandler?(content) + } + } + + func fallbackToOriginalContent() { + store.close() + guard let content = originalContent else { + NSLog("[NotificationService] fallbackToOriginalContent: Original content is missing.") + return + } + + // call contentHandler + contentHandler?(content) + } + + func notificationContent(forEvent event: MXEvent, inSession session: MXSession, onComplete: @escaping (UNNotificationContent?) -> Void) { + guard let content = event.content, content.count > 0 else { + NSLog("[NotificationService][Push] notificationContentForEvent: empty event content") + onComplete(nil) + return + } + guard let room = session.room(withRoomId: event.roomId) else { + NSLog("[NotificationService][Push] notificationBodyForEvent: Unknown room") + onComplete(nil) + return + } + + let pushRule = room.getRoomPushRule() + + room.state { (roomState) in + guard let roomState = roomState else { + NSLog("[NotificationService] notificationContentForEvent: Could not load the room state") + onComplete(nil) + return + } + + var notificationTitle: String? + var notificationBody: String? + + var threadIdentifier = room.roomId + let eventSenderName = roomState.members.memberName(event.sender) + let currentUserId = session.credentials.userId + + switch event.eventType { + case .roomMessage, .roomEncrypted: + if room.isMentionsOnly { + // A local notification will be displayed only for highlighted notification. + var isHighlighted = false + + // Check whether is there an highlight tweak on it + for ruleAction in pushRule?.actions ?? [] { + guard let action = ruleAction as? MXPushRuleAction else { continue } + guard action.actionType == MXPushRuleActionTypeSetTweak else { continue } + guard action.parameters["set_tweak"] as? String == "highlight" else { continue } + // Check the highlight tweak "value" + // If not present, highlight. Else check its value before highlighting + if nil == action.parameters["value"] || true == (action.parameters["value"] as? Bool) { + isHighlighted = true + break + } + } + + if !isHighlighted { + // Ignore this notif. + NSLog("[NotificationService][Push] notificationBodyForEvent: Ignore non highlighted notif in mentions only room") + onComplete(nil) + return + } + } + + var msgType = event.content["msgtype"] as? String + let messageContent = event.content["body"] as? String + + if event.isEncrypted && !self.showDecryptedContentInNotifications { + // Hide the content + msgType = nil + } + + let roomDisplayName = room.summary.displayname + let myUserId = session.myUser.userId + let isIncomingEvent = event.sender != myUserId + + // Display the room name only if it is different than the sender name + if roomDisplayName != nil && roomDisplayName != eventSenderName { + notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any]) + + if msgType == kMXMessageTypeText { + notificationBody = messageContent + } else if msgType == kMXMessageTypeEmote { + notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + } else if msgType == kMXMessageTypeImage { + notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + } else if room.isDirect && isIncomingEvent && msgType == kMXMessageTypeKeyVerificationRequest { + session.crypto.keyVerificationManager.keyVerification(fromKeyVerificationEvent: event, + success:{ (keyVerification) in + guard let request = keyVerification.request, request.state == MXKeyVerificationRequestStatePending else { + onComplete(nil) + return + } + // TODO: Add accept and decline actions to notification + let body = NSString.localizedUserNotificationString(forKey: "KEY_VERIFICATION_REQUEST_FROM_USER", arguments: [eventSenderName as Any]) + + let notificationContent = self.notificationContent(withTitle: notificationTitle, + body: body, + threadIdentifier: threadIdentifier, + userId: currentUserId, + event: event, + pushRule: pushRule) + + onComplete(notificationContent) + }, failure:{ (error) in + NSLog("[NotificationService][Push] notificationContentForEvent: failed to fetch key verification with error: \(error)") + onComplete(nil) + }) + } else { + // Encrypted messages falls here + notificationBody = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER", arguments: [eventSenderName as Any]) + } + } else { + notificationTitle = eventSenderName + + switch msgType { + case kMXMessageTypeText: + notificationBody = messageContent + break + case kMXMessageTypeEmote: + notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + break + case kMXMessageTypeImage: + notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + break + default: + // Encrypted messages falls here + notificationBody = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER", arguments: [eventSenderName as Any]) + break + } + } + break + case .callInvite: + let offer = event.content["offer"] as? [AnyHashable: Any] + let sdp = offer?["sdp"] as? String + let isVideoCall = sdp?.contains("m=video") ?? false + + if isVideoCall { + notificationBody = NSString.localizedUserNotificationString(forKey: "VIDEO_CALL_FROM_USER", arguments: [eventSenderName as Any]) + } else { + notificationBody = NSString.localizedUserNotificationString(forKey: "VOICE_CALL_FROM_USER", arguments: [eventSenderName as Any]) + } + + // call notifications should stand out from normal messages, so we don't stack them + threadIdentifier = nil + case .roomMember: + let roomDisplayName = room.summary.displayname + + if roomDisplayName != nil && roomDisplayName != eventSenderName { + notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_NAMED_ROOM", arguments: [eventSenderName as Any, roomDisplayName as Any]) + } else { + notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_CHAT", arguments: [eventSenderName as Any]) + } + case .sticker: + let roomDisplayName = room.summary.displayname + + if roomDisplayName != nil && roomDisplayName != eventSenderName { + notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any]) + } else { + notificationTitle = eventSenderName + } + + notificationBody = NSString.localizedUserNotificationString(forKey: "STICKER_FROM_USER", arguments: [eventSenderName as Any]) + default: + break + } + + guard (notificationBody != nil) else { + onComplete(nil) + return + } + + let notificationContent = self.notificationContent(withTitle: notificationTitle, + body: notificationBody, + threadIdentifier: threadIdentifier, + userId: currentUserId, + event: event, + pushRule: pushRule) + + onComplete(notificationContent) + } + } + + func notificationContent(withTitle title: String?, + body: String?, + threadIdentifier: String?, + userId: String?, + event: MXEvent, + pushRule: MXPushRule?) -> UNNotificationContent { + let notificationContent = UNMutableNotificationContent() + + if let title = title { + notificationContent.title = title + } + if let body = body { + notificationContent.body = body + } + if let threadIdentifier = threadIdentifier { + notificationContent.threadIdentifier = threadIdentifier + } + if let categoryIdentifier = self.notificationCategoryIdentifier(forEvent: event) { + notificationContent.categoryIdentifier = categoryIdentifier + } + if let soundName = notificationSoundName(fromPushRule: pushRule) { + notificationContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: soundName)) + } else { + notificationContent.sound = UNNotificationSound.default + } + notificationContent.userInfo = notificationUserInfo(forEvent: event, andUserId: userId) + + return notificationContent + } + + func notificationUserInfo(forEvent event: MXEvent, andUserId userId: String?) -> [AnyHashable: Any] { + var notificationUserInfo: [AnyHashable: Any] = [ + "type": "full", + "room_id": event.roomId as Any, + "event_id": event.eventId as Any + ] + if let userId = userId { + notificationUserInfo["user_id"] = userId + } + return notificationUserInfo + } + + func notificationSoundName(fromPushRule pushRule: MXPushRule?) -> String? { + var soundName: String? + + // Set sound name based on the value provided in action of MXPushRule + for ruleAction in pushRule?.actions ?? [] { + guard let action = ruleAction as? MXPushRuleAction else { continue } + guard action.actionType == MXPushRuleActionTypeSetTweak else { continue } + guard action.parameters["set_tweak"] as? String == "sound" else { continue } + soundName = action.parameters["value"] as? String + if soundName == "default" { + soundName = "message.caf" + } + } + + return soundName + } + + func notificationCategoryIdentifier(forEvent event: MXEvent) -> String? { + let isNotificationContentShown = !event.isEncrypted || self.showDecryptedContentInNotifications + + guard isNotificationContentShown else { + return nil + } + + guard event.eventType == .roomMessage || event.eventType == .roomEncrypted else { + return nil + } + + return "QUICK_REPLY" + } + +} + +extension MXRoom { + + func getRoomPushRule() -> MXPushRule? { + guard let rules = self.mxSession.notificationCenter.rules.global.room else { + return nil + } + + for rule in rules { + guard let pushRule = rule as? MXPushRule else { continue } + // the rule id is the room Id + // it is the server trick to avoid duplicated rule on the same room. + if pushRule.ruleId == self.roomId { + return pushRule + } + } + + return nil + } + + var isMentionsOnly: Bool { + // Check push rules at room level + guard let rule = self.getRoomPushRule() else { + return false + } + + for ruleAction in rule.actions { + guard let action = ruleAction as? MXPushRuleAction else { continue } + if action.actionType == MXPushRuleActionTypeDontNotify { + return rule.enabled + } + } + + return false + } + +} diff --git a/RiotNSE/RiotNSE.entitlements b/RiotNSE/RiotNSE.entitlements new file mode 100644 index 000000000..e540aaaec --- /dev/null +++ b/RiotNSE/RiotNSE.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.im.vector + + +