Kaynağa Gözat

Strip legacy SwiftMessages alert pipeline

trioneer 2 hafta önce
ebeveyn
işleme
c1bdf9a4c4

+ 0 - 261
Trio/Sources/Modules/Main/MainStateModel.swift

@@ -1,6 +1,5 @@
 import Combine
 import Combine
 import LoopKitUI
 import LoopKitUI
-import SwiftMessages
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
@@ -12,194 +11,6 @@ extension Main {
         @Published var modal: Modal?
         @Published var modal: Modal?
         @Published var secondaryModal: SecondaryModalWrapper?
         @Published var secondaryModal: SecondaryModalWrapper?
 
 
-        @Persisted(key: "UserNotificationsManager.snoozeUntilDate") private var snoozeUntilDate: Date = .distantPast
-        private var timers: [TimeInterval: Timer] = [:]
-
-        private func showTriggeredView(
-            message: MessageContent,
-            interval _: TimeInterval,
-            config: SwiftMessages.Config,
-            view: MessageView
-        ) {
-            view.customConfigureTheme(
-                colorSchemePreference: colorSchemePreference
-            )
-            setupAction(message: message, view: view)
-
-            SwiftMessages.show(config: config, view: view)
-        }
-
-        // Add or replace timer for a specific TimeInterval
-        private func addOrReplaceTriggerTimer(message: MessageContent, config: SwiftMessages.Config, view: MessageView) {
-            let trigger = message.trigger as! UNTimeIntervalNotificationTrigger
-            guard trigger.timeInterval > 0 else { return }
-            let interval = trigger.timeInterval
-
-            SwiftMessages.hide(id: view.id)
-
-            // If a timer already exists for this interval, invalidate it
-            if let existingTimer = timers[interval] {
-                existingTimer.invalidate()
-            }
-
-            // Create a new timer with the provided interval
-            let newTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
-                self?.showTriggeredView(message: message, interval: interval, config: config, view: view)
-                self?.timers[interval] = nil
-            }
-
-            timers[interval] = newTimer
-        }
-
-        // Cancel all timers (optional cleanup method)
-        private func cancelAllTimers() {
-            timers.values.forEach { $0.invalidate() }
-            timers.removeAll()
-        }
-
-        private func setupPumpConfig() {
-            // display the pump configuration immediatly
-            if let pump = provider.deviceManager.pumpManager,
-               let bluetooth = provider.bluetoothProvider
-            {
-                let view = PumpConfig.PumpSettingsView(
-                    pumpManager: pump,
-                    bluetoothManager: bluetooth,
-                    completionDelegate: self
-                ).asAny()
-                router.mainSecondaryModalView.send(view)
-            }
-        }
-
-        private func setupButton(message _: MessageContent, view: MessageView) {
-            view.button?.setImage(UIImage(), for: .normal)
-            view.iconLabel = nil
-            let buttonImage = UIImage(systemName: "chevron.right")?.withTintColor(.white)
-            view.button?.setImage(buttonImage, for: .normal)
-            view.button?.backgroundColor = view.backgroundView.backgroundColor
-            view.button?.tintColor = view.iconImageView?.tintColor
-        }
-
-        private func setupAction(message: MessageContent, view: MessageView) {
-            switch message.action {
-            case .snooze:
-                setupButton(message: message, view: view)
-                view.buttonTapHandler = { _ in
-                    // Popup Snooze view when user taps on Glucose Notification
-                    SwiftMessages.hide()
-                    self.router.mainModalScreen.send(.snooze)
-                }
-            case .pumpConfig:
-                setupButton(message: message, view: view)
-                view.buttonTapHandler = { _ in
-                    SwiftMessages.hide()
-                    self.setupPumpConfig()
-                }
-            default: // break
-                view.button?.setImage(UIImage(), for: .normal)
-                view.buttonTapHandler = { _ in
-                    SwiftMessages.hide()
-                }
-            }
-        }
-
-        private func isApnPumpConfigAction(_ message: MessageContent) -> Bool {
-            if message.type != .error, message.action == .pumpConfig {
-                setupPumpConfig()
-                return true
-            }
-            return false
-        }
-
-        private func showAlertMessage(_ message: MessageContent) {
-            if message.useAPN, !alertPermissionsChecker.notificationsDisabled
-            {
-                showAPN(message)
-            } else {
-                showSwiftMessage(message)
-            }
-        }
-
-        private func showAPN(_ message: MessageContent) {
-            DispatchQueue.main.async {
-                self.broadcaster.notify(alertMessageNotificationObserver.self, on: .main) {
-                    $0.alertMessageNotification(message)
-                }
-            }
-        }
-
-        // Read the color scheme preference from UserDefaults; defaults to system default setting
-        @AppStorage("colorSchemePreference") private var colorSchemePreference: ColorSchemeOption = .systemDefault
-
-        private func showSwiftMessage(_ message: MessageContent) {
-            if snoozeUntilDate > Date(), message.action == .snooze {
-                return
-            }
-
-            var config = SwiftMessages.defaultConfig
-            let view = MessageView.viewFromNib(layout: .cardView)
-
-            view.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
-            config.prefersStatusBarHidden = true
-
-            // Set id so that multiple notifications are not queued while waiting for user response; only the latest will be shown
-            if message.subtype == .glucose || message.subtype == .carb {
-                view.id = message.type.rawValue + message.subtype.rawValue
-            }
-
-            let titleContent: String
-
-            let iconName = UIApplication.shared.alternateIconName ?? "trioBlack"
-            let iconImage = UIImage(named: iconName) ?? UIImage()
-
-            view.configureContent(
-                title: "title",
-                body: message.content,
-                iconImage: nil,
-                iconText: nil,
-                buttonImage: nil,
-                buttonTitle: nil,
-                buttonTapHandler: nil
-            )
-
-            view.configureIcon(withSize: CGSize(width: 40, height: 40), contentMode: .scaleAspectFit)
-            view.iconImageView!.image = iconImage
-            view.iconImageView?.layer.cornerRadius = 10
-
-            view.customConfigureTheme(
-                colorSchemePreference: colorSchemePreference
-            )
-
-            view.iconImageView?.image = iconImage
-
-            switch message.type {
-            case .info,
-                 .other:
-                config.duration = .seconds(seconds: 5)
-                titleContent = message.title != "" ? message.title : String(localized: "Info", comment: "Info title")
-            case .warning:
-                config.duration = .forever
-                titleContent = message.title != "" ? message
-                    .title : String(localized: "Warning", comment: "Warning title")
-            case .error:
-                config.duration = .forever
-                titleContent = message.title != "" ? message
-                    .title : String(localized: "Error", comment: "Error title")
-            }
-
-            view.titleLabel?.text = titleContent
-            config.dimMode = .gray(interactive: true)
-
-            setupAction(message: message, view: view)
-            if message.trigger != nil {
-                addOrReplaceTriggerTimer(message: message, config: config, view: view)
-            }
-
-            guard message.type == .error || message.action != .pumpConfig, message.trigger == nil, !view.isHidden else { return }
-
-            SwiftMessages.show(config: config, view: view)
-        }
-
         override func subscribe() {
         override func subscribe() {
             router.mainModalScreen
             router.mainModalScreen
                 .map { $0?.modal(resolver: self.resolver!) }
                 .map { $0?.modal(resolver: self.resolver!) }
@@ -215,14 +26,6 @@ extension Main {
                 }
                 }
                 .store(in: &lifetime)
                 .store(in: &lifetime)
 
 
-            router.alertMessage
-                .receive(on: DispatchQueue.main)
-                .sink { message in
-                    guard !self.isApnPumpConfigAction(message) else { return }
-                    self.showAlertMessage(message)
-                }
-                .store(in: &lifetime)
-
             router.mainSecondaryModalView
             router.mainSecondaryModalView
                 .receive(on: DispatchQueue.main)
                 .receive(on: DispatchQueue.main)
                 .sink { view in
                 .sink { view in
@@ -240,67 +43,3 @@ extension Main {
         }
         }
     }
     }
 }
 }
