105 lines
3.7 KiB
Swift
105 lines
3.7 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - Row Background Environment Key
|
|
|
|
/// Lets parent views communicate their background color to descendants
|
|
/// so that elements like the online-indicator border can match dynamically.
|
|
private struct RowBackgroundColorKey: EnvironmentKey {
|
|
static let defaultValue: Color? = nil
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
var rowBackgroundColor: Color? {
|
|
get { self[RowBackgroundColorKey.self] }
|
|
set { self[RowBackgroundColorKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
// MARK: - AvatarView
|
|
|
|
struct AvatarView: View {
|
|
let initials: String
|
|
let colorIndex: Int
|
|
let size: CGFloat
|
|
var isOnline: Bool = false
|
|
var isSavedMessages: Bool = false
|
|
/// Override for the online-indicator border (matches row background).
|
|
var onlineBorderColor: Color?
|
|
|
|
@Environment(\.colorScheme) private var colorScheme
|
|
@Environment(\.rowBackgroundColor) private var rowBackgroundColor
|
|
|
|
private var avatarPair: (tint: Color, text: Color) {
|
|
RosettaColors.avatarColors[colorIndex % RosettaColors.avatarColors.count]
|
|
}
|
|
|
|
/// Mantine "light" variant: shade-3 in dark, shade-6 (tint) in light.
|
|
private var textColor: Color {
|
|
colorScheme == .dark ? avatarPair.text : avatarPair.tint
|
|
}
|
|
|
|
private var fontSize: CGFloat { size * 0.38 }
|
|
/// Desktop parity: 12px dot on 50px avatar = 24%.
|
|
private var badgeSize: CGFloat { size * 0.24 }
|
|
/// Desktop parity: 2px border on 50px avatar = 4%.
|
|
private var badgeBorderWidth: CGFloat { max(size * 0.04, 1.5) }
|
|
|
|
/// Mantine dark body background (#1A1B1E).
|
|
private static let mantineDarkBody = Color(hex: 0x1A1B1E)
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if isSavedMessages {
|
|
Circle().fill(RosettaColors.primaryBlue)
|
|
} else {
|
|
// Mantine "light" variant: opaque base + semi-transparent tint
|
|
Circle().fill(colorScheme == .dark ? Self.mantineDarkBody : .white)
|
|
Circle().fill(avatarPair.tint.opacity(colorScheme == .dark ? 0.15 : 0.10))
|
|
}
|
|
|
|
if isSavedMessages {
|
|
Image(systemName: "bookmark.fill")
|
|
.font(.system(size: fontSize, weight: .semibold))
|
|
.foregroundStyle(.white)
|
|
} else {
|
|
Text(initials)
|
|
.font(.system(size: fontSize, weight: .bold, design: .rounded))
|
|
.foregroundStyle(textColor)
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(0.5)
|
|
}
|
|
}
|
|
.frame(width: size, height: size)
|
|
.overlay(alignment: .bottomTrailing) {
|
|
if isOnline && !isSavedMessages {
|
|
Circle()
|
|
.fill(RosettaColors.primaryBlue)
|
|
.frame(width: badgeSize, height: badgeSize)
|
|
.overlay {
|
|
Circle()
|
|
.stroke(
|
|
onlineBorderColor ?? rowBackgroundColor ?? RosettaColors.Adaptive.background,
|
|
lineWidth: badgeBorderWidth
|
|
)
|
|
}
|
|
.offset(x: 1, y: -1)
|
|
}
|
|
}
|
|
.accessibilityLabel(isSavedMessages ? "Saved Messages" : initials)
|
|
.accessibilityAddTraits(isOnline ? [.isStaticText] : [])
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
HStack(spacing: 16) {
|
|
AvatarView(initials: "AJ", colorIndex: 0, size: 56, isOnline: true)
|
|
AvatarView(initials: "BS", colorIndex: 2, size: 56, isOnline: false)
|
|
AvatarView(initials: "S", colorIndex: 4, size: 56, isSavedMessages: true)
|
|
AvatarView(initials: "CD", colorIndex: 6, size: 40, isOnline: true)
|
|
}
|
|
.padding()
|
|
.background(RosettaColors.Adaptive.background)
|
|
}
|