Reply-бар: Telegram-parity стилизация, alignment, preview-текст и cross-platform аудит
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user