feat: 接入消息未读数、全部已读与首页红点角标同步。

固定消息中心为首页入口,并在推送到达、已读后刷新桌面角标。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-21 18:02:43 +08:00
co-authored by Cursor
parent e7f1d777dd
commit caeeb9a1cf
18 changed files with 748 additions and 37 deletions
@@ -1,6 +1,32 @@
import UIKit
import UserNotifications
/// App 桌面图标角标设置能力,便于业务同步未读消息数量并进行测试替换。
@MainActor
protocol ApplicationIconBadgeSetting: AnyObject {
/// 将桌面图标角标更新为指定非负数量。
func setBadgeCount(_ count: Int) async
}
/// 使用 UserNotifications 更新 App 桌面图标角标的系统实现。
@MainActor
final class SystemApplicationIconBadgeSetter: ApplicationIconBadgeSetting {
/// 全局角标设置器。
static let shared = SystemApplicationIconBadgeSetter()
private init() {}
func setBadgeCount(_ count: Int) async {
do {
try await UNUserNotificationCenter.current().setBadgeCount(max(count, 0))
} catch {
#if DEBUG
print("Update application icon badge failed: \(error.localizedDescription)")
#endif
}
}
}
/// 极光 SDK 的最小能力边界,便于推送生命周期逻辑使用测试替身。
@MainActor
protocol PushSDKProviding: AnyObject {
@@ -85,6 +111,7 @@ final class PushNotificationManager: NSObject {
private let appStore: AppStore
private let defaults: UserDefaults
private let router: any PushRouting
private let applicationIconBadgeSetter: any ApplicationIconBadgeSetting
private var isInitialized = false
private var didRequestAuthorization = false
@@ -99,13 +126,15 @@ final class PushNotificationManager: NSObject {
api: (any PushRegistrationServing)? = nil,
appStore: AppStore = .shared,
defaults: UserDefaults = .standard,
router: (any PushRouting)? = nil
router: (any PushRouting)? = nil,
applicationIconBadgeSetter: (any ApplicationIconBadgeSetting)? = nil
) {
self.sdk = sdk ?? JPushSDKAdapter()
self.api = api ?? NetworkServices.shared.pushAPI
self.appStore = appStore
self.defaults = defaults
self.router = router ?? PushRouteCoordinator.shared
self.applicationIconBadgeSetter = applicationIconBadgeSetter ?? SystemApplicationIconBadgeSetter.shared
super.init()
}
@@ -152,6 +181,12 @@ final class PushNotificationManager: NSObject {
uploadTask = nil
queuedForcedUpload = false
router.resetPendingRoute()
Task { await updateApplicationIconBadgeCount(0) }
}
/// 将桌面 App Icon 角标更新为最新未读消息数量。
func updateApplicationIconBadgeCount(_ count: Int) async {
await applicationIconBadgeSetter.setBadgeCount(max(count, 0))
}
/// App 回到前台时补偿失败或尚未完成的 Registration ID 上报。
@@ -204,6 +239,7 @@ final class PushNotificationManager: NSObject {
/// 处理后台静默通知并向极光上报到达,不触发页面跳转。
func handleRemoteNotification(_ userInfo: [AnyHashable: Any]) {
JPUSHService.handleRemoteNotification(userInfo)
NotificationCenter.default.post(name: NotificationName.unreadMessageCountDidChange, object: nil)
}
private func observeJPushNetworkLogin() {
@@ -317,6 +353,9 @@ extension PushNotificationManager: JPUSHRegisterDelegate {
withCompletionHandler completionHandler: @escaping (Int) -> Void
) {
JPUSHService.handleRemoteNotification(notification.request.content.userInfo)
Task { @MainActor in
NotificationCenter.default.post(name: NotificationName.unreadMessageCountDidChange, object: nil)
}
let options: UNNotificationPresentationOptions = [.banner, .list, .sound, .badge]
completionHandler(Int(options.rawValue))
}