Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
Matt Bruce 2026-02-09 14:05:17 -06:00
parent 4f46304a5b
commit 4190c95b84
11 changed files with 79 additions and 303 deletions

View File

@ -64,7 +64,7 @@ struct ClockSettingsView: View {
SettingsNavigationRow( SettingsNavigationRow(
title: String(localized: "settings.debug.icon_generator.title", defaultValue: "Icon Generator"), title: String(localized: "settings.debug.icon_generator.title", defaultValue: "Icon Generator"),
subtitle: String(localized: "settings.debug.icon_generator.subtitle", defaultValue: "Generate and save app icon"), subtitle: String(localized: "settings.debug.icon_generator.subtitle", defaultValue: "Generate and save app icon"),
backgroundColor: AppSurface.primary backgroundColor: .clear
) { ) {
IconGeneratorView(config: .noiseClock, appName: "TheNoiseClock") IconGeneratorView(config: .noiseClock, appName: "TheNoiseClock")
} }
@ -72,7 +72,7 @@ struct ClockSettingsView: View {
SettingsNavigationRow( SettingsNavigationRow(
title: String(localized: "settings.debug.branding_preview.title", defaultValue: "Branding Preview"), title: String(localized: "settings.debug.branding_preview.title", defaultValue: "Branding Preview"),
subtitle: String(localized: "settings.debug.branding_preview.subtitle", defaultValue: "Preview icon and launch screen"), subtitle: String(localized: "settings.debug.branding_preview.subtitle", defaultValue: "Preview icon and launch screen"),
backgroundColor: AppSurface.primary backgroundColor: .clear
) { ) {
BrandingPreviewView( BrandingPreviewView(
iconConfig: .noiseClock, iconConfig: .noiseClock,
@ -82,27 +82,26 @@ struct ClockSettingsView: View {
} }
if let onResetOnboarding { if let onResetOnboarding {
Divider() SettingsDivider(color: AppBorder.subtle)
.background(AppBorder.subtle)
Button { Button {
onResetOnboarding() onResetOnboarding()
} label: { } label: {
HStack { SettingsCardRow(verticalPadding: Design.Spacing.medium) {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { HStack {
Text(String(localized: "settings.debug.reset_onboarding.title", defaultValue: "Reset Onboarding")) VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
.typography(.body) Text(String(localized: "settings.debug.reset_onboarding.title", defaultValue: "Reset Onboarding"))
.foregroundStyle(AppTextColors.primary) .typography(.body)
Text(String(localized: "settings.debug.reset_onboarding.subtitle", defaultValue: "Show onboarding screens again on next launch")) .foregroundStyle(AppTextColors.primary)
.typography(.caption) Text(String(localized: "settings.debug.reset_onboarding.subtitle", defaultValue: "Show onboarding screens again on next launch"))
.foregroundStyle(AppTextColors.secondary) .typography(.caption)
.foregroundStyle(AppTextColors.secondary)
}
Spacer()
Image(systemName: "arrow.counterclockwise")
.foregroundStyle(AppAccent.primary)
} }
Spacer()
Image(systemName: "arrow.counterclockwise")
.foregroundStyle(AppAccent.primary)
} }
.padding(Design.Spacing.medium)
.background(AppSurface.primary)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
} }

View File

@ -34,10 +34,7 @@ struct AdvancedAppearanceSection: View {
) )
} }
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.advanced_appearance.randomize_color.title", defaultValue: "Randomize Color"), title: String(localized: "settings.advanced_appearance.randomize_color.title", defaultValue: "Randomize Color"),
@ -46,10 +43,7 @@ struct AdvancedAppearanceSection: View {
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsSlider( SettingsSlider(
title: String(localized: "settings.advanced_appearance.glow.title", defaultValue: "Glow"), title: String(localized: "settings.advanced_appearance.glow.title", defaultValue: "Glow"),
@ -61,10 +55,7 @@ struct AdvancedAppearanceSection: View {
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsSlider( SettingsSlider(
title: String(localized: "settings.advanced_appearance.clock_opacity.title", defaultValue: "Clock Opacity"), title: String(localized: "settings.advanced_appearance.clock_opacity.title", defaultValue: "Clock Opacity"),

View File

@ -30,24 +30,18 @@ struct AdvancedDisplaySection: View {
.accessibilityIdentifier("settings.keepAwake.toggle") .accessibilityIdentifier("settings.keepAwake.toggle")
if style.autoBrightness { if style.autoBrightness {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
HStack { SettingsLabelValueRow(
Text(String(localized: "settings.advanced_display.current_brightness", defaultValue: "Current Brightness")) title: String(localized: "settings.advanced_display.current_brightness", defaultValue: "Current Brightness"),
.font(.subheadline.weight(.medium)) verticalPadding: Design.Spacing.medium
.foregroundStyle(AppTextColors.primary) ) {
Spacer()
Text(style.effectiveBrightness, format: .percent.precision(.fractionLength(0))) Text(style.effectiveBrightness, format: .percent.precision(.fractionLength(0)))
.font(.subheadline) .font(.subheadline)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.contentTransition(.numericText()) .contentTransition(.numericText())
.animation(.snappy(duration: 0.3), value: style.effectiveBrightness) .animation(.snappy(duration: 0.3), value: style.effectiveBrightness)
} }
.padding(.vertical, Design.Spacing.medium)
.padding(.horizontal, Design.Spacing.small)
} }
} }
} }

View File

@ -46,33 +46,27 @@ struct BasicAppearanceSection: View {
} }
if style.selectedColorTheme == "Custom" { if style.selectedColorTheme == "Custom" {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
ColorPicker( SettingsCardRow {
String(localized: "settings.colors.digit_color", defaultValue: "Digit Color"), ColorPicker(
selection: $digitColor, String(localized: "settings.colors.digit_color", defaultValue: "Digit Color"),
supportsOpacity: false selection: $digitColor,
) supportsOpacity: false
)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.padding(.horizontal, Design.Spacing.small) }
.padding(.vertical, Design.Spacing.small)
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
ColorPicker( SettingsCardRow {
String(localized: "settings.colors.background_color", defaultValue: "Background Color"), ColorPicker(
selection: $backgroundColor, String(localized: "settings.colors.background_color", defaultValue: "Background Color"),
supportsOpacity: true selection: $backgroundColor,
) supportsOpacity: true
)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.padding(.horizontal, Design.Spacing.small) }
.padding(.vertical, Design.Spacing.small)
} }
} }
} }

View File

@ -28,10 +28,7 @@ struct BasicDisplaySection: View {
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.display.show_seconds.title", defaultValue: "Show Seconds"), title: String(localized: "settings.display.show_seconds.title", defaultValue: "Show Seconds"),
@ -41,10 +38,7 @@ struct BasicDisplaySection: View {
) )
if !style.use24Hour { if !style.use24Hour {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.display.show_ampm.title", defaultValue: "Show AM/PM"), title: String(localized: "settings.display.show_ampm.title", defaultValue: "Show AM/PM"),
@ -54,10 +48,7 @@ struct BasicDisplaySection: View {
) )
} }
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.display.auto_brightness.title", defaultValue: "Auto Brightness"), title: String(localized: "settings.display.auto_brightness.title", defaultValue: "Auto Brightness"),
@ -67,10 +58,7 @@ struct BasicDisplaySection: View {
) )
if UIDevice.current.orientation.isPortrait || UIDevice.current.orientation == .unknown { if UIDevice.current.orientation.isPortrait || UIDevice.current.orientation == .unknown {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.display.horizontal_mode.title", defaultValue: "Horizontal Mode"), title: String(localized: "settings.display.horizontal_mode.title", defaultValue: "Horizontal Mode"),

View File

@ -62,10 +62,7 @@ struct FontSection: View {
} }
} }
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
// Font Weight // Font Weight
SettingsNavigationRow( SettingsNavigationRow(
@ -82,10 +79,7 @@ struct FontSection: View {
} }
if style.fontFamily == .system { if style.fontFamily == .system {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
// Font Design // Font Design
SettingsNavigationRow( SettingsNavigationRow(
@ -102,20 +96,15 @@ struct FontSection: View {
} }
} }
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
HStack { SettingsLabelValueRow(
Text(String(localized: "settings.font.preview.title", defaultValue: "Preview")).styled(.subheadingEmphasis) title: String(localized: "settings.font.preview.title", defaultValue: "Preview")
Spacer() ) {
Text(String(localized: "settings.font.preview.sample_time", defaultValue: "12:34")) Text(String(localized: "settings.font.preview.sample_time", defaultValue: "12:34"))
.font(FontUtils.createFont(name: style.fontFamily, weight: style.fontWeight, design: style.fontDesign, size: 24)) .font(FontUtils.createFont(name: style.fontFamily, weight: style.fontWeight, design: style.fontDesign, size: 24))
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
} }
.padding(.vertical, Design.Spacing.small)
.padding(.horizontal, Design.Spacing.small)
} }
} }
} }

