Files
mobile-ios/Rosetta/DesignSystem/Components/AvatarView.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)
}