mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 16:45:19 +00:00
Liquid Glass theme with in-app appearance switcher (#1337)
* 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>
This commit is contained in:
co-authored by
jack
Claude Fable 5
parent
c8381737bb
commit
af954b05ea
@@ -9,11 +9,11 @@ struct LocationChannelsSheet: View {
|
||||
@Binding var isPresented: Bool
|
||||
@EnvironmentObject private var locationChannelsModel: LocationChannelsModel
|
||||
@EnvironmentObject private var peerListModel: PeerListModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@ThemedPalette private var palette
|
||||
@State private var customGeohash: String = ""
|
||||
@State private var customError: String? = nil
|
||||
|
||||
private var backgroundColor: Color { colorScheme == .dark ? .black : .white }
|
||||
private var backgroundColor: Color { palette.background }
|
||||
|
||||
private enum Strings {
|
||||
static let title: LocalizedStringKey = "location_channels.title"
|
||||
@@ -97,12 +97,12 @@ struct LocationChannelsSheet: View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
Text(Strings.title)
|
||||
.font(.bitchatSystem(size: 18, design: .monospaced))
|
||||
.bitchatFont(size: 18)
|
||||
Spacer()
|
||||
closeButton
|
||||
}
|
||||
Text(Strings.description)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Group {
|
||||
@@ -110,7 +110,7 @@ struct LocationChannelsSheet: View {
|
||||
case .notDetermined:
|
||||
Button(action: { locationChannelsModel.enableLocationChannels() }) {
|
||||
Text(Strings.requestPermissions)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(standardGreen)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 6)
|
||||
@@ -121,7 +121,7 @@ struct LocationChannelsSheet: View {
|
||||
case .denied, .restricted:
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(Strings.permissionDenied)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(.secondary)
|
||||
Button(Strings.openSettings, action: SystemSettings.location.open)
|
||||
.buttonStyle(.plain)
|
||||
@@ -136,7 +136,7 @@ struct LocationChannelsSheet: View {
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
.background(backgroundColor)
|
||||
.themedSurface()
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarHidden(true)
|
||||
@@ -147,7 +147,7 @@ struct LocationChannelsSheet: View {
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 420, minHeight: 520)
|
||||
#endif
|
||||
.background(backgroundColor)
|
||||
.themedSheetBackground()
|
||||
.onAppear {
|
||||
// Refresh channels when opening
|
||||
if locationChannelsModel.permissionState == .authorized {
|
||||
@@ -171,7 +171,7 @@ struct LocationChannelsSheet: View {
|
||||
private var closeButton: some View {
|
||||
Button(action: { isPresented = false }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
|
||||
.bitchatFont(size: 13, weight: .semibold)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -223,7 +223,7 @@ struct LocationChannelsSheet: View {
|
||||
HStack(spacing: 8) {
|
||||
ProgressView()
|
||||
Text(Strings.loadingNearby)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.bitchatFont(size: 12)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 10)
|
||||
@@ -246,8 +246,8 @@ struct LocationChannelsSheet: View {
|
||||
.padding(.top, 12)
|
||||
Button(action: SystemSettings.location.open) {
|
||||
Text(Strings.removeAccess)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(Color(red: 0.75, green: 0.1, blue: 0.1))
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(palette.alertRed)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color.red.opacity(0.08))
|
||||
@@ -259,9 +259,9 @@ struct LocationChannelsSheet: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 6)
|
||||
.background(backgroundColor)
|
||||
.themedSurface()
|
||||
}
|
||||
.background(backgroundColor)
|
||||
.themedSurface()
|
||||
}
|
||||
|
||||
private var sectionDivider: some View {
|
||||
@@ -270,15 +270,13 @@ struct LocationChannelsSheet: View {
|
||||
.frame(height: 1)
|
||||
}
|
||||
|
||||
private var dividerColor: Color {
|
||||
colorScheme == .dark ? Color.white.opacity(0.12) : Color.black.opacity(0.08)
|
||||
}
|
||||
private var dividerColor: Color { palette.divider }
|
||||
|
||||
private var customTeleportSection: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 2) {
|
||||
Text(verbatim: "#")
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.bitchatFont(size: 14)
|
||||
.foregroundColor(.secondary)
|
||||
TextField("geohash", text: $customGeohash)
|
||||
#if os(iOS)
|
||||
@@ -286,7 +284,7 @@ struct LocationChannelsSheet: View {
|
||||
.autocorrectionDisabled(true)
|
||||
.keyboardType(.asciiCapable)
|
||||
#endif
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.bitchatFont(size: 14)
|
||||
.onChange(of: customGeohash) { newValue in
|
||||
let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz")
|
||||
let filtered = newValue
|
||||
@@ -312,13 +310,13 @@ struct LocationChannelsSheet: View {
|
||||
}) {
|
||||
HStack(spacing: 6) {
|
||||
Text(Strings.teleport)
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.bitchatFont(size: 14)
|
||||
Image(systemName: "face.dashed")
|
||||
.font(.bitchatSystem(size: 14))
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.bitchatFont(size: 14)
|
||||
.padding(.vertical, 6)
|
||||
.padding(.horizontal, 10)
|
||||
.background(Color.secondary.opacity(0.12))
|
||||
@@ -328,7 +326,7 @@ struct LocationChannelsSheet: View {
|
||||
}
|
||||
if let err = customError {
|
||||
Text(err)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
@@ -337,7 +335,7 @@ struct LocationChannelsSheet: View {
|
||||
private func bookmarkedSection(_ entries: [String]) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(Strings.bookmarked)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(.secondary)
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(Array(entries.enumerated()), id: \.offset) { index, gh in
|
||||
@@ -409,18 +407,18 @@ struct LocationChannelsSheet: View {
|
||||
let parts = splitTitleAndCount(title)
|
||||
HStack(spacing: 4) {
|
||||
Text(parts.base)
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.bitchatFont(size: 14)
|
||||
.fontWeight(titleBold ? .bold : .regular)
|
||||
.foregroundColor(titleColor ?? Color.primary)
|
||||
if let count = parts.countSuffix, !count.isEmpty {
|
||||
Text(count)
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.bitchatFont(size: 11)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
let subtitleFull = Strings.subtitle(prefix: subtitlePrefix, name: subtitleName)
|
||||
Text(subtitleFull)
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
@@ -428,7 +426,7 @@ struct LocationChannelsSheet: View {
|
||||
Spacer()
|
||||
if isSelected {
|
||||
Text(verbatim: "✔︎")
|
||||
.font(.bitchatSystem(size: 16, design: .monospaced))
|
||||
.bitchatFont(size: 16)
|
||||
.foregroundColor(standardGreen)
|
||||
}
|
||||
trailingAccessory()
|
||||
@@ -478,26 +476,22 @@ extension LocationChannelsSheet {
|
||||
Toggle(isOn: torToggleBinding) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(Strings.torTitle)
|
||||
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
|
||||
.bitchatFont(size: 12, weight: .semibold)
|
||||
.foregroundColor(.primary)
|
||||
Text(Strings.torSubtitle)
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.bitchatFont(size: 11)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.toggleStyle(IRCToggleStyle(accent: standardGreen, onLabel: Strings.toggleOn, offLabel: Strings.toggleOff))
|
||||
.toggleStyle(IRCToggleStyle(accent: palette.accent, onLabel: Strings.toggleOn, offLabel: Strings.toggleOff))
|
||||
}
|
||||
.padding(12)
|
||||
.background(Color.secondary.opacity(0.12))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
|
||||
private var standardGreen: Color {
|
||||
(colorScheme == .dark) ? Color.green : Color(red: 0, green: 0.5, blue: 0)
|
||||
}
|
||||
private var standardBlue: Color {
|
||||
Color(red: 0.0, green: 0.478, blue: 1.0)
|
||||
}
|
||||
private var standardGreen: Color { palette.primary }
|
||||
private var standardBlue: Color { palette.accentBlue }
|
||||
}
|
||||
|
||||
private struct IRCToggleStyle: ToggleStyle {
|
||||
@@ -512,7 +506,7 @@ private struct IRCToggleStyle: ToggleStyle {
|
||||
Spacer()
|
||||
Text(configuration.isOn ? onLabel : offLabel)
|
||||
.textCase(.uppercase)
|
||||
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
|
||||
.bitchatFont(size: 12, weight: .semibold)
|
||||
.foregroundColor(configuration.isOn ? accent : .secondary)
|
||||
.padding(.vertical, 4)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
Reference in New Issue
Block a user