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) - } - } - } -}