View File

@ -28,10 +28,7 @@ struct NightModeSection: View {
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.night_mode.auto.title", defaultValue: "Auto Night Mode"), title: String(localized: "settings.night_mode.auto.title", defaultValue: "Auto Night Mode"),
@ -41,10 +38,7 @@ struct NightModeSection: View {
) )
if style.autoNightMode { if style.autoNightMode {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsSlider( SettingsSlider(
title: String(localized: "settings.night_mode.light_threshold.title", defaultValue: "Light Threshold"), title: String(localized: "settings.night_mode.light_threshold.title", defaultValue: "Light Threshold"),
@ -57,10 +51,7 @@ struct NightModeSection: View {
) )
} }
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.night_mode.scheduled.title", defaultValue: "Scheduled Night Mode"), title: String(localized: "settings.night_mode.scheduled.title", defaultValue: "Scheduled Night Mode"),
@ -70,53 +61,38 @@ struct NightModeSection: View {
) )
if style.scheduledNightMode { if style.scheduledNightMode {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
HStack { SettingsLabelValueRow(
Text(String(localized: "settings.night_mode.start_time", defaultValue: "Start Time")) title: String(localized: "settings.night_mode.start_time", defaultValue: "Start Time"),
.font(.subheadline.weight(.medium)) verticalPadding: Design.Spacing.medium
.foregroundStyle(AppTextColors.primary) ) {
Spacer() SettingsTimePicker(timeString: $style.nightModeStartTime, accentColor: AppAccent.primary)
TimePickerView(timeString: $style.nightModeStartTime)
} }
.padding(.vertical, Design.Spacing.medium)
.padding(.horizontal, Design.Spacing.small)
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
HStack { SettingsLabelValueRow(
Text(String(localized: "settings.night_mode.end_time", defaultValue: "End Time")) title: String(localized: "settings.night_mode.end_time", defaultValue: "End Time"),
.font(.subheadline.weight(.medium)) verticalPadding: Design.Spacing.medium
.foregroundStyle(AppTextColors.primary) ) {
Spacer() SettingsTimePicker(timeString: $style.nightModeEndTime, accentColor: AppAccent.primary)
TimePickerView(timeString: $style.nightModeEndTime)
} }
.padding(.vertical, Design.Spacing.medium)
.padding(.horizontal, Design.Spacing.small)
} }
if style.isNightModeActive { if style.isNightModeActive {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
HStack(spacing: Design.Spacing.xSmall) { SettingsCardRow {
Image(systemName: "moon.fill") HStack(spacing: Design.Spacing.xSmall) {
.foregroundStyle(AppStatus.error) Image(systemName: "moon.fill")
Text(String(localized: "settings.night_mode.active", defaultValue: "Night Mode Active")) .foregroundStyle(AppStatus.error)
.font(.subheadline.weight(.medium)) Text(String(localized: "settings.night_mode.active", defaultValue: "Night Mode Active"))
.foregroundStyle(AppStatus.error) .font(.subheadline.weight(.medium))
Spacer() .foregroundStyle(AppStatus.error)
Spacer()
}
} }
.padding(.vertical, Design.Spacing.small)
.padding(.horizontal, Design.Spacing.small)
} }
} }
} }

