diff options
Diffstat (limited to 'Hutch/App')
| -rw-r--r-- | Hutch/App/AppStorageKeys.swift | 2 | ||||
| -rw-r--r-- | Hutch/App/HutchApp.swift | 5 | ||||
| -rw-r--r-- | Hutch/App/ThemeManager.swift | 76 |
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()) + } +} |
