Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-31 11:54:14 -06:00
parent 74ece5a71a
commit 7bd01554de
9 changed files with 125 additions and 119 deletions

View File

@ -12,14 +12,25 @@ import Bedrock
struct ContentView: View { struct ContentView: View {
// MARK: - Body // MARK: - Body
private enum Tab: Hashable {
case clock
case alarms
case noise
case settings
}
@State private var selectedTab: Tab = .clock
@State private var clockViewModel = ClockViewModel()
var body: some View { var body: some View {
TabView { TabView(selection: $selectedTab) {
NavigationStack { NavigationStack {
ClockView() ClockView(viewModel: clockViewModel)
} }
.tabItem { .tabItem {
Label("Clock", systemImage: "clock") Label("Clock", systemImage: "clock")
} }
.tag(Tab.clock)
NavigationStack { NavigationStack {
AlarmView() AlarmView()
@ -27,6 +38,7 @@ struct ContentView: View {
.tabItem { .tabItem {
Label("Alarms", systemImage: "alarm") Label("Alarms", systemImage: "alarm")
} }
.tag(Tab.alarms)
NavigationStack { NavigationStack {
NoiseView() NoiseView()
@ -34,6 +46,19 @@ struct ContentView: View {
.tabItem { .tabItem {
Label("Noise", systemImage: "waveform") Label("Noise", systemImage: "waveform")
} }
.tag(Tab.noise)
NavigationStack {
ClockSettingsView(style: clockViewModel.style) { newStyle in
clockViewModel.updateStyle(newStyle)
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
}
.tabItem {
Label("Settings", systemImage: "gearshape")
}
.tag(Tab.settings)
} }
.accentColor(AppAccent.primary) .accentColor(AppAccent.primary)
.background(Color.Branding.primary.ignoresSafeArea()) .background(Color.Branding.primary.ignoresSafeArea())

View File

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import Bedrock
/// Main alarm management view /// Main alarm management view
struct AlarmView: View { struct AlarmView: View {
@ -26,6 +27,8 @@ struct AlarmView: View {
.onTapGesture { .onTapGesture {
showAddAlarm = true showAddAlarm = true
} }
.frame(maxWidth: Design.Size.maxContentWidthPortrait)
.frame(maxWidth: .infinity, alignment: .center)
} else { } else {
List { List {
ForEach(viewModel.alarms) { alarm in ForEach(viewModel.alarms) { alarm in
@ -43,6 +46,8 @@ struct AlarmView: View {
} }
.onDelete(perform: deleteAlarm) .onDelete(perform: deleteAlarm)
} }
.frame(maxWidth: Design.Size.maxContentWidthPortrait)
.frame(maxWidth: .infinity, alignment: .center)
} }
} }
.navigationTitle("Alarms") .navigationTitle("Alarms")

View File

@ -354,7 +354,7 @@ class ClockStyle: Codable, Equatable {
// Color-aware brightness adaptation // Color-aware brightness adaptation
let colorAwareBrightness = getColorAwareBrightness() let colorAwareBrightness = getColorAwareBrightness()
Design.debugLog("[brightness] effectiveBrightness: Color-aware brightness = \(String(format: \"%.2f\", colorAwareBrightness))") Design.debugLog("[brightness] effectiveBrightness: Color-aware brightness = \(String(format: "%.2f", colorAwareBrightness))")
return colorAwareBrightness return colorAwareBrightness
} }

View File

@ -61,15 +61,15 @@ class AmbientLightService {
let previousBrightness = UIScreen.main.brightness let previousBrightness = UIScreen.main.brightness
Design.debugLog("[ambient] AmbientLightService.setBrightness:") Design.debugLog("[ambient] AmbientLightService.setBrightness:")
Design.debugLog("[ambient] - Requested brightness: \(String(format: \"%.2f\", brightness))") Design.debugLog("[ambient] - Requested brightness: \(String(format: "%.2f", brightness))")
Design.debugLog("[ambient] - Clamped brightness: \(String(format: \"%.2f\", clampedBrightness))") Design.debugLog("[ambient] - Clamped brightness: \(String(format: "%.2f", clampedBrightness))")
Design.debugLog("[ambient] - Previous screen brightness: \(String(format: \"%.2f\", previousBrightness))") Design.debugLog("[ambient] - Previous screen brightness: \(String(format: "%.2f", previousBrightness))")
UIScreen.main.brightness = clampedBrightness UIScreen.main.brightness = clampedBrightness
currentBrightness = clampedBrightness currentBrightness = clampedBrightness
Design.debugLog("[ambient] - New screen brightness: \(String(format: \"%.2f\", UIScreen.main.brightness))") Design.debugLog("[ambient] - New screen brightness: \(String(format: "%.2f", UIScreen.main.brightness))")
Design.debugLog("[ambient] - Service currentBrightness: \(String(format: \"%.2f\", currentBrightness))") Design.debugLog("[ambient] - Service currentBrightness: \(String(format: "%.2f", currentBrightness))")
} }
/// Get current screen brightness /// Get current screen brightness
@ -90,7 +90,7 @@ class AmbientLightService {
let previousBrightness = currentBrightness let previousBrightness = currentBrightness
currentBrightness = newBrightness currentBrightness = newBrightness
Design.debugLog("[ambient] AmbientLightService: Brightness changed from \(String(format: \"%.2f\", previousBrightness)) to \(String(format: \"%.2f\", newBrightness))") Design.debugLog("[ambient] AmbientLightService: Brightness changed from \(String(format: "%.2f", previousBrightness)) to \(String(format: "%.2f", newBrightness))")
// Notify that brightness changed // Notify that brightness changed
onBrightnessChange?() onBrightnessChange?()

View File

@ -221,16 +221,16 @@ class ClockViewModel {
Design.debugLog("[brightness] Auto Brightness Debug:") Design.debugLog("[brightness] Auto Brightness Debug:")
Design.debugLog("[brightness] - Auto brightness enabled: \(style.autoBrightness)") Design.debugLog("[brightness] - Auto brightness enabled: \(style.autoBrightness)")
Design.debugLog("[brightness] - Current screen brightness: \(String(format: \"%.2f\", currentScreenBrightness))") Design.debugLog("[brightness] - Current screen brightness: \(String(format: "%.2f", currentScreenBrightness))")
Design.debugLog("[brightness] - Target brightness: \(String(format: \"%.2f\", targetBrightness))") Design.debugLog("[brightness] - Target brightness: \(String(format: "%.2f", targetBrightness))")
Design.debugLog("[brightness] - Night mode active: \(isNightMode)") Design.debugLog("[brightness] - Night mode active: \(isNightMode)")
Design.debugLog("[brightness] - Color theme: \(style.selectedColorTheme)") Design.debugLog("[brightness] - Color theme: \(style.selectedColorTheme)")
Design.debugLog("[brightness] - Ambient light threshold: \(String(format: \"%.2f\", style.ambientLightThreshold))") Design.debugLog("[brightness] - Ambient light threshold: \(String(format: "%.2f", style.ambientLightThreshold))")
ambientLightService.setBrightness(targetBrightness) ambientLightService.setBrightness(targetBrightness)
Design.debugLog("[brightness] - Brightness set to: \(String(format: \"%.2f\", targetBrightness))") Design.debugLog("[brightness] - Brightness set to: \(String(format: "%.2f", targetBrightness))")
Design.debugLog("[brightness] - Actual screen brightness now: \(String(format: \"%.2f\", UIScreen.main.brightness))") Design.debugLog("[brightness] - Actual screen brightness now: \(String(format: "%.2f", UIScreen.main.brightness))")
Design.debugLog("[brightness] ---") Design.debugLog("[brightness] ---")
} else { } else {
Design.debugLog("[brightness] Auto Brightness: DISABLED") Design.debugLog("[brightness] Auto Brightness: DISABLED")

View File

@ -18,7 +18,6 @@ struct ClockSettingsView: View {
@State private var digitColor: Color = .white @State private var digitColor: Color = .white
@State private var backgroundColor: Color = .black @State private var backgroundColor: Color = .black
@State private var showAdvancedSettings = false @State private var showAdvancedSettings = false
@Environment(\.dismiss) private var dismiss
// MARK: - Init // MARK: - Init
init(style: ClockStyle, onCommit: @escaping (ClockStyle) -> Void) { init(style: ClockStyle, onCommit: @escaping (ClockStyle) -> Void) {
@ -28,94 +27,88 @@ struct ClockSettingsView: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
NavigationStack { ScrollView {
ScrollView { VStack(spacing: Design.Spacing.xxLarge) {
VStack(spacing: Design.Spacing.xxLarge) { BasicAppearanceSection(
BasicAppearanceSection( style: $style,
style: $style, digitColor: $digitColor,
digitColor: $digitColor, backgroundColor: $backgroundColor
backgroundColor: $backgroundColor )
)
BasicDisplaySection(style: $style) BasicDisplaySection(style: $style)
if showAdvancedSettings { if showAdvancedSettings {
AdvancedAppearanceSection(style: $style) AdvancedAppearanceSection(style: $style)
FontSection(style: $style) FontSection(style: $style)
NightModeSection(style: $style) NightModeSection(style: $style)
OverlaySection(style: $style) OverlaySection(style: $style)
AdvancedDisplaySection(style: $style) AdvancedDisplaySection(style: $style)
} }
SettingsSectionHeader( SettingsSectionHeader(
title: "Advanced", title: "Advanced",
systemImage: "gearshape", systemImage: "gearshape",
accentColor: AppAccent.primary
)
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
SettingsToggle(
title: "Show Advanced Settings",
subtitle: "Reveal additional customization options",
isOn: $showAdvancedSettings,
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
}
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { #if DEBUG
SettingsToggle( SettingsSectionHeader(
title: "Show Advanced Settings", title: "Debug",
subtitle: "Reveal additional customization options", systemImage: "ant.fill",
isOn: $showAdvancedSettings, accentColor: AppStatus.error
accentColor: AppAccent.primary )
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
SettingsNavigationRow(
title: "Icon Generator",
subtitle: "Generate and save app icon",
backgroundColor: AppSurface.primary
) {
IconGeneratorView(config: .noiseClock, appName: "TheNoiseClock")
}
SettingsNavigationRow(
title: "Branding Preview",
subtitle: "Preview icon and launch screen",
backgroundColor: AppSurface.primary
) {
BrandingPreviewView(
iconConfig: .noiseClock,
launchConfig: .noiseClock,
appName: "TheNoiseClock"
) )
} }
#if DEBUG
SettingsSectionHeader(
title: "Debug",
systemImage: "ant.fill",
accentColor: AppStatus.error
)
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
SettingsNavigationRow(
title: "Icon Generator",
subtitle: "Generate and save app icon",
backgroundColor: AppSurface.primary
) {
IconGeneratorView(config: .noiseClock, appName: "TheNoiseClock")
}
SettingsNavigationRow(
title: "Branding Preview",
subtitle: "Preview icon and launch screen",
backgroundColor: AppSurface.primary
) {
BrandingPreviewView(
iconConfig: .noiseClock,
launchConfig: .noiseClock,
appName: "TheNoiseClock"
)
}
}
#endif
} }
.padding(.horizontal, Design.Spacing.large) #endif
.padding(.top, Design.Spacing.large)
.padding(.bottom, Design.Spacing.xxxLarge)
}
.background(AppSurface.primary)
.navigationTitle("Clock Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") { dismiss() }
.foregroundStyle(AppAccent.primary)
}
}
.onAppear {
digitColor = Color(hex: style.digitColorHex) ?? .white
backgroundColor = Color(hex: style.backgroundHex) ?? .black
}
.onDisappear {
onCommit(style)
} }
.frame(maxWidth: Design.Size.maxContentWidthPortrait)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.horizontal, Design.Spacing.large)
.padding(.top, Design.Spacing.large)
.padding(.bottom, Design.Spacing.xxxLarge)
}
.background(AppSurface.primary)
.navigationTitle("Clock Settings")
.navigationBarTitleDisplayMode(.inline)
.onAppear {
digitColor = Color(hex: style.digitColorHex) ?? .white
backgroundColor = Color(hex: style.backgroundHex) ?? .black
}
.onDisappear {
onCommit(style)
} }
} }
} }

View File

@ -12,8 +12,7 @@ import Bedrock
struct ClockView: View { struct ClockView: View {
// MARK: - Properties // MARK: - Properties
@State private var viewModel = ClockViewModel() @Bindable var viewModel: ClockViewModel
@State private var showSettings = false
@State private var showFullScreenHint = false @State private var showFullScreenHint = false
// MARK: - Body // MARK: - Body
@ -43,20 +42,10 @@ struct ClockView: View {
} }
.ignoresSafeArea(.all, edges: viewModel.isDisplayMode ? .bottom : []) .ignoresSafeArea(.all, edges: viewModel.isDisplayMode ? .bottom : [])
.statusBarHidden(viewModel.isDisplayMode) .statusBarHidden(viewModel.isDisplayMode)
.sheet(isPresented: $showSettings) {
ClockSettingsView(style: viewModel.style) { newStyle in
viewModel.updateStyle(newStyle)
}
.presentationDetents([.large])
.presentationDragIndicator(.hidden)
.presentationBackgroundInteraction(.enabled)
.presentationBackground(AppSurface.overlay)
}
.overlay { .overlay {
// Toolbar overlay // Toolbar overlay
ClockToolbar( ClockToolbar(
isDisplayMode: viewModel.isDisplayMode, isDisplayMode: viewModel.isDisplayMode,
onSettingsTap: { showSettings = true },
onFullScreenTap: { onFullScreenTap: {
if !viewModel.isDisplayMode { if !viewModel.isDisplayMode {
viewModel.toggleDisplayMode() viewModel.toggleDisplayMode()
@ -87,7 +76,7 @@ struct ClockView: View {
// MARK: - Preview // MARK: - Preview
#Preview { #Preview {
NavigationStack { NavigationStack {
ClockView() ClockView(viewModel: ClockViewModel())
} }
.frame(width: 400, height: 600) .frame(width: 400, height: 600)
.background(Color.black) .background(Color.black)

View File

@ -12,7 +12,6 @@ struct ClockToolbar: View {
// MARK: - Properties // MARK: - Properties
let isDisplayMode: Bool let isDisplayMode: Bool
let onSettingsTap: () -> Void
let onFullScreenTap: () -> Void let onFullScreenTap: () -> Void
// MARK: - Body // MARK: - Body
@ -30,15 +29,6 @@ struct ClockToolbar: View {
.transition(.opacity) .transition(.opacity)
} }
.accessibilityLabel("Enter Full Screen Mode") .accessibilityLabel("Enter Full Screen Mode")
Button {
onSettingsTap()
} label: {
Image(systemName: "gear")
.font(.title2)
.transition(.opacity)
}
.accessibilityLabel("Clock Settings")
} }
} }
} }
@ -52,7 +42,6 @@ struct ClockToolbar: View {
NavigationStack { NavigationStack {
ClockToolbar( ClockToolbar(
isDisplayMode: false, isDisplayMode: false,
onSettingsTap: {},
onFullScreenTap: {} onFullScreenTap: {}
) )
} }

View File

@ -27,14 +27,19 @@ struct NoiseView: View {
var body: some View { var body: some View {
GeometryReader { geometry in GeometryReader { geometry in
let isLandscape = geometry.size.width > geometry.size.height let isLandscape = geometry.size.width > geometry.size.height
let maxWidth = isLandscape ? Design.Size.maxContentWidthLandscape : Design.Size.maxContentWidthPortrait
if isLandscape { Group {
// Landscape layout: Player on left, sounds on right if isLandscape {
landscapeLayout // Landscape layout: Player on left, sounds on right
} else { landscapeLayout
// Portrait layout: Stacked vertically } else {
portraitLayout // Portrait layout: Stacked vertically
portraitLayout
}
} }
.frame(maxWidth: maxWidth)
.frame(maxWidth: .infinity, alignment: .center)
} }
.animation(.easeInOut(duration: 0.3), value: selectedSound) .animation(.easeInOut(duration: 0.3), value: selectedSound)
} }