| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 |
- import Foundation
- import UserNotifications
- import WatchConnectivity
- final class WatchNotificationHandler: NSObject, UNUserNotificationCenterDelegate {
- static let shared = WatchNotificationHandler()
- override private init() {
- super.init()
- }
- func configure() {
- let center = UNUserNotificationCenter.current()
- center.delegate = self
- registerCategories(on: center)
- }
- private func registerCategories(on center: UNUserNotificationCenter) {
- center.getNotificationCategories { existingCategories in
- let glucoseCategory = NotificationCategoryFactory.createGlucoseCategory()
- var categories = existingCategories
- categories.update(with: glucoseCategory)
- // UNUserNotificationCenter methods should be called on main thread
- Task { @MainActor in
- center.setNotificationCategories(categories)
- }
- }
- }
- /// UNUserNotificationCenterDelegate method called when user interacts with a notification on watch.
- /// This can be called off the main thread. WCSession.transferUserInfo is thread-safe.
- func userNotificationCenter(
- _: UNUserNotificationCenter,
- didReceive response: UNNotificationResponse,
- withCompletionHandler completionHandler: @escaping () -> Void
- ) {
- defer { completionHandler() }
- guard let action = NotificationResponseAction(rawValue: response.actionIdentifier) else { return }
- sendSnoozeRequest(for: action)
- }
- /// Sends snooze request to iPhone via WatchConnectivity.
- /// WCSession.transferUserInfo is thread-safe and can be called from any thread.
- /// Relies on the watch app's WCSession owner (e.g., WatchState) to handle
- /// session activation and delegate management.
- private func sendSnoozeRequest(for action: NotificationResponseAction) {
- guard WCSession.isSupported() else { return }
- let payload: [String: Any] = [WatchMessageKeys.snoozeDuration: action.minutes]
- let session = WCSession.default
- // Try sendMessage first if session is reachable and activated (faster, immediate delivery)
- // Fall back to transferUserInfo if not reachable or if sendMessage fails
- if session.isReachable, session.activationState == .activated {
- session.sendMessage(payload, replyHandler: nil) { _ in
- // Fallback to transferUserInfo if sendMessage fails
- session.transferUserInfo(payload)
- }
- } else {
- // Session not reachable or not activated - use transferUserInfo (queued delivery)
- session.transferUserInfo(payload)
- }
- }
- }
|