Add honeycomb pattern toggle and make theme switching reactive

Adds a toggleable honeycomb hexagonal grid overlay (matching the website pattern)
that can be enabled independently of any theme via the Appearance screen. Uses a
cached tiled UIImage approach consistent with the existing grain texture system.

Replaces the destructive refreshID-based theme switching (which destroyed all
NavigationStacks and dismissed sheets) with @Observable AppThemeSource. Color
resolution now happens through Swift's Observation framework, so all views using
Color.appPrimary etc. automatically re-render when the theme changes — no view
identity reset needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-07 11:36:24 -06:00
parent 28339544e5
commit 73dd440d7b
9 changed files with 247 additions and 48 deletions

View File

@@ -65,34 +65,11 @@ struct MainTabView: View {
.onChange(of: authManager.isAuthenticated) { _, _ in
selectedTab = .residences
}
.onChange(of: themeManager.currentTheme) { _, _ in
updateTabBarAppearance()
}
.onAppear {
// FIX_SKIPPED(F-10): UITabBar.appearance() is the standard SwiftUI pattern
// for customizing tab bar appearance. The global side effect persists but
// there is no safe alternative without UIKit hosting.
// Configure tab bar appearance
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
// Use theme-aware colors
appearance.backgroundColor = UIColor(Color.appBackgroundSecondary)
// Selected item uses Dynamic Type caption2 style (A-2)
appearance.stackedLayoutAppearance.selected.iconColor = UIColor(Color.appPrimary)
appearance.stackedLayoutAppearance.selected.titleTextAttributes = [
.foregroundColor: UIColor(Color.appPrimary),
.font: UIFont.preferredFont(forTextStyle: .caption2)
]
// Normal item uses Dynamic Type caption2 style (A-2)
appearance.stackedLayoutAppearance.normal.iconColor = UIColor(Color.appTextSecondary)
appearance.stackedLayoutAppearance.normal.titleTextAttributes = [
.foregroundColor: UIColor(Color.appTextSecondary),
.font: UIFont.preferredFont(forTextStyle: .caption2)
]
UITabBar.appearance().standardAppearance = appearance
UITabBar.appearance().scrollEdgeAppearance = appearance
updateTabBarAppearance()
// Handle pending navigation from push notification
if pushManager.pendingNavigationTaskId != nil {
@@ -119,6 +96,27 @@ struct MainTabView: View {
selectedTab = .residences
}
}
private func updateTabBarAppearance() {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor(Color.appBackgroundSecondary)
appearance.stackedLayoutAppearance.selected.iconColor = UIColor(Color.appPrimary)
appearance.stackedLayoutAppearance.selected.titleTextAttributes = [
.foregroundColor: UIColor(Color.appPrimary),
.font: UIFont.preferredFont(forTextStyle: .caption2)
]
appearance.stackedLayoutAppearance.normal.iconColor = UIColor(Color.appTextSecondary)
appearance.stackedLayoutAppearance.normal.titleTextAttributes = [
.foregroundColor: UIColor(Color.appTextSecondary),
.font: UIFont.preferredFont(forTextStyle: .caption2)
]
UITabBar.appearance().standardAppearance = appearance
UITabBar.appearance().scrollEdgeAppearance = appearance
}
}
#Preview {