diff --git a/PRD.md b/PRD.md
index df60658..fe19f9b 100644
--- a/PRD.md
+++ b/PRD.md
@@ -223,6 +223,7 @@ These principles are fundamental to the project's long-term success and must be
### App Structure
- **Main App**: `TheNoiseClockApp.swift` - Entry point with WindowGroup
+- **Branded launch**: AppLaunchView wrapped in a Color.Branding.primary ZStack
- **Tab-based navigation**: Three main tabs (Clock, Alarms, Noise)
- **SwiftUI framework**: Modern declarative UI framework with iOS 18+ and iOS 26 features
- **Dark theme**: Preferred color scheme set to dark
@@ -327,19 +328,18 @@ These principles are fundamental to the project's long-term success and must be
### Visual Design
- **Rounded corners**: Modern iOS design language
- **Modern animations**: iOS 18+ smooth and bouncy animations
-- **Color consistency**: Blue accent color throughout
+- **Color consistency**: Bedrock theme with branded surfaces and accents
+- **Branded launch**: AppLaunchView with matching launch screen background
- **Accessibility**: Proper labels and hidden decorative elements
-- **Form-based layouts**: Organized sections for settings and alarm editing
-- **Interactive controls**: Toggles, sliders, color pickers, and date pickers
+- **Card-based layouts**: Bedrock settings cards for grouping controls
+- **Interactive controls**: Bedrock toggles, sliders, pickers, and color pickers
### Settings Interface
-- **Form-based layout**: Organized sections for different setting categories
-- **Interactive controls**: Toggles, sliders, color pickers, enum-based pickers
+- **Bedrock layout**: Section headers with card-based grouping
+- **Interactive controls**: SettingsToggle, SettingsSlider, menu pickers, color pickers
- **Type-safe font selection**: FontFamily.allCases, Font.Weight.allCases, Font.Design.allCases pickers
- **Real-time updates**: Changes apply immediately with live preview
-- **Sheet presentation**: Modal settings with detents
-- **iPad optimization**: Settings sheet opens at full size (.large) on iPad for better usability
-- **iPhone compatibility**: Settings sheet uses medium/large detents on iPhone for optimal space usage
+- **Sheet presentation**: Full-screen settings sheet for uninterrupted editing
- **Enum-based architecture**: Type-safe picker selections eliminate string-based errors
## File Structure and Organization
@@ -349,6 +349,7 @@ Following the separation of concerns principle, the codebase is organized into f
```
TheNoiseClock/
+├── README.md # App Store copy + project overview
├── AudioPlaybackKit/ # Swift Package for reusable audio functionality
│ ├── Package.swift # Package configuration (iOS 17.0+, tvOS 17.0+)
│ └── Sources/AudioPlaybackKit/
@@ -366,69 +367,101 @@ TheNoiseClock/
│ ├── App/
│ │ ├── TheNoiseClockApp.swift # App entry point and configuration
│ │ └── ContentView.swift # Main tab navigation coordinator
-│ ├── Core/
-│ │ ├── Constants/
+│ ├── Configuration/
+│ │ ├── AppIdentifiers.swift # xcconfig-backed identifiers
+│ │ ├── Base.xcconfig # Company/app identifiers
+│ │ ├── Debug.xcconfig # Debug settings
+│ │ └── Release.xcconfig # Release settings
+│ ├── Shared/
+│ │ ├── BrandingConfig.swift # Bedrock branding + launch config
+│ │ ├── Design/
│ │ │ ├── AppConstants.swift # App-wide constants and configuration
-│ │ │ └── UIConstants.swift # UI-specific constants (colors, sizes, etc.)
+│ │ │ ├── UIConstants.swift # UI-specific constants (colors, sizes, etc.)
+│ │ │ └── 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+Extensions.swift # Common view modifiers and responsive utilities
+│ │ ├── Models/
+│ │ │ └── SoundCategory.swift # Shared sound category definitions
│ │ └── Utilities/
│ │ ├── ColorUtils.swift # Color manipulation utilities
-│ │ ├── FontUtils.swift # Font sizing, typography, and customization utilities
+│ │ ├── DebugLogger.swift # Debug logging helper
│ │ └── NotificationUtils.swift # Notification helper functions
-│ ├── Models/
-│ │ ├── ClockStyle.swift # Clock customization data model
-│ │ ├── Alarm.swift # Alarm data model
-│ │ └── LegacyAlarm.swift # Backward compatibility model
-│ ├── ViewModels/
-│ │ ├── ClockViewModel.swift # Clock display logic and state
-│ │ └── AlarmViewModel.swift # Alarm management logic
-│ ├── Views/
+│ ├── Features/
│ │ ├── Clock/
-│ │ │ ├── ClockView.swift # Main clock display view
-│ │ │ ├── ClockSettingsView.swift # Clock customization interface
-│ │ │ └── Components/
-│ │ │ ├── TimeDisplayView.swift # Advanced segmented time display with dynamic font sizing
-│ │ │ ├── TimeSegment.swift # Individual time segment with enum-based font properties
-│ │ │ ├── DigitView.swift # Single digit display with binary search font sizing
-│ │ │ ├── ColonView.swift # Unified colon separator (horizontal/vertical)
-│ │ │ ├── DotCircle.swift # Individual dot component for colons
-│ │ │ ├── BatteryOverlayView.swift # Battery level overlay
-│ │ │ ├── DateOverlayView.swift # Date display overlay
-│ │ │ ├── TopOverlayView.swift # Combined overlay container
-│ │ │ └── Settings/
-│ │ │ ├── BasicAppearanceSection.swift
-│ │ │ ├── BasicDisplaySection.swift
-│ │ │ ├── AdvancedAppearanceSection.swift
-│ │ │ ├── AdvancedDisplaySection.swift
-│ │ │ ├── FontSection.swift
-│ │ │ ├── NightModeSection.swift
-│ │ │ ├── OverlaySection.swift
-│ │ │ └── TimePickerView.swift
+│ │ │ ├── Models/
+│ │ │ │ └── ClockStyle.swift
+│ │ │ ├── State/
+│ │ │ │ └── ClockViewModel.swift
+│ │ │ ├── Services/
+│ │ │ │ ├── AmbientLightService.swift
+│ │ │ │ └── BatteryService.swift
+│ │ │ └── Views/
+│ │ │ ├── ClockView.swift
+│ │ │ ├── ClockSettingsView.swift
+│ │ │ └── Components/
+│ │ │ ├── TimeDisplayView.swift
+│ │ │ ├── TimeSegment.swift
+│ │ │ ├── DigitView.swift
+│ │ │ ├── ColonView.swift
+│ │ │ ├── DotCircle.swift
+│ │ │ ├── BatteryOverlayView.swift
+│ │ │ ├── DateOverlayView.swift
+│ │ │ ├── TopOverlayView.swift
+│ │ │ ├── ClockDisplayContainer.swift
+│ │ │ ├── ClockOverlayContainer.swift
+│ │ │ ├── ClockGestureHandler.swift
+│ │ │ ├── ClockTabBarManager.swift
+│ │ │ ├── ClockToolbar.swift
+│ │ │ ├── FullScreenHintView.swift
+│ │ │ └── Settings/
+│ │ │ ├── BasicAppearanceSection.swift
+│ │ │ ├── BasicDisplaySection.swift
+│ │ │ ├── AdvancedAppearanceSection.swift
+│ │ │ ├── AdvancedDisplaySection.swift
+│ │ │ ├── FontSection.swift
+│ │ │ ├── NightModeSection.swift
+│ │ │ ├── OverlaySection.swift
+│ │ │ └── TimePickerView.swift
│ │ ├── Alarms/
-│ │ │ ├── AlarmView.swift # Main alarm management view
-│ │ │ ├── AddAlarmView.swift # Alarm creation interface
-│ │ │ └── Components/
-│ │ │ ├── AlarmRowView.swift # Individual alarm row component
-│ │ │ ├── TimePickerSection.swift # Time selection component
-│ │ │ ├── TimeUntilAlarmSection.swift # Time calculation display
-│ │ │ ├── SoundSelectionView.swift # Sound selection with preview
-│ │ │ ├── LabelEditView.swift # Label editing interface
-│ │ │ └── SnoozeSelectionView.swift # Snooze duration selection
+│ │ │ ├── Models/
+│ │ │ │ └── Alarm.swift
+│ │ │ ├── State/
+│ │ │ │ └── AlarmViewModel.swift
+│ │ │ ├── Services/
+│ │ │ │ ├── AlarmService.swift
+│ │ │ │ ├── AlarmSoundService.swift
+│ │ │ │ ├── FocusModeService.swift
+│ │ │ │ ├── NotificationService.swift
+│ │ │ │ └── NotificationDelegate.swift
+│ │ │ └── Views/
+│ │ │ ├── AlarmView.swift
+│ │ │ ├── AddAlarmView.swift
+│ │ │ ├── EditAlarmView.swift
+│ │ │ └── Components/
+│ │ │ ├── AlarmRowView.swift
+│ │ │ ├── EmptyAlarmsView.swift
+│ │ │ ├── LabelEditView.swift
+│ │ │ ├── NotificationMessageEditView.swift
+│ │ │ ├── SnoozeSelectionView.swift
+│ │ │ ├── SoundSelectionView.swift
+│ │ │ ├── TimePickerSection.swift
+│ │ │ └── TimeUntilAlarmSection.swift
│ │ └── Noise/
-│ │ ├── NoiseView.swift # Main white noise player interface
-│ │ └── Components/
-│ │ ├── SoundCategoryView.swift # Advanced grid-based sound selection
-│ │ └── SoundControlView.swift # Playback controls component
-│ ├── Services/
-│ │ ├── AlarmService.swift # Alarm management service with Focus mode integration
-│ │ ├── NotificationService.swift # Notification handling service
-│ │ ├── FocusModeService.swift # Focus mode integration and notification management
-│ │ ├── BatteryService.swift # Battery monitoring and state management service
-│ │ └── AlarmSoundService.swift # Alarm-specific sound management service
+│ │ └── Views/
+│ │ ├── NoiseView.swift
+│ │ └── Components/
+│ │ ├── SoundCategoryView.swift
+│ │ └── SoundControlView.swift
│ └── Resources/
+│ ├── LaunchScreen.storyboard # Branded native launch screen
│ ├── sounds.json # Ambient sound configuration and definitions
│ ├── alarm-sounds.json # Alarm sound configuration and definitions
│ ├── Ambient.bundle/ # Ambient sound category
@@ -557,6 +590,10 @@ The following changes **automatically require** PRD updates:
- Audio playback with background support
- Wake lock management
- Modern @Observable state management
+- **Bedrock**: Local Swift package for design system and settings UI
+ - App-wide theming with color providers
+ - Branded launch experience and icon tooling
+ - Reusable settings components (cards, toggles, sliders)
- **SwiftUI**: Native iOS UI framework with latest features
- **AVFoundation**: Audio playback with modern async patterns and background support
- **UserNotifications**: Alarm notifications with rich content support
@@ -566,6 +603,10 @@ The following changes **automatically require** PRD updates:
- **UIKit**: UIFont integration for precise text measurement and font customization
- **UIApplication**: Screen wake lock management and idle timer control
+### Build Configuration
+- **xcconfig**: Centralized identifiers (bundle IDs, team ID, app groups)
+- **Info.plist bridge**: App identifiers exposed via AppIdentifiers.swift
+
### Advanced Font and Typography Utilities
- **FontFamily enum**: Type-safe font family selection with allCases support
- **Font.Weight extension**: Enhanced weight enum with allCases and uiFontWeight properties
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ff80fa7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,136 @@
+# TheNoiseClock
+
+TheNoiseClock is a SwiftUI iOS app that blends a bold, full-screen digital clock with white noise playback and a rich alarm system. It is optimized for iOS 18+ with Swift 6, built on a modular architecture, and styled with the Bedrock design system.
+
+---
+
+## App Store Information
+
+### App Name
+TheNoiseClock
+
+### Subtitle (30 chars max)
+Clock, noise, and alarms
+
+### Promotional Text (170 chars max)
+A clean, full-screen clock with ambient soundscapes and a powerful alarm editor. Customize colors, fonts, and glow for a distraction-free display.
+
+### Keywords (100 chars max)
+clock,alarm,white noise,ambient,sleep,timer,focus,night mode,bedside
+
+### Description
+TheNoiseClock is a distraction-free digital clock with built-in white noise and a fully featured alarm system. Designed for late-night focus, bedside use, and calm environments, it fills the screen with bold, stable digits and lets you tune the look and sound to your space.
+
+**Clock**
+- Full-screen, orientation-aware clock with optional seconds and AM/PM
+- Fixed-width digits prevent layout "jumping"
+- Auto-fit sizing and manual scale control
+- Custom fonts, weights, and designs with live preview
+- Glow and opacity controls for low-light comfort
+
+**White Noise**
+- Multiple ambient categories and curated sound packs
+- Seamless looping with background audio support
+- Quick preview on long-press, instant play/stop controls
+
+**Alarms**
+- Unlimited alarms with labels, repeat schedules, and snooze options
+- Alarm sound library with preview
+- Vibration and volume controls per alarm
+- Focus-mode aware scheduling
+
+**Display Mode**
+- Long-press to enter immersive display mode
+- Auto-hides navigation and status bar
+- Optional wake-lock to keep the screen on
+
+### What's New
+- Branded launch experience with Bedrock theming
+- Redesigned settings interface with cards, toggles, and sliders
+- Centralized build identifiers via xcconfig
+
+---
+
+## Features
+
+- Real-time digital clock with full customization
+- Full-screen display mode and Dynamic Island awareness
+- White noise playback with categories and previews
+- Rich alarm editor with scheduling and snooze controls
+- Bedrock-based theming and branded launch
+- iPhone and iPad support with adaptive layouts
+
+---
+
+## Requirements
+
+- iOS 18.0+
+- Xcode 16+
+- Swift 6
+
+---
+
+## Build & Run
+
+Open `TheNoiseClock.xcodeproj` and run the `TheNoiseClock` scheme.
+
+### Terminal Build
+```bash
+cd /Users/mattbruce/Documents/Projects/iPhone/TheNoiseClock
+xcodebuild -project TheNoiseClock/TheNoiseClock.xcodeproj -scheme TheNoiseClock -destination 'platform=iOS Simulator,name=iPad mini (A17 Pro),OS=18.1' build
+```
+
+---
+
+## Branding & Theming
+
+The app uses Bedrock for theme tokens, settings UI, and the launch experience.
+
+- Theme: `TheNoiseClock/Shared/Theme/NoiseClockTheme.swift`
+- Branding config: `TheNoiseClock/Shared/BrandingConfig.swift`
+- Launch screen: `TheNoiseClock/Resources/LaunchScreen.storyboard`
+
+To generate an app icon in DEBUG:
+1. Open Clock Settings -> Debug
+2. Tap **Icon Generator**
+3. Export `AppIcon.png` and drop it into `Assets.xcassets/AppIcon`
+
+---
+
+## Build Configuration (xcconfig)
+
+Identifiers are centralized in xcconfig files:
+
+- `TheNoiseClock/Configuration/Base.xcconfig`
+- `TheNoiseClock/Configuration/Debug.xcconfig`
+- `TheNoiseClock/Configuration/Release.xcconfig`
+
+Update `COMPANY_IDENTIFIER` and `DEVELOPMENT_TEAM` in `Base.xcconfig` to migrate to a new account.
+
+Swift access is provided via:
+`TheNoiseClock/Configuration/AppIdentifiers.swift`
+
+---
+
+## Architecture
+
+TheNoiseClock follows a clean, modular structure:
+
+```
+TheNoiseClock/
+├── AudioPlaybackKit/ # Reusable audio package
+├── TheNoiseClock/ # Main app target
+│ ├── App/ # App entry, TabView
+│ ├── Configuration/ # xcconfig + AppIdentifiers
+│ ├── Features/ # Clock, Alarms, Noise
+│ ├── Shared/ # Theme, branding, utilities
+│ └── Resources/ # Bundles, JSON, assets
+└── TheNoiseClock.xcodeproj/
+```
+
+---
+
+## Support & Contact
+
+For feedback, feature requests, or support:
+support@thenoiseclock.app
diff --git a/TheNoiseClock.xcodeproj/project.pbxproj b/TheNoiseClock.xcodeproj/project.pbxproj
index bb88160..05caf6f 100644
--- a/TheNoiseClock.xcodeproj/project.pbxproj
+++ b/TheNoiseClock.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */; };
+ EAC051B12F2E64AB007F87EA /* Bedrock in Frameworks */ = {isa = PBXBuildFile; productRef = EAC051B02F2E64AB007F87EA /* Bedrock */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -31,6 +32,9 @@
EA384AFB2E6E6B6000CA7D50 /* TheNoiseClock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TheNoiseClock.app; sourceTree = BUILT_PRODUCTS_DIR; };
EA384B082E6E6B6100CA7D50 /* TheNoiseClockTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TheNoiseClockTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
EA384B122E6E6B6100CA7D50 /* TheNoiseClockUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TheNoiseClockUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ EAD6E3AE5A7F4D3DB37CF6D1 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TheNoiseClock/Configuration/Base.xcconfig; sourceTree = SOURCE_ROOT; };
+ EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TheNoiseClock/Configuration/Debug.xcconfig; sourceTree = SOURCE_ROOT; };
+ EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TheNoiseClock/Configuration/Release.xcconfig; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -70,6 +74,7 @@
buildActionMask = 2147483647;
files = (
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */,
+ EAC051B12F2E64AB007F87EA /* Bedrock in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -131,6 +136,7 @@
name = TheNoiseClock;
packageProductDependencies = (
EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */,
+ EAC051B02F2E64AB007F87EA /* Bedrock */,
);
productName = TheNoiseClock;
productReference = EA384AFB2E6E6B6000CA7D50 /* TheNoiseClock.app */;
@@ -216,6 +222,7 @@
minimizedProjectReferenceProxies = 1;
packageReferences = (
EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */,
+ EAC051AF2F2E64AB007F87EA /* XCLocalSwiftPackageReference "../Bedrock" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = EA384AFC2E6E6B6000CA7D50 /* Products */;
@@ -293,6 +300,7 @@
/* Begin XCBuildConfiguration section */
EA384B1A2E6E6B6100CA7D50 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -326,7 +334,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -357,6 +365,7 @@
};
EA384B1B2E6E6B6100CA7D50 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -390,7 +399,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -414,18 +423,19 @@
};
EA384B1D2E6E6B6100CA7D50 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TheNoiseClock/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
- INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18;
LD_RUNPATH_SEARCH_PATHS = (
@@ -433,7 +443,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClock;
+ PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -447,18 +457,19 @@
};
EA384B1E2E6E6B6100CA7D50 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TheNoiseClock/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
- INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18;
LD_RUNPATH_SEARCH_PATHS = (
@@ -466,7 +477,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClock;
+ PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -480,15 +491,16 @@
};
EA384B202E6E6B6100CA7D50 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockTests;
+ PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -502,15 +514,16 @@
};
EA384B212E6E6B6100CA7D50 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockTests;
+ PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -524,13 +537,14 @@
};
EA384B232E6E6B6100CA7D50 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockUITests;
+ PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -544,13 +558,14 @@
};
EA384B242E6E6B6100CA7D50 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 6R7KLBPBLZ;
+ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockUITests;
+ PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -608,6 +623,10 @@
isa = XCLocalSwiftPackageReference;
relativePath = AudioPlaybackKit;
};
+ EAC051AF2F2E64AB007F87EA /* XCLocalSwiftPackageReference "../Bedrock" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = ../Bedrock;
+ };
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -616,6 +635,10 @@
package = EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */;
productName = AudioPlaybackKit;
};
+ EAC051B02F2E64AB007F87EA /* Bedrock */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = Bedrock;
+ };
/* End XCSwiftPackageProductDependency section */
};
rootObject = EA384AF32E6E6B6000CA7D50 /* Project object */;
diff --git a/TheNoiseClock.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist b/TheNoiseClock.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist
index 27be183..9fcb510 100644
--- a/TheNoiseClock.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/TheNoiseClock.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
TheNoiseClock.xcscheme_^#shared#^_
orderHint
- 0
+ 2
diff --git a/TheNoiseClock/App/ContentView.swift b/TheNoiseClock/App/ContentView.swift
index 1267fcc..274a8ea 100644
--- a/TheNoiseClock/App/ContentView.swift
+++ b/TheNoiseClock/App/ContentView.swift
@@ -6,6 +6,7 @@
//
import SwiftUI
+import Bedrock
/// Main tab navigation coordinator
struct ContentView: View {
@@ -34,8 +35,8 @@ struct ContentView: View {
Label("Noise", systemImage: "waveform")
}
}
- .accentColor(UIConstants.Colors.accentColor)
- .preferredColorScheme(.dark)
+ .accentColor(AppAccent.primary)
+ .background(Color.Branding.primary.ignoresSafeArea())
}
}
diff --git a/TheNoiseClock/App/TheNoiseClockApp.swift b/TheNoiseClock/App/TheNoiseClockApp.swift
index 2c002b4..50260de 100644
--- a/TheNoiseClock/App/TheNoiseClockApp.swift
+++ b/TheNoiseClock/App/TheNoiseClockApp.swift
@@ -6,6 +6,7 @@
//
import SwiftUI
+import Bedrock
/// App entry point and configuration
@main
@@ -20,7 +21,15 @@ struct TheNoiseClockApp: App {
// MARK: - Body
var body: some Scene {
WindowGroup {
- ContentView()
+ ZStack {
+ Color.Branding.primary
+ .ignoresSafeArea()
+
+ AppLaunchView(config: .noiseClock) {
+ ContentView()
+ }
+ }
+ .preferredColorScheme(.dark)
}
}
}
diff --git a/TheNoiseClock/Assets.xcassets/AppIcon.appiconset/AppIcon-1024px.png b/TheNoiseClock/Assets.xcassets/AppIcon.appiconset/AppIcon-1024px.png
new file mode 100644
index 0000000..0fe2515
Binary files /dev/null and b/TheNoiseClock/Assets.xcassets/AppIcon.appiconset/AppIcon-1024px.png differ
diff --git a/TheNoiseClock/Assets.xcassets/AppIcon.appiconset/Contents.json b/TheNoiseClock/Assets.xcassets/AppIcon.appiconset/Contents.json
index 8121323..9241206 100644
--- a/TheNoiseClock/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/TheNoiseClock/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,48 +1,9 @@
{
"images" : [
{
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "20x20"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "20x20"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "29x29"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "29x29"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "40x40"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "40x40"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "60x60"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "60x60"
- },
- {
- "idiom" : "ios-marketing",
- "scale" : "1x",
+ "filename" : "AppIcon-1024px.png",
+ "idiom" : "universal",
+ "platform" : "ios",
"size" : "1024x1024"
}
],
diff --git a/TheNoiseClock/Configuration/AppIdentifiers.swift b/TheNoiseClock/Configuration/AppIdentifiers.swift
new file mode 100644
index 0000000..7787fe9
--- /dev/null
+++ b/TheNoiseClock/Configuration/AppIdentifiers.swift
@@ -0,0 +1,45 @@
+//
+// AppIdentifiers.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 1/31/26.
+//
+
+import Foundation
+
+enum AppIdentifiers {
+ static let appGroupIdentifier: String = {
+ Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String
+ ?? "group.com.mbrucedogs.TheNoiseClock"
+ }()
+
+ static let cloudKitContainerIdentifier: String = {
+ Bundle.main.object(forInfoDictionaryKey: "CloudKitContainerIdentifier") as? String
+ ?? "iCloud.com.mbrucedogs.TheNoiseClock"
+ }()
+
+ static let appClipDomain: String = {
+ Bundle.main.object(forInfoDictionaryKey: "AppClipDomain") as? String
+ ?? "thenoiseclock.app"
+ }()
+
+ static var bundleIdentifier: String {
+ Bundle.main.bundleIdentifier ?? "com.mbrucedogs.TheNoiseClock"
+ }
+
+ static var watchBundleIdentifier: String {
+ "\(bundleIdentifier).watchkitapp"
+ }
+
+ static var appClipBundleIdentifier: String {
+ "\(bundleIdentifier).Clip"
+ }
+
+ static var widgetBundleIdentifier: String {
+ "\(bundleIdentifier).Widget"
+ }
+
+ static func appClipURL(recordName: String) -> URL? {
+ URL(string: "https://\(appClipDomain)/appclip?id=\(recordName)")
+ }
+}
diff --git a/TheNoiseClock/Configuration/Base.xcconfig b/TheNoiseClock/Configuration/Base.xcconfig
new file mode 100644
index 0000000..d2c9e4d
--- /dev/null
+++ b/TheNoiseClock/Configuration/Base.xcconfig
@@ -0,0 +1,27 @@
+// Base.xcconfig - Source of truth for all identifiers
+// MIGRATION: Update COMPANY_IDENTIFIER and DEVELOPMENT_TEAM below
+
+// =============================================================================
+// COMPANY IDENTIFIER - CHANGE THIS FOR MIGRATION
+// =============================================================================
+
+COMPANY_IDENTIFIER = com.mbrucedogs
+APP_NAME = TheNoiseClock
+DEVELOPMENT_TEAM = 6R7KLBPBLZ
+
+// =============================================================================
+// DERIVED IDENTIFIERS - DO NOT EDIT
+// =============================================================================
+
+APP_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)
+WATCH_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).watchkitapp
+APPCLIP_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).Clip
+WIDGET_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).Widget
+INTENT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).Intent
+TESTS_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)Tests
+UITESTS_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)UITests
+
+APP_GROUP_IDENTIFIER = group.$(COMPANY_IDENTIFIER).$(APP_NAME)
+CLOUDKIT_CONTAINER_IDENTIFIER = iCloud.$(COMPANY_IDENTIFIER).$(APP_NAME)
+
+APPCLIP_DOMAIN = thenoiseclock.app
diff --git a/TheNoiseClock/Configuration/Debug.xcconfig b/TheNoiseClock/Configuration/Debug.xcconfig
new file mode 100644
index 0000000..7b0c18a
--- /dev/null
+++ b/TheNoiseClock/Configuration/Debug.xcconfig
@@ -0,0 +1,6 @@
+// Debug.xcconfig
+#include "Base.xcconfig"
+
+// Debug-specific settings
+SWIFT_OPTIMIZATION_LEVEL = -Onone
+ENABLE_TESTABILITY = YES
diff --git a/TheNoiseClock/Configuration/Release.xcconfig b/TheNoiseClock/Configuration/Release.xcconfig
new file mode 100644
index 0000000..a0e857f
--- /dev/null
+++ b/TheNoiseClock/Configuration/Release.xcconfig
@@ -0,0 +1,6 @@
+// Release.xcconfig
+#include "Base.xcconfig"
+
+// Release-specific settings
+SWIFT_OPTIMIZATION_LEVEL = -O
+SWIFT_COMPILATION_MODE = wholemodule
diff --git a/TheNoiseClock/Models/Alarm.swift b/TheNoiseClock/Features/Alarms/Models/Alarm.swift
similarity index 100%
rename from TheNoiseClock/Models/Alarm.swift
rename to TheNoiseClock/Features/Alarms/Models/Alarm.swift
diff --git a/TheNoiseClock/Services/AlarmService.swift b/TheNoiseClock/Features/Alarms/Services/AlarmService.swift
similarity index 99%
rename from TheNoiseClock/Services/AlarmService.swift
rename to TheNoiseClock/Features/Alarms/Services/AlarmService.swift
index 1c6c3a0..4ab8c54 100644
--- a/TheNoiseClock/Services/AlarmService.swift
+++ b/TheNoiseClock/Features/Alarms/Services/AlarmService.swift
@@ -134,4 +134,3 @@ class AlarmService {
}
}
}
-
diff --git a/TheNoiseClock/Services/AlarmSoundService.swift b/TheNoiseClock/Features/Alarms/Services/AlarmSoundService.swift
similarity index 100%
rename from TheNoiseClock/Services/AlarmSoundService.swift
rename to TheNoiseClock/Features/Alarms/Services/AlarmSoundService.swift
diff --git a/TheNoiseClock/Services/FocusModeService.swift b/TheNoiseClock/Features/Alarms/Services/FocusModeService.swift
similarity index 100%
rename from TheNoiseClock/Services/FocusModeService.swift
rename to TheNoiseClock/Features/Alarms/Services/FocusModeService.swift
diff --git a/TheNoiseClock/Services/NotificationDelegate.swift b/TheNoiseClock/Features/Alarms/Services/NotificationDelegate.swift
similarity index 100%
rename from TheNoiseClock/Services/NotificationDelegate.swift
rename to TheNoiseClock/Features/Alarms/Services/NotificationDelegate.swift
diff --git a/TheNoiseClock/Services/NotificationService.swift b/TheNoiseClock/Features/Alarms/Services/NotificationService.swift
similarity index 100%
rename from TheNoiseClock/Services/NotificationService.swift
rename to TheNoiseClock/Features/Alarms/Services/NotificationService.swift
diff --git a/TheNoiseClock/ViewModels/AlarmViewModel.swift b/TheNoiseClock/Features/Alarms/State/AlarmViewModel.swift
similarity index 100%
rename from TheNoiseClock/ViewModels/AlarmViewModel.swift
rename to TheNoiseClock/Features/Alarms/State/AlarmViewModel.swift
diff --git a/TheNoiseClock/Views/Alarms/AddAlarmView.swift b/TheNoiseClock/Features/Alarms/Views/AddAlarmView.swift
similarity index 99%
rename from TheNoiseClock/Views/Alarms/AddAlarmView.swift
rename to TheNoiseClock/Features/Alarms/Views/AddAlarmView.swift
index 010aa7e..de8b771 100644
--- a/TheNoiseClock/Views/Alarms/AddAlarmView.swift
+++ b/TheNoiseClock/Features/Alarms/Views/AddAlarmView.swift
@@ -127,4 +127,4 @@ struct AddAlarmView: View {
private func getSoundDisplayName(_ fileName: String) -> String {
return AlarmSoundService.shared.getSoundDisplayName(fileName)
}
-}
\ No newline at end of file
+}
diff --git a/TheNoiseClock/Views/Alarms/AlarmView.swift b/TheNoiseClock/Features/Alarms/Views/AlarmView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/AlarmView.swift
rename to TheNoiseClock/Features/Alarms/Views/AlarmView.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/AlarmRowView.swift b/TheNoiseClock/Features/Alarms/Views/Components/AlarmRowView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/AlarmRowView.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/AlarmRowView.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/EmptyAlarmsView.swift b/TheNoiseClock/Features/Alarms/Views/Components/EmptyAlarmsView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/EmptyAlarmsView.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/EmptyAlarmsView.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/LabelEditView.swift b/TheNoiseClock/Features/Alarms/Views/Components/LabelEditView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/LabelEditView.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/LabelEditView.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/NotificationMessageEditView.swift b/TheNoiseClock/Features/Alarms/Views/Components/NotificationMessageEditView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/NotificationMessageEditView.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/NotificationMessageEditView.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/SnoozeSelectionView.swift b/TheNoiseClock/Features/Alarms/Views/Components/SnoozeSelectionView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/SnoozeSelectionView.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/SnoozeSelectionView.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/SoundSelectionView.swift b/TheNoiseClock/Features/Alarms/Views/Components/SoundSelectionView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/SoundSelectionView.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/SoundSelectionView.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/TimePickerSection.swift b/TheNoiseClock/Features/Alarms/Views/Components/TimePickerSection.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/TimePickerSection.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/TimePickerSection.swift
diff --git a/TheNoiseClock/Views/Alarms/Components/TimeUntilAlarmSection.swift b/TheNoiseClock/Features/Alarms/Views/Components/TimeUntilAlarmSection.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/Components/TimeUntilAlarmSection.swift
rename to TheNoiseClock/Features/Alarms/Views/Components/TimeUntilAlarmSection.swift
diff --git a/TheNoiseClock/Views/Alarms/EditAlarmView.swift b/TheNoiseClock/Features/Alarms/Views/EditAlarmView.swift
similarity index 100%
rename from TheNoiseClock/Views/Alarms/EditAlarmView.swift
rename to TheNoiseClock/Features/Alarms/Views/EditAlarmView.swift
diff --git a/TheNoiseClock/Models/ClockStyle.swift b/TheNoiseClock/Features/Clock/Models/ClockStyle.swift
similarity index 100%
rename from TheNoiseClock/Models/ClockStyle.swift
rename to TheNoiseClock/Features/Clock/Models/ClockStyle.swift
diff --git a/TheNoiseClock/Services/AmbientLightService.swift b/TheNoiseClock/Features/Clock/Services/AmbientLightService.swift
similarity index 100%
rename from TheNoiseClock/Services/AmbientLightService.swift
rename to TheNoiseClock/Features/Clock/Services/AmbientLightService.swift
diff --git a/TheNoiseClock/Services/BatteryService.swift b/TheNoiseClock/Features/Clock/Services/BatteryService.swift
similarity index 100%
rename from TheNoiseClock/Services/BatteryService.swift
rename to TheNoiseClock/Features/Clock/Services/BatteryService.swift
diff --git a/TheNoiseClock/ViewModels/ClockViewModel.swift b/TheNoiseClock/Features/Clock/State/ClockViewModel.swift
similarity index 100%
rename from TheNoiseClock/ViewModels/ClockViewModel.swift
rename to TheNoiseClock/Features/Clock/State/ClockViewModel.swift
diff --git a/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift b/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift
new file mode 100644
index 0000000..d8ce42e
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift
@@ -0,0 +1,129 @@
+//
+// ClockSettingsView.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+/// Settings interface for clock customization
+struct ClockSettingsView: View {
+
+ // MARK: - Properties
+ @State private var style: ClockStyle
+ let onCommit: (ClockStyle) -> Void
+
+ @State private var digitColor: Color = .white
+ @State private var backgroundColor: Color = .black
+ @State private var showAdvancedSettings = false
+ @Environment(\.dismiss) private var dismiss
+
+ // MARK: - Init
+ init(style: ClockStyle, onCommit: @escaping (ClockStyle) -> Void) {
+ self._style = State(initialValue: style)
+ self.onCommit = onCommit
+ }
+
+ // MARK: - Body
+ var body: some View {
+ NavigationStack {
+ ScrollView {
+ VStack(spacing: Design.Spacing.xxLarge) {
+ BasicAppearanceSection(
+ style: $style,
+ digitColor: $digitColor,
+ backgroundColor: $backgroundColor
+ )
+
+ BasicDisplaySection(style: $style)
+
+ if showAdvancedSettings {
+ AdvancedAppearanceSection(style: $style)
+
+ FontSection(style: $style)
+
+ NightModeSection(style: $style)
+
+ OverlaySection(style: $style)
+
+ AdvancedDisplaySection(style: $style)
+ }
+
+ SettingsSectionHeader(
+ title: "Advanced",
+ 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
+ )
+ }
+
+ #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)
+ .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)
+ }
+ }
+ }
+}
+
+// MARK: - Preview
+#Preview {
+ ClockSettingsView(
+ style: ClockStyle(),
+ onCommit: { _ in }
+ )
+}
diff --git a/TheNoiseClock/Views/Clock/ClockView.swift b/TheNoiseClock/Features/Clock/Views/ClockView.swift
similarity index 94%
rename from TheNoiseClock/Views/Clock/ClockView.swift
rename to TheNoiseClock/Features/Clock/Views/ClockView.swift
index 8d5565c..fb5fb17 100644
--- a/TheNoiseClock/Views/Clock/ClockView.swift
+++ b/TheNoiseClock/Features/Clock/Views/ClockView.swift
@@ -6,6 +6,7 @@
//
import SwiftUI
+import Bedrock
/// Main clock display view with settings and display mode
struct ClockView: View {
@@ -46,9 +47,10 @@ struct ClockView: View {
ClockSettingsView(style: viewModel.style) { newStyle in
viewModel.updateStyle(newStyle)
}
- .presentationDetents(UIDevice.current.userInterfaceIdiom == .pad ? [.large] : [.medium, .large])
- .presentationDragIndicator(.visible)
+ .presentationDetents([.large])
+ .presentationDragIndicator(.hidden)
.presentationBackgroundInteraction(.enabled)
+ .presentationBackground(AppSurface.overlay)
}
.overlay {
// Toolbar overlay
diff --git a/TheNoiseClock/Views/Clock/Components/BatteryOverlayView.swift b/TheNoiseClock/Features/Clock/Views/Components/BatteryOverlayView.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/BatteryOverlayView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/BatteryOverlayView.swift
diff --git a/TheNoiseClock/Views/Clock/Components/ClockDisplayContainer.swift b/TheNoiseClock/Features/Clock/Views/Components/ClockDisplayContainer.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/ClockDisplayContainer.swift
rename to TheNoiseClock/Features/Clock/Views/Components/ClockDisplayContainer.swift
diff --git a/TheNoiseClock/Views/Clock/Components/ClockGestureHandler.swift b/TheNoiseClock/Features/Clock/Views/Components/ClockGestureHandler.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/ClockGestureHandler.swift
rename to TheNoiseClock/Features/Clock/Views/Components/ClockGestureHandler.swift
diff --git a/TheNoiseClock/Views/Clock/Components/ClockOverlayContainer.swift b/TheNoiseClock/Features/Clock/Views/Components/ClockOverlayContainer.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/ClockOverlayContainer.swift
rename to TheNoiseClock/Features/Clock/Views/Components/ClockOverlayContainer.swift
diff --git a/TheNoiseClock/Views/Clock/Components/ClockTabBarManager.swift b/TheNoiseClock/Features/Clock/Views/Components/ClockTabBarManager.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/ClockTabBarManager.swift
rename to TheNoiseClock/Features/Clock/Views/Components/ClockTabBarManager.swift
diff --git a/TheNoiseClock/Views/Clock/Components/ClockToolbar.swift b/TheNoiseClock/Features/Clock/Views/Components/ClockToolbar.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/ClockToolbar.swift
rename to TheNoiseClock/Features/Clock/Views/Components/ClockToolbar.swift
diff --git a/TheNoiseClock/Views/Clock/Components/ColonView.swift b/TheNoiseClock/Features/Clock/Views/Components/ColonView.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/ColonView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/ColonView.swift
diff --git a/TheNoiseClock/Views/Clock/Components/DateOverlayView.swift b/TheNoiseClock/Features/Clock/Views/Components/DateOverlayView.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/DateOverlayView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/DateOverlayView.swift
diff --git a/TheNoiseClock/Views/Clock/Components/DigitView.swift b/TheNoiseClock/Features/Clock/Views/Components/DigitView.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/DigitView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/DigitView.swift
diff --git a/TheNoiseClock/Views/Clock/Components/DotCircle.swift b/TheNoiseClock/Features/Clock/Views/Components/DotCircle.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/DotCircle.swift
rename to TheNoiseClock/Features/Clock/Views/Components/DotCircle.swift
diff --git a/TheNoiseClock/Views/Clock/Components/FullScreenHintView.swift b/TheNoiseClock/Features/Clock/Views/Components/FullScreenHintView.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/FullScreenHintView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/FullScreenHintView.swift
diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift
new file mode 100644
index 0000000..962601d
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift
@@ -0,0 +1,75 @@
+//
+// AdvancedAppearanceSection.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+struct AdvancedAppearanceSection: View {
+ @Binding var style: ClockStyle
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: Design.Spacing.small) {
+ SettingsSectionHeader(
+ title: "Advanced Appearance",
+ systemImage: "sparkles",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ SettingsToggle(
+ title: "Randomize Color",
+ subtitle: "Shift the color every minute",
+ isOn: $style.randomizeColor,
+ accentColor: AppAccent.primary
+ )
+
+ SettingsToggle(
+ title: "Stretched (Auto‑Fit)",
+ subtitle: "Fit the clock to available space",
+ isOn: $style.stretched,
+ accentColor: AppAccent.primary
+ )
+
+ if !style.stretched {
+ SettingsSlider(
+ title: "Size",
+ subtitle: "Manual scaling when auto‑fit is off",
+ value: $style.digitScale,
+ in: 0.0...1.0,
+ step: 0.01,
+ format: SliderFormat.percentage,
+ accentColor: AppAccent.primary
+ )
+ }
+
+ SettingsSlider(
+ title: "Glow",
+ subtitle: "Adjust the glow intensity",
+ value: $style.glowIntensity,
+ in: 0.0...1.0,
+ step: 0.01,
+ format: SliderFormat.percentage,
+ accentColor: AppAccent.primary
+ )
+
+ SettingsSlider(
+ title: "Clock Opacity",
+ subtitle: "Set the clock transparency",
+ value: $style.clockOpacity,
+ in: 0.0...1.0,
+ step: 0.01,
+ format: SliderFormat.percentage,
+ accentColor: AppAccent.primary
+ )
+ }
+
+ Text("Fine-tune the visual appearance of your clock.")
+ .font(.caption)
+ .foregroundStyle(AppTextColors.tertiary)
+ }
+ }
+}
diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift
new file mode 100644
index 0000000..981b249
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift
@@ -0,0 +1,67 @@
+//
+// AdvancedDisplaySection.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+struct AdvancedDisplaySection: View {
+ @Binding var style: ClockStyle
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: Design.Spacing.small) {
+ SettingsSectionHeader(
+ title: "Advanced Display",
+ systemImage: "eye",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ SettingsToggle(
+ title: "Keep Awake",
+ subtitle: "Prevent sleep in display mode",
+ isOn: $style.keepAwake,
+ accentColor: AppAccent.primary
+ )
+
+ if style.autoBrightness {
+ HStack {
+ Text("Current Brightness")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+ Spacer()
+ Text("\(Int(style.effectiveBrightness * 100))%")
+ .font(.subheadline)
+ .foregroundStyle(AppTextColors.secondary)
+ }
+ }
+ }
+
+ Text("Advanced display and system integration settings.")
+ .font(.caption)
+ .foregroundStyle(AppTextColors.tertiary)
+
+ SettingsSectionHeader(
+ title: "Focus Modes",
+ systemImage: "moon.zzz.fill",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ SettingsToggle(
+ title: "Respect Focus Modes",
+ subtitle: "Follow Do Not Disturb rules",
+ isOn: $style.respectFocusModes,
+ accentColor: AppAccent.primary
+ )
+ }
+
+ Text("Control how the app behaves when Focus modes are active.")
+ .font(.caption)
+ .foregroundStyle(AppTextColors.tertiary)
+ }
+ }
+}
diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift
new file mode 100644
index 0000000..75725ff
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift
@@ -0,0 +1,106 @@
+//
+// BasicAppearanceSection.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+struct BasicAppearanceSection: View {
+ @Binding var style: ClockStyle
+ @Binding var digitColor: Color
+ @Binding var backgroundColor: Color
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: Design.Spacing.small) {
+ SettingsSectionHeader(
+ title: "Colors",
+ systemImage: "paintpalette.fill",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ VStack(alignment: .leading, spacing: Design.Spacing.medium) {
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text("Color Theme")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+
+ Picker("Color Theme", selection: $style.selectedColorTheme) {
+ ForEach(ClockStyle.availableColorThemes(), id: \.0) { theme in
+ HStack {
+ Circle()
+ .fill(themeColor(for: theme.0))
+ .frame(width: 20, height: 20)
+ Text(theme.1)
+ }
+ .tag(theme.0)
+ }
+ }
+ .pickerStyle(.menu)
+ .onChange(of: style.selectedColorTheme) { _, newTheme in
+ if newTheme != "Custom" {
+ style.applyColorTheme(newTheme)
+ digitColor = Color(hex: style.digitColorHex) ?? .white
+ backgroundColor = Color(hex: style.backgroundHex) ?? .black
+ }
+ }
+ }
+
+ if style.selectedColorTheme == "Custom" {
+ ColorPicker("Digit Color", selection: $digitColor, supportsOpacity: false)
+ .foregroundStyle(AppTextColors.primary)
+ ColorPicker("Background Color", selection: $backgroundColor, supportsOpacity: true)
+ .foregroundStyle(AppTextColors.primary)
+ }
+ }
+ }
+
+ Text("Choose your favorite color theme or create a custom look.")
+ .font(.caption)
+ .foregroundStyle(AppTextColors.tertiary)
+ }
+ .onChange(of: backgroundColor) { _, newValue in
+ style.backgroundHex = newValue.toHex() ?? AppConstants.Defaults.backgroundColorHex
+ style.selectedColorTheme = "Custom"
+ style.clearColorCache()
+ }
+ .onChange(of: digitColor) { _, newValue in
+ style.digitColorHex = newValue.toHex() ?? AppConstants.Defaults.digitColorHex
+ style.selectedColorTheme = "Custom"
+ style.clearColorCache()
+ }
+ }
+
+ /// Get the color for a theme
+ private func themeColor(for theme: String) -> Color {
+ switch theme {
+ case "Custom":
+ return .gray
+ case "Night":
+ return .white
+ case "Day":
+ return .black
+ case "Red":
+ return .red
+ case "Orange":
+ return .orange
+ case "Yellow":
+ return .yellow
+ case "Green":
+ return .green
+ case "Blue":
+ return .blue
+ case "Purple":
+ return .purple
+ case "Pink":
+ return .pink
+ case "White":
+ return .white
+ default:
+ return .gray
+ }
+ }
+}
diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift
new file mode 100644
index 0000000..b55c379
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift
@@ -0,0 +1,68 @@
+//
+// BasicDisplaySection.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+struct BasicDisplaySection: View {
+ @Binding var style: ClockStyle
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: Design.Spacing.small) {
+ SettingsSectionHeader(
+ title: "Display",
+ systemImage: "display",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ SettingsToggle(
+ title: "24‑Hour Format",
+ subtitle: "Use military time",
+ isOn: $style.use24Hour,
+ accentColor: AppAccent.primary
+ )
+
+ SettingsToggle(
+ title: "Show Seconds",
+ subtitle: "Display seconds in the clock",
+ isOn: $style.showSeconds,
+ accentColor: AppAccent.primary
+ )
+
+ if !style.use24Hour {
+ SettingsToggle(
+ title: "Show AM/PM",
+ subtitle: "Add an AM/PM indicator",
+ isOn: $style.showAmPm,
+ accentColor: AppAccent.primary
+ )
+ }
+
+ SettingsToggle(
+ title: "Auto Brightness",
+ subtitle: "Adapt brightness to ambient light",
+ isOn: $style.autoBrightness,
+ accentColor: AppAccent.primary
+ )
+
+ if UIDevice.current.orientation.isPortrait || UIDevice.current.orientation == .unknown {
+ SettingsToggle(
+ title: "Horizontal Mode",
+ subtitle: "Force a wide layout in portrait",
+ isOn: $style.forceHorizontalMode,
+ accentColor: AppAccent.primary
+ )
+ }
+ }
+
+ Text("Basic display settings for your clock.")
+ .font(.caption)
+ .foregroundStyle(AppTextColors.tertiary)
+ }
+ }
+}
diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift
new file mode 100644
index 0000000..1d95e22
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift
@@ -0,0 +1,105 @@
+//
+// FontSection.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+struct FontSection: View {
+ @Binding var style: ClockStyle
+
+ // Computed property for available weights based on selected font
+ private var availableWeights: [Font.Weight] {
+ if style.fontFamily == .system {
+ return Font.Weight.allCases
+ } else {
+ return style.fontFamily.fontWeights
+ }
+ }
+
+ // Computed property for sorted font families (System first, then alphabetical)
+ private var sortedFontFamilies: [FontFamily] {
+ let allFamilies = FontFamily.allCases
+ let systemFamily = allFamilies.filter { $0 == .system }
+ let otherFamilies = allFamilies.filter { $0 != .system }.sorted { $0.rawValue < $1.rawValue }
+ return systemFamily + otherFamilies
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: Design.Spacing.small) {
+ SettingsSectionHeader(
+ title: "Font",
+ systemImage: "textformat",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ VStack(alignment: .leading, spacing: Design.Spacing.medium) {
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text("Family")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+
+ Picker("Family", selection: $style.fontFamily) {
+ ForEach(sortedFontFamilies, id: \.self) { family in
+ Text(family.rawValue).tag(family)
+ }
+ }
+ .pickerStyle(.menu)
+ .onChange(of: style.fontFamily) { _, newFamily in
+ if newFamily != .system {
+ style.fontDesign = .default
+ }
+
+ let weights = newFamily == .system ? Font.Weight.allCases : newFamily.fontWeights
+ if !weights.contains(style.fontWeight) {
+ style.fontWeight = weights.first ?? .regular
+ }
+ }
+ }
+
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text("Weight")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+
+ Picker("Weight", selection: $style.fontWeight) {
+ ForEach(availableWeights, id: \.self) { weight in
+ Text(weight.rawValue).tag(weight)
+ }
+ }
+ .pickerStyle(.menu)
+ }
+
+ if style.fontFamily == .system {
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text("Design")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+
+ Picker("Design", selection: $style.fontDesign) {
+ ForEach(Font.Design.allCases, id: \.self) { design in
+ Text(design.rawValue).tag(design)
+ }
+ }
+ .pickerStyle(.menu)
+ }
+ }
+
+ HStack {
+ Text("Preview")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.secondary)
+ Spacer()
+ Text("12:34")
+ .font(FontUtils.createFont(name: style.fontFamily, weight: style.fontWeight, design: style.fontDesign, size: 24))
+ .foregroundStyle(AppTextColors.primary)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift
new file mode 100644
index 0000000..86b8f23
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift
@@ -0,0 +1,91 @@
+//
+// NightModeSection.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+struct NightModeSection: View {
+ @Binding var style: ClockStyle
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: Design.Spacing.small) {
+ SettingsSectionHeader(
+ title: "Night Mode",
+ systemImage: "moon.stars.fill",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ SettingsToggle(
+ title: "Enable Night Mode",
+ subtitle: "Use a red clock for low light",
+ isOn: $style.nightModeEnabled,
+ accentColor: AppAccent.primary
+ )
+
+ SettingsToggle(
+ title: "Auto Night Mode",
+ subtitle: "Trigger based on ambient light",
+ isOn: $style.autoNightMode,
+ accentColor: AppAccent.primary
+ )
+
+ if style.autoNightMode {
+ SettingsSlider(
+ title: "Light Threshold",
+ subtitle: "Lower values activate sooner",
+ value: $style.ambientLightThreshold,
+ in: 0.1...0.8,
+ step: 0.01,
+ format: SliderFormat.percentage,
+ accentColor: AppAccent.primary
+ )
+ }
+
+ SettingsToggle(
+ title: "Scheduled Night Mode",
+ subtitle: "Enable on a daily schedule",
+ isOn: $style.scheduledNightMode,
+ accentColor: AppAccent.primary
+ )
+
+ if style.scheduledNightMode {
+ HStack {
+ Text("Start Time")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+ Spacer()
+ TimePickerView(timeString: $style.nightModeStartTime)
+ }
+
+ HStack {
+ Text("End Time")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+ Spacer()
+ TimePickerView(timeString: $style.nightModeEndTime)
+ }
+ }
+
+ if style.isNightModeActive {
+ HStack(spacing: Design.Spacing.xSmall) {
+ Image(systemName: "moon.fill")
+ .foregroundStyle(AppStatus.error)
+ Text("Night Mode Active")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppStatus.error)
+ Spacer()
+ }
+ }
+ }
+
+ Text("Night mode displays the clock in red to reduce eye strain in low light environments.")
+ .font(.caption)
+ .foregroundStyle(AppTextColors.tertiary)
+ }
+ }
+}
diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift
new file mode 100644
index 0000000..b5931e4
--- /dev/null
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift
@@ -0,0 +1,66 @@
+//
+// OverlaySection.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 9/7/25.
+//
+
+import SwiftUI
+import Bedrock
+
+struct OverlaySection: View {
+ @Binding var style: ClockStyle
+
+ private let dateFormats = Date.availableDateFormats()
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: Design.Spacing.small) {
+ SettingsSectionHeader(
+ title: "Overlays",
+ systemImage: "rectangle.on.rectangle.angled",
+ accentColor: AppAccent.primary
+ )
+
+ SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
+ SettingsSlider(
+ title: "Overlay Opacity",
+ subtitle: "Adjust battery and date visibility",
+ value: $style.overlayOpacity,
+ in: 0.0...1.0,
+ step: 0.01,
+ format: SliderFormat.percentage,
+ accentColor: AppAccent.primary
+ )
+
+ SettingsToggle(
+ title: "Battery Level",
+ subtitle: "Show battery percentage",
+ isOn: $style.showBattery,
+ accentColor: AppAccent.primary
+ )
+
+ SettingsToggle(
+ title: "Date",
+ subtitle: "Display the current date",
+ isOn: $style.showDate,
+ accentColor: AppAccent.primary
+ )
+
+ if style.showDate {
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text("Date Format")
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(AppTextColors.primary)
+
+ Picker("Date Format", selection: $style.dateFormat) {
+ ForEach(dateFormats, id: \.1) { format in
+ Text(format.0).tag(format.1)
+ }
+ }
+ .pickerStyle(.menu)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/TimePickerView.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/TimePickerView.swift
similarity index 96%
rename from TheNoiseClock/Views/Clock/Components/Settings/TimePickerView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/Settings/TimePickerView.swift
index 7fd09c0..79e098e 100644
--- a/TheNoiseClock/Views/Clock/Components/Settings/TimePickerView.swift
+++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/TimePickerView.swift
@@ -6,6 +6,7 @@
//
import SwiftUI
+import Bedrock
struct TimePickerView: View {
@Binding var timeString: String
@@ -14,6 +15,7 @@ struct TimePickerView: View {
var body: some View {
DatePicker("", selection: $selectedTime, displayedComponents: .hourAndMinute)
.labelsHidden()
+ .tint(AppAccent.primary)
.onAppear {
updateSelectedTimeFromString()
}
diff --git a/TheNoiseClock/Views/Clock/Components/TimeDisplayView.swift b/TheNoiseClock/Features/Clock/Views/Components/TimeDisplayView.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/TimeDisplayView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/TimeDisplayView.swift
diff --git a/TheNoiseClock/Views/Clock/Components/TimeSegment.swift b/TheNoiseClock/Features/Clock/Views/Components/TimeSegment.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/TimeSegment.swift
rename to TheNoiseClock/Features/Clock/Views/Components/TimeSegment.swift
diff --git a/TheNoiseClock/Views/Clock/Components/TopOverlayView.swift b/TheNoiseClock/Features/Clock/Views/Components/TopOverlayView.swift
similarity index 100%
rename from TheNoiseClock/Views/Clock/Components/TopOverlayView.swift
rename to TheNoiseClock/Features/Clock/Views/Components/TopOverlayView.swift
diff --git a/TheNoiseClock/Views/Noise/Components/SoundCategoryView.swift b/TheNoiseClock/Features/Noise/Views/Components/SoundCategoryView.swift
similarity index 100%
rename from TheNoiseClock/Views/Noise/Components/SoundCategoryView.swift
rename to TheNoiseClock/Features/Noise/Views/Components/SoundCategoryView.swift
diff --git a/TheNoiseClock/Views/Noise/Components/SoundControlView.swift b/TheNoiseClock/Features/Noise/Views/Components/SoundControlView.swift
similarity index 100%
rename from TheNoiseClock/Views/Noise/Components/SoundControlView.swift
rename to TheNoiseClock/Features/Noise/Views/Components/SoundControlView.swift
diff --git a/TheNoiseClock/Views/Noise/NoiseView.swift b/TheNoiseClock/Features/Noise/Views/NoiseView.swift
similarity index 100%
rename from TheNoiseClock/Views/Noise/NoiseView.swift
rename to TheNoiseClock/Features/Noise/Views/NoiseView.swift
diff --git a/TheNoiseClock/Info.plist b/TheNoiseClock/Info.plist
index f753731..935b1a1 100644
--- a/TheNoiseClock/Info.plist
+++ b/TheNoiseClock/Info.plist
@@ -6,5 +6,11 @@
audio
+ AppGroupIdentifier
+ $(APP_GROUP_IDENTIFIER)
+ CloudKitContainerIdentifier
+ $(CLOUDKIT_CONTAINER_IDENTIFIER)
+ AppClipDomain
+ $(APPCLIP_DOMAIN)
diff --git a/TheNoiseClock/Resources/LaunchScreen.storyboard b/TheNoiseClock/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..6c8da03
--- /dev/null
+++ b/TheNoiseClock/Resources/LaunchScreen.storyboard
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TheNoiseClock/Shared/BrandingConfig.swift b/TheNoiseClock/Shared/BrandingConfig.swift
new file mode 100644
index 0000000..4911362
--- /dev/null
+++ b/TheNoiseClock/Shared/BrandingConfig.swift
@@ -0,0 +1,53 @@
+//
+// BrandingConfig.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 1/31/26.
+//
+
+import SwiftUI
+import Bedrock
+
+// MARK: - App Branding Colors
+
+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
+ }
+}
+
+// MARK: - App Icon Configuration
+
+extension AppIconConfig {
+ static let noiseClock = AppIconConfig(
+ title: "NOISE CLOCK",
+ subtitle: nil,
+ iconSymbol: "waveform",
+ primaryColor: Color.Branding.primary,
+ secondaryColor: Color.Branding.secondary,
+ accentColor: Color.Branding.accent
+ )
+}
+
+// MARK: - Launch Screen Configuration
+
+extension LaunchScreenConfig {
+ static let noiseClock = LaunchScreenConfig(
+ title: "NOISE CLOCK",
+ tagline: "Sleep in sound",
+ iconSymbols: ["waveform", "clock.fill"],
+ cornerSymbol: "sparkle",
+ decorativeSymbol: "circle.fill",
+ patternStyle: .radial,
+ layoutStyle: .iconAboveTitle,
+ primaryColor: Color.Branding.primary,
+ secondaryColor: Color.Branding.secondary,
+ accentColor: Color.Branding.accent,
+ titleColor: .white,
+ iconSpacing: 10,
+ animationDuration: 0.6,
+ showLoadingIndicator: false
+ )
+}
diff --git a/TheNoiseClock/Core/Constants/AppConstants.swift b/TheNoiseClock/Shared/Design/AppConstants.swift
similarity index 100%
rename from TheNoiseClock/Core/Constants/AppConstants.swift
rename to TheNoiseClock/Shared/Design/AppConstants.swift
diff --git a/TheNoiseClock/Core/Utilities/Font.Design.swift b/TheNoiseClock/Shared/Design/Fonts/Font.Design.swift
similarity index 100%
rename from TheNoiseClock/Core/Utilities/Font.Design.swift
rename to TheNoiseClock/Shared/Design/Fonts/Font.Design.swift
diff --git a/TheNoiseClock/Core/Utilities/Font.Weight.swift b/TheNoiseClock/Shared/Design/Fonts/Font.Weight.swift
similarity index 100%
rename from TheNoiseClock/Core/Utilities/Font.Weight.swift
rename to TheNoiseClock/Shared/Design/Fonts/Font.Weight.swift
diff --git a/TheNoiseClock/Core/Utilities/FontFamily.swift b/TheNoiseClock/Shared/Design/Fonts/FontFamily.swift
similarity index 100%
rename from TheNoiseClock/Core/Utilities/FontFamily.swift
rename to TheNoiseClock/Shared/Design/Fonts/FontFamily.swift
diff --git a/TheNoiseClock/Core/Utilities/FontUtils.swift b/TheNoiseClock/Shared/Design/Fonts/FontUtils.swift
similarity index 99%
rename from TheNoiseClock/Core/Utilities/FontUtils.swift
rename to TheNoiseClock/Shared/Design/Fonts/FontUtils.swift
index 222ad8c..73b01ba 100644
--- a/TheNoiseClock/Core/Utilities/FontUtils.swift
+++ b/TheNoiseClock/Shared/Design/Fonts/FontUtils.swift
@@ -307,4 +307,3 @@ private extension Font.Design {
}
}
}
-
diff --git a/TheNoiseClock/Core/Constants/UIConstants.swift b/TheNoiseClock/Shared/Design/UIConstants.swift
similarity index 100%
rename from TheNoiseClock/Core/Constants/UIConstants.swift
rename to TheNoiseClock/Shared/Design/UIConstants.swift
diff --git a/TheNoiseClock/Core/Extensions/Color+Extensions.swift b/TheNoiseClock/Shared/Extensions/Color+Extensions.swift
similarity index 100%
rename from TheNoiseClock/Core/Extensions/Color+Extensions.swift
rename to TheNoiseClock/Shared/Extensions/Color+Extensions.swift
diff --git a/TheNoiseClock/Core/Extensions/Date+Extensions.swift b/TheNoiseClock/Shared/Extensions/Date+Extensions.swift
similarity index 100%
rename from TheNoiseClock/Core/Extensions/Date+Extensions.swift
rename to TheNoiseClock/Shared/Extensions/Date+Extensions.swift
diff --git a/TheNoiseClock/Core/Extensions/View+Extensions.swift b/TheNoiseClock/Shared/Extensions/View+Extensions.swift
similarity index 100%
rename from TheNoiseClock/Core/Extensions/View+Extensions.swift
rename to TheNoiseClock/Shared/Extensions/View+Extensions.swift
diff --git a/TheNoiseClock/Models/SoundCategory.swift b/TheNoiseClock/Shared/Models/SoundCategory.swift
similarity index 100%
rename from TheNoiseClock/Models/SoundCategory.swift
rename to TheNoiseClock/Shared/Models/SoundCategory.swift
diff --git a/TheNoiseClock/Shared/Theme/NoiseClockTheme.swift b/TheNoiseClock/Shared/Theme/NoiseClockTheme.swift
new file mode 100644
index 0000000..c30d5c5
--- /dev/null
+++ b/TheNoiseClock/Shared/Theme/NoiseClockTheme.swift
@@ -0,0 +1,100 @@
+//
+// NoiseClockTheme.swift
+// TheNoiseClock
+//
+// Created by Matt Bruce on 1/31/26.
+//
+
+import SwiftUI
+import Bedrock
+
+// MARK: - NoiseClock Surface Colors
+
+public enum NoiseClockSurfaceColors: SurfaceColorProvider {
+ public static let primary = Color(red: 0.06, green: 0.08, blue: 0.12)
+ public static let secondary = Color(red: 0.09, green: 0.11, blue: 0.18)
+ public static let tertiary = Color(red: 0.12, green: 0.15, blue: 0.22)
+ public static let overlay = Color(red: 0.08, green: 0.10, blue: 0.16)
+ public static let card = Color(red: 0.10, green: 0.13, blue: 0.20)
+ public static let groupedFill = Color(red: 0.09, green: 0.12, blue: 0.18)
+ public static let sectionFill = Color(red: 0.12, green: 0.16, blue: 0.24)
+}
+
+// MARK: - NoiseClock Text Colors
+
+public enum NoiseClockTextColors: TextColorProvider {
+ public static let primary = Color.white
+ public static let secondary = Color.white.opacity(Design.Opacity.accent)
+ public static let tertiary = Color.white.opacity(Design.Opacity.medium)
+ public static let disabled = Color.white.opacity(Design.Opacity.light)
+ public static let placeholder = Color.white.opacity(Design.Opacity.overlay)
+ public static let inverse = Color.black
+}
+
+// MARK: - NoiseClock Accent Colors
+
+public enum NoiseClockAccentColors: AccentColorProvider {
+ public static let primary = Color(red: 0.45, green: 0.75, blue: 1.00)
+ public static let light = Color(red: 0.65, green: 0.85, blue: 1.00)
+ public static let dark = Color(red: 0.25, green: 0.55, blue: 0.90)
+ public static let secondary = Color(red: 0.90, green: 0.95, blue: 1.00)
+}
+
+// MARK: - NoiseClock Button Colors
+
+public enum NoiseClockButtonColors: ButtonColorProvider {
+ public static let primaryLight = Color(red: 0.55, green: 0.80, blue: 1.00)
+ public static let primaryDark = Color(red: 0.20, green: 0.50, blue: 0.85)
+ public static let secondary = Color.white.opacity(Design.Opacity.subtle)
+ public static let destructive = Color.red.opacity(Design.Opacity.heavy)
+ public static let cancelText = Color.white.opacity(Design.Opacity.strong)
+}
+
+// MARK: - NoiseClock Status Colors
+
+public enum NoiseClockStatusColors: StatusColorProvider {
+ public static let success = Color(red: 0.20, green: 0.80, blue: 0.45)
+ public static let warning = Color(red: 1.00, green: 0.75, blue: 0.20)
+ public static let error = Color(red: 0.90, green: 0.30, blue: 0.30)
+ public static let info = Color(red: 0.50, green: 0.70, blue: 0.95)
+}
+
+// MARK: - NoiseClock Border Colors
+
+public enum NoiseClockBorderColors: BorderColorProvider {
+ public static let subtle = Color.white.opacity(Design.Opacity.subtle)
+ public static let standard = Color.white.opacity(Design.Opacity.hint)
+ public static let emphasized = Color.white.opacity(Design.Opacity.light)
+ public static let selected = NoiseClockAccentColors.primary.opacity(Design.Opacity.medium)
+}
+
+// MARK: - NoiseClock Interactive Colors
+
+public enum NoiseClockInteractiveColors: InteractiveColorProvider {
+ public static let selected = NoiseClockAccentColors.primary.opacity(Design.Opacity.selection)
+ public static let hover = Color.white.opacity(Design.Opacity.subtle)
+ public static let pressed = Color.white.opacity(Design.Opacity.hint)
+ public static let focus = NoiseClockAccentColors.light
+}
+
+// MARK: - NoiseClock Theme
+
+public enum NoiseClockTheme: AppColorTheme {
+ public typealias Surface = NoiseClockSurfaceColors
+ public typealias Text = NoiseClockTextColors
+ public typealias Accent = NoiseClockAccentColors
+ public typealias Button = NoiseClockButtonColors
+ public typealias Status = NoiseClockStatusColors
+ public typealias Border = NoiseClockBorderColors
+ public typealias Interactive = NoiseClockInteractiveColors
+}
+
+// MARK: - Convenience Typealiases
+
+typealias AppSurface = NoiseClockSurfaceColors
+typealias AppTextColors = NoiseClockTextColors
+typealias AppAccent = NoiseClockAccentColors
+typealias AppButtonColors = NoiseClockButtonColors
+typealias AppStatus = NoiseClockStatusColors
+typealias AppBorder = NoiseClockBorderColors
+typealias AppInteractive = NoiseClockInteractiveColors
diff --git a/TheNoiseClock/Core/Utilities/ColorUtils.swift b/TheNoiseClock/Shared/Utilities/ColorUtils.swift
similarity index 100%
rename from TheNoiseClock/Core/Utilities/ColorUtils.swift
rename to TheNoiseClock/Shared/Utilities/ColorUtils.swift
diff --git a/TheNoiseClock/Core/Utilities/DebugLogger.swift b/TheNoiseClock/Shared/Utilities/DebugLogger.swift
similarity index 100%
rename from TheNoiseClock/Core/Utilities/DebugLogger.swift
rename to TheNoiseClock/Shared/Utilities/DebugLogger.swift
diff --git a/TheNoiseClock/Core/Utilities/NotificationUtils.swift b/TheNoiseClock/Shared/Utilities/NotificationUtils.swift
similarity index 100%
rename from TheNoiseClock/Core/Utilities/NotificationUtils.swift
rename to TheNoiseClock/Shared/Utilities/NotificationUtils.swift
diff --git a/TheNoiseClock/Views/Clock/ClockSettingsView.swift b/TheNoiseClock/Views/Clock/ClockSettingsView.swift
deleted file mode 100644
index 3773aa6..0000000
--- a/TheNoiseClock/Views/Clock/ClockSettingsView.swift
+++ /dev/null
@@ -1,83 +0,0 @@
-//
-// ClockSettingsView.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-/// Settings interface for clock customization
-struct ClockSettingsView: View {
-
- // MARK: - Properties
- @State private var style: ClockStyle
- let onCommit: (ClockStyle) -> Void
-
- @State private var digitColor: Color = .white
- @State private var backgroundColor: Color = .black
- @State private var showAdvancedSettings = false
-
- // MARK: - Init
- init(style: ClockStyle, onCommit: @escaping (ClockStyle) -> Void) {
- self._style = State(initialValue: style)
- self.onCommit = onCommit
- }
-
- // MARK: - Body
- var body: some View {
- NavigationView {
- Form {
- // BASIC SETTINGS - Most commonly used
- BasicAppearanceSection(
- style: $style,
- digitColor: $digitColor,
- backgroundColor: $backgroundColor,
- onCommit: onCommit
- )
-
- BasicDisplaySection(style: $style)
-
- // ADVANCED SETTINGS - Toggle to show/hide
- if showAdvancedSettings {
- AdvancedAppearanceSection(
- style: $style,
- digitColor: $digitColor,
- backgroundColor: $backgroundColor,
- onCommit: onCommit
- )
-
- FontSection(style: $style)
-
- NightModeSection(style: $style)
-
- OverlaySection(style: $style)
-
- AdvancedDisplaySection(style: $style)
- }
-
- // TOGGLE FOR ADVANCED SETTINGS
- Section {
- Toggle("Show Advanced Settings", isOn: $showAdvancedSettings)
- }
- }
- .navigationTitle("Clock Settings")
- .navigationBarTitleDisplayMode(.inline)
- .onAppear {
- digitColor = Color(hex: style.digitColorHex) ?? .white
- backgroundColor = Color(hex: style.backgroundHex) ?? .black
- }
- .onDisappear {
- onCommit(style)
- }
- }
- }
-}
-
-// MARK: - Preview
-#Preview {
- ClockSettingsView(
- style: ClockStyle(),
- onCommit: { _ in }
- )
-}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/AdvancedAppearanceSection.swift b/TheNoiseClock/Views/Clock/Components/Settings/AdvancedAppearanceSection.swift
deleted file mode 100644
index ef58dd1..0000000
--- a/TheNoiseClock/Views/Clock/Components/Settings/AdvancedAppearanceSection.swift
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// AdvancedAppearanceSection.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-struct AdvancedAppearanceSection: View {
- @Binding var style: ClockStyle
- @Binding var digitColor: Color
- @Binding var backgroundColor: Color
- let onCommit: (ClockStyle) -> Void
-
- var body: some View {
- Section(header: Text("Advanced Appearance"), footer: Text("Fine-tune the visual appearance of your clock.")) {
- Toggle("Randomize Color (every minute)", isOn: $style.randomizeColor)
-
- Toggle("Stretched (auto-fit)", isOn: $style.stretched)
-
- if !style.stretched {
- HStack {
- Text("Size")
- Slider(value: $style.digitScale, in: 0.0...1.0)
- Text("\(Int((min(max(style.digitScale, 0.0), 1.0)) * 100))%")
- .frame(width: 50, alignment: .trailing)
- }
- }
-
- HStack {
- Text("Glow")
- Slider(value: $style.glowIntensity, in: 0...1)
- Text("\(Int(style.glowIntensity * 100))%")
- .frame(width: 50, alignment: .trailing)
- }
-
- HStack {
- Text("Clock Opacity")
- Slider(value: $style.clockOpacity, in: 0.0...1.0)
- Text("\(Int(style.clockOpacity * 100))%")
- .frame(width: 50, alignment: .trailing)
- }
- }
- }
-}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/AdvancedDisplaySection.swift b/TheNoiseClock/Views/Clock/Components/Settings/AdvancedDisplaySection.swift
deleted file mode 100644
index 9021bfc..0000000
--- a/TheNoiseClock/Views/Clock/Components/Settings/AdvancedDisplaySection.swift
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// AdvancedDisplaySection.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-struct AdvancedDisplaySection: View {
- @Binding var style: ClockStyle
-
- var body: some View {
- Section(header: Text("Advanced Display"), footer: Text("Advanced display and system integration settings.")) {
- Toggle("Keep Awake in Display Mode", isOn: $style.keepAwake)
-
- if style.autoBrightness {
- HStack {
- Text("Current Brightness")
- Spacer()
- Text("\(Int(style.effectiveBrightness * 100))%")
- .foregroundColor(.secondary)
- }
- }
- }
-
- Section(header: Text("Focus Modes"), footer: Text("Control how the app behaves when Focus modes (Do Not Disturb) are active.")) {
- Toggle("Respect Focus Modes", isOn: $style.respectFocusModes)
- }
- }
-}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/BasicAppearanceSection.swift b/TheNoiseClock/Views/Clock/Components/Settings/BasicAppearanceSection.swift
deleted file mode 100644
index 2beaf73..0000000
--- a/TheNoiseClock/Views/Clock/Components/Settings/BasicAppearanceSection.swift
+++ /dev/null
@@ -1,86 +0,0 @@
-//
-// BasicAppearanceSection.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-struct BasicAppearanceSection: View {
- @Binding var style: ClockStyle
- @Binding var digitColor: Color
- @Binding var backgroundColor: Color
- let onCommit: (ClockStyle) -> Void
-
- var body: some View {
- Section(header: Text("Colors"), footer: Text("Choose your favorite color theme or create a custom look.")) {
- // Color Theme Picker
- Picker("Color Theme", selection: $style.selectedColorTheme) {
- ForEach(ClockStyle.availableColorThemes(), id: \.0) { theme in
- HStack {
- Circle()
- .fill(themeColor(for: theme.0))
- .frame(width: 20, height: 20)
- Text(theme.1)
- }
- .tag(theme.0)
- }
- }
- .pickerStyle(.menu)
- .onChange(of: style.selectedColorTheme) { _, newTheme in
- if newTheme != "Custom" {
- style.applyColorTheme(newTheme)
- digitColor = Color(hex: style.digitColorHex) ?? .white
- backgroundColor = Color(hex: style.backgroundHex) ?? .black
- }
- }
-
- // Custom color pickers (only show if Custom is selected)
- if style.selectedColorTheme == "Custom" {
- ColorPicker("Digit Color", selection: $digitColor, supportsOpacity: false)
- ColorPicker("Background Color", selection: $backgroundColor, supportsOpacity: true)
- }
- }
- .onChange(of: backgroundColor) { _, newValue in
- style.backgroundHex = newValue.toHex() ?? AppConstants.Defaults.backgroundColorHex
- style.selectedColorTheme = "Custom"
- style.clearColorCache()
- }
- .onChange(of: digitColor) { _, newValue in
- style.digitColorHex = newValue.toHex() ?? AppConstants.Defaults.digitColorHex
- style.selectedColorTheme = "Custom"
- style.clearColorCache()
- }
- }
-
- /// Get the color for a theme
- private func themeColor(for theme: String) -> Color {
- switch theme {
- case "Custom":
- return .gray
- case "Night":
- return .white
- case "Day":
- return .black
- case "Red":
- return .red
- case "Orange":
- return .orange
- case "Yellow":
- return .yellow
- case "Green":
- return .green
- case "Blue":
- return .blue
- case "Purple":
- return .purple
- case "Pink":
- return .pink
- case "White":
- return .white
- default:
- return .gray
- }
- }
-}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/BasicDisplaySection.swift b/TheNoiseClock/Views/Clock/Components/Settings/BasicDisplaySection.swift
deleted file mode 100644
index c69823a..0000000
--- a/TheNoiseClock/Views/Clock/Components/Settings/BasicDisplaySection.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// BasicDisplaySection.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-struct BasicDisplaySection: View {
- @Binding var style: ClockStyle
-
- var body: some View {
- Section(header: Text("Display"), footer: Text("Basic display settings for your clock.")) {
- Toggle("24‑Hour Format", isOn: $style.use24Hour)
- Toggle("Show Seconds", isOn: $style.showSeconds)
-
- if !style.use24Hour {
- Toggle("Show AM/PM", isOn: $style.showAmPm)
- }
-
- Toggle("Auto Brightness", isOn: $style.autoBrightness)
-
- // Only show horizontal mode option in portrait orientation
- if UIDevice.current.orientation.isPortrait || UIDevice.current.orientation == .unknown {
- Toggle("Horizontal Mode", isOn: $style.forceHorizontalMode)
- }
- }
- }
-}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/FontSection.swift b/TheNoiseClock/Views/Clock/Components/Settings/FontSection.swift
deleted file mode 100644
index 8ab2ece..0000000
--- a/TheNoiseClock/Views/Clock/Components/Settings/FontSection.swift
+++ /dev/null
@@ -1,81 +0,0 @@
-//
-// FontSection.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-struct FontSection: View {
- @Binding var style: ClockStyle
-
- // Computed property for available weights based on selected font
- private var availableWeights: [Font.Weight] {
- if style.fontFamily == .system {
- return Font.Weight.allCases
- } else {
- return style.fontFamily.fontWeights
- }
- }
-
- // Computed property for sorted font families (System first, then alphabetical)
- private var sortedFontFamilies: [FontFamily] {
- let allFamilies = FontFamily.allCases
- let systemFamily = allFamilies.filter { $0 == .system }
- let otherFamilies = allFamilies.filter { $0 != .system }.sorted { $0.rawValue < $1.rawValue }
- return systemFamily + otherFamilies
- }
-
- var body: some View {
- Section(header: Text("Font")) {
- // Font Family
- Picker("Family", selection: $style.fontFamily) {
- ForEach(sortedFontFamilies, id: \.self) { family in
- Text(family.rawValue).tag(family)
- }
- }
- .pickerStyle(.menu)
- .onChange(of: style.fontFamily) { _, newFamily in
- // Auto-set design to default for non-system fonts
- if newFamily != .system {
- style.fontDesign = .default
- }
-
- // Auto-set weight to first available weight if current weight is not available
- let weights = newFamily == .system ? Font.Weight.allCases : newFamily.fontWeights
- if !weights.contains(style.fontWeight) {
- style.fontWeight = weights.first ?? .regular
- }
- }
-
- // Font Weight - show available weights for selected font
- Picker("Weight", selection: $style.fontWeight) {
- ForEach(availableWeights, id: \.self) { weight in
- Text(weight.rawValue).tag(weight)
- }
- }
- .pickerStyle(.menu)
-
- // Font Design - only show for system font
- if style.fontFamily == .system {
- Picker("Design", selection: $style.fontDesign) {
- ForEach(Font.Design.allCases, id: \.self) { design in
- Text(design.rawValue).tag(design)
- }
- }
- .pickerStyle(.menu)
- }
-
- // Font Preview
- HStack {
- Text("Preview:")
- .foregroundColor(.secondary)
- Spacer()
- Text("12:34")
- .font(FontUtils.createFont(name: style.fontFamily, weight: style.fontWeight, design: style.fontDesign, size: 24))
- .foregroundColor(.primary)
- }
- }
- }
-}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/NightModeSection.swift b/TheNoiseClock/Views/Clock/Components/Settings/NightModeSection.swift
deleted file mode 100644
index d508f12..0000000
--- a/TheNoiseClock/Views/Clock/Components/Settings/NightModeSection.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// NightModeSection.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-struct NightModeSection: View {
- @Binding var style: ClockStyle
-
- var body: some View {
- Section(header: Text("Night Mode"), footer: Text("Night mode displays the clock in red to reduce eye strain in low light environments.")) {
- Toggle("Enable Night Mode", isOn: $style.nightModeEnabled)
-
- Toggle("Auto Night Mode", isOn: $style.autoNightMode)
-
- if style.autoNightMode {
- HStack {
- Text("Light Threshold")
- Spacer()
- Slider(value: $style.ambientLightThreshold, in: 0.1...0.8)
- Text("\(Int(style.ambientLightThreshold * 100))%")
- .frame(width: 50, alignment: .trailing)
- }
- }
-
- Toggle("Scheduled Night Mode", isOn: $style.scheduledNightMode)
-
- if style.scheduledNightMode {
- HStack {
- Text("Start Time")
- Spacer()
- TimePickerView(timeString: $style.nightModeStartTime)
- }
-
- HStack {
- Text("End Time")
- Spacer()
- TimePickerView(timeString: $style.nightModeEndTime)
- }
- }
-
- if style.isNightModeActive {
- HStack {
- Image(systemName: "moon.fill")
- .foregroundColor(.red)
- Text("Night Mode Active")
- .foregroundColor(.red)
- Spacer()
- }
- }
- }
- }
-}
diff --git a/TheNoiseClock/Views/Clock/Components/Settings/OverlaySection.swift b/TheNoiseClock/Views/Clock/Components/Settings/OverlaySection.swift
deleted file mode 100644
index 120fa16..0000000
--- a/TheNoiseClock/Views/Clock/Components/Settings/OverlaySection.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// OverlaySection.swift
-// TheNoiseClock
-//
-// Created by Matt Bruce on 9/7/25.
-//
-
-import SwiftUI
-
-struct OverlaySection: View {
- @Binding var style: ClockStyle
-
- private let dateFormats = Date.availableDateFormats()
-
- var body: some View {
- Section(header: Text("Overlays")) {
- HStack {
- Text("Overlay Opacity")
- Slider(value: $style.overlayOpacity, in: 0.0...1.0)
- Text("\(Int(style.overlayOpacity * 100))%")
- .frame(width: 50, alignment: .trailing)
- }
-
- Toggle("Battery Level", isOn: $style.showBattery)
- Toggle("Date", isOn: $style.showDate)
-
- if style.showDate {
- Picker("Date Format", selection: $style.dateFormat) {
- ForEach(dateFormats, id: \.1) { format in
- Text(format.0).tag(format.1)
- }
- }
- .pickerStyle(.menu)
- }
- }
- }
-}