mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:05:20 +00:00
* Centralize UI theme colors into semantic ThemePalette tokens Introduce AppTheme/ThemePalette (Utils/Theme.swift) with an environment key and @ThemedPalette property wrapper, persisted via @AppStorage. Views now resolve background/primary/secondary/accentBlue/alertRed/ divider tokens from the environment instead of computing colors inline, removing the backgroundColor/textColor/secondaryTextColor prop-drilling through the header, composer, and people-sheet hierarchy. Matrix theme output is pixel-identical; this is groundwork for a user-selectable theme switcher. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add liquid glass theme with in-app appearance switcher Add a .liquidGlass AppTheme alongside the matrix terminal theme, selectable from a one-line APPEARANCE row in the app info sheet (persisted via @AppStorage, applies live). Liquid glass renders system fonts and colors over a subtle gradient backdrop, with the header and composer floating as Liquid Glass panels (real .glassEffect() on iOS/macOS 26, compiler-gated with an ultraThinMaterial fallback for older SDKs). The message list scrolls underneath the chrome via safe-area insets, and all sheets share the same backdrop and surface language. The matrix theme is unchanged. Details: - Theme is threaded through ChatMessageFormatter so message AttributedStrings switch font design per theme; the per-message format cache gains a variant key so themes never serve each other's cached strings - New palette tokens: accent (interactive tint) and locationAccent (geohash green), replacing scattered hardcoded greens/blues in the voice note, waveform, verification, and people-sheet views - Header controls get full-height tap targets and the people count becomes a real Button (previously a tap gesture on the cluster) - CommandSuggestionsView renders nothing when empty instead of a zero-height view that pushed the composer input off-center - New appearance strings localized for all 29 catalog languages Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
142 lines
4.4 KiB
Swift
142 lines
4.4 KiB
Swift
//
|
|
// DeliveryStatusView.swift
|
|
// bitchat
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import SwiftUI
|
|
import BitFoundation
|
|
|
|
struct DeliveryStatusView: View {
|
|
@ThemedPalette private var palette
|
|
let status: DeliveryStatus
|
|
|
|
// MARK: - Computed Properties
|
|
|
|
private var textColor: Color { palette.primary }
|
|
|
|
private var secondaryTextColor: Color { palette.secondary }
|
|
|
|
private enum Strings {
|
|
static func delivered(to nickname: String) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.delivered_to", comment: "Tooltip for delivered private messages"),
|
|
locale: .current,
|
|
nickname
|
|
)
|
|
}
|
|
|
|
static func read(by nickname: String) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.read_by", comment: "Tooltip for read private messages"),
|
|
locale: .current,
|
|
nickname
|
|
)
|
|
}
|
|
|
|
static func failed(_ reason: String) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.failed", comment: "Tooltip for failed message delivery"),
|
|
locale: .current,
|
|
reason
|
|
)
|
|
}
|
|
|
|
static func deliveredToMembers(_ reached: Int, _ total: Int) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.delivered_members", comment: "Tooltip for partially delivered messages"),
|
|
locale: .current,
|
|
reached,
|
|
total
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
switch status {
|
|
case .sending:
|
|
Image(systemName: "circle")
|
|
.font(.bitchatSystem(size: 10))
|
|
.foregroundColor(secondaryTextColor.opacity(0.6))
|
|
|
|
case .sent:
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
.foregroundColor(secondaryTextColor.opacity(0.6))
|
|
|
|
case .delivered(let nickname, _):
|
|
HStack(spacing: -2) {
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
}
|
|
.foregroundColor(textColor.opacity(0.8))
|
|
.help(Strings.delivered(to: nickname))
|
|
|
|
case .read(let nickname, _):
|
|
HStack(spacing: -2) {
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10, weight: .bold))
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10, weight: .bold))
|
|
}
|
|
.foregroundColor(palette.accentBlue)
|
|
.help(Strings.read(by: nickname))
|
|
|
|
case .failed(let reason):
|
|
Image(systemName: "exclamationmark.triangle")
|
|
.font(.bitchatSystem(size: 10))
|
|
.foregroundColor(Color.red.opacity(0.8))
|
|
.help(Strings.failed(reason))
|
|
|
|
case .partiallyDelivered(let reached, let total):
|
|
HStack(spacing: 1) {
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
Text(verbatim: "\(reached)/\(total)")
|
|
.bitchatFont(size: 10)
|
|
}
|
|
.foregroundColor(secondaryTextColor.opacity(0.6))
|
|
.help(Strings.deliveredToMembers(reached, total))
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
let statuses: [DeliveryStatus] = [
|
|
.sending,
|
|
.sent,
|
|
.delivered(to: "John Doe", at: Date()),
|
|
.read(by: "Jane Doe", at: Date()),
|
|
.failed(reason: "Offline"),
|
|
.partiallyDelivered(reached: 2, total: 5)
|
|
]
|
|
|
|
List {
|
|
ForEach(statuses, id: \.self) { status in
|
|
HStack {
|
|
Text(status.displayText)
|
|
Spacer()
|
|
DeliveryStatusView(status: status)
|
|
}
|
|
}
|
|
}
|
|
.environment(\.colorScheme, .light)
|
|
|
|
List {
|
|
ForEach(statuses, id: \.self) { status in
|
|
HStack {
|
|
Text(status.displayText)
|
|
Spacer()
|
|
DeliveryStatusView(status: status)
|
|
}
|
|
}
|
|
}
|
|
.environment(\.colorScheme, .dark)
|
|
}
|