View File

@ -30,10 +30,7 @@ struct OverlaySection: View {
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.overlays.date.title", defaultValue: "Date"), title: String(localized: "settings.overlays.date.title", defaultValue: "Date"),
@ -42,10 +39,7 @@ struct OverlaySection: View {
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.overlays.next_alarm.title", defaultValue: "Next Alarm"), title: String(localized: "settings.overlays.next_alarm.title", defaultValue: "Next Alarm"),
@ -54,10 +48,7 @@ struct OverlaySection: View {
accentColor: AppAccent.primary accentColor: AppAccent.primary
) )
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsToggle( SettingsToggle(
title: String(localized: "settings.overlays.noise_controls.title", defaultValue: "Noise Controls"), title: String(localized: "settings.overlays.noise_controls.title", defaultValue: "Noise Controls"),
@ -67,10 +58,7 @@ struct OverlaySection: View {
) )
if style.showDate { if style.showDate {
Rectangle() SettingsDivider(color: AppBorder.subtle)
.fill(AppBorder.subtle)
.frame(height: 1)
.padding(.horizontal, Design.Spacing.small)
SettingsNavigationRow( SettingsNavigationRow(
title: String(localized: "settings.overlays.date_format.title", defaultValue: "Date Format"), title: String(localized: "settings.overlays.date_format.title", defaultValue: "Date Format"),

View File

@ -1,57 +0,0 @@
//
// TimePickerView.swift
// TheNoiseClock
//
// Created by Matt Bruce on 9/7/25.
//
import SwiftUI
import Bedrock
struct TimePickerView: View {
@Binding var timeString: String
@State private var selectedTime = Date()
var body: some View {
DatePicker("", selection: $selectedTime, displayedComponents: .hourAndMinute)
.labelsHidden()
.tint(AppAccent.primary)
.onAppear {
updateSelectedTimeFromString()
}
.onChange(of: selectedTime) { _, newTime in
updateStringFromTime(newTime)
}
.onChange(of: timeString) { _, _ in
updateSelectedTimeFromString()
}
}
private func updateSelectedTimeFromString() {
let components = timeString.split(separator: ":")
guard components.count == 2,
let hour = Int(components[0]),
let minute = Int(components[1]) else {
return
}
let calendar = Calendar.current
let now = Date()
let dateComponents = calendar.dateComponents([.year, .month, .day], from: now)
var newComponents = dateComponents
newComponents.hour = hour
newComponents.minute = minute
if let newDate = calendar.date(from: newComponents) {
selectedTime = newDate
}
}
private func updateStringFromTime(_ time: Date) {
let calendar = Calendar.current
let hour = calendar.component(.hour, from: time)
let minute = calendar.component(.minute, from: time)
timeString = String(format: "%02d:%02d", hour, minute)
}
}

View File

@ -1,9 +1,6 @@
{ {
"sourceLanguage" : "en", "sourceLanguage" : "en",
"strings" : { "strings" : {
"" : {
},
"alarm_intent.error.alarm_not_found" : { "alarm_intent.error.alarm_not_found" : {
"localizations" : { "localizations" : {
"en" : { "en" : {

View File

@ -1,83 +0,0 @@
//
// SettingsSelectionView.swift
// TheNoiseClock
//
// Created by Matt Bruce on 9/8/25.
//
import SwiftUI
import Bedrock
/// A reusable selection view for settings that navigates to a new screen.
struct SettingsSelectionView<T: Hashable>: View {
@Binding var selection: T
let options: [T]
let title: String
let toString: (T) -> String
@Environment(\.dismiss) private var dismiss
var body: some View {
ZStack {
AppSurface.primary.ignoresSafeArea()
ScrollView {
VStack(spacing: Design.Spacing.medium) {
SettingsSectionHeader(
title: title,
systemImage: "checklist",
accentColor: AppAccent.primary
)
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
VStack(spacing: 0) {
ForEach(options, id: \.self) { option in
Button(action: {
selection = option
dismiss()
}) {
HStack {
Text(toString(option))
.typography(.body)
.foregroundStyle(AppTextColors.primary)
Spacer()
if selection == option {
Image(systemName: "checkmark")
.foregroundStyle(AppAccent.primary)
.font(.body.bold())
}
}
.padding(Design.Spacing.medium)
.background(Color.clear)
}
.buttonStyle(.plain)
if option != options.last {
Divider()
.background(AppBorder.subtle)
.padding(.horizontal, Design.Spacing.medium)
}
}
}
}
}
.padding(.horizontal, Design.Spacing.large)
.padding(.top, Design.Spacing.large)
.padding(.bottom, Design.Spacing.xxxLarge)
}
}
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
}
}
#Preview {
NavigationStack {
SettingsSelectionView(
selection: .constant("Option 1"),
options: ["Option 1", "Option 2", "Option 3"],
title: "Test Selection",
toString: { $0 }
)
}
}