refactored with bedrock and organized folders
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
0909f93368
commit
a3398f0dd0
159
PRD.md
159
PRD.md
@ -223,6 +223,7 @@ These principles are fundamental to the project's long-term success and must be
|
|||||||
|
|
||||||
### App Structure
|
### App Structure
|
||||||
- **Main App**: `TheNoiseClockApp.swift` - Entry point with WindowGroup
|
- **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)
|
- **Tab-based navigation**: Three main tabs (Clock, Alarms, Noise)
|
||||||
- **SwiftUI framework**: Modern declarative UI framework with iOS 18+ and iOS 26 features
|
- **SwiftUI framework**: Modern declarative UI framework with iOS 18+ and iOS 26 features
|
||||||
- **Dark theme**: Preferred color scheme set to dark
|
- **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
|
### Visual Design
|
||||||
- **Rounded corners**: Modern iOS design language
|
- **Rounded corners**: Modern iOS design language
|
||||||
- **Modern animations**: iOS 18+ smooth and bouncy animations
|
- **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
|
- **Accessibility**: Proper labels and hidden decorative elements
|
||||||
- **Form-based layouts**: Organized sections for settings and alarm editing
|
- **Card-based layouts**: Bedrock settings cards for grouping controls
|
||||||
- **Interactive controls**: Toggles, sliders, color pickers, and date pickers
|
- **Interactive controls**: Bedrock toggles, sliders, pickers, and color pickers
|
||||||
|
|
||||||
### Settings Interface
|
### Settings Interface
|
||||||
- **Form-based layout**: Organized sections for different setting categories
|
- **Bedrock layout**: Section headers with card-based grouping
|
||||||
- **Interactive controls**: Toggles, sliders, color pickers, enum-based pickers
|
- **Interactive controls**: SettingsToggle, SettingsSlider, menu pickers, color pickers
|
||||||
- **Type-safe font selection**: FontFamily.allCases, Font.Weight.allCases, Font.Design.allCases pickers
|
- **Type-safe font selection**: FontFamily.allCases, Font.Weight.allCases, Font.Design.allCases pickers
|
||||||
- **Real-time updates**: Changes apply immediately with live preview
|
- **Real-time updates**: Changes apply immediately with live preview
|
||||||
- **Sheet presentation**: Modal settings with detents
|
- **Sheet presentation**: Full-screen settings sheet for uninterrupted editing
|
||||||
- **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
|
|
||||||
- **Enum-based architecture**: Type-safe picker selections eliminate string-based errors
|
- **Enum-based architecture**: Type-safe picker selections eliminate string-based errors
|
||||||
|
|
||||||
## File Structure and Organization
|
## File Structure and Organization
|
||||||
@ -349,6 +349,7 @@ Following the separation of concerns principle, the codebase is organized into f
|
|||||||
|
|
||||||
```
|
```
|
||||||
TheNoiseClock/
|
TheNoiseClock/
|
||||||
|
├── README.md # App Store copy + project overview
|
||||||
├── AudioPlaybackKit/ # Swift Package for reusable audio functionality
|
├── AudioPlaybackKit/ # Swift Package for reusable audio functionality
|
||||||
│ ├── Package.swift # Package configuration (iOS 17.0+, tvOS 17.0+)
|
│ ├── Package.swift # Package configuration (iOS 17.0+, tvOS 17.0+)
|
||||||
│ └── Sources/AudioPlaybackKit/
|
│ └── Sources/AudioPlaybackKit/
|
||||||
@ -366,69 +367,101 @@ TheNoiseClock/
|
|||||||
│ ├── App/
|
│ ├── App/
|
||||||
│ │ ├── TheNoiseClockApp.swift # App entry point and configuration
|
│ │ ├── TheNoiseClockApp.swift # App entry point and configuration
|
||||||
│ │ └── ContentView.swift # Main tab navigation coordinator
|
│ │ └── ContentView.swift # Main tab navigation coordinator
|
||||||
│ ├── Core/
|
│ ├── Configuration/
|
||||||
│ │ ├── Constants/
|
│ │ ├── 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
|
│ │ │ ├── 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/
|
│ │ ├── Extensions/
|
||||||
│ │ │ ├── Color+Extensions.swift # Color utilities and extensions
|
│ │ │ ├── Color+Extensions.swift # Color utilities and extensions
|
||||||
│ │ │ ├── Date+Extensions.swift # Date formatting and utilities
|
│ │ │ ├── Date+Extensions.swift # Date formatting and utilities
|
||||||
│ │ │ └── View+Extensions.swift # Common view modifiers and responsive utilities
|
│ │ │ └── View+Extensions.swift # Common view modifiers and responsive utilities
|
||||||
|
│ │ ├── Models/
|
||||||
|
│ │ │ └── SoundCategory.swift # Shared sound category definitions
|
||||||
│ │ └── Utilities/
|
│ │ └── Utilities/
|
||||||
│ │ ├── ColorUtils.swift # Color manipulation utilities
|
│ │ ├── ColorUtils.swift # Color manipulation utilities
|
||||||
│ │ ├── FontUtils.swift # Font sizing, typography, and customization utilities
|
│ │ ├── DebugLogger.swift # Debug logging helper
|
||||||
│ │ └── NotificationUtils.swift # Notification helper functions
|
│ │ └── NotificationUtils.swift # Notification helper functions
|
||||||
│ ├── Models/
|
│ ├── Features/
|
||||||
│ │ ├── 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/
|
|
||||||
│ │ ├── Clock/
|
│ │ ├── Clock/
|
||||||
│ │ │ ├── ClockView.swift # Main clock display view
|
│ │ │ ├── Models/
|
||||||
│ │ │ ├── ClockSettingsView.swift # Clock customization interface
|
│ │ │ │ └── ClockStyle.swift
|
||||||
│ │ │ └── Components/
|
│ │ │ ├── State/
|
||||||
│ │ │ ├── TimeDisplayView.swift # Advanced segmented time display with dynamic font sizing
|
│ │ │ │ └── ClockViewModel.swift
|
||||||
│ │ │ ├── TimeSegment.swift # Individual time segment with enum-based font properties
|
│ │ │ ├── Services/
|
||||||
│ │ │ ├── DigitView.swift # Single digit display with binary search font sizing
|
│ │ │ │ ├── AmbientLightService.swift
|
||||||
│ │ │ ├── ColonView.swift # Unified colon separator (horizontal/vertical)
|
│ │ │ │ └── BatteryService.swift
|
||||||
│ │ │ ├── DotCircle.swift # Individual dot component for colons
|
│ │ │ └── Views/
|
||||||
│ │ │ ├── BatteryOverlayView.swift # Battery level overlay
|
│ │ │ ├── ClockView.swift
|
||||||
│ │ │ ├── DateOverlayView.swift # Date display overlay
|
│ │ │ ├── ClockSettingsView.swift
|
||||||
│ │ │ ├── TopOverlayView.swift # Combined overlay container
|
│ │ │ └── Components/
|
||||||
│ │ │ └── Settings/
|
│ │ │ ├── TimeDisplayView.swift
|
||||||
│ │ │ ├── BasicAppearanceSection.swift
|
│ │ │ ├── TimeSegment.swift
|
||||||
│ │ │ ├── BasicDisplaySection.swift
|
│ │ │ ├── DigitView.swift
|
||||||
│ │ │ ├── AdvancedAppearanceSection.swift
|
│ │ │ ├── ColonView.swift
|
||||||
│ │ │ ├── AdvancedDisplaySection.swift
|
│ │ │ ├── DotCircle.swift
|
||||||
│ │ │ ├── FontSection.swift
|
│ │ │ ├── BatteryOverlayView.swift
|
||||||
│ │ │ ├── NightModeSection.swift
|
│ │ │ ├── DateOverlayView.swift
|
||||||
│ │ │ ├── OverlaySection.swift
|
│ │ │ ├── TopOverlayView.swift
|
||||||
│ │ │ └── TimePickerView.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/
|
│ │ ├── Alarms/
|
||||||
│ │ │ ├── AlarmView.swift # Main alarm management view
|
│ │ │ ├── Models/
|
||||||
│ │ │ ├── AddAlarmView.swift # Alarm creation interface
|
│ │ │ │ └── Alarm.swift
|
||||||
│ │ │ └── Components/
|
│ │ │ ├── State/
|
||||||
│ │ │ ├── AlarmRowView.swift # Individual alarm row component
|
│ │ │ │ └── AlarmViewModel.swift
|
||||||
│ │ │ ├── TimePickerSection.swift # Time selection component
|
│ │ │ ├── Services/
|
||||||
│ │ │ ├── TimeUntilAlarmSection.swift # Time calculation display
|
│ │ │ │ ├── AlarmService.swift
|
||||||
│ │ │ ├── SoundSelectionView.swift # Sound selection with preview
|
│ │ │ │ ├── AlarmSoundService.swift
|
||||||
│ │ │ ├── LabelEditView.swift # Label editing interface
|
│ │ │ │ ├── FocusModeService.swift
|
||||||
│ │ │ └── SnoozeSelectionView.swift # Snooze duration selection
|
│ │ │ │ ├── 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/
|
│ │ └── Noise/
|
||||||
│ │ ├── NoiseView.swift # Main white noise player interface
|
│ │ └── Views/
|
||||||
│ │ └── Components/
|
│ │ ├── NoiseView.swift
|
||||||
│ │ ├── SoundCategoryView.swift # Advanced grid-based sound selection
|
│ │ └── Components/
|
||||||
│ │ └── SoundControlView.swift # Playback controls component
|
│ │ ├── SoundCategoryView.swift
|
||||||
│ ├── Services/
|
│ │ └── SoundControlView.swift
|
||||||
│ │ ├── 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
|
|
||||||
│ └── Resources/
|
│ └── Resources/
|
||||||
|
│ ├── LaunchScreen.storyboard # Branded native launch screen
|
||||||
│ ├── sounds.json # Ambient sound configuration and definitions
|
│ ├── sounds.json # Ambient sound configuration and definitions
|
||||||
│ ├── alarm-sounds.json # Alarm sound configuration and definitions
|
│ ├── alarm-sounds.json # Alarm sound configuration and definitions
|
||||||
│ ├── Ambient.bundle/ # Ambient sound category
|
│ ├── Ambient.bundle/ # Ambient sound category
|
||||||
@ -557,6 +590,10 @@ The following changes **automatically require** PRD updates:
|
|||||||
- Audio playback with background support
|
- Audio playback with background support
|
||||||
- Wake lock management
|
- Wake lock management
|
||||||
- Modern @Observable state 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
|
- **SwiftUI**: Native iOS UI framework with latest features
|
||||||
- **AVFoundation**: Audio playback with modern async patterns and background support
|
- **AVFoundation**: Audio playback with modern async patterns and background support
|
||||||
- **UserNotifications**: Alarm notifications with rich content 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
|
- **UIKit**: UIFont integration for precise text measurement and font customization
|
||||||
- **UIApplication**: Screen wake lock management and idle timer control
|
- **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
|
### Advanced Font and Typography Utilities
|
||||||
- **FontFamily enum**: Type-safe font family selection with allCases support
|
- **FontFamily enum**: Type-safe font family selection with allCases support
|
||||||
- **Font.Weight extension**: Enhanced weight enum with allCases and uiFontWeight properties
|
- **Font.Weight extension**: Enhanced weight enum with allCases and uiFontWeight properties
|
||||||
|
|||||||
136
README.md
Normal file
136
README.md
Normal file
@ -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
|
||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */; };
|
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */; };
|
||||||
|
EAC051B12F2E64AB007F87EA /* Bedrock in Frameworks */ = {isa = PBXBuildFile; productRef = EAC051B02F2E64AB007F87EA /* Bedrock */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy 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; };
|
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; };
|
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; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@ -70,6 +74,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */,
|
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */,
|
||||||
|
EAC051B12F2E64AB007F87EA /* Bedrock in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -131,6 +136,7 @@
|
|||||||
name = TheNoiseClock;
|
name = TheNoiseClock;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */,
|
EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */,
|
||||||
|
EAC051B02F2E64AB007F87EA /* Bedrock */,
|
||||||
);
|
);
|
||||||
productName = TheNoiseClock;
|
productName = TheNoiseClock;
|
||||||
productReference = EA384AFB2E6E6B6000CA7D50 /* TheNoiseClock.app */;
|
productReference = EA384AFB2E6E6B6000CA7D50 /* TheNoiseClock.app */;
|
||||||
@ -216,6 +222,7 @@
|
|||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */,
|
EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */,
|
||||||
|
EAC051AF2F2E64AB007F87EA /* XCLocalSwiftPackageReference "../Bedrock" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = EA384AFC2E6E6B6000CA7D50 /* Products */;
|
productRefGroup = EA384AFC2E6E6B6000CA7D50 /* Products */;
|
||||||
@ -293,6 +300,7 @@
|
|||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
EA384B1A2E6E6B6100CA7D50 /* Debug */ = {
|
EA384B1A2E6E6B6100CA7D50 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@ -326,7 +334,7 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@ -357,6 +365,7 @@
|
|||||||
};
|
};
|
||||||
EA384B1B2E6E6B6100CA7D50 /* Release */ = {
|
EA384B1B2E6E6B6100CA7D50 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@ -390,7 +399,7 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@ -414,18 +423,19 @@
|
|||||||
};
|
};
|
||||||
EA384B1D2E6E6B6100CA7D50 /* Debug */ = {
|
EA384B1D2E6E6B6100CA7D50 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = TheNoiseClock/Info.plist;
|
INFOPLIST_FILE = TheNoiseClock/Info.plist;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18;
|
IPHONEOS_DEPLOYMENT_TARGET = 18;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -433,7 +443,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClock;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@ -447,18 +457,19 @@
|
|||||||
};
|
};
|
||||||
EA384B1E2E6E6B6100CA7D50 /* Release */ = {
|
EA384B1E2E6E6B6100CA7D50 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = TheNoiseClock/Info.plist;
|
INFOPLIST_FILE = TheNoiseClock/Info.plist;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18;
|
IPHONEOS_DEPLOYMENT_TARGET = 18;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -466,7 +477,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClock;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@ -480,15 +491,16 @@
|
|||||||
};
|
};
|
||||||
EA384B202E6E6B6100CA7D50 /* Debug */ = {
|
EA384B202E6E6B6100CA7D50 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockTests;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@ -502,15 +514,16 @@
|
|||||||
};
|
};
|
||||||
EA384B212E6E6B6100CA7D50 /* Release */ = {
|
EA384B212E6E6B6100CA7D50 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockTests;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@ -524,13 +537,14 @@
|
|||||||
};
|
};
|
||||||
EA384B232E6E6B6100CA7D50 /* Debug */ = {
|
EA384B232E6E6B6100CA7D50 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockUITests;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@ -544,13 +558,14 @@
|
|||||||
};
|
};
|
||||||
EA384B242E6E6B6100CA7D50 /* Release */ = {
|
EA384B242E6E6B6100CA7D50 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockUITests;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@ -608,6 +623,10 @@
|
|||||||
isa = XCLocalSwiftPackageReference;
|
isa = XCLocalSwiftPackageReference;
|
||||||
relativePath = AudioPlaybackKit;
|
relativePath = AudioPlaybackKit;
|
||||||
};
|
};
|
||||||
|
EAC051AF2F2E64AB007F87EA /* XCLocalSwiftPackageReference "../Bedrock" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = ../Bedrock;
|
||||||
|
};
|
||||||
/* End XCLocalSwiftPackageReference section */
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
@ -616,6 +635,10 @@
|
|||||||
package = EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */;
|
package = EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */;
|
||||||
productName = AudioPlaybackKit;
|
productName = AudioPlaybackKit;
|
||||||
};
|
};
|
||||||
|
EAC051B02F2E64AB007F87EA /* Bedrock */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Bedrock;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = EA384AF32E6E6B6000CA7D50 /* Project object */;
|
rootObject = EA384AF32E6E6B6000CA7D50 /* Project object */;
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<key>TheNoiseClock.xcscheme_^#shared#^_</key>
|
<key>TheNoiseClock.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>2</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Bedrock
|
||||||
|
|
||||||
/// Main tab navigation coordinator
|
/// Main tab navigation coordinator
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ -34,8 +35,8 @@ struct ContentView: View {
|
|||||||
Label("Noise", systemImage: "waveform")
|
Label("Noise", systemImage: "waveform")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.accentColor(UIConstants.Colors.accentColor)
|
.accentColor(AppAccent.primary)
|
||||||
.preferredColorScheme(.dark)
|
.background(Color.Branding.primary.ignoresSafeArea())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Bedrock
|
||||||
|
|
||||||
/// App entry point and configuration
|
/// App entry point and configuration
|
||||||
@main
|
@main
|
||||||
@ -20,7 +21,15 @@ struct TheNoiseClockApp: App {
|
|||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ZStack {
|
||||||
|
Color.Branding.primary
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
AppLaunchView(config: .noiseClock) {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.preferredColorScheme(.dark)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@ -1,48 +1,9 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "iphone",
|
"filename" : "AppIcon-1024px.png",
|
||||||
"scale" : "2x",
|
"idiom" : "universal",
|
||||||
"size" : "20x20"
|
"platform" : "ios",
|
||||||
},
|
|
||||||
{
|
|
||||||
"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",
|
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
45
TheNoiseClock/Configuration/AppIdentifiers.swift
Normal file
45
TheNoiseClock/Configuration/AppIdentifiers.swift
Normal file
@ -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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
27
TheNoiseClock/Configuration/Base.xcconfig
Normal file
27
TheNoiseClock/Configuration/Base.xcconfig
Normal file
@ -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
|
||||||
6
TheNoiseClock/Configuration/Debug.xcconfig
Normal file
6
TheNoiseClock/Configuration/Debug.xcconfig
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Debug.xcconfig
|
||||||
|
#include "Base.xcconfig"
|
||||||
|
|
||||||
|
// Debug-specific settings
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = -Onone
|
||||||
|
ENABLE_TESTABILITY = YES
|
||||||
6
TheNoiseClock/Configuration/Release.xcconfig
Normal file
6
TheNoiseClock/Configuration/Release.xcconfig
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Release.xcconfig
|
||||||
|
#include "Base.xcconfig"
|
||||||
|
|
||||||
|
// Release-specific settings
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = -O
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule
|
||||||
@ -134,4 +134,3 @@ class AlarmService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,4 +127,4 @@ struct AddAlarmView: View {
|
|||||||
private func getSoundDisplayName(_ fileName: String) -> String {
|
private func getSoundDisplayName(_ fileName: String) -> String {
|
||||||
return AlarmSoundService.shared.getSoundDisplayName(fileName)
|
return AlarmSoundService.shared.getSoundDisplayName(fileName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
129
TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift
Normal file
129
TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift
Normal file
@ -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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Bedrock
|
||||||
|
|
||||||
/// Main clock display view with settings and display mode
|
/// Main clock display view with settings and display mode
|
||||||
struct ClockView: View {
|
struct ClockView: View {
|
||||||
@ -46,9 +47,10 @@ struct ClockView: View {
|
|||||||
ClockSettingsView(style: viewModel.style) { newStyle in
|
ClockSettingsView(style: viewModel.style) { newStyle in
|
||||||
viewModel.updateStyle(newStyle)
|
viewModel.updateStyle(newStyle)
|
||||||
}
|
}
|
||||||
.presentationDetents(UIDevice.current.userInterfaceIdiom == .pad ? [.large] : [.medium, .large])
|
.presentationDetents([.large])
|
||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.hidden)
|
||||||
.presentationBackgroundInteraction(.enabled)
|
.presentationBackgroundInteraction(.enabled)
|
||||||
|
.presentationBackground(AppSurface.overlay)
|
||||||
}
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
// Toolbar overlay
|
// Toolbar overlay
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Bedrock
|
||||||
|
|
||||||
struct TimePickerView: View {
|
struct TimePickerView: View {
|
||||||
@Binding var timeString: String
|
@Binding var timeString: String
|
||||||
@ -14,6 +15,7 @@ struct TimePickerView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
DatePicker("", selection: $selectedTime, displayedComponents: .hourAndMinute)
|
DatePicker("", selection: $selectedTime, displayedComponents: .hourAndMinute)
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
.tint(AppAccent.primary)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
updateSelectedTimeFromString()
|
updateSelectedTimeFromString()
|
||||||
}
|
}
|
||||||
@ -6,5 +6,11 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>AppGroupIdentifier</key>
|
||||||
|
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||||
|
<key>CloudKitContainerIdentifier</key>
|
||||||
|
<string>$(CLOUDKIT_CONTAINER_IDENTIFIER)</string>
|
||||||
|
<key>AppClipDomain</key>
|
||||||
|
<string>$(APPCLIP_DOMAIN)</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
24
TheNoiseClock/Resources/LaunchScreen.storyboard
Normal file
24
TheNoiseClock/Resources/LaunchScreen.storyboard
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" red="0.08" green="0.10" blue="0.16" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
53
TheNoiseClock/Shared/BrandingConfig.swift
Normal file
53
TheNoiseClock/Shared/BrandingConfig.swift
Normal file
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -307,4 +307,3 @@ private extension Font.Design {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
100
TheNoiseClock/Shared/Theme/NoiseClockTheme.swift
Normal file
100
TheNoiseClock/Shared/Theme/NoiseClockTheme.swift
Normal file
@ -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
|
||||||
@ -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 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user