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

This commit is contained in:
Matt Bruce 2026-01-31 11:16:38 -06:00
parent c9b1b3cf62
commit 3307ba5433
27 changed files with 130 additions and 204 deletions

8
PRD.md
View File

@ -376,17 +376,13 @@ TheNoiseClock/
│ │ ├── Design/
│ │ │ ├── BrandingConfig.swift # Bedrock branding + launch config
│ │ │ ├── AppConstants.swift # App-wide constants and configuration
│ │ │ ├── UIConstants.swift # UI constants namespace
│ │ │ ├── UIConstants+Colors.swift # Theme-backed colors
│ │ │ ├── UIConstants+Layout.swift # Spacing/radius/opacity
│ │ │ ├── UIConstants+Animation.swift # Animation curves
│ │ │ ├── NoiseClockTheme.swift # Bedrock app color theme
│ │ │ ├── Design+NoiseClock.swift # App-specific Design extensions
│ │ │ └── Fonts/
│ │ │ ├── Font.Design.swift
│ │ │ ├── Font.Weight.swift
│ │ │ ├── FontFamily.swift
│ │ │ └── FontUtils.swift
│ │ ├── Theme/
│ │ │ └── NoiseClockTheme.swift # Bedrock app color theme
│ │ ├── Extensions/
│ │ │ ├── Color+Extensions.swift # Color utilities and extensions
│ │ │ ├── Date+Extensions.swift # Date formatting and utilities

View File

@ -86,7 +86,7 @@ xcodebuild -project TheNoiseClock/TheNoiseClock.xcodeproj -scheme TheNoiseClock
The app uses Bedrock for theme tokens, settings UI, and the launch experience.
- Theme: `TheNoiseClock/Shared/Theme/NoiseClockTheme.swift`
- Theme: `TheNoiseClock/Shared/Design/NoiseClockTheme.swift`
- Branding config: `TheNoiseClock/Shared/Design/BrandingConfig.swift`
- Launch screen: `TheNoiseClock/Resources/LaunchScreen.storyboard`

View File

