diff --git a/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift b/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift index 2504098..4d619af 100644 --- a/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift +++ b/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift @@ -64,7 +64,7 @@ struct ClockSettingsView: View { SettingsNavigationRow( 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"), - backgroundColor: AppSurface.primary + backgroundColor: .clear ) { IconGeneratorView(config: .noiseClock, appName: "TheNoiseClock") } @@ -72,7 +72,7 @@ struct ClockSettingsView: View { SettingsNavigationRow( 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"), - backgroundColor: AppSurface.primary + backgroundColor: .clear ) { BrandingPreviewView( iconConfig: .noiseClock, @@ -82,27 +82,26 @@ struct ClockSettingsView: View { } if let onResetOnboarding { - Divider() - .background(AppBorder.subtle) + SettingsDivider(color: AppBorder.subtle) Button { onResetOnboarding() } label: { - HStack { - VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { - Text(String(localized: "settings.debug.reset_onboarding.title", defaultValue: "Reset Onboarding")) - .typography(.body) - .foregroundStyle(AppTextColors.primary) - Text(String(localized: "settings.debug.reset_onboarding.subtitle", defaultValue: "Show onboarding screens again on next launch")) - .typography(.caption) - .foregroundStyle(AppTextColors.secondary) + SettingsCardRow(verticalPadding: Design.Spacing.medium) { + HStack { + VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { + Text(String(localized: "settings.debug.reset_onboarding.title", defaultValue: "Reset Onboarding")) + .typography(.body) + .foregroundStyle(AppTextColors.primary) + Text(String(localized: "settings.debug.reset_onboarding.subtitle", defaultValue: "Show onboarding screens again on next launch")) + .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) } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift index e9f785a..93f9a43 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift @@ -34,10 +34,7 @@ struct AdvancedAppearanceSection: View { ) } - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.advanced_appearance.randomize_color.title", defaultValue: "Randomize Color"), @@ -46,10 +43,7 @@ struct AdvancedAppearanceSection: View { accentColor: AppAccent.primary ) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsSlider( title: String(localized: "settings.advanced_appearance.glow.title", defaultValue: "Glow"), @@ -61,10 +55,7 @@ struct AdvancedAppearanceSection: View { accentColor: AppAccent.primary ) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsSlider( title: String(localized: "settings.advanced_appearance.clock_opacity.title", defaultValue: "Clock Opacity"), diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift index d142de6..e0e1da6 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift @@ -30,24 +30,18 @@ struct AdvancedDisplaySection: View { .accessibilityIdentifier("settings.keepAwake.toggle") if style.autoBrightness { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) - HStack { - Text(String(localized: "settings.advanced_display.current_brightness", defaultValue: "Current Brightness")) - .font(.subheadline.weight(.medium)) - .foregroundStyle(AppTextColors.primary) - Spacer() + SettingsLabelValueRow( + title: String(localized: "settings.advanced_display.current_brightness", defaultValue: "Current Brightness"), + verticalPadding: Design.Spacing.medium + ) { Text(style.effectiveBrightness, format: .percent.precision(.fractionLength(0))) .font(.subheadline) .foregroundStyle(AppTextColors.secondary) .contentTransition(.numericText()) .animation(.snappy(duration: 0.3), value: style.effectiveBrightness) } - .padding(.vertical, Design.Spacing.medium) - .padding(.horizontal, Design.Spacing.small) } } } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift index b11528e..7473668 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift @@ -46,33 +46,27 @@ struct BasicAppearanceSection: View { } if style.selectedColorTheme == "Custom" { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) - ColorPicker( - String(localized: "settings.colors.digit_color", defaultValue: "Digit Color"), - selection: $digitColor, - supportsOpacity: false - ) + SettingsCardRow { + ColorPicker( + String(localized: "settings.colors.digit_color", defaultValue: "Digit Color"), + selection: $digitColor, + supportsOpacity: false + ) .foregroundStyle(AppTextColors.primary) - .padding(.horizontal, Design.Spacing.small) - .padding(.vertical, Design.Spacing.small) + } - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) - ColorPicker( - String(localized: "settings.colors.background_color", defaultValue: "Background Color"), - selection: $backgroundColor, - supportsOpacity: true - ) + SettingsCardRow { + ColorPicker( + String(localized: "settings.colors.background_color", defaultValue: "Background Color"), + selection: $backgroundColor, + supportsOpacity: true + ) .foregroundStyle(AppTextColors.primary) - .padding(.horizontal, Design.Spacing.small) - .padding(.vertical, Design.Spacing.small) + } } } } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift index 6bbc122..793b5cf 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift @@ -28,10 +28,7 @@ struct BasicDisplaySection: View { accentColor: AppAccent.primary ) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.display.show_seconds.title", defaultValue: "Show Seconds"), @@ -41,10 +38,7 @@ struct BasicDisplaySection: View { ) if !style.use24Hour { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.display.show_ampm.title", defaultValue: "Show AM/PM"), @@ -54,10 +48,7 @@ struct BasicDisplaySection: View { ) } - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( 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 { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.display.horizontal_mode.title", defaultValue: "Horizontal Mode"), diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift index bb72843..6f10be9 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift @@ -62,10 +62,7 @@ struct FontSection: View { } } - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) // Font Weight SettingsNavigationRow( @@ -82,10 +79,7 @@ struct FontSection: View { } if style.fontFamily == .system { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) // Font Design SettingsNavigationRow( @@ -102,20 +96,15 @@ struct FontSection: View { } } - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) - HStack { - Text(String(localized: "settings.font.preview.title", defaultValue: "Preview")).styled(.subheadingEmphasis) - Spacer() + SettingsLabelValueRow( + title: String(localized: "settings.font.preview.title", defaultValue: "Preview") + ) { 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)) .foregroundStyle(AppTextColors.primary) } - .padding(.vertical, Design.Spacing.small) - .padding(.horizontal, Design.Spacing.small) } } } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift index aff22b6..8cc43dd 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift @@ -28,10 +28,7 @@ struct NightModeSection: View { accentColor: AppAccent.primary ) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.night_mode.auto.title", defaultValue: "Auto Night Mode"), @@ -41,10 +38,7 @@ struct NightModeSection: View { ) if style.autoNightMode { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsSlider( title: String(localized: "settings.night_mode.light_threshold.title", defaultValue: "Light Threshold"), @@ -57,10 +51,7 @@ struct NightModeSection: View { ) } - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.night_mode.scheduled.title", defaultValue: "Scheduled Night Mode"), @@ -70,53 +61,38 @@ struct NightModeSection: View { ) if style.scheduledNightMode { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) - HStack { - Text(String(localized: "settings.night_mode.start_time", defaultValue: "Start Time")) - .font(.subheadline.weight(.medium)) - .foregroundStyle(AppTextColors.primary) - Spacer() - TimePickerView(timeString: $style.nightModeStartTime) + SettingsLabelValueRow( + title: String(localized: "settings.night_mode.start_time", defaultValue: "Start Time"), + verticalPadding: Design.Spacing.medium + ) { + SettingsTimePicker(timeString: $style.nightModeStartTime, accentColor: AppAccent.primary) } - .padding(.vertical, Design.Spacing.medium) - .padding(.horizontal, Design.Spacing.small) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) - HStack { - Text(String(localized: "settings.night_mode.end_time", defaultValue: "End Time")) - .font(.subheadline.weight(.medium)) - .foregroundStyle(AppTextColors.primary) - Spacer() - TimePickerView(timeString: $style.nightModeEndTime) + SettingsLabelValueRow( + title: String(localized: "settings.night_mode.end_time", defaultValue: "End Time"), + verticalPadding: Design.Spacing.medium + ) { + SettingsTimePicker(timeString: $style.nightModeEndTime, accentColor: AppAccent.primary) } - .padding(.vertical, Design.Spacing.medium) - .padding(.horizontal, Design.Spacing.small) } if style.isNightModeActive { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: "moon.fill") - .foregroundStyle(AppStatus.error) - Text(String(localized: "settings.night_mode.active", defaultValue: "Night Mode Active")) - .font(.subheadline.weight(.medium)) - .foregroundStyle(AppStatus.error) - Spacer() + SettingsCardRow { + HStack(spacing: Design.Spacing.xSmall) { + Image(systemName: "moon.fill") + .foregroundStyle(AppStatus.error) + Text(String(localized: "settings.night_mode.active", defaultValue: "Night Mode Active")) + .font(.subheadline.weight(.medium)) + .foregroundStyle(AppStatus.error) + Spacer() + } } - .padding(.vertical, Design.Spacing.small) - .padding(.horizontal, Design.Spacing.small) } } } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift index e618e64..1582cce 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift @@ -30,10 +30,7 @@ struct OverlaySection: View { accentColor: AppAccent.primary ) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.overlays.date.title", defaultValue: "Date"), @@ -42,10 +39,7 @@ struct OverlaySection: View { accentColor: AppAccent.primary ) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.overlays.next_alarm.title", defaultValue: "Next Alarm"), @@ -54,10 +48,7 @@ struct OverlaySection: View { accentColor: AppAccent.primary ) - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsToggle( title: String(localized: "settings.overlays.noise_controls.title", defaultValue: "Noise Controls"), @@ -67,10 +58,7 @@ struct OverlaySection: View { ) if style.showDate { - Rectangle() - .fill(AppBorder.subtle) - .frame(height: 1) - .padding(.horizontal, Design.Spacing.small) + SettingsDivider(color: AppBorder.subtle) SettingsNavigationRow( title: String(localized: "settings.overlays.date_format.title", defaultValue: "Date Format"), diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/TimePickerView.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/TimePickerView.swift deleted file mode 100644 index 8967d6b..0000000 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/TimePickerView.swift +++ /dev/null @@ -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) - } -} diff --git a/TheNoiseClock/Localizable.xcstrings b/TheNoiseClock/Localizable.xcstrings index dcad2ee..4464768 100644 --- a/TheNoiseClock/Localizable.xcstrings +++ b/TheNoiseClock/Localizable.xcstrings @@ -1,9 +1,6 @@ { "sourceLanguage" : "en", "strings" : { - "" : { - - }, "alarm_intent.error.alarm_not_found" : { "localizations" : { "en" : { diff --git a/TheNoiseClock/Shared/Components/SettingsSelectionView.swift b/TheNoiseClock/Shared/Components/SettingsSelectionView.swift deleted file mode 100644 index 08e2cb6..0000000 --- a/TheNoiseClock/Shared/Components/SettingsSelectionView.swift +++ /dev/null @@ -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: 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 } - ) - } -}