Add 4 new themes: Nord, Dracula, Solarized Dark, Tokyo Night

This commit is contained in:
pranavharshith 2026-07-25 18:55:33 +05:30
parent ca18843bb0
commit 2471b53459
3 changed files with 148 additions and 24 deletions

View File

@ -1128,6 +1128,50 @@
}
}
},
"app_info.appearance.nord" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "nord"
}
}
}
},
"app_info.appearance.dracula" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "dracula"
}
}
}
},
"app_info.appearance.solarized_dark" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "solarized dark"
}
}
}
},
"app_info.appearance.tokyo_night" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "tokyo night"
}
}
}
},
"app_info.appearance.title" : {
"extractionState" : "manual",
"localizations" : {

View File

@ -12,6 +12,10 @@ import SwiftUI
enum AppTheme: String, CaseIterable, Identifiable {
case matrix
case liquidGlass
case nord
case dracula
case solarizedDark
case tokyoNight
var id: String { rawValue }
@ -22,6 +26,10 @@ enum AppTheme: String, CaseIterable, Identifiable {
switch self {
case .matrix: return "app_info.appearance.matrix"
case .liquidGlass: return "app_info.appearance.liquid_glass"
case .nord: return "app_info.appearance.nord"
case .dracula: return "app_info.appearance.dracula"
case .solarizedDark: return "app_info.appearance.solarized_dark"
case .tokyoNight: return "app_info.appearance.tokyo_night"
}
}
@ -31,13 +39,20 @@ enum AppTheme: String, CaseIterable, Identifiable {
switch self {
case .matrix: return .monospaced
case .liquidGlass: return .default
case .nord: return .default
case .dracula: return .monospaced
case .solarizedDark: return .monospaced
case .tokyoNight: return .default
}
}
/// Whether chrome surfaces (header/composer bars, input field) render as
/// translucent glass/material instead of the flat matrix background.
var usesGlassChrome: Bool {
self == .liquidGlass
switch self {
case .liquidGlass: return true
case .matrix, .nord, .dracula, .solarizedDark, .tokyoNight: return false
}
}
/// Discriminator mixed into per-message formatting caches so cached
@ -47,16 +62,22 @@ enum AppTheme: String, CaseIterable, Identifiable {
switch self {
case .matrix: return ""
case .liquidGlass: return "lg:"
case .nord: return "nd:"
case .dracula: return "dc:"
case .solarizedDark: return "sd:"
case .tokyoNight: return "tn:"
}
}
/// Resolves the semantic color palette for this theme under the given color scheme.
func palette(for colorScheme: ColorScheme) -> ThemePalette {
switch self {
case .matrix:
return .matrix(colorScheme)
case .liquidGlass:
return .liquidGlass(colorScheme)
case .matrix: return .matrix(colorScheme)
case .liquidGlass: return .liquidGlass(colorScheme)
case .nord: return .nord(colorScheme)
case .dracula: return .dracula(colorScheme)
case .solarizedDark: return .solarizedDark(colorScheme)
case .tokyoNight: return .tokyoNight(colorScheme)
}
}
}
@ -109,6 +130,62 @@ struct ThemePalette {
)
}
static func nord(_ colorScheme: ColorScheme) -> ThemePalette {
let isDark = colorScheme == .dark
return ThemePalette(
background: isDark ? Color(red: 0.18, green: 0.20, blue: 0.25) : Color(red: 0.93, green: 0.94, blue: 0.96),
primary: isDark ? Color(red: 0.93, green: 0.94, blue: 0.96) : Color(red: 0.18, green: 0.20, blue: 0.25),
secondary: isDark ? Color(red: 0.85, green: 0.87, blue: 0.91) : Color(red: 0.30, green: 0.34, blue: 0.40),
accent: isDark ? Color(red: 0.53, green: 0.75, blue: 0.82) : Color(red: 0.37, green: 0.51, blue: 0.67),
locationAccent: isDark ? Color(red: 0.64, green: 0.75, blue: 0.55) : Color(red: 0.64, green: 0.75, blue: 0.55),
accentBlue: isDark ? Color(red: 0.51, green: 0.63, blue: 0.76) : Color(red: 0.51, green: 0.63, blue: 0.76),
alertRed: isDark ? Color(red: 0.75, green: 0.38, blue: 0.42) : Color(red: 0.75, green: 0.38, blue: 0.42),
divider: isDark ? Color(red: 0.30, green: 0.34, blue: 0.40) : Color(red: 0.85, green: 0.87, blue: 0.91)
)
}
static func dracula(_ colorScheme: ColorScheme) -> ThemePalette {
let isDark = colorScheme == .dark
return ThemePalette(
background: isDark ? Color(red: 0.16, green: 0.16, blue: 0.21) : Color(red: 0.97, green: 0.97, blue: 0.95),
primary: isDark ? Color(red: 0.97, green: 0.97, blue: 0.95) : Color(red: 0.16, green: 0.16, blue: 0.21),
secondary: isDark ? Color(red: 0.38, green: 0.45, blue: 0.64) : Color(red: 0.38, green: 0.45, blue: 0.64),
accent: isDark ? Color(red: 1.0, green: 0.47, blue: 0.78) : Color(red: 0.80, green: 0.28, blue: 0.56),
locationAccent: isDark ? Color(red: 0.31, green: 0.98, blue: 0.48) : Color(red: 0.22, green: 0.60, blue: 0.33),
accentBlue: isDark ? Color(red: 0.55, green: 0.91, blue: 0.99) : Color(red: 0.27, green: 0.73, blue: 0.83),
alertRed: isDark ? Color(red: 1.0, green: 0.33, blue: 0.33) : Color(red: 0.82, green: 0.18, blue: 0.18),
divider: isDark ? Color(red: 0.27, green: 0.28, blue: 0.35) : Color(red: 0.88, green: 0.88, blue: 0.88)
)
}
static func solarizedDark(_ colorScheme: ColorScheme) -> ThemePalette {
let isDark = colorScheme == .dark
return ThemePalette(
background: isDark ? Color(red: 0.0, green: 0.17, blue: 0.21) : Color(red: 0.99, green: 0.96, blue: 0.89),
primary: isDark ? Color(red: 0.51, green: 0.58, blue: 0.59) : Color(red: 0.40, green: 0.48, blue: 0.51),
secondary: isDark ? Color(red: 0.40, green: 0.48, blue: 0.51) : Color(red: 0.51, green: 0.58, blue: 0.59),
accent: isDark ? Color(red: 0.16, green: 0.63, blue: 0.60) : Color(red: 0.16, green: 0.63, blue: 0.60),
locationAccent: isDark ? Color(red: 0.52, green: 0.60, blue: 0.0) : Color(red: 0.52, green: 0.60, blue: 0.0),
accentBlue: isDark ? Color(red: 0.15, green: 0.55, blue: 0.82) : Color(red: 0.15, green: 0.55, blue: 0.82),
alertRed: isDark ? Color(red: 0.86, green: 0.20, blue: 0.18) : Color(red: 0.86, green: 0.20, blue: 0.18),
divider: isDark ? Color(red: 0.03, green: 0.24, blue: 0.26) : Color(red: 0.93, green: 0.91, blue: 0.84)
)
}
static func tokyoNight(_ colorScheme: ColorScheme) -> ThemePalette {
let isDark = colorScheme == .dark
return ThemePalette(
background: isDark ? Color(red: 0.10, green: 0.11, blue: 0.15) : Color(red: 0.84, green: 0.84, blue: 0.86),
primary: isDark ? Color(red: 0.66, green: 0.69, blue: 0.84) : Color(red: 0.22, green: 0.38, blue: 0.75),
secondary: isDark ? Color(red: 0.34, green: 0.37, blue: 0.54) : Color(red: 0.52, green: 0.55, blue: 0.71),
accent: isDark ? Color(red: 0.49, green: 0.81, blue: 1.0) : Color(red: 0.18, green: 0.49, blue: 0.91),
locationAccent: isDark ? Color(red: 0.62, green: 0.81, blue: 0.42) : Color(red: 0.35, green: 0.46, blue: 0.22),
accentBlue: isDark ? Color(red: 0.48, green: 0.64, blue: 0.97) : Color(red: 0.18, green: 0.49, blue: 0.91),
alertRed: isDark ? Color(red: 0.97, green: 0.46, blue: 0.56) : Color(red: 0.78, green: 0.26, blue: 0.26),
divider: isDark ? Color(red: 0.18, green: 0.24, blue: 0.39) : Color(red: 0.71, green: 0.71, blue: 0.77)
)
}
private static var systemBackground: Color {
#if os(iOS)
Color(UIColor.systemBackground)

View File

@ -298,27 +298,30 @@ struct AppInfoView: View {
@ViewBuilder
private var settingsContent: some View {
VStack(alignment: .leading, spacing: 24) {
// Appearance single row: label left, theme chips right
HStack(spacing: 12) {
// Appearance label above theme chips (scrollable for many themes)
VStack(alignment: .leading, spacing: 8) {
SectionHeader(Strings.appearanceTitle)
Spacer()
ForEach(AppTheme.allCases) { theme in
Button {
appThemeRawValue = theme.rawValue
} label: {
Text(theme.displayNameKey)
.bitchatFont(size: 13, weight: selectedTheme == theme ? .semibold : .regular)
.foregroundColor(selectedTheme == theme ? palette.accent : secondaryTextColor)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(selectedTheme == theme ? palette.accent.opacity(0.15) : Color.clear)
)
.contentShape(Rectangle())
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(AppTheme.allCases) { theme in
Button {
appThemeRawValue = theme.rawValue
} label: {
Text(theme.displayNameKey)
.bitchatFont(size: 13, weight: selectedTheme == theme ? .semibold : .regular)
.foregroundColor(selectedTheme == theme ? palette.accent : secondaryTextColor)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(selectedTheme == theme ? palette.accent.opacity(0.15) : Color.clear)
)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityAddTraits(selectedTheme == theme ? .isSelected : [])
}
}
.buttonStyle(.plain)
.accessibilityAddTraits(selectedTheme == theme ? .isSelected : [])
}
}