-
-extension MessageView {
-    func currentColorScheme() -> ColorScheme {
-        let userInterfaceStyle = UITraitCollection.current.userInterfaceStyle
-        return userInterfaceStyle == .dark ? .dark : .light
-    }
-
-    func customConfigureTheme(colorSchemePreference: ColorSchemeOption) {
-        let defaultSystemColorScheme = currentColorScheme()
-        var backgroundColor = UIColor.systemBackground
-        var foregroundColor = UIColor.white
-        let ApnBackground = UIColor(named: "ApnBackground") ?? UIColor.lightGray
-        let iOSlightTrioDark = UIColor(named: "ApnBackgroundLightDark") ?? UIColor.lightGray
-
-        switch colorSchemePreference {
-        case .systemDefault:
-            backgroundColor = ApnBackground
-            foregroundColor = UIColor.label
-        case .dark:
-            backgroundColor = defaultSystemColorScheme == .light ? iOSlightTrioDark : ApnBackground
-            foregroundColor = defaultSystemColorScheme == .light ? UIColor.black : UIColor.white
-        case .light:
-            backgroundColor = defaultSystemColorScheme == .light ? ApnBackground : UIColor.gray
-            foregroundColor = defaultSystemColorScheme == .light ? UIColor.black : UIColor.white
-        }
-
-        iconImageView?.tintColor = foregroundColor
-        backgroundView.backgroundColor = backgroundColor
-        titleLabel?.textColor = foregroundColor
-        bodyLabel?.textColor = foregroundColor
-        iconImageView?.isHidden = iconImageView?.image == nil
-
-        backgroundView.layer.cornerRadius = 25
-
-        let adjustedFont = UIFont.systemFont(ofSize: 13.0, weight: .bold)
-        let preferredTitleFont = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: adjustedFont)
-        let preferredBodyFont = UIFont.preferredFontforStyle(forTextStyle: .footnote)
-        // Set the title and body font to the dynamic type sizes
-        titleLabel?.adjustsFontForContentSizeCategory = true
-        titleLabel?.font = preferredTitleFont
-        bodyLabel?.adjustsFontForContentSizeCategory = true
-        bodyLabel?.font = preferredBodyFont
-        // Set custom colors for title and body text
-        titleLabel?.textColor = foregroundColor
-        bodyLabel?.textColor = foregroundColor
-    }
-}
-
-@available(iOS 16.0, *)
-extension Main.StateModel: CompletionDelegate {
-    func completionNotifyingDidComplete(_: CompletionNotifying) {
-        // close the window
-        router.mainSecondaryModalView.send(nil)
-    }
-}
-
-// Extension to convert SwiftUI TextStyle to UIFont
-extension UIFont {
-    static func preferredFontforStyle(forTextStyle: UIFont.TextStyle) -> UIFont {
-        let uiFontMetrics = UIFontMetrics.default
-        let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: forTextStyle)
-        return uiFontMetrics.scaledFont(for: UIFont(descriptor: descriptor, size: 0))
-    }
-}

