Уведомления CarPlay, панель вложений с Lottie, фикс reply preview, плавная анимация клавиатуры, стабильность WebSocket

This commit is contained in:
2026-03-22 01:58:13 +05:00
parent 65e5991f97
commit 9289bb2efd
20 changed files with 645 additions and 172 deletions

View File

@@ -1,4 +1,5 @@
import UserNotifications
import Intents
/// Notification Service Extension runs as a separate process even when the main app
/// is terminated. Intercepts push notifications with `mutable-content: 1` and:
@@ -6,6 +7,7 @@ import UserNotifications
/// 2. Increments the app icon badge from shared App Group storage
/// 3. Normalizes sender_public_key in userInfo (Android parity: multi-key fallback)
/// 4. Filters muted chats
/// 5. Creates Communication Notification via INSendMessageIntent (CarPlay + Focus parity)
final class NotificationService: UNNotificationServiceExtension {
private static let appGroupID = "group.com.rosetta.dev"
@@ -76,7 +78,18 @@ final class NotificationService: UNNotificationServiceExtension {
content.categoryIdentifier = "message"
}
contentHandler(content)
// 6. Create Communication Notification via INSendMessageIntent.
// This makes the notification appear on CarPlay and work with Focus filters.
// Apple requires INSendMessageIntent for messaging notifications on CarPlay (iOS 15+).
let senderName = Self.firstNonBlank(content.userInfo, keys: Self.senderNameKeyNames)
?? content.title
let finalContent = Self.makeCommunicationNotification(
content: content,
senderName: senderName,
senderKey: senderKey
)
contentHandler(finalContent)
}
override func serviceExtensionTimeWillExpire() {
@@ -86,6 +99,56 @@ final class NotificationService: UNNotificationServiceExtension {
}
}
// MARK: - Communication Notification (CarPlay + Focus)
/// Wraps the notification content with an INSendMessageIntent so iOS treats it
/// as a Communication Notification. This enables:
/// - Display on CarPlay
/// - Proper grouping in Focus modes
/// - Sender name/avatar in notification UI
private static func makeCommunicationNotification(
content: UNMutableNotificationContent,
senderName: String,
senderKey: String
) -> UNNotificationContent {
let handle = INPersonHandle(value: senderKey, type: .unknown)
let sender = INPerson(
personHandle: handle,
nameComponents: nil,
displayName: senderName.isEmpty ? "Rosetta" : senderName,
image: nil,
contactIdentifier: nil,
customIdentifier: senderKey
)
let intent = INSendMessageIntent(
recipients: nil,
outgoingMessageType: .outgoingMessageText,
content: content.body,
speakableGroupName: nil,
conversationIdentifier: senderKey,
serviceName: "Rosetta",
sender: sender,
attachments: nil
)
// Donate the intent so Siri can learn communication patterns.
let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .incoming
interaction.donate(completion: nil)
// Update the notification content with the intent.
// This returns a new content object that iOS recognizes as a Communication Notification.
do {
let updatedContent = try content.updating(from: intent)
return updatedContent
} catch {
// If updating fails, return original content notification still works,
// just without CarPlay / Communication Notification features.
return content
}
}
// MARK: - Helpers
/// Android parity: extract sender key from multiple possible key names.