diff options
| author | Christian Cleberg <[email protected]> | 2026-04-13 19:06:54 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-13 19:06:54 -0500 |
| commit | a6ad2b9815d72575206bdca860eef9db850cdf1c (patch) | |
| tree | b4d5f27aa1655e5e40bab178550d6e2576d7df8b /Hutch/App | |
| parent | 02a44d115b3784231cad640e992d9c18a7f67b40 (diff) | |
| download | hutch-a6ad2b9815d72575206bdca860eef9db850cdf1c.tar.gz hutch-a6ad2b9815d72575206bdca860eef9db850cdf1c.tar.bz2 hutch-a6ad2b9815d72575206bdca860eef9db850cdf1c.zip | |
feat(theme): AMOLED true-black rows via themedRow()
Apply listRowBackground(Color.black) per row in AMOLED mode using ThemedRowStyle
and themedRow() across Lists and Forms. Use themedList() for scroll/grouped
backgrounds. Treat segmented and clear list rows with isAMOLED-aware
listRowBackground where Color.clear was required. Removes reliance on UIKit
appearance for list cells on iOS 16+.
Implements: https://todo.sr.ht/~ccleberg/hutch/24
Diffstat (limited to 'Hutch/App')
| -rw-r--r-- | Hutch/App/HutchApp.swift | 2 | ||||
| -rw-r--r-- | Hutch/App/RootView.swift | 21 | ||||
| -rw-r--r-- | Hutch/App/ThemeManager.swift | 49 |
3 files changed, 71 insertions, 1 deletions
diff --git a/Hutch/App/HutchApp.swift b/Hutch/App/HutchApp.swift index 0fb1875..9c643d9 100644 --- a/Hutch/App/HutchApp.swift +++ b/Hutch/App/HutchApp.swift @@ -13,7 +13,9 @@ struct HutchApp: App { .environment(appState) .environment(networkMonitor) .environment(\.displayDensity, displayDensity) + .environment(\.isAMOLEDTheme, appTheme == .amoled) .environment(\.defaultMinListRowHeight, displayDensity == .compact ? 36 : 44) + .background(appTheme == .amoled ? Color.black : Color.clear) .preferredColorScheme(appTheme.colorScheme) .onOpenURL { url in if let link = DeepLink(url: url) { diff --git a/Hutch/App/RootView.swift b/Hutch/App/RootView.swift index b4cf3bd..ed029bb 100644 --- a/Hutch/App/RootView.swift +++ b/Hutch/App/RootView.swift @@ -4,6 +4,7 @@ import SwiftUI /// full-screen sheet for token entry on first launch. struct RootView: View { @Environment(AppState.self) private var appState + @Environment(\.isAMOLEDTheme) private var isAMOLED @State private var homePath = NavigationPath() @State private var morePath = NavigationPath() @State private var repoPath = NavigationPath() @@ -125,6 +126,7 @@ struct RootView: View { .id(appState.sessionIdentity) .defaultAppStorage(appState.accountDefaults) .modifier(SidebarAdaptableTabStyle()) + .modifier(AMOLEDToolbarStyle(isAMOLED: isAMOLED)) .modifier(TabKeyboardShortcuts(selectedTab: Binding( get: { appState.selectedTab }, set: { appState.selectedTab = $0 } @@ -427,6 +429,25 @@ private struct TabKeyboardShortcuts: ViewModifier { } } +// MARK: - AMOLED Toolbar Styling + +/// Applies true-black backgrounds to the tab bar and navigation bar when the AMOLED theme is active. +private struct AMOLEDToolbarStyle: ViewModifier { + let isAMOLED: Bool + + func body(content: Content) -> some View { + if isAMOLED { + content + .toolbarBackground(Color.black, for: .tabBar) + .toolbarBackground(.visible, for: .tabBar) + .toolbarBackground(Color.black, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + } else { + content + } + } +} + // MARK: - iPad Sidebar Adaptable /// Applies `.tabViewStyle(.sidebarAdaptable)` on iOS 18+ so the tab bar diff --git a/Hutch/App/ThemeManager.swift b/Hutch/App/ThemeManager.swift index ee74921..af54094 100644 --- a/Hutch/App/ThemeManager.swift +++ b/Hutch/App/ThemeManager.swift @@ -6,6 +6,7 @@ enum AppTheme: String, CaseIterable, Identifiable { case system case light case dark + case amoled var id: String { rawValue } @@ -14,6 +15,7 @@ enum AppTheme: String, CaseIterable, Identifiable { case .system: "System" case .light: "Light" case .dark: "Dark" + case .amoled: "AMOLED" } } @@ -22,6 +24,7 @@ enum AppTheme: String, CaseIterable, Identifiable { case .system: nil case .light: .light case .dark: .dark + case .amoled: .dark } } } @@ -46,25 +49,51 @@ private struct DisplayDensityKey: EnvironmentKey { static let defaultValue: DisplayDensity = .standard } +private struct IsAMOLEDThemeKey: EnvironmentKey { + static let defaultValue: Bool = false +} + extension EnvironmentValues { var displayDensity: DisplayDensity { get { self[DisplayDensityKey.self] } set { self[DisplayDensityKey.self] = newValue } } + + var isAMOLEDTheme: Bool { + get { self[IsAMOLEDThemeKey.self] } + set { self[IsAMOLEDThemeKey.self] = newValue } + } } // MARK: - Themed List Modifier -/// Combined modifier for List/Form that applies compact section spacing. +/// Combined modifier for List/Form that applies compact section spacing and AMOLED black backgrounds. /// Apply once per List or Form. struct ThemedListStyle: ViewModifier { @Environment(\.displayDensity) private var density + @Environment(\.isAMOLEDTheme) private var isAMOLED 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) + .modifier(AMOLEDListBackground(isAMOLED: isAMOLED)) + } +} + +/// Hides the default grouped list background and replaces it with true black for OLED screens. +private struct AMOLEDListBackground: ViewModifier { + let isAMOLED: Bool + + func body(content: Content) -> some View { + if isAMOLED { + content + .scrollContentBackground(.hidden) + .background(Color.black) + } else { + content + } } } @@ -74,3 +103,21 @@ extension View { modifier(ThemedListStyle()) } } + +// MARK: - Themed Row Modifier + +struct ThemedRowStyle: ViewModifier { + @Environment(\.isAMOLEDTheme) private var isAMOLED + + func body(content: Content) -> some View { + content + .listRowBackground(isAMOLED ? Color.black : nil) + } +} + +extension View { + /// Apply to each row view inside a List or Form section to get a true-black background in AMOLED mode. + func themedRow() -> some View { + modifier(ThemedRowStyle()) + } +} |