@ -37,7 +37,7 @@ struct AddAlarmView: View {
NavigationLink(destination: LabelEditView(label: $alarmLabel)) {
HStack {
Image(systemName: "textformat")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Label")
Spacer()
@ -50,7 +50,7 @@ struct AddAlarmView: View {
NavigationLink(destination: NotificationMessageEditView(message: $notificationMessage)) {
HStack {
Image(systemName: "message")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Message")
Spacer()
@ -64,7 +64,7 @@ struct AddAlarmView: View {
NavigationLink(destination: SoundSelectionView(selectedSound: $selectedSoundName)) {
HStack {
Image(systemName: "music.note")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Sound")
Spacer()
@ -77,7 +77,7 @@ struct AddAlarmView: View {
NavigationLink(destination: SnoozeSelectionView(snoozeDuration: $snoozeDuration)) {
HStack {
Image(systemName: "clock.arrow.circlepath")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Snooze")
Spacer()
@ -96,7 +96,7 @@ struct AddAlarmView: View {
Button("Cancel") {
isPresented = false
}
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
}
ToolbarItem(placement: .navigationBarTrailing) {
@ -116,7 +116,7 @@ struct AddAlarmView: View {
isPresented = false
}
}
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.fontWeight(.semibold)
}
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// Component for displaying individual alarm row
struct AlarmRowView: View {
@ -18,18 +19,18 @@ struct AlarmRowView: View {
// MARK: - Body
var body: some View {
HStack {
VStack(alignment: .leading, spacing: UIConstants.Spacing.extraSmall) {
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
Text(alarm.formattedTime())
.font(.headline)
.foregroundColor(UIConstants.Colors.primaryText)
.foregroundColor(AppTextColors.primary)
Text(alarm.label)
.font(.subheadline)
.foregroundColor(UIConstants.Colors.secondaryText)
.foregroundColor(AppTextColors.secondary)
Text("\(AlarmSoundService.shared.getSoundDisplayName(alarm.soundName))")
.font(.caption)
.foregroundColor(UIConstants.Colors.secondaryText)
.foregroundColor(AppTextColors.secondary)
}
Spacer()

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// Empty state view for when no alarms are configured
struct EmptyAlarmsView: View {
@ -15,7 +16,7 @@ struct EmptyAlarmsView: View {
// MARK: - Body
var body: some View {
VStack(spacing: UIConstants.Spacing.medium) {
VStack(spacing: Design.Spacing.medium) {
// Icon
Image(systemName: "alarm")
.font(.largeTitle)

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// View for editing alarm label
struct LabelEditView: View {
@ -13,15 +14,15 @@ struct LabelEditView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack(spacing: UIConstants.Spacing.large) {
VStack(spacing: Design.Spacing.large) {
TextField("Alarm Label", text: $label)
.textFieldStyle(RoundedBorderTextFieldStyle())
.contentPadding(horizontal: UIConstants.Spacing.large)
.contentPadding(horizontal: Design.Spacing.large)
Spacer()
}
.navigationTitle("Label")
.navigationBarTitleDisplayMode(.inline)
.contentPadding(vertical: UIConstants.Spacing.large)
.contentPadding(vertical: Design.Spacing.large)
}
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// View for editing alarm notification message
struct NotificationMessageEditView: View {
@ -13,13 +14,13 @@ struct NotificationMessageEditView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack(spacing: UIConstants.Spacing.large) {
VStack(spacing: Design.Spacing.large) {
TextField("Notification message", text: $message)
.textFieldStyle(RoundedBorderTextFieldStyle())
.contentPadding(horizontal: UIConstants.Spacing.large)
.contentPadding(horizontal: Design.Spacing.large)
// Preview section
VStack(alignment: .leading, spacing: UIConstants.Spacing.small) {
VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text("Preview:")
.font(.headline)
.foregroundColor(.secondary)
@ -37,13 +38,13 @@ struct NotificationMessageEditView: View {
.background(Color(.systemGray6))
.cornerRadius(8)
}
.contentPadding(horizontal: UIConstants.Spacing.large)
.contentPadding(horizontal: Design.Spacing.large)
Spacer()
}
.navigationTitle("Message")
.navigationBarTitleDisplayMode(.inline)
.contentPadding(vertical: UIConstants.Spacing.large)
.contentPadding(vertical: Design.Spacing.large)
}
}

View File

@ -23,7 +23,7 @@ struct SnoozeSelectionView: View {
Spacer()
if snoozeDuration == duration {
Image(systemName: "checkmark")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
}
}
.contentShape(Rectangle())

View File

@ -30,7 +30,7 @@ struct SoundSelectionView: View {
Spacer()
if selectedSound == sound.fileName {
Image(systemName: "checkmark")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
}
}
.contentShape(Rectangle())
@ -56,7 +56,7 @@ struct SoundSelectionView: View {
}
}) {
Image(systemName: isPlaying && currentlyPlayingSound == selectedSound ? "stop.fill" : "play.fill")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
}
}
}

View File

@ -15,7 +15,7 @@ struct TimeUntilAlarmSection: View {
VStack(spacing: 4) {
HStack {
Image(systemName: "calendar")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
Text(timeUntilAlarm)
.font(.subheadline)
.foregroundColor(.secondary)

View File

@ -55,7 +55,7 @@ struct EditAlarmView: View {
NavigationLink(destination: LabelEditView(label: $alarmLabel)) {
HStack {
Image(systemName: "textformat")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Label")
Spacer()
@ -68,7 +68,7 @@ struct EditAlarmView: View {
NavigationLink(destination: NotificationMessageEditView(message: $notificationMessage)) {
HStack {
Image(systemName: "message")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Message")
Spacer()
@ -82,7 +82,7 @@ struct EditAlarmView: View {
NavigationLink(destination: SoundSelectionView(selectedSound: $selectedSoundName)) {
HStack {
Image(systemName: "music.note")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Sound")
Spacer()
@ -95,7 +95,7 @@ struct EditAlarmView: View {
NavigationLink(destination: SnoozeSelectionView(snoozeDuration: $snoozeDuration)) {
HStack {
Image(systemName: "clock.arrow.circlepath")
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.frame(width: 24)
Text("Snooze")
Spacer()
@ -114,7 +114,7 @@ struct EditAlarmView: View {
Button("Cancel") {
dismiss()
}
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
}
ToolbarItem(placement: .navigationBarTrailing) {
@ -136,7 +136,7 @@ struct EditAlarmView: View {
dismiss()
}
}
.foregroundColor(UIConstants.Colors.accentColor)
.foregroundColor(AppAccent.primary)
.fontWeight(.semibold)
}
}

View File

@ -10,6 +10,7 @@ import Combine
import Observation
import AudioPlaybackKit
import SwiftUI
import Bedrock
/// ViewModel for clock display and management
@Observable
@ -60,7 +61,7 @@ class ClockViewModel {
// MARK: - Public Interface
func toggleDisplayMode() {
withAnimation(UIConstants.AnimationCurves.bouncy) {
withAnimation(Design.Animation.spring(bounce: Design.Animation.springBounce)) {
isDisplayMode.toggle()
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// Container component that handles the main clock display layout and scaling
struct ClockDisplayContainer: View {
@ -20,7 +21,7 @@ struct ClockDisplayContainer: View {
return GeometryReader { geometry in
let isPortrait = geometry.size.height >= geometry.size.width
let hasOverlay = style.showBattery || style.showDate
let topSpacing = hasOverlay ? (isPortrait ? UIConstants.Spacing.huge : UIConstants.Spacing.large) : 0
let topSpacing = hasOverlay ? (isPortrait ? Design.Spacing.xxLarge : Design.Spacing.large) : 0
VStack(spacing: 0) {
// Top spacing to account for overlay
@ -50,7 +51,7 @@ struct ClockDisplayContainer: View {
Spacer()
}
.animation(UIConstants.AnimationCurves.smooth, value: isDisplayMode)
.animation(.smooth(duration: Design.Animation.standard), value: isDisplayMode)
}
}
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// Container component that handles the positioning and display of top overlays
struct ClockOverlayContainer: View {
@ -20,12 +21,12 @@ struct ClockOverlayContainer: View {
TopOverlayView(
showBattery: style.showBattery,
showDate: style.showDate,
color: style.digitColor.opacity(UIConstants.Opacity.primary),
color: style.digitColor.opacity(Design.Opacity.heavy),
opacity: style.overlayOpacity,
dateFormat: style.dateFormat
)
.padding(.top, UIConstants.Spacing.small)
.padding(.horizontal, UIConstants.Spacing.large)
.padding(.top, Design.Spacing.small)
.padding(.horizontal, Design.Spacing.large)
.transition(.opacity)
}
Spacer()

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// Component for displaying segmented time with customizable formatting
struct TimeDisplayView: View {
@ -132,8 +133,8 @@ struct TimeDisplayView: View {
}
.offset(y: portraitMode && forceHorizontalMode ? -containerSize.height * 0.10 : 0) // Push up in horizontal mode
.scaleEffect(finalScale, anchor: .center)
.animation(UIConstants.AnimationCurves.smooth, value: finalScale)
.animation(UIConstants.AnimationCurves.smooth, value: showSeconds) // Smooth animation for seconds toggle
.animation(.smooth(duration: Design.Animation.standard), value: finalScale)
.animation(.smooth(duration: Design.Animation.standard), value: showSeconds) // Smooth animation for seconds toggle
.minimumScaleFactor(0.1)
.clipped() // Prevent overflow beyond bounds
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
/// Component for displaying top overlay with battery and date information
struct TopOverlayView: View {
@ -37,8 +38,8 @@ struct TopOverlayView: View {
)
}
}
.padding(.horizontal, UIConstants.Spacing.medium)
.padding(.vertical, UIConstants.Spacing.small)
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.small)
.transition(.opacity)
.onAppear {
batteryService.startMonitoring()

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
import AudioPlaybackKit
/// Category-based sound selection view with grid layout
@ -67,7 +68,7 @@ struct SoundCategoryView: View {
// MARK: - Body
var body: some View {
VStack(spacing: UIConstants.Spacing.medium) {
VStack(spacing: Design.Spacing.medium) {
// Search Bar
searchBar
@ -79,7 +80,7 @@ struct SoundCategoryView: View {
soundGrid
}
}
.padding(.top, UIConstants.Spacing.medium)
.padding(.top, Design.Spacing.medium)
}
// MARK: - Subviews
@ -94,15 +95,15 @@ struct SoundCategoryView: View {
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
}
.padding(.horizontal, UIConstants.Spacing.medium)
.padding(.vertical, UIConstants.Spacing.small)
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.small)
.background(Color(.systemGray6))
.cornerRadius(10)
}
private var categoryTabs: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: UIConstants.Spacing.small) {
HStack(spacing: Design.Spacing.small) {
ForEach(categories) { category in
CategoryTab(
title: category.displayName,
@ -113,12 +114,12 @@ struct SoundCategoryView: View {
}
}
}
.padding(.horizontal, UIConstants.Spacing.medium)
.padding(.horizontal, Design.Spacing.medium)
}
}
private var soundGrid: some View {
LazyVStack(spacing: UIConstants.Spacing.small) {
LazyVStack(spacing: Design.Spacing.small) {
ForEach(filteredSounds) { sound in
SoundCard(
sound: sound,
@ -134,7 +135,7 @@ struct SoundCategoryView: View {
)
}
}
.padding(.horizontal, UIConstants.Spacing.medium)
.padding(.horizontal, Design.Spacing.medium)
}
}
@ -157,10 +158,10 @@ struct CategoryTab: View {
.foregroundColor(.secondary)
}
}
.padding(.horizontal, UIConstants.Spacing.medium)
.padding(.vertical, UIConstants.Spacing.small)
.background(isSelected ? UIConstants.Colors.accentColor : Color(.systemGray6))
.foregroundColor(isSelected ? .white : UIConstants.Colors.primaryText)
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.small)
.background(isSelected ? AppAccent.primary : Color(.systemGray6))
.foregroundColor(isSelected ? .white : AppTextColors.primary)
.cornerRadius(20)
}
.buttonStyle(.plain)
@ -175,20 +176,20 @@ struct SoundCard: View {
let onPreview: () -> Void
var body: some View {
HStack(spacing: UIConstants.Spacing.medium) {
HStack(spacing: Design.Spacing.medium) {
// Sound Icon (Left)
ZStack {
Circle()
.fill(isSelected ? UIConstants.Colors.accentColor : Color(.systemGray5))
.fill(isSelected ? AppAccent.primary : Color(.systemGray5))
.frame(width: 50, height: 50)
Image(systemName: soundIcon)
.font(.title3)
.foregroundColor(isSelected ? .white : UIConstants.Colors.primaryText)
.foregroundColor(isSelected ? .white : AppTextColors.primary)
if isPreviewing {
Circle()
.stroke(UIConstants.Colors.accentColor, lineWidth: 2)
.stroke(AppAccent.primary, lineWidth: 2)
.frame(width: 58, height: 58)
.scaleEffect(1.02)
.animation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true), value: isPreviewing)
@ -200,7 +201,7 @@ struct SoundCard: View {
// Sound Name
Text(sound.name)
.font(.subheadline.weight(.medium))
.foregroundColor(UIConstants.Colors.primaryText)
.foregroundColor(AppTextColors.primary)
.lineLimit(1)
// Description
@ -216,7 +217,7 @@ struct SoundCard: View {
.foregroundColor(.white)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(UIConstants.Colors.accentColor, in: Capsule())
.background(AppAccent.primary, in: Capsule())
Spacer()
}
@ -224,14 +225,14 @@ struct SoundCard: View {
Spacer()
}
.padding(.horizontal, UIConstants.Spacing.medium)
.padding(.vertical, UIConstants.Spacing.small)
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.small)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(isSelected ? UIConstants.Colors.accentColor.opacity(0.1) : Color(.systemBackground))
.fill(isSelected ? AppAccent.primary.opacity(0.1) : Color(.systemBackground))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(isSelected ? UIConstants.Colors.accentColor : Color.clear, lineWidth: 2)
.stroke(isSelected ? AppAccent.primary : Color.clear, lineWidth: 2)
)
)
.onTapGesture {

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
import AudioPlaybackKit
/// Component for audio playback controls
@ -19,18 +20,18 @@ struct SoundControlView: View {
// MARK: - Body
var body: some View {
VStack(spacing: UIConstants.Spacing.medium) {
VStack(spacing: Design.Spacing.medium) {
// Sound info header
if let sound = selectedSound {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(sound.name)
.font(.headline.weight(.semibold))
.foregroundColor(UIConstants.Colors.primaryText)
.foregroundColor(AppTextColors.primary)
Text(sound.description)
.font(.caption)
.foregroundColor(UIConstants.Colors.secondaryText)
.foregroundColor(AppTextColors.secondary)
.lineLimit(2)
}
@ -42,10 +43,10 @@ struct SoundControlView: View {
.foregroundColor(.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(UIConstants.Colors.accentColor, in: Capsule())
.background(AppAccent.primary, in: Capsule())
}
.contentPadding(horizontal: UIConstants.Spacing.medium, vertical: UIConstants.Spacing.small)
.background(UIConstants.Colors.overlayBackground, in: RoundedRectangle(cornerRadius: UIConstants.CornerRadius.medium))
.contentPadding(horizontal: Design.Spacing.medium, vertical: Design.Spacing.small)
.background(AppSurface.overlay, in: RoundedRectangle(cornerRadius: Design.CornerRadius.large))
}
// Main control button
@ -56,7 +57,7 @@ struct SoundControlView: View {
onPlay(sound)
}
}) {
HStack(spacing: UIConstants.Spacing.small) {
HStack(spacing: Design.Spacing.small) {
Image(systemName: isPlaying ? "stop.fill" : "play.fill")
.font(.title2.weight(.semibold))
.foregroundColor(.white)
@ -65,11 +66,11 @@ struct SoundControlView: View {
.font(.headline.weight(.semibold))
.foregroundColor(.white)
}
.contentPadding(horizontal: UIConstants.Spacing.large, vertical: UIConstants.Spacing.medium)
.contentPadding(horizontal: Design.Spacing.large, vertical: Design.Spacing.medium)
.background(
RoundedRectangle(cornerRadius: UIConstants.CornerRadius.large)
.fill(isPlaying ? Color.red : UIConstants.Colors.accentColor)
.shadow(color: (isPlaying ? Color.red : UIConstants.Colors.accentColor).opacity(0.3), radius: 8, x: 0, y: 4)
RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge)
.fill(isPlaying ? Color.red : AppAccent.primary)
.shadow(color: (isPlaying ? Color.red : AppAccent.primary).opacity(0.3), radius: 8, x: 0, y: 4)
)
}
.disabled(selectedSound == nil)
@ -79,11 +80,11 @@ struct SoundControlView: View {
.animation(.easeInOut(duration: 0.2), value: selectedSound)
}
.frame(maxWidth: 400) // Reasonable max width for iPad
.padding(UIConstants.Spacing.medium)
.background(UIConstants.Colors.overlayBackground, in: RoundedRectangle(cornerRadius: UIConstants.CornerRadius.large))
.padding(Design.Spacing.medium)
.background(AppSurface.overlay, in: RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge))
.overlay(
RoundedRectangle(cornerRadius: UIConstants.CornerRadius.large)
.stroke(UIConstants.Colors.overlayBorder, lineWidth: UIConstants.BorderWidth.normal)
RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge)
.stroke(AppBorder.subtle, lineWidth: Design.LineWidth.thin)
)
}
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
import AudioPlaybackKit
/// Main noise/audio player view
@ -57,7 +58,7 @@ struct NoiseView: View {
private var portraitLayout: some View {
VStack(spacing: 0) {
// Fixed header
VStack(alignment: .leading, spacing: UIConstants.Spacing.medium) {
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text("Ambient Sounds")
.sectionTitleStyle()
@ -67,8 +68,8 @@ struct NoiseView: View {
.centered()
}
}
.contentPadding(horizontal: UIConstants.Spacing.large)
.padding(.top, UIConstants.Spacing.large)
.contentPadding(horizontal: Design.Spacing.large)
.padding(.top, Design.Spacing.large)
.background(Color(.systemBackground))
// Scrollable sound selection
@ -77,15 +78,15 @@ struct NoiseView: View {
sounds: viewModel.availableSounds,
selectedSound: $selectedSound
)
.contentPadding(horizontal: UIConstants.Spacing.large, vertical: UIConstants.Spacing.large)
.contentPadding(horizontal: Design.Spacing.large, vertical: Design.Spacing.large)
}
}
}
private var landscapeLayout: some View {
HStack(spacing: UIConstants.Spacing.large) {
HStack(spacing: Design.Spacing.large) {
// Left side: Player controls
VStack(alignment: .leading, spacing: UIConstants.Spacing.medium) {
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text("Ambient Sounds")
.sectionTitleStyle()
@ -93,7 +94,7 @@ struct NoiseView: View {
soundControlView
} else {
// Placeholder when no sound selected
VStack(spacing: UIConstants.Spacing.small) {
VStack(spacing: Design.Spacing.small) {
Image(systemName: "music.note")
.font(.largeTitle)
.foregroundColor(.secondary)
@ -103,14 +104,14 @@ struct NoiseView: View {
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
.padding(.vertical, UIConstants.Spacing.large)
.padding(.vertical, Design.Spacing.large)
}
Spacer()
}
.frame(maxWidth: 400) // Reasonable width for player section
.contentPadding(horizontal: UIConstants.Spacing.large)
.padding(.top, UIConstants.Spacing.large)
.contentPadding(horizontal: Design.Spacing.large)
.padding(.top, Design.Spacing.large)
// Right side: Sound selection
VStack(spacing: 0) {
@ -119,11 +120,11 @@ struct NoiseView: View {
sounds: viewModel.availableSounds,
selectedSound: $selectedSound
)
.contentPadding(horizontal: UIConstants.Spacing.large, vertical: UIConstants.Spacing.large)
.contentPadding(horizontal: Design.Spacing.large, vertical: Design.Spacing.large)
}
}
}
.contentPadding(horizontal: UIConstants.Spacing.medium)
.contentPadding(horizontal: Design.Spacing.medium)
}
}

View File

@ -12,9 +12,9 @@ import Bedrock
extension Color {
enum Branding {
static let primary = Color(red: 0.08, green: 0.10, blue: 0.16)
static let secondary = Color(red: 0.03, green: 0.05, blue: 0.10)
static let accent = Color.white
static let primary = AppSurface.primary
static let secondary = AppSurface.overlay
static let accent = AppTextColors.primary
}
}

View File

@ -0,0 +1,13 @@
//
// Design+NoiseClock.swift
// TheNoiseClock
//
// Created by Matt Bruce on 1/31/26.
//
import SwiftUI
import Bedrock
extension Design.CornerRadius {
static let appLarge: CGFloat = 16
}

View File

@ -1,19 +0,0 @@
//
// UIConstants+Animation.swift
// TheNoiseClock
//
// Created by Matt Bruce on 1/31/26.
//
import SwiftUI
import Bedrock
extension UIConstants {
// MARK: - Animation Curves
enum AnimationCurves {
static let smooth = Animation.smooth(duration: Design.Animation.standard)
static let bouncy = Animation.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)
static let quick = Animation.easeInOut(duration: Design.Animation.quick)
}
}

View File

@ -1,21 +0,0 @@
//
// UIConstants+Colors.swift
// TheNoiseClock
//
// Created by Matt Bruce on 1/31/26.
//
import SwiftUI
extension UIConstants {
// MARK: - Colors
enum Colors {
static let accentColor = AppAccent.primary
static let primaryText = AppTextColors.primary
static let secondaryText = AppTextColors.secondary
static let background = AppSurface.primary
static let overlayBackground = AppSurface.overlay
static let overlayBorder = AppBorder.subtle
}
}

View File

@ -1,45 +0,0 @@
//
// UIConstants+Layout.swift
// TheNoiseClock
//
// Created by Matt Bruce on 1/31/26.
//
import SwiftUI
import Bedrock
extension UIConstants {
// MARK: - Spacing
enum Spacing {
static let extraSmall: CGFloat = Design.Spacing.xSmall
static let small: CGFloat = Design.Spacing.small
static let medium: CGFloat = Design.Spacing.medium
static let large: CGFloat = Design.Spacing.large
static let extraLarge: CGFloat = Design.Spacing.xLarge
static let huge: CGFloat = Design.Spacing.xxLarge
}
// MARK: - Corner Radius
enum CornerRadius {
static let small: CGFloat = Design.CornerRadius.small
static let medium: CGFloat = Design.CornerRadius.large
static let large: CGFloat = 16
static let extraLarge: CGFloat = Design.CornerRadius.xxLarge
}
// MARK: - Border Width
enum BorderWidth {
static let thin: CGFloat = Design.LineWidth.hairline
static let normal: CGFloat = Design.LineWidth.thin
static let thick: CGFloat = Design.LineWidth.standard
}
// MARK: - Opacity
enum Opacity {
static let disabled: Double = Design.Opacity.light
static let secondary: Double = Design.Opacity.accent
static let primary: Double = Design.Opacity.heavy
static let full: Double = 1.0
}
}

View File

@ -1,11 +0,0 @@
//
// UIConstants.swift
// TheNoiseClock
//
// Created by Matt Bruce on 9/7/25.
//
import SwiftUI
/// UI-specific constants for styling and layout.
enum UIConstants {}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import Bedrock
extension View {
@ -13,10 +14,10 @@ extension View {
/// - Returns: View with card styling applied
func cardStyle() -> some View {
self
.background(UIConstants.Colors.overlayBackground, in: RoundedRectangle(cornerRadius: UIConstants.CornerRadius.large))
.background(AppSurface.overlay, in: RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge))
.overlay(
RoundedRectangle(cornerRadius: UIConstants.CornerRadius.large)
.stroke(UIConstants.Colors.overlayBorder, lineWidth: UIConstants.BorderWidth.normal)
RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge)
.stroke(AppBorder.subtle, lineWidth: Design.LineWidth.thin)
)
}
@ -25,12 +26,12 @@ extension View {
/// - isEnabled: Whether the button is enabled
/// - color: Button color
/// - Returns: View with button styling applied
func buttonStyle(isEnabled: Bool = true, color: Color = UIConstants.Colors.accentColor) -> some View {
func buttonStyle(isEnabled: Bool = true, color: Color = AppAccent.primary) -> some View {
self
.padding(UIConstants.Spacing.medium)
.background(isEnabled ? color : color.opacity(UIConstants.Opacity.disabled))
.padding(Design.Spacing.medium)
.background(isEnabled ? color : color.opacity(Design.Opacity.light))
.foregroundColor(.white)
.cornerRadius(UIConstants.CornerRadius.small)
.cornerRadius(Design.CornerRadius.small)
.disabled(!isEnabled)
}
@ -67,7 +68,7 @@ extension View {
func sectionTitleStyle() -> some View {
self
.font(.title2.weight(.bold))
.foregroundColor(UIConstants.Colors.primaryText)
.foregroundColor(AppTextColors.primary)
}
/// Center content horizontally with spacers
@ -84,7 +85,7 @@ extension View {
/// - Parameter title: The title text
/// - Returns: View with section header styling
func sectionHeader(title: String) -> some View {
VStack(alignment: .leading, spacing: UIConstants.Spacing.medium) {
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text(title)
.sectionTitleStyle()
self
@ -96,7 +97,7 @@ extension View {
/// - horizontal: Horizontal padding amount
/// - vertical: Vertical padding amount
/// - Returns: View with standard padding applied
func contentPadding(horizontal: CGFloat = UIConstants.Spacing.large, vertical: CGFloat? = nil) -> some View {
func contentPadding(horizontal: CGFloat = Design.Spacing.large, vertical: CGFloat? = nil) -> some View {
self
.padding(.horizontal, horizontal)
.padding(.vertical, vertical ?? horizontal)