From c12f9f9b7851eae715ab63e98473d9250aa7ff5e Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Thu, 21 May 2026 15:55:54 +0300 Subject: [PATCH] Add iOS notification scheduler --- .../TeleTuiIOSCore/PlatformServices.swift | 51 +++++++++++++++++++ .../Sources/TeleTuiIOSSmokeTests/main.swift | 11 ++++ 2 files changed, 62 insertions(+) diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/PlatformServices.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/PlatformServices.swift index f3de052..5d3b8fc 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/PlatformServices.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/PlatformServices.swift @@ -1,6 +1,10 @@ import Foundation import AVFoundation +#if canImport(UserNotifications) +import UserNotifications +#endif + #if canImport(UIKit) import UIKit #endif @@ -62,6 +66,49 @@ public protocol NotificationScheduling: Sendable { func schedule(chat: ChatSummary, message: Message) async throws } +public struct SystemNotificationScheduler: NotificationScheduling { + public init() {} + + public func schedule(chat: ChatSummary, message: Message) async throws { + #if canImport(UserNotifications) + let content = UNMutableNotificationContent() + content.title = chat.title + content.body = "\(message.senderName): \(message.text)" + content.sound = .default + + let request = UNNotificationRequest( + identifier: "chat-\(chat.id)-message-\(message.id)", + content: content, + trigger: nil + ) + try await UNUserNotificationCenter.current().add(request) + #endif + } +} + +public struct NotificationCoordinator: Sendable { + public var policy: NotificationPolicy + public var scheduler: NotificationScheduling + public var mentionOnly: Bool + + public init( + policy: NotificationPolicy = NotificationPolicy(), + scheduler: NotificationScheduling, + mentionOnly: Bool = false + ) { + self.policy = policy + self.scheduler = scheduler + self.mentionOnly = mentionOnly + } + + public func handle(chat: ChatSummary, message: Message) async throws { + guard policy.shouldNotify(chat: chat, message: message, mentionOnly: mentionOnly) else { + return + } + try await scheduler.schedule(chat: chat, message: message) + } +} + public actor RecordingNotificationScheduler: NotificationScheduling { public private(set) var scheduled: [(ChatSummary, Message)] = [] @@ -70,6 +117,10 @@ public actor RecordingNotificationScheduler: NotificationScheduling { public func schedule(chat: ChatSummary, message: Message) async throws { scheduled.append((chat, message)) } + + public func scheduledCount() -> Int { + scheduled.count + } } public protocol URLOpening: Sendable { diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift index a908473..d7da3cd 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift @@ -161,6 +161,17 @@ struct TeleTuiIOSSmokeTests { precondition(policy.shouldNotify(chat: chat, message: incomingMention, mentionOnly: true)) precondition(!policy.shouldNotify(chat: chat, message: incomingPlain, mentionOnly: true)) precondition(!policy.shouldNotify(chat: muted, message: incomingMention, mentionOnly: false)) + let scheduler = RecordingNotificationScheduler() + let mentionCoordinator = NotificationCoordinator(scheduler: scheduler, mentionOnly: true) + try await mentionCoordinator.handle(chat: chat, message: incomingPlain) + var scheduledCount = await scheduler.scheduledCount() + precondition(scheduledCount == 0) + try await mentionCoordinator.handle(chat: chat, message: incomingMention) + scheduledCount = await scheduler.scheduledCount() + precondition(scheduledCount == 1) + try await mentionCoordinator.handle(chat: muted, message: incomingMention) + scheduledCount = await scheduler.scheduledCount() + precondition(scheduledCount == 1) let clipboard = InMemoryClipboardWriter() await clipboard.write(text: "copied")