From 9a4d8c3445fd85bf1d4247167f2a389dea986b47 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 2 Mar 2024 19:19:31 +0100 Subject: [PATCH 01/31] add DcNotificationService --- DcNotificationService/Info.plist | 13 ++ .../NotificationService.swift | 28 ++++ deltachat-ios.xcodeproj/project.pbxproj | 154 +++++++++++++++++- 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 DcNotificationService/Info.plist create mode 100644 DcNotificationService/NotificationService.swift diff --git a/DcNotificationService/Info.plist b/DcNotificationService/Info.plist new file mode 100644 index 000000000..57421ebf9 --- /dev/null +++ b/DcNotificationService/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift new file mode 100644 index 000000000..5abbbd1c9 --- /dev/null +++ b/DcNotificationService/NotificationService.swift @@ -0,0 +1,28 @@ +import UserNotifications + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + if let bestAttemptContent = bestAttemptContent { + // Modify the notification content here... + bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" + + contentHandler(bestAttemptContent) + } + } + + 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. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } + +} diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index c3b2ef9bf..536fc93df 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -200,6 +200,8 @@ B259D64329B771D5008FB706 /* BackupTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B259D64229B771D5008FB706 /* BackupTransferViewController.swift */; }; B26B3BC7236DC3DC008ED35A /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26B3BC6236DC3DC008ED35A /* SwitchCell.swift */; }; B2C42570265C325C00B95377 /* MultilineLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C4256F265C325C00B95377 /* MultilineLabelCell.swift */; }; + B2D0E4952B93A4B200791949 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D0E4942B93A4B200791949 /* NotificationService.swift */; }; + B2D0E4992B93A4B200791949 /* DcNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = B2D0E4922B93A4B200791949 /* DcNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; B2D4B63B29C38D1900B47DA8 /* ChatsAndMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D4B63A29C38D1900B47DA8 /* ChatsAndMediaViewController.swift */; }; B2F899E129F96A67003797D5 /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2F899E029F96A67003797D5 /* AllMediaViewController.swift */; }; D80F62792B59D1CC00877059 /* DefaultReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80F62782B59D1CC00877059 /* DefaultReactions.swift */; }; @@ -218,6 +220,13 @@ remoteGlobalIDString = 30E8F20F2447285600CE2C90; remoteInfo = DcShare; }; + B2D0E4972B93A4B200791949 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7A9FB1381FB061E2001FEA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B2D0E4912B93A4B200791949; + remoteInfo = DcNotificationService; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -238,6 +247,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + B2D0E4992B93A4B200791949 /* DcNotificationService.appex in Embed Foundation Extensions */, 30E8F21A2447285600CE2C90 /* Delta Chat.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -557,6 +567,9 @@ B2B9BC1126245F2200F35832 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; B2B9BC1226245F2200F35832 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = ""; }; B2C4256F265C325C00B95377 /* MultilineLabelCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineLabelCell.swift; sourceTree = ""; }; + B2D0E4922B93A4B200791949 /* DcNotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DcNotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + B2D0E4942B93A4B200791949 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + B2D0E4962B93A4B200791949 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B2D4B63A29C38D1900B47DA8 /* ChatsAndMediaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatsAndMediaViewController.swift; sourceTree = ""; }; B2D729E927C57B9000A4E0BE /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; B2D729EA27C57B9000A4E0BE /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -592,6 +605,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B2D0E48F2B93A4B200791949 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -801,6 +821,7 @@ 7837B63F21E54DC600CDE126 /* .swiftlint.yml */, 7A9FB1421FB061E2001FEA36 /* deltachat-ios */, 30E8F2112447285600CE2C90 /* DcShare */, + B2D0E4932B93A4B200791949 /* DcNotificationService */, 7A9FB1411FB061E2001FEA36 /* Products */, 7A9FB4F81FB084E6001FEA36 /* Frameworks */, AFE4D4B4B038293E63BC1537 /* Pods */, @@ -815,6 +836,7 @@ children = ( 7A9FB1401FB061E2001FEA36 /* deltachat-ios.app */, 30E8F2102447285600CE2C90 /* Delta Chat.appex */, + B2D0E4922B93A4B200791949 /* DcNotificationService.appex */, ); name = Products; sourceTree = ""; @@ -1052,6 +1074,15 @@ path = Settings; sourceTree = ""; }; + B2D0E4932B93A4B200791949 /* DcNotificationService */ = { + isa = PBXGroup; + children = ( + B2D0E4942B93A4B200791949 /* NotificationService.swift */, + B2D0E4962B93A4B200791949 /* Info.plist */, + ); + path = DcNotificationService; + sourceTree = ""; + }; D80F62772B59D1B800877059 /* Send Reaction */ = { isa = PBXGroup; children = ( @@ -1117,12 +1148,30 @@ ); dependencies = ( 30E8F2192447285600CE2C90 /* PBXTargetDependency */, + B2D0E4982B93A4B200791949 /* PBXTargetDependency */, ); name = "deltachat-ios"; productName = "deltachat-ios"; productReference = 7A9FB1401FB061E2001FEA36 /* deltachat-ios.app */; productType = "com.apple.product-type.application"; }; + B2D0E4912B93A4B200791949 /* DcNotificationService */ = { + isa = PBXNativeTarget; + buildConfigurationList = B2D0E49C2B93A4B200791949 /* Build configuration list for PBXNativeTarget "DcNotificationService" */; + buildPhases = ( + B2D0E48E2B93A4B200791949 /* Sources */, + B2D0E48F2B93A4B200791949 /* Frameworks */, + B2D0E4902B93A4B200791949 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DcNotificationService; + productName = DcNotificationService; + productReference = B2D0E4922B93A4B200791949 /* DcNotificationService.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -1130,7 +1179,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1140; + LastSwiftUpdateCheck = 1520; LastUpgradeCheck = 1500; ORGANIZATIONNAME = "merlinux GmbH"; TargetAttributes = { @@ -1158,6 +1207,9 @@ }; }; }; + B2D0E4912B93A4B200791949 = { + CreatedOnToolsVersion = 15.2; + }; }; }; buildConfigurationList = 7A9FB13B1FB061E2001FEA36 /* Build configuration list for PBXProject "deltachat-ios" */; @@ -1219,6 +1271,7 @@ targets = ( 7A9FB13F1FB061E2001FEA36 /* deltachat-ios */, 30E8F20F2447285600CE2C90 /* DcShare */, + B2D0E4912B93A4B200791949 /* DcNotificationService */, ); }; /* End PBXProject section */ @@ -1250,6 +1303,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B2D0E4902B93A4B200791949 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -1571,6 +1631,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B2D0E48E2B93A4B200791949 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B2D0E4952B93A4B200791949 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1579,6 +1647,11 @@ target = 30E8F20F2447285600CE2C90 /* DcShare */; targetProxy = 30E8F2182447285600CE2C90 /* PBXContainerItemProxy */; }; + B2D0E4982B93A4B200791949 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B2D0E4912B93A4B200791949 /* DcNotificationService */; + targetProxy = B2D0E4972B93A4B200791949 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -2112,6 +2185,76 @@ }; name = Release; }; + B2D0E49A2B93A4B200791949 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8Y86453UA8; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = DcNotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = DcNotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 merlinux GmbH. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = chat.delta.DcNotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + B2D0E49B2B93A4B200791949 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8Y86453UA8; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = DcNotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = DcNotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 merlinux GmbH. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = chat.delta.DcNotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2142,6 +2285,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + B2D0E49C2B93A4B200791949 /* Build configuration list for PBXNativeTarget "DcNotificationService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B2D0E49A2B93A4B200791949 /* Debug */, + B2D0E49B2B93A4B200791949 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 7A9FB1381FB061E2001FEA36 /* Project object */; From 2a1b7470af8153feea8b17fb508fdf9cddcbcde2 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 3 Mar 2024 16:41:02 +0100 Subject: [PATCH 02/31] fix issue number one: different deployment targets stand in the way of 'Notification Service Extension' to work --- deltachat-ios.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 536fc93df..76ce40341 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -2201,7 +2201,7 @@ INFOPLIST_FILE = DcNotificationService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = DcNotificationService; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 merlinux GmbH. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2237,7 +2237,7 @@ INFOPLIST_FILE = DcNotificationService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = DcNotificationService; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 merlinux GmbH. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From fd7ec26504750f7a3ac2cdb19cdacf09e0621f6f Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 3 Mar 2024 17:33:27 +0100 Subject: [PATCH 03/31] fix compiler complaining about bad versions --- RELEASE.md | 1 + deltachat-ios.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 9c216aece..119fcf04f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -29,6 +29,7 @@ in Xcode: b) increase the build number in the same dialog c) navigate to "DcShare / Build Settings / Versioning" and adapt "Marketing Version" and "Current Project Version" + d) same for "DcNotificationService" 6. a) select "Any iOS Device (arm64)" in the toolbar b) select menu "Product/Archive" diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 76ce40341..7fa8e639e 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -2193,7 +2193,7 @@ CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 101; DEVELOPMENT_TEAM = 8Y86453UA8; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2208,7 +2208,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.43.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.delta.DcNotificationService; @@ -2229,7 +2229,7 @@ CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 101; DEVELOPMENT_TEAM = 8Y86453UA8; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2244,7 +2244,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.43.1; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.delta.DcNotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; From 98d0c520b131a3b9954788205d5028aa65a293d5 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 4 Mar 2024 17:39:18 +0100 Subject: [PATCH 04/31] assign DcNotificationService to group.chat.delta.ios so it can access the same files and assets as the main app --- .../DcNotificationService.entitlements | 14 ++++++++++++++ deltachat-ios.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 DcNotificationService/DcNotificationService.entitlements diff --git a/DcNotificationService/DcNotificationService.entitlements b/DcNotificationService/DcNotificationService.entitlements new file mode 100644 index 000000000..849391a7b --- /dev/null +++ b/DcNotificationService/DcNotificationService.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.chat.delta.ios + + keychain-access-groups + + $(AppIdentifierPrefix)group.chat.delta.ios + + + diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 7fa8e639e..786993839 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -527,6 +527,7 @@ B2815ADF29EF249300F803D2 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = ""; }; B2815AE029EF249300F803D2 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; B2815AE129EF249300F803D2 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ro.lproj/Localizable.stringsdict; sourceTree = ""; }; + B281BC4C2B962C19006988F1 /* DcNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DcNotificationService.entitlements; sourceTree = ""; }; B294715823E1A16700885D02 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; B294715923E1A16700885D02 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; B294715A23E1A16700885D02 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nb; path = nb.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -1077,6 +1078,7 @@ B2D0E4932B93A4B200791949 /* DcNotificationService */ = { isa = PBXGroup; children = ( + B281BC4C2B962C19006988F1 /* DcNotificationService.entitlements */, B2D0E4942B93A4B200791949 /* NotificationService.swift */, B2D0E4962B93A4B200791949 /* Info.plist */, ); @@ -2191,6 +2193,7 @@ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = DcNotificationService/DcNotificationService.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 101; @@ -2227,6 +2230,7 @@ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = DcNotificationService/DcNotificationService.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 101; From 314f49f0439bdc07b3fb8702e2d9d30f8ef614d0 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 4 Mar 2024 17:41:43 +0100 Subject: [PATCH 05/31] remove dead code --- deltachat-ios/DC/DcAccount.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/deltachat-ios/DC/DcAccount.swift b/deltachat-ios/DC/DcAccount.swift index b95d74d4d..4089736b4 100644 --- a/deltachat-ios/DC/DcAccount.swift +++ b/deltachat-ios/DC/DcAccount.swift @@ -90,11 +90,6 @@ public class DcAccounts { } public func openDatabase(writeable: Bool) { - var version = "" - if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { - version += " " + appVersion - } - if var sharedDbLocation = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier) { sharedDbLocation.appendPathComponent("accounts", isDirectory: true) accountsPointer = dc_accounts_new(sharedDbLocation.path, writeable ? 1 : 0) From c04fa76f9bbc9a13bcdcef21a734807477baff6e Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 3 Mar 2024 17:54:07 +0100 Subject: [PATCH 06/31] make notifications discardable, use more reasonable fallback --- .../NotificationService.swift | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 5abbbd1c9..b42bdd0ec 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -1,18 +1,42 @@ import UserNotifications +import DcCore class NotificationService: UNNotificationServiceExtension { - + let dcAccounts = DcAccounts.shared var contentHandler: ((UNNotificationContent) -> Void)? - var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler - bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - - if let bestAttemptContent = bestAttemptContent { - // Modify the notification content here... - bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" - + guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { return } + + dcAccounts.openDatabase(writeable: false) + let eventEmitter = dcAccounts.getEventEmitter() + if !dcAccounts.backgroundFetch(timeout: 25) { + contentHandler(bestAttemptContent) + return + } + + var messageCount = 0 + while true { + guard let event = eventEmitter.getNextEvent() else { break } + if event.id == DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE { break } + if event.id == DC_EVENT_INCOMING_MSG { + let dcContext = dcAccounts.get(id: event.accountId) + let chat = dcContext.getChat(chatId: event.data1Int) + if !UserDefaults.standard.bool(forKey: "notifications_disabled") && !chat.isMuted { + messageCount += 1 + let msg = dcContext.getMessage(id: event.data2Int) + let contact = dcContext.getContact(id: msg.fromContactId) + bestAttemptContent.title = chat.isGroup ? chat.name : msg.getSenderName(contact) + bestAttemptContent.body = msg.summary(chars: 80) ?? "" + } + } + } + + if messageCount == 0 { + let silentContent = UNMutableNotificationContent() + contentHandler(silentContent) + } else { contentHandler(bestAttemptContent) } } @@ -20,9 +44,8 @@ class NotificationService: UNNotificationServiceExtension { 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. - if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { - contentHandler(bestAttemptContent) - } - } + // For Delta Chat, it is just fine to do nothing - assume eg. bad network or mail servers not reachable, + // then a "You have new messages" is the best that can be done. + } } From 33636ddcfc86acaf0ae8d6953709d42c1714e3d0 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 4 Mar 2024 19:01:30 +0100 Subject: [PATCH 07/31] set body to 'N messages' as needed --- DcNotificationService/NotificationService.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index b42bdd0ec..ff8f5dc68 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -17,6 +17,7 @@ class NotificationService: UNNotificationServiceExtension { } var messageCount = 0 + var uniqueChats: [String: Bool] = [:] while true { guard let event = eventEmitter.getNextEvent() else { break } if event.id == DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE { break } @@ -25,6 +26,8 @@ class NotificationService: UNNotificationServiceExtension { let chat = dcContext.getChat(chatId: event.data1Int) if !UserDefaults.standard.bool(forKey: "notifications_disabled") && !chat.isMuted { messageCount += 1 + uniqueChats["\(dcContext.id)-\(chat.id)"] = true + let msg = dcContext.getMessage(id: event.data2Int) let contact = dcContext.getContact(id: msg.fromContactId) bestAttemptContent.title = chat.isGroup ? chat.name : msg.getSenderName(contact) @@ -36,7 +39,14 @@ class NotificationService: UNNotificationServiceExtension { if messageCount == 0 { let silentContent = UNMutableNotificationContent() contentHandler(silentContent) + } else if messageCount == 1 { + contentHandler(bestAttemptContent) } else { + if uniqueChats.count == 1 { + bestAttemptContent.body = String.localized(stringID: "n_messages", count: messageCount) + } else { + bestAttemptContent.body = String.localizedStringWithFormat(String.localized("n_messages_in_m_chats"), messageCount, uniqueChats.count) + } contentHandler(bestAttemptContent) } } From e546b3d8f23f1d4bdf0f217907b9d0b0894e9088 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 4 Mar 2024 23:01:26 +0100 Subject: [PATCH 08/31] skip String.localized() for now as not working in extension (but is working in DcShare, sth. is missing) --- DcNotificationService/NotificationService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index ff8f5dc68..b019b7928 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -43,9 +43,9 @@ class NotificationService: UNNotificationServiceExtension { contentHandler(bestAttemptContent) } else { if uniqueChats.count == 1 { - bestAttemptContent.body = String.localized(stringID: "n_messages", count: messageCount) + bestAttemptContent.body = "\(messageCount) messages" } else { - bestAttemptContent.body = String.localizedStringWithFormat(String.localized("n_messages_in_m_chats"), messageCount, uniqueChats.count) + bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" } contentHandler(bestAttemptContent) } From 2717d9b3c5c13a64b904b914798b0e684f9d93b5 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 6 Mar 2024 15:26:27 +0100 Subject: [PATCH 09/31] link DcCore, to fix 'umbrella header missing' - not sure why that worked before without --- deltachat-ios.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 786993839..eebe74569 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -199,6 +199,7 @@ B2172F3C29C125F2002C289E /* AdvancedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2172F3B29C125F2002C289E /* AdvancedViewController.swift */; }; B259D64329B771D5008FB706 /* BackupTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B259D64229B771D5008FB706 /* BackupTransferViewController.swift */; }; B26B3BC7236DC3DC008ED35A /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26B3BC6236DC3DC008ED35A /* SwitchCell.swift */; }; + B2B730BC2B98B1FB006C5EF9 /* DcCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 304219D1243F588500516852 /* DcCore.framework */; }; B2C42570265C325C00B95377 /* MultilineLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C4256F265C325C00B95377 /* MultilineLabelCell.swift */; }; B2D0E4952B93A4B200791949 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D0E4942B93A4B200791949 /* NotificationService.swift */; }; B2D0E4992B93A4B200791949 /* DcNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = B2D0E4922B93A4B200791949 /* DcNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -610,6 +611,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B2B730BC2B98B1FB006C5EF9 /* DcCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; From 8c08f439efd9e0ce1d85fb20230f3e70d7021138 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 6 Mar 2024 19:26:20 +0100 Subject: [PATCH 10/31] tune down unrelevant messages until we can discard them --- .../NotificationService.swift | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index b019b7928..4b642fee4 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -37,9 +37,23 @@ class NotificationService: UNNotificationServiceExtension { } if messageCount == 0 { - let silentContent = UNMutableNotificationContent() - contentHandler(silentContent) + let canSilenceContent = false + if canSilenceContent { + let silentContent = UNMutableNotificationContent() + contentHandler(silentContent) + } else { + bestAttemptContent.sound = nil + bestAttemptContent.body = "No more relevant messages" + if #available(iOS 15.0, *) { + bestAttemptContent.interruptionLevel = .passive + bestAttemptContent.relevanceScore = 0.0 + } + contentHandler(bestAttemptContent) + } } else if messageCount == 1 { + if #available(iOS 15.0, *) { + bestAttemptContent.relevanceScore = 1.0 + } contentHandler(bestAttemptContent) } else { if uniqueChats.count == 1 { @@ -47,6 +61,9 @@ class NotificationService: UNNotificationServiceExtension { } else { bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" } + if #available(iOS 15.0, *) { + bestAttemptContent.relevanceScore = 1.0 + } contentHandler(bestAttemptContent) } } From 9c3c64599a12f3cd6aef447e60cb61aebc3eb5cc Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 6 Mar 2024 23:21:11 +0100 Subject: [PATCH 11/31] unify summary for groups --- DcNotificationService/NotificationService.swift | 6 +++--- deltachat-ios/Helper/NotificationManager.swift | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 4b642fee4..517805fc9 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -29,9 +29,9 @@ class NotificationService: UNNotificationServiceExtension { uniqueChats["\(dcContext.id)-\(chat.id)"] = true let msg = dcContext.getMessage(id: event.data2Int) - let contact = dcContext.getContact(id: msg.fromContactId) - bestAttemptContent.title = chat.isGroup ? chat.name : msg.getSenderName(contact) - bestAttemptContent.body = msg.summary(chars: 80) ?? "" + let sender = msg.getSenderName(dcContext.getContact(id: msg.fromContactId)) + bestAttemptContent.title = chat.isGroup ? chat.name : sender + bestAttemptContent.body = (chat.isGroup ? "\(sender): " : "") + (msg.summary(chars: 80) ?? "") } } } diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 181921822..83f17b806 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -83,10 +83,10 @@ public class NotificationManager { let msg = self.dcContext.getMessage(id: messageId) let fromContact = self.dcContext.getContact(id: msg.fromContactId) let accountEmail = self.dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email + let sender = msg.getSenderName(fromContact) let content = UNMutableNotificationContent() - content.title = chat.isGroup ? chat.name : msg.getSenderName(fromContact) - content.body = msg.summary(chars: 80) ?? "" - content.subtitle = chat.isGroup ? msg.getSenderName(fromContact) : "" + content.title = chat.isGroup ? chat.name : sender + content.body = (chat.isGroup ? "\(sender): " : "") + (msg.summary(chars: 80) ?? "") content.userInfo = ui content.sound = .default From 8cb1f63d6606e0a69859bd14bf30e4d2fb6ac087 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 6 Mar 2024 23:44:41 +0100 Subject: [PATCH 12/31] update badge counter --- DcNotificationService/NotificationService.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 517805fc9..9290ea171 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -3,10 +3,8 @@ import DcCore class NotificationService: UNNotificationServiceExtension { let dcAccounts = DcAccounts.shared - var contentHandler: ((UNNotificationContent) -> Void)? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - self.contentHandler = contentHandler guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { return } dcAccounts.openDatabase(writeable: false) @@ -35,6 +33,7 @@ class NotificationService: UNNotificationServiceExtension { } } } + bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber if messageCount == 0 { let canSilenceContent = false From c774325d80be8905967f5d3c396581461133f850 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 6 Mar 2024 23:47:38 +0100 Subject: [PATCH 13/31] close databases when fetch is done --- DcNotificationService/NotificationService.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 9290ea171..ee3057239 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -34,6 +34,7 @@ class NotificationService: UNNotificationServiceExtension { } } bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber + dcAccounts.closeDatabase() if messageCount == 0 { let canSilenceContent = false From df488254bc49c4226c07c682d35c960919ca4041 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 7 Mar 2024 00:02:53 +0100 Subject: [PATCH 14/31] if a notification spans more than one chat, add all of them to the title (iOS will truncate and the summary be sth as 'N messages in M chats') --- DcNotificationService/NotificationService.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index ee3057239..423ca36ee 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -15,7 +15,7 @@ class NotificationService: UNNotificationServiceExtension { } var messageCount = 0 - var uniqueChats: [String: Bool] = [:] + var uniqueChats: [String: String] = [:] while true { guard let event = eventEmitter.getNextEvent() else { break } if event.id == DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE { break } @@ -23,13 +23,13 @@ class NotificationService: UNNotificationServiceExtension { let dcContext = dcAccounts.get(id: event.accountId) let chat = dcContext.getChat(chatId: event.data1Int) if !UserDefaults.standard.bool(forKey: "notifications_disabled") && !chat.isMuted { - messageCount += 1 - uniqueChats["\(dcContext.id)-\(chat.id)"] = true - let msg = dcContext.getMessage(id: event.data2Int) let sender = msg.getSenderName(dcContext.getContact(id: msg.fromContactId)) bestAttemptContent.title = chat.isGroup ? chat.name : sender bestAttemptContent.body = (chat.isGroup ? "\(sender): " : "") + (msg.summary(chars: 80) ?? "") + + uniqueChats["\(dcContext.id)-\(chat.id)"] = bestAttemptContent.title + messageCount += 1 } } } @@ -59,6 +59,7 @@ class NotificationService: UNNotificationServiceExtension { if uniqueChats.count == 1 { bestAttemptContent.body = "\(messageCount) messages" } else { + bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" } if #available(iOS 15.0, *) { From 8426beeb9851b6e0981135b958ccd38f42e30ada Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 7 Mar 2024 00:23:59 +0100 Subject: [PATCH 15/31] force UI updates in case app was suspended --- .../NotificationService.swift | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 423ca36ee..9d9a479a5 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -33,10 +33,9 @@ class NotificationService: UNNotificationServiceExtension { } } } - bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber - dcAccounts.closeDatabase() if messageCount == 0 { + dcAccounts.closeDatabase() let canSilenceContent = false if canSilenceContent { let silentContent = UNMutableNotificationContent() @@ -50,21 +49,21 @@ class NotificationService: UNNotificationServiceExtension { } contentHandler(bestAttemptContent) } - } else if messageCount == 1 { - if #available(iOS 15.0, *) { - bestAttemptContent.relevanceScore = 1.0 - } - contentHandler(bestAttemptContent) } else { - if uniqueChats.count == 1 { - bestAttemptContent.body = "\(messageCount) messages" - } else { - bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") - bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" + bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber + dcAccounts.closeDatabase() + if messageCount > 1 { + if uniqueChats.count == 1 { + bestAttemptContent.body = "\(messageCount) messages" + } else { + bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") + bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" + } } if #available(iOS 15.0, *) { bestAttemptContent.relevanceScore = 1.0 } + UserDefaults.shared?.set(true, forKey: UserDefaults.hasExtensionAttemptedToSend) // force UI updates in case app was suspended contentHandler(bestAttemptContent) } } From 84a988c7bec8b3333bc6b8ce9cbeee8de7d322db Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 7 Mar 2024 17:56:11 +0100 Subject: [PATCH 16/31] a trigger seems not to be needed, therefore also no need to remove pending notifications --- .../Helper/NotificationManager.swift | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 83f17b806..fb272533f 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -43,7 +43,6 @@ public class NotificationManager { public static func removeNotificationsForChat(dcContext: DcContext, chatId: Int) { DispatchQueue.global().async { - NotificationManager.removePendingNotificationsFor(dcContext: dcContext, chatId: chatId) NotificationManager.removeDeliveredNotificationsFor(dcContext: dcContext, chatId: chatId) NotificationManager.updateApplicationIconBadge() } @@ -106,13 +105,12 @@ public class NotificationManager { logger.error("Failed to copy file \(url) for notification preview generation: \(error)") } } - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false) if #available(iOS 12.0, *) { content.threadIdentifier = "\(accountEmail)\(chatId)" } let request = UNNotificationRequest(identifier: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId).\(msg.messageId)", content: content, - trigger: trigger) + trigger: nil) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) logger.info("notifications: added \(content.title) \(content.body) \(content.userInfo)") } @@ -150,21 +148,6 @@ public class NotificationManager { nc.removeDeliveredNotifications(withIdentifiers: identifiers) } } - - private static func removePendingNotificationsFor(dcContext: DcContext, chatId: Int) { - var identifiers = [String]() - let nc = UNUserNotificationCenter.current() - nc.getPendingNotificationRequests { notificationRequests in - let accountEmail = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email - for request in notificationRequests { - if !request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty { - identifiers.append(request.identifier) - } - } - nc.removePendingNotificationRequests(withIdentifiers: identifiers) - } - } - deinit { NotificationCenter.default.removeObserver(self) From c004172e5973fe73ccd480ba8dbd40fed46bb003 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 7 Mar 2024 17:58:02 +0100 Subject: [PATCH 17/31] because of accounts not using PUSH, we do not have a notification for each message; group everything in the same thread to avoid confusion when 'Alice Chat' is also present in thread 'Alice Chat, Bob Chat' --- DcNotificationService/NotificationService.swift | 6 ++++++ deltachat-ios/Helper/NotificationManager.swift | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 9d9a479a5..d282f58cb 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -7,6 +7,12 @@ class NotificationService: UNNotificationServiceExtension { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { return } + // as we're mixing in notifications from accounts without PUSH and we cannot add multiple notifications, + // it is best to move everything to the same thread + if #available(iOS 12.0, *) { + bestAttemptContent.threadIdentifier = "all" + } + dcAccounts.openDatabase(writeable: false) let eventEmitter = dcAccounts.getEventEmitter() if !dcAccounts.backgroundFetch(timeout: 25) { diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index fb272533f..6b05ec550 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -106,7 +106,7 @@ public class NotificationManager { } } if #available(iOS 12.0, *) { - content.threadIdentifier = "\(accountEmail)\(chatId)" + content.threadIdentifier = "all" } let request = UNNotificationRequest(identifier: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId).\(msg.messageId)", content: content, From 020e98f4338c7fcc98ab6083b3e88a0bebee0f23 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 7 Mar 2024 18:52:31 +0100 Subject: [PATCH 18/31] threadIdentifier not needed if the same everywhere; this also groups in notifications not hitting the extension or handled by serviceExtensionTimeWillExpire --- DcNotificationService/NotificationService.swift | 5 +---- deltachat-ios/Helper/NotificationManager.swift | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index d282f58cb..aea831bb4 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -8,10 +8,7 @@ class NotificationService: UNNotificationServiceExtension { guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { return } // as we're mixing in notifications from accounts without PUSH and we cannot add multiple notifications, - // it is best to move everything to the same thread - if #available(iOS 12.0, *) { - bestAttemptContent.threadIdentifier = "all" - } + // it is best to move everything to the same thread - and set just no threadIdentifier dcAccounts.openDatabase(writeable: false) let eventEmitter = dcAccounts.getEventEmitter() diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 6b05ec550..2c386ffab 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -105,9 +105,6 @@ public class NotificationManager { logger.error("Failed to copy file \(url) for notification preview generation: \(error)") } } - if #available(iOS 12.0, *) { - content.threadIdentifier = "all" - } let request = UNNotificationRequest(identifier: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId).\(msg.messageId)", content: content, trigger: nil) From bc171eb0c90c2ce0cab472051ebfaf8dd9a1f005 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 9 Mar 2024 18:18:37 +0100 Subject: [PATCH 19/31] move 'future' code to a comment --- .../NotificationService.swift | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index aea831bb4..2b22784a5 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -39,19 +39,15 @@ class NotificationService: UNNotificationServiceExtension { if messageCount == 0 { dcAccounts.closeDatabase() - let canSilenceContent = false - if canSilenceContent { - let silentContent = UNMutableNotificationContent() - contentHandler(silentContent) - } else { - bestAttemptContent.sound = nil - bestAttemptContent.body = "No more relevant messages" - if #available(iOS 15.0, *) { - bestAttemptContent.interruptionLevel = .passive - bestAttemptContent.relevanceScore = 0.0 - } - contentHandler(bestAttemptContent) + // with `com.apple.developer.usernotifications.filtering` entitlement, + // one can use `contentHandler(UNMutableNotificationContent())` to not display a notifcation + bestAttemptContent.sound = nil + bestAttemptContent.body = "No more relevant messages" + if #available(iOS 15.0, *) { + bestAttemptContent.interruptionLevel = .passive + bestAttemptContent.relevanceScore = 0.0 } + contentHandler(bestAttemptContent) } else { bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber dcAccounts.closeDatabase() From 94129979bd12dd3b3f4ca37d746e6c12cb12f4cb Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 9 Mar 2024 18:19:27 +0100 Subject: [PATCH 20/31] set all of account_id/chat_id/message_id for all notifications --- DcNotificationService/NotificationService.swift | 6 ++++++ deltachat-ios/Helper/NotificationManager.swift | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 2b22784a5..16a3f41e7 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -30,6 +30,9 @@ class NotificationService: UNNotificationServiceExtension { let sender = msg.getSenderName(dcContext.getContact(id: msg.fromContactId)) bestAttemptContent.title = chat.isGroup ? chat.name : sender bestAttemptContent.body = (chat.isGroup ? "\(sender): " : "") + (msg.summary(chars: 80) ?? "") + bestAttemptContent.userInfo["account_id"] = dcContext.id + bestAttemptContent.userInfo["chat_id"] = chat.id + bestAttemptContent.userInfo["message_id"] = msg.id uniqueChats["\(dcContext.id)-\(chat.id)"] = bestAttemptContent.title messageCount += 1 @@ -52,9 +55,12 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber dcAccounts.closeDatabase() if messageCount > 1 { + bestAttemptContent.userInfo["message_id"] = 0 if uniqueChats.count == 1 { bestAttemptContent.body = "\(messageCount) messages" } else { + bestAttemptContent.userInfo["account_id"] = 0 + bestAttemptContent.userInfo["chat_id"] = 0 bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" } diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 2c386ffab..a68430c4f 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -86,7 +86,9 @@ public class NotificationManager { let content = UNMutableNotificationContent() content.title = chat.isGroup ? chat.name : sender content.body = (chat.isGroup ? "\(sender): " : "") + (msg.summary(chars: 80) ?? "") - content.userInfo = ui + content.userInfo["account_id"] = self.dcContext.id + content.userInfo["chat_id"] = chat.id + content.userInfo["message_id"] = msg.id content.sound = .default if msg.type == DC_MSG_IMAGE || msg.type == DC_MSG_GIF, From c8583036b63267e89b9687fe340a32b0810322c3 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 9 Mar 2024 19:04:11 +0100 Subject: [PATCH 21/31] switch to correct account on tapping a notification; do not just dismiss all notifications on account switch, account switch is lightweight meanwhile --- deltachat-ios/AppDelegate.swift | 22 +++++++++++-------- .../Helper/NotificationManager.swift | 2 -- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/deltachat-ios/AppDelegate.swift b/deltachat-ios/AppDelegate.swift index 0674293c3..c360d7717 100644 --- a/deltachat-ios/AppDelegate.swift +++ b/deltachat-ios/AppDelegate.swift @@ -516,15 +516,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // this method will be called if the user tapped on a notification func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - if !response.notification.request.identifier.containsExact(subSequence: Constants.notificationIdentifier).isEmpty { - logger.info("Notifications: notification tapped") - let userInfo = response.notification.request.content.userInfo - if let chatId = userInfo["chat_id"] as? Int, - let msgId = userInfo["message_id"] as? Int { - if !appCoordinator.isShowingChat(chatId: chatId) { - appCoordinator.showChat(chatId: chatId, msgId: msgId, animated: false, clearViewControllerStack: true) - } - } + logger.info("Notifications: notification tapped") + let userInfo = response.notification.request.content.userInfo + if let accountId = userInfo["account_id"] as? Int, + let chatId = userInfo["chat_id"] as? Int, + let msgId = userInfo["message_id"] as? Int { + if accountId != dcAccounts.getSelected().id { + UserDefaults.standard.setValue(dcAccounts.getSelected().id, forKey: Constants.Keys.lastSelectedAccountKey) + _ = dcAccounts.select(id: accountId) + reloadDcContext() + } + if !appCoordinator.isShowingChat(chatId: chatId) { + appCoordinator.showChat(chatId: chatId, msgId: msgId, animated: false, clearViewControllerStack: true) + } } completionHandler() diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index a68430c4f..4a29103a3 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -19,7 +19,6 @@ public class NotificationManager { } public func reloadDcContext() { - NotificationManager.removeAllNotifications() dcContext = dcAccounts.getSelected() } @@ -38,7 +37,6 @@ public class NotificationManager { public static func removeAllNotifications() { let nc = UNUserNotificationCenter.current() nc.removeAllDeliveredNotifications() - nc.removeAllPendingNotificationRequests() } public static func removeNotificationsForChat(dcContext: DcContext, chatId: Int) { From 99e75ea7e33fa463dc214e48bcd73499d7c98fd0 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 9 Mar 2024 23:53:31 +0100 Subject: [PATCH 22/31] respect account_id when removing notifications for a chat --- .../Helper/NotificationManager.swift | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 4a29103a3..80bec77d1 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -38,10 +38,24 @@ public class NotificationManager { let nc = UNUserNotificationCenter.current() nc.removeAllDeliveredNotifications() } - + + // set chatId to 0 to remove unspecific notifications of any account + // (unspecific notification are sth. as "N message in M chats") public static func removeNotificationsForChat(dcContext: DcContext, chatId: Int) { DispatchQueue.global().async { - NotificationManager.removeDeliveredNotificationsFor(dcContext: dcContext, chatId: chatId) + let nc = UNUserNotificationCenter.current() + nc.getDeliveredNotifications { notifications in + var toRemove = [String]() + for notification in notifications { + let notificationAccountId = notification.request.content.userInfo["account_id"] as? Int ?? 0 + let notificationChatId = notification.request.content.userInfo["chat_id"] as? Int ?? 0 + if notificationChatId == chatId && (notificationAccountId == dcContext.id || chatId == 0) { + toRemove.append(notification.request.identifier) + } + } + nc.removeDeliveredNotifications(withIdentifiers: toRemove) + } + NotificationManager.updateApplicationIconBadge() } } @@ -131,20 +145,6 @@ public class NotificationManager { } } } - - private static func removeDeliveredNotificationsFor(dcContext: DcContext, chatId: Int) { - var identifiers = [String]() - let nc = UNUserNotificationCenter.current() - nc.getDeliveredNotifications { notifications in - let accountEmail = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email - for notification in notifications { - if !notification.request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty { - identifiers.append(notification.request.identifier) - } - } - nc.removeDeliveredNotifications(withIdentifiers: identifiers) - } - } deinit { NotificationCenter.default.removeObserver(self) From 91252e3c9118e6f49c34f47a42fde58cb3c0f31d Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 10 Mar 2024 00:51:56 +0100 Subject: [PATCH 23/31] handle unspecific notifications --- .../NotificationService.swift | 5 ++-- deltachat-ios/AppDelegate.swift | 23 ++++++++++++------- .../Helper/NotificationManager.swift | 5 ++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 16a3f41e7..34b023c45 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -55,12 +55,11 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber dcAccounts.closeDatabase() if messageCount > 1 { - bestAttemptContent.userInfo["message_id"] = 0 + bestAttemptContent.userInfo["message_id"] = nil if uniqueChats.count == 1 { bestAttemptContent.body = "\(messageCount) messages" } else { - bestAttemptContent.userInfo["account_id"] = 0 - bestAttemptContent.userInfo["chat_id"] = 0 + bestAttemptContent.userInfo["open_as_overview"] = true // leaving chat_id as is removes the notification when one of the chats is opened (does not matter which) bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" } diff --git a/deltachat-ios/AppDelegate.swift b/deltachat-ios/AppDelegate.swift index c360d7717..3a29ccb59 100644 --- a/deltachat-ios/AppDelegate.swift +++ b/deltachat-ios/AppDelegate.swift @@ -517,17 +517,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // this method will be called if the user tapped on a notification func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { logger.info("Notifications: notification tapped") + let userInfo = response.notification.request.content.userInfo - if let accountId = userInfo["account_id"] as? Int, - let chatId = userInfo["chat_id"] as? Int, - let msgId = userInfo["message_id"] as? Int { - if accountId != dcAccounts.getSelected().id { - UserDefaults.standard.setValue(dcAccounts.getSelected().id, forKey: Constants.Keys.lastSelectedAccountKey) - _ = dcAccounts.select(id: accountId) + if let accountId = userInfo["account_id"] as? Int { + let prevAccountId = dcAccounts.getSelected().id + if accountId != prevAccountId { + if !dcAccounts.select(id: accountId) { + completionHandler() + return + } + UserDefaults.standard.setValue(prevAccountId, forKey: Constants.Keys.lastSelectedAccountKey) reloadDcContext() } - if !appCoordinator.isShowingChat(chatId: chatId) { - appCoordinator.showChat(chatId: chatId, msgId: msgId, animated: false, clearViewControllerStack: true) + + if userInfo["open_as_overview"] as? Bool ?? false { + appCoordinator.popTabsToRootViewControllers() + appCoordinator.showTab(index: appCoordinator.chatsTab) + } else if let chatId = userInfo["chat_id"] as? Int, !appCoordinator.isShowingChat(chatId: chatId) { + appCoordinator.showChat(chatId: chatId, msgId: userInfo["message_id"] as? Int, animated: false, clearViewControllerStack: true) } } diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 80bec77d1..cc730847b 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -39,8 +39,6 @@ public class NotificationManager { nc.removeAllDeliveredNotifications() } - // set chatId to 0 to remove unspecific notifications of any account - // (unspecific notification are sth. as "N message in M chats") public static func removeNotificationsForChat(dcContext: DcContext, chatId: Int) { DispatchQueue.global().async { let nc = UNUserNotificationCenter.current() @@ -49,7 +47,8 @@ public class NotificationManager { for notification in notifications { let notificationAccountId = notification.request.content.userInfo["account_id"] as? Int ?? 0 let notificationChatId = notification.request.content.userInfo["chat_id"] as? Int ?? 0 - if notificationChatId == chatId && (notificationAccountId == dcContext.id || chatId == 0) { + // unspecific notifications are always removed + if notificationChatId == 0 || (notificationChatId == chatId && notificationAccountId == dcContext.id) { toRemove.append(notification.request.identifier) } } From 45526b8aad2d6c712fb6bec5d07f15818a953f5a Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 10 Mar 2024 01:12:10 +0100 Subject: [PATCH 24/31] =?UTF-8?q?do=20not=20add=20images=20to=20notificati?= =?UTF-8?q?ons;=20this=20is=20easily=20compromising,=20just=20saying=20'Im?= =?UTF-8?q?age'=20as=20on=20android=20is=20good=20enough.=20also,=20images?= =?UTF-8?q?=20may=20slow=20down=20things=20siginificantly=20as=20copied=20?= =?UTF-8?q?uncompressed=20in=20the=20existing=20implementation=20(nb:=20wo?= =?UTF-8?q?uld=20be=20cool=20to=20show=20an=20emoji=20as=20'=F0=9F=93=B7'?= =?UTF-8?q?=20instead=20of=20the=20string=20'Image'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DcCore/DcCore/Helper/Constants.swift | 1 - deltachat-ios/Helper/Constants.swift | 2 -- .../Helper/NotificationManager.swift | 21 +------------------ 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/DcCore/DcCore/Helper/Constants.swift b/DcCore/DcCore/Helper/Constants.swift index d44d2d80b..573903564 100644 --- a/DcCore/DcCore/Helper/Constants.swift +++ b/DcCore/DcCore/Helper/Constants.swift @@ -7,5 +7,4 @@ public struct Constants { static let deltachatImapEmailKey = "__DELTACHAT_IMAP_EMAIL_KEY__" static let deltachatImapPasswordKey = "__DELTACHAT_IMAP_PASSWORD_KEY__" } - public static let notificationIdentifier = "deltachat-ios-local-notifications" } diff --git a/deltachat-ios/Helper/Constants.swift b/deltachat-ios/Helper/Constants.swift index bc5327692..769189773 100644 --- a/deltachat-ios/Helper/Constants.swift +++ b/deltachat-ios/Helper/Constants.swift @@ -15,8 +15,6 @@ struct Constants { } static let backgroundImageName = "BACKGROUND_IMAGE" - static let notificationIdentifier = "deltachat-ios-local-notifications" - } struct Time { diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index cc730847b..e9161cf89 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -92,7 +92,6 @@ public class NotificationManager { if !chat.isMuted { let msg = self.dcContext.getMessage(id: messageId) let fromContact = self.dcContext.getContact(id: msg.fromContactId) - let accountEmail = self.dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email let sender = msg.getSenderName(fromContact) let content = UNMutableNotificationContent() content.title = chat.isGroup ? chat.name : sender @@ -102,25 +101,7 @@ public class NotificationManager { content.userInfo["message_id"] = msg.id content.sound = .default - if msg.type == DC_MSG_IMAGE || msg.type == DC_MSG_GIF, - let url = msg.fileURL { - do { - // make a copy of the file first since UNNotificationAttachment will move attached files into the attachment data store - // so that they can be accessed by all of the appropriate processes - let tempUrl = url.deletingLastPathComponent() - .appendingPathComponent("notification_tmp") - .appendingPathExtension(url.pathExtension) - try FileManager.default.copyItem(at: url, to: tempUrl) - if let attachment = try? UNNotificationAttachment(identifier: Constants.notificationIdentifier, url: tempUrl, options: nil) { - content.attachments = [attachment] - } - } catch let error { - logger.error("Failed to copy file \(url) for notification preview generation: \(error)") - } - } - let request = UNNotificationRequest(identifier: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId).\(msg.messageId)", - content: content, - trigger: nil) + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) logger.info("notifications: added \(content.title) \(content.body) \(content.userInfo)") } From c271aef11b1b1d5c70209ee4b8b3404889883c50 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 11 Mar 2024 14:10:37 +0100 Subject: [PATCH 25/31] create explicit silenceNotification() method --- .../NotificationService.swift | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 34b023c45..e429098dd 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -42,15 +42,7 @@ class NotificationService: UNNotificationServiceExtension { if messageCount == 0 { dcAccounts.closeDatabase() - // with `com.apple.developer.usernotifications.filtering` entitlement, - // one can use `contentHandler(UNMutableNotificationContent())` to not display a notifcation - bestAttemptContent.sound = nil - bestAttemptContent.body = "No more relevant messages" - if #available(iOS 15.0, *) { - bestAttemptContent.interruptionLevel = .passive - bestAttemptContent.relevanceScore = 0.0 - } - contentHandler(bestAttemptContent) + contentHandler(silenceNotification(bestAttemptContent)) } else { bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber dcAccounts.closeDatabase() @@ -79,4 +71,16 @@ class NotificationService: UNNotificationServiceExtension { // For Delta Chat, it is just fine to do nothing - assume eg. bad network or mail servers not reachable, // then a "You have new messages" is the best that can be done. } + + private func silenceNotification(_ bestAttemptContent: UNMutableNotificationContent) -> UNMutableNotificationContent { + // with `com.apple.developer.usernotifications.filtering` entitlement, + // one can use `contentHandler(UNMutableNotificationContent())` to not display a notifcation + bestAttemptContent.sound = nil + bestAttemptContent.body = "No more relevant messages" + if #available(iOS 15.0, *) { + bestAttemptContent.interruptionLevel = .passive + bestAttemptContent.relevanceScore = 0.0 + } + return bestAttemptContent + } } From 8ee77e24143196bc36f5ef2413d8cd6b6570fec3 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 12 Mar 2024 18:53:57 +0100 Subject: [PATCH 26/31] Localize notification-strings --- DcNotificationService/NotificationService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index e429098dd..139feeba0 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -49,11 +49,11 @@ class NotificationService: UNNotificationServiceExtension { if messageCount > 1 { bestAttemptContent.userInfo["message_id"] = nil if uniqueChats.count == 1 { - bestAttemptContent.body = "\(messageCount) messages" + bestAttemptContent.body = String.localized(stringID: "n_messages", parameter: messageCount) } else { bestAttemptContent.userInfo["open_as_overview"] = true // leaving chat_id as is removes the notification when one of the chats is opened (does not matter which) bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") - bestAttemptContent.body = "\(messageCount) messages in \(uniqueChats.count) chats" + bestAttemptContent.body = String.localized(stringID: "n_messages_in_m_chats", parameter: messageCount, uniqueChats.count) } } if #available(iOS 15.0, *) { From c4a1316d380a2480aadbccb91b1732c6e708df5b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 12 Mar 2024 21:15:36 +0100 Subject: [PATCH 27/31] Enable localization for NSE --- deltachat-ios.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index eebe74569..6462a7c6a 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -208,6 +208,8 @@ D80F62792B59D1CC00877059 /* DefaultReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80F62782B59D1CC00877059 /* DefaultReactions.swift */; }; D84AED242B55E8EB00D753F6 /* ReactionsOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84AED232B55E8EB00D753F6 /* ReactionsOverviewViewController.swift */; }; D84AED272B566C0700D753F6 /* ReactionsOverviewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84AED262B566C0700D753F6 /* ReactionsOverviewTableViewCell.swift */; }; + D85664D42BA0EF580062517B /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; }; + D85664D52BA0EF580062517B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; }; D8975E412B7285ED00E5EB9F /* ContextMenuProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8975E402B7285ED00E5EB9F /* ContextMenuProvider.swift */; }; D8FB03FD2B4EF20700A355F8 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FB03FC2B4EF20700A355F8 /* ReactionsView.swift */; }; D8FB04002B4F0CF100A355F8 /* EmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FB03FF2B4F0CF000A355F8 /* EmojiView.swift */; }; @@ -1311,6 +1313,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D85664D42BA0EF580062517B /* Localizable.stringsdict in Resources */, + D85664D52BA0EF580062517B /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 0e6b345dfeffd28519d3fb1307623ed79a148c31 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 12 Mar 2024 12:54:15 +0100 Subject: [PATCH 28/31] let NSE-fetch and MainApp run mutually exclusive --- .../Extensions/UserDefaults+Extensions.swift | 19 +++++++++++++++++++ .../NotificationService.swift | 10 ++++++++++ deltachat-ios/AppDelegate.swift | 19 +++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/DcCore/DcCore/Extensions/UserDefaults+Extensions.swift b/DcCore/DcCore/Extensions/UserDefaults+Extensions.swift index fcbe90f4d..2febe5e7f 100644 --- a/DcCore/DcCore/Extensions/UserDefaults+Extensions.swift +++ b/DcCore/DcCore/Extensions/UserDefaults+Extensions.swift @@ -3,7 +3,26 @@ public extension UserDefaults { static var hasExtensionAttemptedToSend = "hasExtensionAttemptedToSend" static var hasSavedKeyToKeychain = "hasSavedKeyToKeychain" static var upgradedKeychainEntry = "upgradedKeychainEntry_" + static var mainAppRunningKey = "mainAppRunning" + static var nseFetchingKey = "nseFetching" + static var shared: UserDefaults? { return UserDefaults(suiteName: "group.chat.delta.ios") } + + static var mainAppRunning: Bool { + return shared?.bool(forKey: mainAppRunningKey) ?? false + } + + static func setMainAppRunning(_ value: Bool = true) { + shared?.setValue(value, forKey: mainAppRunningKey) + } + + static var nseFetching: Bool { + return shared?.bool(forKey: nseFetchingKey) ?? false + } + + static func setNseFetching(_ value: Bool = true) { + shared?.setValue(value, forKey: nseFetchingKey) + } } diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 139feeba0..7e3fa094f 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -7,15 +7,24 @@ class NotificationService: UNNotificationServiceExtension { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { return } + if UserDefaults.mainAppRunning { + contentHandler(silenceNotification(bestAttemptContent)) + return + } + UserDefaults.setNseFetching() + // as we're mixing in notifications from accounts without PUSH and we cannot add multiple notifications, // it is best to move everything to the same thread - and set just no threadIdentifier dcAccounts.openDatabase(writeable: false) let eventEmitter = dcAccounts.getEventEmitter() + if !dcAccounts.backgroundFetch(timeout: 25) { + UserDefaults.setNseFetching(false) contentHandler(bestAttemptContent) return } + UserDefaults.setNseFetching(false) var messageCount = 0 var uniqueChats: [String: String] = [:] @@ -70,6 +79,7 @@ class NotificationService: UNNotificationServiceExtension { // For Delta Chat, it is just fine to do nothing - assume eg. bad network or mail servers not reachable, // then a "You have new messages" is the best that can be done. + UserDefaults.setNseFetching(false) } private func silenceNotification(_ bestAttemptContent: UNMutableNotificationContent) -> UNMutableNotificationContent { diff --git a/deltachat-ios/AppDelegate.swift b/deltachat-ios/AppDelegate.swift index 3a29ccb59..fc53e57ad 100644 --- a/deltachat-ios/AppDelegate.swift +++ b/deltachat-ios/AppDelegate.swift @@ -55,6 +55,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD logger.info("➡️ didFinishLaunchingWithOptions") + // The NSE ("Notification Service Extension") must not run at the same time as the app. + // The other way round, the NSE is not started with the app running. + UserDefaults.setMainAppRunning() + var pollSeconds = 0 + while UserDefaults.nseFetching && pollSeconds < 30 { + logger.info("➡️ wait for NSE to terminate") + usleep(1_000_000) + pollSeconds += 1 + } + UserDefaults.setNseFetching(false) // NSE terminated unexpectedly, do not always wait 30 seconds + let webPCoder = SDImageWebPCoder.shared SDImageCodersManager.shared.addCoder(webPCoder) let svgCoder = SDImageSVGKCoder.shared @@ -231,6 +242,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func applicationWillEnterForeground(_: UIApplication) { logger.info("➡️ applicationWillEnterForeground") applicationInForeground = true + UserDefaults.setMainAppRunning() dcAccounts.startIo() DispatchQueue.global().async { [weak self] in @@ -271,6 +283,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // applicationDidBecomeActive() is called on initial app start _and_ after applicationWillEnterForeground() func applicationDidBecomeActive(_: UIApplication) { logger.info("➡️ applicationDidBecomeActive") + UserDefaults.setMainAppRunning() applicationInForeground = true } @@ -286,6 +299,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func applicationWillTerminate(_: UIApplication) { logger.info("⬅️ applicationWillTerminate") + UserDefaults.setMainAppRunning(false) uninstallEventHandler() dcAccounts.closeDatabase() if let reachability = reachability { @@ -326,6 +340,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } else if app.backgroundTimeRemaining < 10 { logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), stopping") self.dcAccounts.stopIo() + UserDefaults.setMainAppRunning(false) // to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended; // we increase the probabilty that this happens by waiting a moment before calling unregisterBackgroundTask() @@ -424,8 +439,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD private func performFetch(completionHandler: ((UIBackgroundFetchResult) -> Void)? = nil) { // `didReceiveRemoteNotification` as well as `performFetchWithCompletionHandler` might be called if we're in foreground, // in this case, there is no need to wait for things or do sth. - if appIsInForeground() { - logger.info("➡️ app already in foreground") + if appIsInForeground() || UserDefaults.nseFetching { + logger.info("➡️ app already in foreground or NSE running") pushToDebugArray("OK1") completionHandler?(.newData) return From 2356834f6c1a7badf8ab44fa19e0e7465d175cdb Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 20 Mar 2024 23:04:34 +0100 Subject: [PATCH 29/31] fix references to Localizable.strings|stringsdict after rebasing (re-add at 'Build Phases / Copy Bundle Resources') --- deltachat-ios.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 6462a7c6a..f41e5bf1e 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -199,6 +199,8 @@ B2172F3C29C125F2002C289E /* AdvancedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2172F3B29C125F2002C289E /* AdvancedViewController.swift */; }; B259D64329B771D5008FB706 /* BackupTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B259D64229B771D5008FB706 /* BackupTransferViewController.swift */; }; B26B3BC7236DC3DC008ED35A /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26B3BC6236DC3DC008ED35A /* SwitchCell.swift */; }; + B29BE2FB2BAB93EC002DBF1F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; }; + B29BE2FC2BAB93EC002DBF1F /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; }; B2B730BC2B98B1FB006C5EF9 /* DcCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 304219D1243F588500516852 /* DcCore.framework */; }; B2C42570265C325C00B95377 /* MultilineLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C4256F265C325C00B95377 /* MultilineLabelCell.swift */; }; B2D0E4952B93A4B200791949 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D0E4942B93A4B200791949 /* NotificationService.swift */; }; @@ -208,8 +210,6 @@ D80F62792B59D1CC00877059 /* DefaultReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80F62782B59D1CC00877059 /* DefaultReactions.swift */; }; D84AED242B55E8EB00D753F6 /* ReactionsOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84AED232B55E8EB00D753F6 /* ReactionsOverviewViewController.swift */; }; D84AED272B566C0700D753F6 /* ReactionsOverviewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84AED262B566C0700D753F6 /* ReactionsOverviewTableViewCell.swift */; }; - D85664D42BA0EF580062517B /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; }; - D85664D52BA0EF580062517B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; }; D8975E412B7285ED00E5EB9F /* ContextMenuProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8975E402B7285ED00E5EB9F /* ContextMenuProvider.swift */; }; D8FB03FD2B4EF20700A355F8 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FB03FC2B4EF20700A355F8 /* ReactionsView.swift */; }; D8FB04002B4F0CF100A355F8 /* EmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FB03FF2B4F0CF000A355F8 /* EmojiView.swift */; }; @@ -1313,8 +1313,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D85664D42BA0EF580062517B /* Localizable.stringsdict in Resources */, - D85664D52BA0EF580062517B /* Localizable.strings in Resources */, + B29BE2FB2BAB93EC002DBF1F /* Localizable.strings in Resources */, + B29BE2FC2BAB93EC002DBF1F /* Localizable.stringsdict in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From bea2abbbb58fbe14e979f700f8ca0e2420c338ec Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 20 Mar 2024 23:30:02 +0100 Subject: [PATCH 30/31] remove irrelevant notification on app-start or background-fetch use a simple 'Tap to open' as the notification content - usually, there was some message before/after or there are muted ones, so that openes less questions in most cases and makes at least a more sense than adding a Notification saying "No relevant things" :) --- DcNotificationService/NotificationService.swift | 4 +++- deltachat-ios/AppDelegate.swift | 6 ++++++ deltachat-ios/Helper/NotificationManager.swift | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 7e3fa094f..8541d71c7 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -85,8 +85,10 @@ class NotificationService: UNNotificationServiceExtension { private func silenceNotification(_ bestAttemptContent: UNMutableNotificationContent) -> UNMutableNotificationContent { // with `com.apple.developer.usernotifications.filtering` entitlement, // one can use `contentHandler(UNMutableNotificationContent())` to not display a notifcation + // and remove all "irrelevant" handling bestAttemptContent.sound = nil - bestAttemptContent.body = "No more relevant messages" + bestAttemptContent.body = String.localized("videochat_tap_to_open") + bestAttemptContent.userInfo["irrelevant"] = true if #available(iOS 15.0, *) { bestAttemptContent.interruptionLevel = .passive bestAttemptContent.relevanceScore = 0.0 diff --git a/deltachat-ios/AppDelegate.swift b/deltachat-ios/AppDelegate.swift index fc53e57ad..f51ae2d56 100644 --- a/deltachat-ios/AppDelegate.swift +++ b/deltachat-ios/AppDelegate.swift @@ -173,6 +173,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD increaseDebugCounter("notify-remote-launch") pushToDebugArray("📡'") performFetch() + } else { + NotificationManager.removeIrrelevantNotifications() } if dcAccounts.getSelected().isConfigured() && !UserDefaults.standard.bool(forKey: "notifications_disabled") { @@ -254,6 +256,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } AppDelegate.emitMsgsChangedIfShareExtensionWasUsed() + + NotificationManager.removeIrrelevantNotifications() } } @@ -437,6 +441,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } private func performFetch(completionHandler: ((UIBackgroundFetchResult) -> Void)? = nil) { + NotificationManager.removeIrrelevantNotifications() + // `didReceiveRemoteNotification` as well as `performFetchWithCompletionHandler` might be called if we're in foreground, // in this case, there is no need to wait for things or do sth. if appIsInForeground() || UserDefaults.nseFetching { diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index e9161cf89..67e4da927 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -59,6 +59,20 @@ public class NotificationManager { } } + public static func removeIrrelevantNotifications() { + let nc = UNUserNotificationCenter.current() + nc.getDeliveredNotifications { notifications in + var toRemove = [String]() + for notification in notifications { + let irrelevant = notification.request.content.userInfo["irrelevant"] as? Bool ?? false + if irrelevant { + toRemove.append(notification.request.identifier) + } + } + nc.removeDeliveredNotifications(withIdentifiers: toRemove) + } + } + private func initObservers() { anyIncomingMsgObserver = NotificationCenter.default.addObserver( forName: eventIncomingMsgAnyAccount, From fbdb862b5ddf632c364b24e02d492552e8093985 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 22 Mar 2024 11:44:29 +0100 Subject: [PATCH 31/31] slight code improvements --- DcNotificationService/NotificationService.swift | 9 +++++++-- deltachat-ios/AppDelegate.swift | 2 -- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 8541d71c7..a037b8d59 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -37,8 +37,13 @@ class NotificationService: UNNotificationServiceExtension { if !UserDefaults.standard.bool(forKey: "notifications_disabled") && !chat.isMuted { let msg = dcContext.getMessage(id: event.data2Int) let sender = msg.getSenderName(dcContext.getContact(id: msg.fromContactId)) - bestAttemptContent.title = chat.isGroup ? chat.name : sender - bestAttemptContent.body = (chat.isGroup ? "\(sender): " : "") + (msg.summary(chars: 80) ?? "") + if chat.isGroup { + bestAttemptContent.title = chat.name + bestAttemptContent.body = "\(sender): " + (msg.summary(chars: 80) ?? "") + } else { + bestAttemptContent.title = sender + bestAttemptContent.body = msg.summary(chars: 80) ?? "" + } bestAttemptContent.userInfo["account_id"] = dcContext.id bestAttemptContent.userInfo["chat_id"] = chat.id bestAttemptContent.userInfo["message_id"] = msg.id diff --git a/deltachat-ios/AppDelegate.swift b/deltachat-ios/AppDelegate.swift index f51ae2d56..316ba3e55 100644 --- a/deltachat-ios/AppDelegate.swift +++ b/deltachat-ios/AppDelegate.swift @@ -537,8 +537,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // this method will be called if the user tapped on a notification func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - logger.info("Notifications: notification tapped") - let userInfo = response.notification.request.content.userInfo if let accountId = userInfo["account_id"] as? Int { let prevAccountId = dcAccounts.getSelected().id