Reply-бар: Telegram-parity стилизация, alignment, preview-текст и cross-platform аудит

This commit is contained in:
2026-03-29 19:39:08 +05:00
parent 469f182155
commit 44a74ad327
11 changed files with 541 additions and 70 deletions

View File

@@ -2,21 +2,31 @@ import ActivityKit
import SwiftUI
import WidgetKit
// Mantine v8 avatar tint colors (shade-6) exact hex values, desktop parity
private let mantineTints: [Color] = [
Color(red: 34/255, green: 139/255, blue: 230/255), // #228be6 blue
Color(red: 21/255, green: 170/255, blue: 191/255), // #15aabf cyan
Color(red: 190/255, green: 75/255, blue: 219/255), // #be4bdb grape
Color(red: 64/255, green: 192/255, blue: 87/255), // #40c057 green
Color(red: 76/255, green: 110/255, blue: 245/255), // #4c6ef5 indigo
Color(red: 130/255, green: 201/255, blue: 30/255), // #82c91e lime
Color(red: 253/255, green: 126/255, blue: 20/255), // #fd7e14 orange
Color(red: 230/255, green: 73/255, blue: 128/255), // #e64980 pink
Color(red: 250/255, green: 82/255, blue: 82/255), // #fa5252 red
Color(red: 18/255, green: 184/255, blue: 134/255), // #12b886 teal
Color(red: 121/255, green: 80/255, blue: 242/255), // #7950f2 violet
// Mantine v8 avatar palette exact hex parity with RosettaColors.avatarColors
// tint = shade-6, text = shade-3
private struct AvatarPalette {
let tint: Color
let text: Color
}
private let mantinePalette: [AvatarPalette] = [
AvatarPalette(tint: Color(red: 0x22/255, green: 0x8B/255, blue: 0xE6/255), text: Color(red: 0x74/255, green: 0xC0/255, blue: 0xFC/255)), // blue
AvatarPalette(tint: Color(red: 0x15/255, green: 0xAA/255, blue: 0xBF/255), text: Color(red: 0x66/255, green: 0xD9/255, blue: 0xE8/255)), // cyan
AvatarPalette(tint: Color(red: 0xBE/255, green: 0x4B/255, blue: 0xDB/255), text: Color(red: 0xE5/255, green: 0x99/255, blue: 0xF7/255)), // grape
AvatarPalette(tint: Color(red: 0x40/255, green: 0xC0/255, blue: 0x57/255), text: Color(red: 0x8C/255, green: 0xE9/255, blue: 0x9A/255)), // green
AvatarPalette(tint: Color(red: 0x4C/255, green: 0x6E/255, blue: 0xF5/255), text: Color(red: 0x91/255, green: 0xA7/255, blue: 0xFF/255)), // indigo
AvatarPalette(tint: Color(red: 0x82/255, green: 0xC9/255, blue: 0x1E/255), text: Color(red: 0xC0/255, green: 0xEB/255, blue: 0x75/255)), // lime
AvatarPalette(tint: Color(red: 0xFD/255, green: 0x7E/255, blue: 0x14/255), text: Color(red: 0xFF/255, green: 0xC0/255, blue: 0x78/255)), // orange
AvatarPalette(tint: Color(red: 0xE6/255, green: 0x49/255, blue: 0x80/255), text: Color(red: 0xFA/255, green: 0xA2/255, blue: 0xC1/255)), // pink
AvatarPalette(tint: Color(red: 0xFA/255, green: 0x52/255, blue: 0x52/255), text: Color(red: 0xFF/255, green: 0xA8/255, blue: 0xA8/255)), // red
AvatarPalette(tint: Color(red: 0x12/255, green: 0xB8/255, blue: 0x86/255), text: Color(red: 0x63/255, green: 0xE6/255, blue: 0xBE/255)), // teal
AvatarPalette(tint: Color(red: 0x79/255, green: 0x50/255, blue: 0xF2/255), text: Color(red: 0xB1/255, green: 0x97/255, blue: 0xFC/255)), // violet
]
// Mantine dark body background
private let mantineDarkBody = Color(red: 0x1A/255, green: 0x1B/255, blue: 0x1E/255)
private let appGroupID = "group.com.rosetta.dev"
@main
@@ -118,7 +128,7 @@ struct CallLiveActivity: Widget {
.padding(.top, 4)
}
} compactLeading: {
avatarView(context: context, size: 26, fontSize: 10)
avatarView(context: context, size: 26, fontSize: 9)
} compactTrailing: {
if context.state.isActive {
Text(fmt(context.state.durationSec))
@@ -137,36 +147,34 @@ struct CallLiveActivity: Widget {
}
}
// MARK: - Avatar
// MARK: - Avatar (Mantine "light" dark variant matches AvatarView in main app)
@ViewBuilder
private func avatarView(context: ActivityViewContext<CallActivityAttributes>, size: CGFloat, fontSize: CGFloat) -> some View {
if let img = loadSharedAvatar() {
if let data = context.attributes.avatarThumb,
let img = UIImage(data: data) {
Image(uiImage: img)
.resizable()
.scaledToFill()
.frame(width: size, height: size)
.clipShape(Circle())
} else {
let idx = min(context.attributes.colorIndex, mantineTints.count - 1)
let idx = min(max(context.attributes.colorIndex, 0), mantinePalette.count - 1)
let palette = mantinePalette[idx]
ZStack {
Circle().fill(mantineTints[max(0, idx)])
// Base: Mantine dark body
Circle().fill(mantineDarkBody)
// Overlay: tint at 15% opacity (dark mode)
Circle().fill(palette.tint.opacity(0.15))
// Initials: shade-3 text color
Text(makeInitials(context.attributes.peerName, context.attributes.peerPublicKey))
.font(.system(size: fontSize, weight: .bold))
.foregroundColor(.white)
.font(.system(size: fontSize, weight: .bold, design: .rounded))
.foregroundColor(palette.text)
}
.frame(width: size, height: size)
}
}
private func loadSharedAvatar() -> UIImage? {
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)?
.appendingPathComponent("call_avatar.jpg"),
let data = try? Data(contentsOf: url),
let img = UIImage(data: data) else { return nil }
return img
}
// MARK: - Helpers
private func fmt(_ sec: Int) -> String {