Push-уведомления: Telegram-parity in-app баннер, threadIdentifier группировка и letter-avatar в NSE
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
import Intents
|
||||
|
||||
@@ -161,7 +162,13 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Normalize sender_public_key in userInfo for tap navigation.
|
||||
// 5. Group notifications by conversation (Telegram parity).
|
||||
// iOS stacks notifications from the same chat together.
|
||||
if !senderKey.isEmpty {
|
||||
content.threadIdentifier = senderKey
|
||||
}
|
||||
|
||||
// 6. Normalize sender_public_key in userInfo for tap navigation.
|
||||
var updatedInfo = content.userInfo
|
||||
if !senderKey.isEmpty {
|
||||
updatedInfo["sender_public_key"] = senderKey
|
||||
@@ -217,11 +224,13 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
senderKey: String
|
||||
) -> UNNotificationContent {
|
||||
let handle = INPersonHandle(value: senderKey, type: .unknown)
|
||||
let displayName = senderName.isEmpty ? "Rosetta" : senderName
|
||||
let avatarImage = generateLetterAvatar(name: displayName, key: senderKey)
|
||||
let sender = INPerson(
|
||||
personHandle: handle,
|
||||
nameComponents: nil,
|
||||
displayName: senderName.isEmpty ? "Rosetta" : senderName,
|
||||
image: nil,
|
||||
displayName: displayName,
|
||||
image: avatarImage,
|
||||
contactIdentifier: nil,
|
||||
customIdentifier: senderKey
|
||||
)
|
||||
@@ -237,6 +246,11 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
attachments: nil
|
||||
)
|
||||
|
||||
// Set avatar on sender parameter (Telegram parity: 50x50 letter avatar).
|
||||
if let avatarImage {
|
||||
intent.setImage(avatarImage, forParameterNamed: \.sender)
|
||||
}
|
||||
|
||||
// Donate the intent so Siri can learn communication patterns.
|
||||
let interaction = INInteraction(intent: intent, response: nil)
|
||||
interaction.direction = .incoming
|
||||
@@ -254,6 +268,68 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Letter Avatar (Telegram parity: colored circle with initials)
|
||||
|
||||
/// Mantine avatar color palette — matches AvatarView in main app.
|
||||
private static let avatarColors: [(bg: UInt32, text: UInt32)] = [
|
||||
(0x4C6EF5, 0xDBE4FF), // indigo
|
||||
(0x7950F2, 0xE5DBFF), // violet
|
||||
(0xF06595, 0xFFDEEB), // pink
|
||||
(0xFF6B6B, 0xFFE3E3), // red
|
||||
(0xFD7E14, 0xFFE8CC), // orange
|
||||
(0xFAB005, 0xFFF3BF), // yellow
|
||||
(0x40C057, 0xD3F9D8), // green
|
||||
(0x12B886, 0xC3FAE8), // teal
|
||||
(0x15AABF, 0xC5F6FA), // cyan
|
||||
(0x228BE6, 0xD0EBFF), // blue
|
||||
(0xBE4BDB, 0xF3D9FA), // grape
|
||||
]
|
||||
|
||||
/// Generates a 50x50 circular letter avatar as INImage for notification display.
|
||||
private static func generateLetterAvatar(name: String, key: String) -> INImage? {
|
||||
let size: CGFloat = 50
|
||||
let colorIndex = abs(key.hashValue) % avatarColors.count
|
||||
let colors = avatarColors[colorIndex]
|
||||
let initial = String(name.prefix(1)).uppercased()
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: size, height: size), false, 2.0)
|
||||
guard let ctx = UIGraphicsGetCurrentContext() else { return nil }
|
||||
|
||||
// Background circle.
|
||||
let bgColor = UIColor(
|
||||
red: CGFloat((colors.bg >> 16) & 0xFF) / 255,
|
||||
green: CGFloat((colors.bg >> 8) & 0xFF) / 255,
|
||||
blue: CGFloat(colors.bg & 0xFF) / 255,
|
||||
alpha: 1
|
||||
)
|
||||
ctx.setFillColor(bgColor.cgColor)
|
||||
ctx.fillEllipse(in: CGRect(x: 0, y: 0, width: size, height: size))
|
||||
|
||||
// Initial letter.
|
||||
let textColor = UIColor(
|
||||
red: CGFloat((colors.text >> 16) & 0xFF) / 255,
|
||||
green: CGFloat((colors.text >> 8) & 0xFF) / 255,
|
||||
blue: CGFloat(colors.text & 0xFF) / 255,
|
||||
alpha: 1
|
||||
)
|
||||
let font = UIFont.systemFont(ofSize: size * 0.38, weight: .bold)
|
||||
let attrs: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: textColor]
|
||||
let textSize = (initial as NSString).size(withAttributes: attrs)
|
||||
let textRect = CGRect(
|
||||
x: (size - textSize.width) / 2,
|
||||
y: (size - textSize.height) / 2,
|
||||
width: textSize.width,
|
||||
height: textSize.height
|
||||
)
|
||||
(initial as NSString).draw(in: textRect, withAttributes: attrs)
|
||||
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
guard let pngData = image?.pngData() else { return nil }
|
||||
return INImage(imageData: pngData)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Android parity: extract sender key from multiple possible key names.
|
||||
|
||||
Reference in New Issue
Block a user