summaryrefslogtreecommitdiff
path: root/Hutch/App
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-13 19:06:54 -0500
committerChristian Cleberg <[email protected]>2026-04-13 19:06:54 -0500
commita6ad2b9815d72575206bdca860eef9db850cdf1c (patch)
treeb4d5f27aa1655e5e40bab178550d6e2576d7df8b /Hutch/App
parent02a44d115b3784231cad640e992d9c18a7f67b40 (diff)
downloadhutch-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.swift2
-rw-r--r--Hutch/App/RootView.swift21
-rw-r--r--Hutch/App/ThemeManager.swift49
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())
+ }
+}