summaryrefslogtreecommitdiff
path: root/Hutch/App
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-12 19:47:57 -0500
committerChristian Cleberg <[email protected]>2026-04-12 19:47:57 -0500
commit1354f2464180ece6d13296a543a964ddf6d2d393 (patch)
tree5cf6ddb89c950941067e2a1080a150911d5fb4e7 /Hutch/App
parent4ad1b74c5887ddf0445fde29ffacfc6eb5d49b3f (diff)
downloadhutch-1354f2464180ece6d13296a543a964ddf6d2d393.tar.gz
hutch-1354f2464180ece6d13296a543a964ddf6d2d393.tar.bz2
hutch-1354f2464180ece6d13296a543a964ddf6d2d393.zip
feat: add theme and high-density display modes
Refs: https://todo.sr.ht/~ccleberg/hutch/24 Implements: https://todo.sr.ht/~ccleberg/hutch/25
Diffstat (limited to 'Hutch/App')
-rw-r--r--Hutch/App/AppStorageKeys.swift2
-rw-r--r--Hutch/App/HutchApp.swift5
-rw-r--r--Hutch/App/ThemeManager.swift76
3 files changed, 83 insertions, 0 deletions
diff --git a/Hutch/App/AppStorageKeys.swift b/Hutch/App/AppStorageKeys.swift
index ff309d4..eaf54f0 100644
--- a/Hutch/App/AppStorageKeys.swift
+++ b/Hutch/App/AppStorageKeys.swift
@@ -13,4 +13,6 @@ enum AppStorageKeys {
static let homeBuildsExpanded = "homeBuildsExpanded"
static let buildsAutoRefreshInterval = "buildsAutoRefreshInterval"
static let buildsRepoFilter = "buildsRepoFilter"
+ static let appTheme = "appTheme"
+ static let displayDensity = "displayDensity"
}
diff --git a/Hutch/App/HutchApp.swift b/Hutch/App/HutchApp.swift
index e65833c..016ddd0 100644
--- a/Hutch/App/HutchApp.swift
+++ b/Hutch/App/HutchApp.swift
@@ -4,12 +4,17 @@ import SwiftUI
struct HutchApp: App {
@State private var appState = AppState()
@State private var networkMonitor = NetworkMonitor()
+ @AppStorage(AppStorageKeys.appTheme) private var appTheme: AppTheme = .system
+ @AppStorage(AppStorageKeys.displayDensity) private var displayDensity: DisplayDensity = .standard
var body: some Scene {
WindowGroup {
RootView()
.environment(appState)
.environment(networkMonitor)
+ .environment(\.displayDensity, displayDensity)
+ .environment(\.defaultMinListRowHeight, displayDensity == .compact ? 36 : 44)
+ .preferredColorScheme(appTheme.colorScheme)
.onOpenURL { url in
if let link = DeepLink(url: url) {
appState.pendingDeepLink = link
diff --git a/Hutch/App/ThemeManager.swift b/Hutch/App/ThemeManager.swift
new file mode 100644
index 0000000..ee74921
--- /dev/null
+++ b/Hutch/App/ThemeManager.swift
@@ -0,0 +1,76 @@
+import SwiftUI
+
+// MARK: - Theme & Density Types
+
+enum AppTheme: String, CaseIterable, Identifiable {
+ case system
+ case light
+ case dark
+
+ var id: String { rawValue }
+
+ var label: String {
+ switch self {
+ case .system: "System"
+ case .light: "Light"
+ case .dark: "Dark"
+ }
+ }
+
+ var colorScheme: ColorScheme? {
+ switch self {
+ case .system: nil
+ case .light: .light
+ case .dark: .dark
+ }
+ }
+}
+
+enum DisplayDensity: String, CaseIterable, Identifiable {
+ case standard
+ case compact
+
+ var id: String { rawValue }
+
+ var label: String {
+ switch self {
+ case .standard: "Standard"
+ case .compact: "Compact"
+ }
+ }
+}
+
+// MARK: - Environment Keys
+
+private struct DisplayDensityKey: EnvironmentKey {
+ static let defaultValue: DisplayDensity = .standard
+}
+
+extension EnvironmentValues {
+ var displayDensity: DisplayDensity {
+ get { self[DisplayDensityKey.self] }
+ set { self[DisplayDensityKey.self] = newValue }
+ }
+}
+
+// MARK: - Themed List Modifier
+
+/// Combined modifier for List/Form that applies compact section spacing.
+/// Apply once per List or Form.
+struct ThemedListStyle: ViewModifier {
+ @Environment(\.displayDensity) private var density
+
+ func body(content: Content) -> some View {
+ content
+ .listSectionSpacing(density == .compact ? .compact : .default)
+ .contentMargins(.vertical, density == .compact ? 2 : 8, for: .scrollContent)
+ .controlSize(density == .compact ? .small : .regular)
+ }
+}
+
+extension View {
+ /// Apply themed appearance (density spacing) to a List or Form.
+ func themedList() -> some View {
+ modifier(ThemedListStyle())
+ }
+}