+ 0 - 28
Trio/Sources/Router/Router.swift

@@ -2,43 +2,15 @@ import Combine
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
-enum MessageType: String {
-    case info
-    case warning
-    case error
-    case other
-}
-
-enum MessageSubtype: String {
-    case pump
-    case cgm
-    case carb
-    case glucose
-    case algorithm
-    case misc
-}
-
-struct MessageContent {
-    var content: String
-    var type: MessageType = .info
-    var subtype: MessageSubtype = .algorithm
-    var title: String = ""
-    var useAPN: Bool = true
-    var trigger: UNNotificationTrigger? = nil
-    var action: NotificationAction = .none
-}
-
 protocol Router {
 protocol Router {
     var mainModalScreen: CurrentValueSubject<Screen?, Never> { get }
     var mainModalScreen: CurrentValueSubject<Screen?, Never> { get }
     var mainSecondaryModalView: CurrentValueSubject<AnyView?, Never> { get }
     var mainSecondaryModalView: CurrentValueSubject<AnyView?, Never> { get }
-    var alertMessage: PassthroughSubject<MessageContent, Never> { get }
     func view(for screen: Screen) -> AnyView
     func view(for screen: Screen) -> AnyView
 }
 }
 
 
 final class BaseRouter: Router {
 final class BaseRouter: Router {
     let mainModalScreen = CurrentValueSubject<Screen?, Never>(nil)
     let mainModalScreen = CurrentValueSubject<Screen?, Never>(nil)
     let mainSecondaryModalView = CurrentValueSubject<AnyView?, Never>(nil)
     let mainSecondaryModalView = CurrentValueSubject<AnyView?, Never>(nil)
-    let alertMessage = PassthroughSubject<MessageContent, Never>()
     private let resolver: Resolver
     private let resolver: Resolver
 
 
     init(resolver: Resolver) {
     init(resolver: Resolver) {

+ 1 - 132
Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -12,18 +12,6 @@ protocol UserNotificationsManager {
     @MainActor func applySnooze(for duration: TimeInterval) async
     @MainActor func applySnooze(for duration: TimeInterval) async
 }
 }
 
 
-enum NotificationAction: String {
-    static let key = "action"
-
-    case snooze
-    case pumpConfig
-    case none
-}
-
-protocol alertMessageNotificationObserver {
-    func alertMessageNotification(_ message: MessageContent)
-}
-
 // MARK: - SnoozeObserver Protocol
 // MARK: - SnoozeObserver Protocol
 
 
 protocol SnoozeObserver {
 protocol SnoozeObserver {
@@ -32,9 +20,7 @@ protocol SnoozeObserver {
 
 
 final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, Injectable {
 final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, Injectable {
     enum Identifier: String {
     enum Identifier: String {
-        case glucoseNotification = "Trio.glucoseNotification"
         case carbsRequiredNotification = "Trio.carbsRequiredNotification"
         case carbsRequiredNotification = "Trio.carbsRequiredNotification"
-        case alertMessageNotification = "Trio.alertMessageNotification"
     }
     }
 
 
     @Injected() var alertPermissionsChecker: AlertPermissionsChecker!
     @Injected() var alertPermissionsChecker: AlertPermissionsChecker!
@@ -42,7 +28,6 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var apsManager: APSManager!
     @Injected() private var apsManager: APSManager!
-    @Injected() private var router: Router!
     @Injected() private var trioAlertManager: TrioAlertManager!
     @Injected() private var trioAlertManager: TrioAlertManager!
 
 
     @Persisted(key: "UserNotificationsManager.snoozeUntilDate") private var snoozeUntilDate: Date = .distantPast
     @Persisted(key: "UserNotificationsManager.snoozeUntilDate") private var snoozeUntilDate: Date = .distantPast
@@ -68,7 +53,6 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
                 .share()
                 .share()
                 .eraseToAnyPublisher()
                 .eraseToAnyPublisher()
 
 
-        broadcaster.register(alertMessageNotificationObserver.self, observer: self)
         Task { await updateGlucoseBadge() }
         Task { await updateGlucoseBadge() }
         configureNotificationCategories()
         configureNotificationCategories()
         clearLegacyCarbsRequiredNotification()
         clearLegacyCarbsRequiredNotification()
@@ -210,96 +194,6 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     @MainActor func applySnooze(for duration: TimeInterval) async {
     @MainActor func applySnooze(for duration: TimeInterval) async {
         await trioAlertManager.applySnooze(for: duration)
         await trioAlertManager.applySnooze(for: duration)
     }
     }
-
-    private func addRequest(
-        identifier: Identifier,
-        content: UNMutableNotificationContent,
-        deleteOld: Bool = false,
-        trigger: UNNotificationTrigger? = nil,
-        messageType: MessageType = MessageType.other,
-        messageSubtype: MessageSubtype = MessageSubtype.misc,
-        action: NotificationAction = NotificationAction.none
-    ) {
-        let messageCont = MessageContent(
-            content: content.body,
-            type: messageType,
-            subtype: messageSubtype,
-            title: content.title,
-            useAPN: false,
-            trigger: trigger,
-            action: action
-        )
-        var alertIdentifier = identifier.rawValue
-        if identifier == .alertMessageNotification {
-            alertIdentifier += content.body
-        }
-        if deleteOld {
-            DispatchQueue.main.async {
-                self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [alertIdentifier])
-                self.notificationCenter.removePendingNotificationRequests(withIdentifiers: [alertIdentifier])
-            }
-        }
-        if alertPermissionsChecker.notificationsDisabled {
-            router.alertMessage.send(messageCont)
-            return
-        }
-
-        let request = UNNotificationRequest(identifier: alertIdentifier, content: content, trigger: trigger)
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-            self.notificationCenter.add(request) { error in
-                if let error = error {
-                    warning(.service, "Unable to addNotificationRequest", error: error)
-                    return
-                }
-
-                debug(.service, "Sending \(identifier) notification for \(request.content.title)")
-            }
-        }
-    }
-}
-
-extension BaseUserNotificationsManager: alertMessageNotificationObserver {
-    func alertMessageNotification(_ message: MessageContent) {
-        let content = UNMutableNotificationContent()
-        // Pump / algorithm / glucose / carb subtypes used to route to dedicated
-        // UN identifiers — all of those have moved into the unified
-        // `TrioAlertManager` pipeline.
-        let identifier: Identifier = .alertMessageNotification
-
-        if message.title == "" {
-            switch message.type {
-            case .info:
-                content.title = String(localized: "Info", comment: "Info title")
-            case .warning:
-                content.title = String(localized: "Warning", comment: "Warning title")
-            case .error:
-                content.title = String(localized: "Error", comment: "Error title")
-            default:
-                content.title = message.title
-            }
-        } else {
-            content.title = message.title
-        }
-        switch message.action {
-        case .snooze:
-            content.userInfo[NotificationAction.key] = NotificationAction.snooze.rawValue
-        case .pumpConfig:
-            content.userInfo[NotificationAction.key] = NotificationAction.pumpConfig.rawValue
-        default: break
-        }
-
-        content.body = String(localized: "\(message.content)", comment: "Info message")
-        content.sound = .default
-        addRequest(
-            identifier: identifier,
-            content: content,
-            deleteOld: true,
-            trigger: message.trigger,
-            messageType: message.type,
-            messageSubtype: message.subtype,
-            action: message.action
-        )
-    }
 }
 }
 
 
 extension BaseUserNotificationsManager: UNUserNotificationCenterDelegate {
 extension BaseUserNotificationsManager: UNUserNotificationCenterDelegate {
@@ -331,37 +225,12 @@ extension BaseUserNotificationsManager: UNUserNotificationCenterDelegate {
             return
             return
         }
         }
 
 
-        // Handle quick snooze actions (from notification action buttons)
+        // Handle quick snooze actions (from notification action buttons).
         if let quickAction = NotificationResponseAction(rawValue: response.actionIdentifier) {
         if let quickAction = NotificationResponseAction(rawValue: response.actionIdentifier) {
             Task { @MainActor [weak self] in
             Task { @MainActor [weak self] in
                 guard let self else { return }
                 guard let self else { return }
                 await self.applySnooze(for: quickAction.duration)
                 await self.applySnooze(for: quickAction.duration)
             }
             }
-            return
-        }
-
-        // Handle other notification actions (e.g., tapping notification body)
-        guard let actionRaw = response.notification.request.content.userInfo[NotificationAction.key] as? String,
-              let action = NotificationAction(rawValue: actionRaw)
-        else { return }
-
-        // Ensure UI operations happen on main thread using Task for consistency
-        Task { @MainActor [weak self] in
-            guard let self = self else { return }
-            switch action {
-            case .snooze:
-                self.router.mainModalScreen.send(.snooze)
-            case .pumpConfig:
-                let messageCont = MessageContent(
-                    content: response.notification.request.content.body,
-                    type: MessageType.other,
-                    subtype: .pump,
-                    useAPN: false,
-                    action: .pumpConfig
-                )
-                self.router.alertMessage.send(messageCont)
-            default: break
-            }
         }
         }
     }
     }
 }
 }