From 78f2c8a5c81de4079938ff25582d9c5fbcced77b Mon Sep 17 00:00:00 2001 From: Fin Date: Fri, 26 Jul 2024 16:32:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E9=80=81=E6=94=AF=E6=8C=81=E6=8C=81?= =?UTF-8?q?=E7=BB=AD=E5=93=8D=E9=93=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bark.xcodeproj/project.pbxproj | 74 ++++++++++ Bark/AppDelegate.swift | 30 ++-- Common/SharedDefines.swift | 12 ++ Controller/MessageListViewModel.swift | 13 +- .../NotificationService.swift | 25 +++- .../Processor/ArchiveProcessor.swift | 2 +- .../Processor/AutoCopyProcessor.swift | 2 +- .../Processor/BadgeProcessor.swift | 2 +- .../Processor/CallProcessor.swift | 130 ++++++++++++++++++ .../Processor/CiphertextProcessor.swift | 12 +- .../Processor/IconProcessor.swift | 2 +- .../Processor/ImageProcessor.swift | 2 +- .../Processor/LevelProcessor.swift | 2 +- .../NotificationContentProcessor.swift | 16 ++- 14 files changed, 287 insertions(+), 37 deletions(-) create mode 100644 Common/SharedDefines.swift create mode 100644 NotificationServiceExtension/Processor/CallProcessor.swift diff --git a/Bark.xcodeproj/project.pbxproj b/Bark.xcodeproj/project.pbxproj index ee6503bf..948c0590 100644 --- a/Bark.xcodeproj/project.pbxproj +++ b/Bark.xcodeproj/project.pbxproj @@ -126,6 +126,39 @@ 06C595362481160F006B98F3 /* BKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C595352481160F006B98F3 /* BKLabel.swift */; }; 06CF784721C7A50300A052D7 /* NotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CF784B21C7A51200A052D7 /* NotificationService.swift */; }; + 06D69E202C1159E200161A35 /* glass.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320500250B6DD3001561EC /* glass.caf */; }; + 06D69E212C1159E200161A35 /* sherwoodforest.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F5250B6DD2001561EC /* sherwoodforest.caf */; }; + 06D69E222C1159E200161A35 /* ladder.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F9250B6DD2001561EC /* ladder.caf */; }; + 06D69E232C1159E200161A35 /* chime.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320509250B6DD4001561EC /* chime.caf */; }; + 06D69E242C1159E200161A35 /* anticipate.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204FE250B6DD2001561EC /* anticipate.caf */; }; + 06D69E252C1159E200161A35 /* update.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320504250B6DD3001561EC /* update.caf */; }; + 06D69E262C1159E200161A35 /* suspense.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320506250B6DD3001561EC /* suspense.caf */; }; + 06D69E272C1159E200161A35 /* newmail.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320503250B6DD3001561EC /* newmail.caf */; }; + 06D69E282C1159E200161A35 /* noir.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320508250B6DD3001561EC /* noir.caf */; }; + 06D69E292C1159E200161A35 /* birdsong.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204FC250B6DD2001561EC /* birdsong.caf */; }; + 06D69E2A2C1159E200161A35 /* minuet.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320505250B6DD3001561EC /* minuet.caf */; }; + 06D69E2B2C1159E200161A35 /* shake.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F2250B6DD1001561EC /* shake.caf */; }; + 06D69E2C2C1159E200161A35 /* newsflash.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0632050E250B6DD4001561EC /* newsflash.caf */; }; + 06D69E2D2C1159E200161A35 /* paymentsuccess.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F1250B6DD1001561EC /* paymentsuccess.caf */; }; + 06D69E2E2C1159E200161A35 /* descent.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F8250B6DD2001561EC /* descent.caf */; }; + 06D69E2F2C1159E200161A35 /* mailsent.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320507250B6DD3001561EC /* mailsent.caf */; }; + 06D69E302C1159E200161A35 /* tiptoes.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204FA250B6DD2001561EC /* tiptoes.caf */; }; + 06D69E312C1159E200161A35 /* telegraph.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320501250B6DD3001561EC /* telegraph.caf */; }; + 06D69E322C1159E200161A35 /* healthnotification.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F6250B6DD2001561EC /* healthnotification.caf */; }; + 06D69E332C1159E200161A35 /* typewriters.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204FD250B6DD2001561EC /* typewriters.caf */; }; + 06D69E342C1159E200161A35 /* bell.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0632050C250B6DD4001561EC /* bell.caf */; }; + 06D69E352C1159E200161A35 /* bloom.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F4250B6DD1001561EC /* bloom.caf */; }; + 06D69E362C1159E200161A35 /* spell.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0632050A250B6DD4001561EC /* spell.caf */; }; + 06D69E372C1159E200161A35 /* choo.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204FF250B6DD3001561EC /* choo.caf */; }; + 06D69E382C1159E200161A35 /* multiwayinvitation.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06320502250B6DD3001561EC /* multiwayinvitation.caf */; }; + 06D69E392C1159E200161A35 /* horn.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0632050D250B6DD4001561EC /* horn.caf */; }; + 06D69E3A2C1159E200161A35 /* electronic.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0632050B250B6DD4001561EC /* electronic.caf */; }; + 06D69E3B2C1159E200161A35 /* calypso.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F7250B6DD2001561EC /* calypso.caf */; }; + 06D69E3C2C1159E200161A35 /* gotosleep.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F0250B6DD1001561EC /* gotosleep.caf */; }; + 06D69E3D2C1159E200161A35 /* silence.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06BBB8CD2567B8E60076F63E /* silence.caf */; }; + 06D69E3E2C1159E200161A35 /* alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F3250B6DD1001561EC /* alarm.caf */; }; + 06D69E3F2C1159E200161A35 /* fanfare.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204FB250B6DD2001561EC /* fanfare.caf */; }; + 06D69E412C11983E00161A35 /* CallProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D69E402C11983E00161A35 /* CallProcessor.swift */; }; 06E944682C06E40600AC86AB /* NotificationContentProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944672C06E40600AC86AB /* NotificationContentProcessor.swift */; }; 06E9446A2C06E4A200AC86AB /* CiphertextProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944692C06E4A200AC86AB /* CiphertextProcessor.swift */; }; 06E9446D2C06FEC900AC86AB /* LevelProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E9446C2C06FEC900AC86AB /* LevelProcessor.swift */; }; @@ -149,6 +182,8 @@ 06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */; }; 06F08EAF29B5D9FF006AB9CA /* HUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAE29B5D9FF006AB9CA /* HUD.swift */; }; 06F11E7727D9D5FB00F00298 /* QRScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */; }; + 06FB04042C53575400F3A213 /* SharedDefines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FB04032C53575400F3A213 /* SharedDefines.swift */; }; + 06FB04052C53575400F3A213 /* SharedDefines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FB04032C53575400F3A213 /* SharedDefines.swift */; }; 3428272069AFAFE2C683FEB0 /* libPods-Bark.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CCC722470308049D180876C7 /* libPods-Bark.a */; }; 879AE4D4178855A9672009E4 /* libPods-NotificationServiceExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B7F8BDFAA047451561798F58 /* libPods-NotificationServiceExtension.a */; }; B963F7D5BA7AC2571E71EF66 /* libPods-BarkTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 76381A752CCCD4DA6BB2A566 /* libPods-BarkTests.a */; }; @@ -316,6 +351,7 @@ 06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 06CF784421C7A50300A052D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 06CF784B21C7A51200A052D7 /* NotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 06D69E402C11983E00161A35 /* CallProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallProcessor.swift; sourceTree = ""; }; 06E944672C06E40600AC86AB /* NotificationContentProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentProcessor.swift; sourceTree = ""; }; 06E944692C06E4A200AC86AB /* CiphertextProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CiphertextProcessor.swift; sourceTree = ""; }; 06E9446C2C06FEC900AC86AB /* LevelProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelProcessor.swift; sourceTree = ""; }; @@ -336,6 +372,7 @@ 06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocalizedString+Extension.swift"; sourceTree = ""; }; 06F08EAE29B5D9FF006AB9CA /* HUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUD.swift; sourceTree = ""; }; 06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerViewController.swift; sourceTree = ""; }; + 06FB04032C53575400F3A213 /* SharedDefines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedDefines.swift; sourceTree = ""; }; 121D9B1ED4E8D26F345BC5C0 /* Pods-BarkTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BarkTests.release.xcconfig"; path = "Target Support Files/Pods-BarkTests/Pods-BarkTests.release.xcconfig"; sourceTree = ""; }; 138CE8CB688587E893BC5C44 /* Pods-Bark.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Bark.debug.xcconfig"; path = "Target Support Files/Pods-Bark/Pods-Bark.debug.xcconfig"; sourceTree = ""; }; 519481D715B40109627E1B49 /* Pods-NotificationServiceExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.release.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.release.xcconfig"; sourceTree = ""; }; @@ -519,6 +556,7 @@ 065BE44A2563D8E1002A8CA4 /* Reusable.swift */, 0637FA8520E0AB6600E80174 /* UIColor+Extension.swift */, 0603706620E1E31600F4CA05 /* Defines.swift */, + 06FB04032C53575400F3A213 /* SharedDefines.swift */, 0603706A20E20A7C00F4CA05 /* String+Extension.swift */, 0637FA7F20E0981E00E80174 /* BarkSettings.swift */, 0604F7DE20620D4900B32F09 /* ServerManager.swift */, @@ -612,6 +650,7 @@ 06E944722C06FF9200AC86AB /* ArchiveProcessor.swift */, 06E944772C0701F300AC86AB /* IconProcessor.swift */, 06E9447B2C07052F00AC86AB /* ImageProcessor.swift */, + 06D69E402C11983E00161A35 /* CallProcessor.swift */, 06E944792C0704E500AC86AB /* ImageDownloader.swift */, ); path = Processor; @@ -850,6 +889,38 @@ buildActionMask = 2147483647; files = ( 06F08EAA29B1DE9F006AB9CA /* Localizable.strings in Resources */, + 06D69E202C1159E200161A35 /* glass.caf in Resources */, + 06D69E212C1159E200161A35 /* sherwoodforest.caf in Resources */, + 06D69E222C1159E200161A35 /* ladder.caf in Resources */, + 06D69E232C1159E200161A35 /* chime.caf in Resources */, + 06D69E242C1159E200161A35 /* anticipate.caf in Resources */, + 06D69E252C1159E200161A35 /* update.caf in Resources */, + 06D69E262C1159E200161A35 /* suspense.caf in Resources */, + 06D69E272C1159E200161A35 /* newmail.caf in Resources */, + 06D69E282C1159E200161A35 /* noir.caf in Resources */, + 06D69E292C1159E200161A35 /* birdsong.caf in Resources */, + 06D69E2A2C1159E200161A35 /* minuet.caf in Resources */, + 06D69E2B2C1159E200161A35 /* shake.caf in Resources */, + 06D69E2C2C1159E200161A35 /* newsflash.caf in Resources */, + 06D69E2D2C1159E200161A35 /* paymentsuccess.caf in Resources */, + 06D69E2E2C1159E200161A35 /* descent.caf in Resources */, + 06D69E2F2C1159E200161A35 /* mailsent.caf in Resources */, + 06D69E302C1159E200161A35 /* tiptoes.caf in Resources */, + 06D69E312C1159E200161A35 /* telegraph.caf in Resources */, + 06D69E322C1159E200161A35 /* healthnotification.caf in Resources */, + 06D69E332C1159E200161A35 /* typewriters.caf in Resources */, + 06D69E342C1159E200161A35 /* bell.caf in Resources */, + 06D69E352C1159E200161A35 /* bloom.caf in Resources */, + 06D69E362C1159E200161A35 /* spell.caf in Resources */, + 06D69E372C1159E200161A35 /* choo.caf in Resources */, + 06D69E382C1159E200161A35 /* multiwayinvitation.caf in Resources */, + 06D69E392C1159E200161A35 /* horn.caf in Resources */, + 06D69E3A2C1159E200161A35 /* electronic.caf in Resources */, + 06D69E3B2C1159E200161A35 /* calypso.caf in Resources */, + 06D69E3C2C1159E200161A35 /* gotosleep.caf in Resources */, + 06D69E3D2C1159E200161A35 /* silence.caf in Resources */, + 06D69E3E2C1159E200161A35 /* alarm.caf in Resources */, + 06D69E3F2C1159E200161A35 /* fanfare.caf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1053,6 +1124,7 @@ 06B1158F247BB1FB006D91FB /* Message.swift in Sources */, 06172FDA27F6DAEF002333A4 /* ServerListTableViewCell.swift in Sources */, 0653677829B727A60038BDB8 /* CryptoSettingRelay.swift in Sources */, + 06FB04042C53575400F3A213 /* SharedDefines.swift in Sources */, 061894C529962EB900E001C2 /* GradientButton.swift in Sources */, 06C595362481160F006B98F3 /* BKLabel.swift in Sources */, 0637FA7820E0926D00E80174 /* BarkTargetType.swift in Sources */, @@ -1112,6 +1184,7 @@ 06E944732C06FF9200AC86AB /* ArchiveProcessor.swift in Sources */, 06E9447A2C0704E500AC86AB /* ImageDownloader.swift in Sources */, 06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */, + 06FB04052C53575400F3A213 /* SharedDefines.swift in Sources */, 06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */, 0653677629B719BC0038BDB8 /* CryptoSettingManager.swift in Sources */, 06E9446F2C06FF1E00AC86AB /* BadgeProcessor.swift in Sources */, @@ -1120,6 +1193,7 @@ 06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */, 06E944782C0701F300AC86AB /* IconProcessor.swift in Sources */, 06BBB89125650CCF0076F63E /* ArchiveSettingManager.swift in Sources */, + 06D69E412C11983E00161A35 /* CallProcessor.swift in Sources */, 06B11591247BC132006D91FB /* Message.swift in Sources */, 06E9447C2C07052F00AC86AB /* ImageProcessor.swift in Sources */, 06E9446A2C06E4A200AC86AB /* CiphertextProcessor.swift in Sources */, diff --git a/Bark/AppDelegate.swift b/Bark/AppDelegate.swift index bd964e20..9d5fcd98 100644 --- a/Bark/AppDelegate.swift +++ b/Bark/AppDelegate.swift @@ -8,7 +8,6 @@ import CloudKit import CrashReporter -//import IceCream import IQKeyboardManagerSwift import Material import UIKit @@ -34,7 +33,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - self.window = UIWindow(frame: UIScreen.main.bounds) self.window?.backgroundColor = UIColor.black @@ -140,24 +138,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD ServerManager.shared.syncAllServers() } - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - notificatonHandler(userInfo: notification.request.content.userInfo) - } - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { notificatonHandler(userInfo: response.notification.request.content.userInfo) } -// func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { -// if let dict = userInfo as? [String: NSObject], -// let notification = CKNotification(fromRemoteNotificationDictionary: dict), -// let subscriptionID = notification.subscriptionID, IceCreamSubscription.allIDs.contains(subscriptionID) -// { -// NotificationCenter.default.post(name: Notifications.cloudKitDataDidChangeRemotely.name, object: nil, userInfo: userInfo) -// completionHandler(.newData) -// } -// } - + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { + if UIApplication.shared.applicationState == .active { + stopCallNotificationProcessor() + } + return .alert + } + private func notificatonHandler(userInfo: [AnyHashable: Any]) { let navigationController = Client.shared.currentNavigationController func presentController() { @@ -239,6 +230,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // 设置 -1 可以清除应用角标,但不清除通知中心的推送 // 设置 0 会将通知中心的所有推送一起清空掉 UIApplication.shared.applicationIconBadgeNumber = -1 + // 如果有响铃通知,则关闭响铃 + stopCallNotificationProcessor() } func applicationDidBecomeActive(_ application: UIApplication) { @@ -248,4 +241,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } + + /// 停止响铃 + func stopCallNotificationProcessor() { + CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFNotificationName(kStopCallProcessorKey as CFString), nil, nil, true) + } } diff --git a/Common/SharedDefines.swift b/Common/SharedDefines.swift new file mode 100644 index 00000000..92dc2b53 --- /dev/null +++ b/Common/SharedDefines.swift @@ -0,0 +1,12 @@ +// +// SharedDefines.swift +// Bark +// +// Created by huangfeng on 2024/7/26. +// Copyright © 2024 Fin. All rights reserved. +// + +import Foundation + + +let kStopCallProcessorKey = "stopCallProcessorNotification" diff --git a/Controller/MessageListViewModel.swift b/Controller/MessageListViewModel.swift index 2ff13041..710dd352 100644 --- a/Controller/MessageListViewModel.swift +++ b/Controller/MessageListViewModel.swift @@ -60,7 +60,7 @@ class MessageListViewModel: ViewModel, ViewModelType { return [] } var messages: [Message] = [] - for i in startIndex ..< endIndex { + for i in startIndex.. Void)? = nil + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { Task { guard var bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { @@ -16,6 +21,9 @@ class NotificationService: UNNotificationServiceExtension { return } + // 所有的 processor, 按顺序从上往下对推送进行处理 + // ciphertext 需要放在最前面,有可能所有的推送数据都在密文里 + // call 需要放在最后面,因为这个 Processor 不会主动退出, 会一直等到 ServiceExtension 被终止 let processors: [NotificationContentProcessorItem] = [ .ciphertext, .level, @@ -23,19 +31,30 @@ class NotificationService: UNNotificationServiceExtension { .autoCopy, .archive, .setIcon, - .setImage + .setImage, + .call ] - for item in processors { + // 各个 processor 依次对推送进行处理 + for processor in processors.map({ $0.processor }) { do { - bestAttemptContent = try await item.processor.process(content: bestAttemptContent) + self.currentNotificationProcessor = processor + bestAttemptContent = try await processor.process(identifier: request.identifier, content: bestAttemptContent) } catch NotificationContentProcessorError.error(let content) { contentHandler(content) return } } + // 处理完后交付推送 contentHandler(bestAttemptContent) } } + + override func serviceExtensionTimeWillExpire() { + if let handler = self.currentContentHandler { + self.currentNotificationProcessor?.serviceExtensionTimeWillExpire(contentHandler: handler) + } + super.serviceExtensionTimeWillExpire() + } } diff --git a/NotificationServiceExtension/Processor/ArchiveProcessor.swift b/NotificationServiceExtension/Processor/ArchiveProcessor.swift index fdafed27..3f232c35 100644 --- a/NotificationServiceExtension/Processor/ArchiveProcessor.swift +++ b/NotificationServiceExtension/Processor/ArchiveProcessor.swift @@ -15,7 +15,7 @@ class ArchiveProcessor: NotificationContentProcessor { return try? Realm() }() - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { let userInfo = bestAttemptContent.userInfo var isArchive: Bool = ArchiveSettingManager.shared.isArchive diff --git a/NotificationServiceExtension/Processor/AutoCopyProcessor.swift b/NotificationServiceExtension/Processor/AutoCopyProcessor.swift index be9f99e7..54f4fb03 100644 --- a/NotificationServiceExtension/Processor/AutoCopyProcessor.swift +++ b/NotificationServiceExtension/Processor/AutoCopyProcessor.swift @@ -9,7 +9,7 @@ import Foundation class AutoCopyProcessor: NotificationContentProcessor { - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { let userInfo = bestAttemptContent.userInfo if userInfo["autocopy"] as? String == "1" || userInfo["automaticallycopy"] as? String == "1" diff --git a/NotificationServiceExtension/Processor/BadgeProcessor.swift b/NotificationServiceExtension/Processor/BadgeProcessor.swift index b7d80d73..7043b09b 100644 --- a/NotificationServiceExtension/Processor/BadgeProcessor.swift +++ b/NotificationServiceExtension/Processor/BadgeProcessor.swift @@ -10,7 +10,7 @@ import Foundation /// 通知角标 class BadgeProcessor: NotificationContentProcessor { - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { if let badgeStr = bestAttemptContent.userInfo["badge"] as? String, let badge = Int(badgeStr) { bestAttemptContent.badge = NSNumber(value: badge) } diff --git a/NotificationServiceExtension/Processor/CallProcessor.swift b/NotificationServiceExtension/Processor/CallProcessor.swift new file mode 100644 index 00000000..ebb224fa --- /dev/null +++ b/NotificationServiceExtension/Processor/CallProcessor.swift @@ -0,0 +1,130 @@ +// +// CallProcessor.swift +// NotificationServiceExtension +// +// Created by huangfeng on 2024/6/6. +// Copyright © 2024 Fin. All rights reserved. +// + +import AudioToolbox +import Foundation + +class CallProcessor: NotificationContentProcessor { + /// 循环播放的铃声 + var soundID: SystemSoundID = 0 + /// 播放完毕后,返回的 content + var content: UNMutableNotificationContent? = nil + /// 是否需要停止播放,由主APP发出停止通知赋值 + var needsStop = false + + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + guard let call = bestAttemptContent.userInfo["call"] as? String, call == "1" else { + return bestAttemptContent + } + self.content = bestAttemptContent + + self.registerObserver() + self.sendLocalNotification(identifier: identifier, content: bestAttemptContent) + await startAudioWork() + return bestAttemptContent + } + + func serviceExtensionTimeWillExpire(contentHandler: (UNNotificationContent) -> Void) { + if let content { + contentHandler(content) + } + } + + /// 生成一个本地推送 + private func sendLocalNotification(identifier: String, content: UNMutableNotificationContent) { + // 推送id和推送的内容都使用远程APNS的 + content.sound = nil + let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil) + UNUserNotificationCenter.current().add(request) + } + + // 开始播放铃声,startAudioWork(completion:) 方法的异步包装 + private func startAudioWork() async { + return await withCheckedContinuation { continuation in + self.startAudioWork { + continuation.resume() + } + } + } + + /// 铃声播放结束时的回调 + var startAudioWorkCompletion: (() -> Void)? = nil + /// 播放铃声 + private func startAudioWork(completion: @escaping () -> Void) { + guard let content else { + completion() + return + } + self.startAudioWorkCompletion = completion + + let sound = ((content.userInfo["aps"] as? [String: Any])?["sound"] as? String)?.split(separator: ".") + let soundName = sound?.first ?? "multiwayinvitation" + let soundType = sound?.last ?? "caf" + + // 先找自定义上传的铃声,再找内置铃声 + guard let audioPath = getSoundInCustomSoundsDirectory(soundName: "\(soundName).\(soundType)") ?? + Bundle.main.path(forResource: String(soundName), ofType: String(soundType)) + else { + completion() + return + } + + let fileUrl = URL(string: audioPath) + // 创建响铃任务 + AudioServicesCreateSystemSoundID(fileUrl! as CFURL, &soundID) + // 播放震动、响铃 + AudioServicesPlayAlertSound(soundID) + // 监听响铃完成状态 + let selfPointer = unsafeBitCast(self, to: UnsafeMutableRawPointer.self) + AudioServicesAddSystemSoundCompletion(soundID, nil, nil, { sound, clientData in + guard let pointer = clientData else { return } + let processor = unsafeBitCast(pointer, to: CallProcessor.self) + if processor.needsStop { + processor.startAudioWorkCompletion?() + return + } + // 音频文件一次播放完成,再次播放 + AudioServicesPlayAlertSound(sound) + }, selfPointer) + } + + /// 停止播放 + private func stopAudioWork() { + AudioServicesRemoveSystemSoundCompletion(soundID) + AudioServicesDisposeSystemSoundID(soundID) + } + + /// 注册停止通知 + func registerObserver() { + let notification = CFNotificationCenterGetDarwinNotifyCenter() + let observer = Unmanaged.passUnretained(self).toOpaque() + CFNotificationCenterAddObserver(notification, observer, { _, pointer, _, _, _ in + guard let observer = pointer else { return } + let processor = Unmanaged.fromOpaque(observer).takeUnretainedValue() + processor.needsStop = true + }, kStopCallProcessorKey as CFString, nil, .deliverImmediately) + } + + func getSoundInCustomSoundsDirectory(soundName: String) -> String? { + // 扩展访问不到主APP中的铃声,需要先共享铃声文件,再实现自定义铃声响铃 + guard let soundsDirectoryUrl = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first?.appending("/Sounds") else { + return nil + } + let path = soundsDirectoryUrl.appending("/\(soundName)") + if FileManager.default.fileExists(atPath: path) { + return path + } + return nil + } + + deinit { + let observer = Unmanaged.passUnretained(self).toOpaque() + let name = CFNotificationName(kStopCallProcessorKey as CFString) + CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), observer, name, nil) + } +} diff --git a/NotificationServiceExtension/Processor/CiphertextProcessor.swift b/NotificationServiceExtension/Processor/CiphertextProcessor.swift index e47ce6c3..782ece8a 100644 --- a/NotificationServiceExtension/Processor/CiphertextProcessor.swift +++ b/NotificationServiceExtension/Processor/CiphertextProcessor.swift @@ -11,7 +11,7 @@ import SwiftyJSON /// 加密推送 class CiphertextProcessor: NotificationContentProcessor { - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { var userInfo = bestAttemptContent.userInfo guard let ciphertext = userInfo["ciphertext"] as? String else { return bestAttemptContent @@ -22,6 +22,7 @@ class CiphertextProcessor: NotificationContentProcessor { var map = try decrypt(ciphertext: ciphertext, iv: userInfo["iv"] as? String) var alert = [String: Any]() + var soundName: String? = nil if let title = map["title"] as? String { bestAttemptContent.title = title alert["title"] = title @@ -37,13 +38,18 @@ class CiphertextProcessor: NotificationContentProcessor { if !sound.hasSuffix(".caf") { sound = "\(sound).caf" } + soundName = sound bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: sound)) } if let badge = map["badge"] as? Int { bestAttemptContent.badge = badge as NSNumber } - - map["aps"] = ["alert": alert] + var aps: [String: Any] = ["alert": alert] + if let soundName { + aps["sound"] = soundName + } + map["aps"] = aps + userInfo = map bestAttemptContent.userInfo = userInfo return bestAttemptContent diff --git a/NotificationServiceExtension/Processor/IconProcessor.swift b/NotificationServiceExtension/Processor/IconProcessor.swift index b976ad53..8d9d00fe 100644 --- a/NotificationServiceExtension/Processor/IconProcessor.swift +++ b/NotificationServiceExtension/Processor/IconProcessor.swift @@ -10,7 +10,7 @@ import Foundation import Intents class IconProcessor: NotificationContentProcessor { - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { if #available(iOSApplicationExtension 15.0, *) { let userInfo = bestAttemptContent.userInfo diff --git a/NotificationServiceExtension/Processor/ImageProcessor.swift b/NotificationServiceExtension/Processor/ImageProcessor.swift index e87bd3da..9a424601 100644 --- a/NotificationServiceExtension/Processor/ImageProcessor.swift +++ b/NotificationServiceExtension/Processor/ImageProcessor.swift @@ -10,7 +10,7 @@ import Foundation import MobileCoreServices class ImageProcessor: NotificationContentProcessor { - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { let userInfo = bestAttemptContent.userInfo guard let imageUrl = userInfo["image"] as? String, let imageFileUrl = await ImageDownloader.downloadImage(imageUrl) diff --git a/NotificationServiceExtension/Processor/LevelProcessor.swift b/NotificationServiceExtension/Processor/LevelProcessor.swift index 116ba419..e08c1caf 100644 --- a/NotificationServiceExtension/Processor/LevelProcessor.swift +++ b/NotificationServiceExtension/Processor/LevelProcessor.swift @@ -10,7 +10,7 @@ import Foundation /// 通知中断级别 class LevelProcessor: NotificationContentProcessor { - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent { if #available(iOSApplicationExtension 15.0, *) { if let level = bestAttemptContent.userInfo["level"] as? String { let interruptionLevels: [String: UNNotificationInterruptionLevel] = [ diff --git a/NotificationServiceExtension/Processor/NotificationContentProcessor.swift b/NotificationServiceExtension/Processor/NotificationContentProcessor.swift index a9691278..ef6dea16 100644 --- a/NotificationServiceExtension/Processor/NotificationContentProcessor.swift +++ b/NotificationServiceExtension/Processor/NotificationContentProcessor.swift @@ -17,6 +17,7 @@ enum NotificationContentProcessorItem { case archive case setIcon case setImage + case call var processor: NotificationContentProcessor { switch self { @@ -34,6 +35,8 @@ enum NotificationContentProcessorItem { return IconProcessor() case .setImage: return ImageProcessor() + case .call: + return CallProcessor() } } } @@ -44,8 +47,17 @@ enum NotificationContentProcessorError: Swift.Error { public protocol NotificationContentProcessor { /// 处理 UNMutableNotificationContent - /// - Parameter bestAttemptContent: 需要处理的 UNMutableNotificationContent + /// - Parameters: + /// - identifier: request.identifier, 有些 Processor 需要,例如 CallProcessor 需要这个去添加 LocalNotification + /// - bestAttemptContent: 需要处理的 UNMutableNotificationContent /// - Returns: 处理成功后的 UNMutableNotificationContent /// - Throws: 处理失败后,应该中断处理 - func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent + func process(identifier: String, content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent + + /// serviceExtension 即将终止,不管 processor 是否处理完成,最好立即调用 contentHandler 交付已完成的部分,否则会原样展示服务器传递过来的推送 + func serviceExtensionTimeWillExpire(contentHandler: (UNNotificationContent) -> Void) +} + +extension NotificationContentProcessor { + func serviceExtensionTimeWillExpire(contentHandler: (UNNotificationContent) -> Void) {} }