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
|
||||
- **Main App**: `TheNoiseClockApp.swift` - Entry point with WindowGroup
|
||||
- **Branded launch**: AppLaunchView wrapped in a Color.Branding.primary ZStack
|
||||
- **Tab-based navigation**: Three main tabs (Clock, Alarms, Noise)
|
||||
- **SwiftUI framework**: Modern declarative UI framework with iOS 18+ and iOS 26 features
|
||||
- **Dark theme**: Preferred color scheme set to dark
|
||||
@ -327,19 +328,18 @@ These principles are fundamental to the project's long-term success and must be
|
||||
### Visual Design
|
||||
- **Rounded corners**: Modern iOS design language
|
||||
- **Modern animations**: iOS 18+ smooth and bouncy animations
|
||||
- **Color consistency**: Blue accent color throughout
|
||||
- **Color consistency**: Bedrock theme with branded surfaces and accents
|
||||
- **Branded launch**: AppLaunchView with matching launch screen background
|
||||
- **Accessibility**: Proper labels and hidden decorative elements
|
||||
- **Form-based layouts**: Organized sections for settings and alarm editing
|
||||
- **Interactive controls**: Toggles, sliders, color pickers, and date pickers
|
||||
- **Card-based layouts**: Bedrock settings cards for grouping controls
|
||||
- **Interactive controls**: Bedrock toggles, sliders, pickers, and color pickers
|
||||
|
||||
### Settings Interface
|
||||
- **Form-based layout**: Organized sections for different setting categories
|
||||
- **Interactive controls**: Toggles, sliders, color pickers, enum-based pickers
|
||||
- **Bedrock layout**: Section headers with card-based grouping
|
||||
- **Interactive controls**: SettingsToggle, SettingsSlider, menu pickers, color pickers
|
||||
- **Type-safe font selection**: FontFamily.allCases, Font.Weight.allCases, Font.Design.allCases pickers
|
||||
- **Real-time updates**: Changes apply immediately with live preview
|
||||
- **Sheet presentation**: Modal settings with detents
|
||||
- **iPad optimization**: Settings sheet opens at full size (.large) on iPad for better usability
|
||||
- **iPhone compatibility**: Settings sheet uses medium/large detents on iPhone for optimal space usage
|
||||
- **Sheet presentation**: Full-screen settings sheet for uninterrupted editing
|
||||
- **Enum-based architecture**: Type-safe picker selections eliminate string-based errors
|
||||
|
||||
## File Structure and Organization
|
||||
@ -349,6 +349,7 @@ Following the separation of concerns principle, the codebase is organized into f
|
||||
|
||||
```
|
||||
TheNoiseClock/
|
||||
├── README.md # App Store copy + project overview
|
||||
├── AudioPlaybackKit/ # Swift Package for reusable audio functionality
|
||||
│ ├── Package.swift # Package configuration (iOS 17.0+, tvOS 17.0+)
|
||||
│ └── Sources/AudioPlaybackKit/
|
||||
@ -366,69 +367,101 @@ TheNoiseClock/
|
||||
│ ├── App/
|
||||
│ │ ├── TheNoiseClockApp.swift # App entry point and configuration
|
||||
│ │ └── ContentView.swift # Main tab navigation coordinator
|
||||
│ ├── Core/
|
||||
│ │ ├── Constants/
|
||||
│ ├── Configuration/
|
||||
│ │ ├── AppIdentifiers.swift # xcconfig-backed identifiers
|
||||
│ │ ├── Base.xcconfig # Company/app identifiers
|
||||
│ │ ├── Debug.xcconfig # Debug settings
|
||||
│ │ └── Release.xcconfig # Release settings
|
||||
│ ├── Shared/
|
||||
│ │ ├── BrandingConfig.swift # Bedrock branding + launch config
|
||||
│ │ ├── Design/
|
||||
│ │ │ ├── AppConstants.swift # App-wide constants and configuration
|
||||
│ │ │ └── UIConstants.swift # UI-specific constants (colors, sizes, etc.)
|
||||
│ │ │ ├── UIConstants.swift # UI-specific constants (colors, sizes, etc.)
|
||||
│ │ │ └── Fonts/
|
||||
│ │ │ ├── Font.Design.swift
|
||||
│ │ │ ├── Font.Weight.swift
|
||||
│ │ │ ├── FontFamily.swift
|
||||
│ │ │ └── FontUtils.swift
|
||||
│ │ ├── Theme/
|
||||
│ │ │ └── NoiseClockTheme.swift # Bedrock app color theme
|
||||
│ │ ├── Extensions/
|
||||
│ │ │ ├── Color+Extensions.swift # Color utilities and extensions
|
||||
│ │ │ ├── Date+Extensions.swift # Date formatting and utilities
|
||||
│ │ │ └── View+Extensions.swift # Common view modifiers and responsive utilities
|
||||
│ │ ├── Models/
|
||||
│ │ │ └── SoundCategory.swift # Shared sound category definitions
|
||||
│ │ └── Utilities/
|
||||
│ │ ├── ColorUtils.swift # Color manipulation utilities
|
||||
│ │ ├── FontUtils.swift # Font sizing, typography, and customization utilities
|
||||
│ │ ├── DebugLogger.swift # Debug logging helper
|
||||
│ │ └── NotificationUtils.swift # Notification helper functions
|
||||
│ ├── Models/
|
||||
│ │ ├── ClockStyle.swift # Clock customization data model
|
||||
│ │ ├── Alarm.swift # Alarm data model
|
||||
│ │ └── LegacyAlarm.swift # Backward compatibility model
|
||||
│ ├── ViewModels/
|
||||
│ │ ├── ClockViewModel.swift # Clock display logic and state
|
||||
│ │ └── AlarmViewModel.swift # Alarm management logic
|
||||
│ ├── Views/
|
||||
│ ├── Features/
|
||||
│ │ ├── Clock/
|
||||
│ │ │ ├── ClockView.swift # Main clock display view
|
||||
│ │ │ ├── ClockSettingsView.swift # Clock customization interface
|
||||
│ │ │ └── Components/
|
||||
│ │ │ ├── TimeDisplayView.swift # Advanced segmented time display with dynamic font sizing
|
||||
│ │ │ ├── TimeSegment.swift # Individual time segment with enum-based font properties
|
||||
│ │ │ ├── DigitView.swift # Single digit display with binary search font sizing
|
||||
│ │ │ ├── ColonView.swift # Unified colon separator (horizontal/vertical)
|
||||
│ │ │ ├── DotCircle.swift # Individual dot component for colons
|
||||
│ │ │ ├── BatteryOverlayView.swift # Battery level overlay
|
||||
│ │ │ ├── DateOverlayView.swift # Date display overlay
|
||||
│ │ │ ├── TopOverlayView.swift # Combined overlay container
|
||||
│ │ │ └── Settings/
|
||||
│ │ │ ├── BasicAppearanceSection.swift
|
||||
│ │ │ ├── BasicDisplaySection.swift
|
||||
│ │ │ ├── AdvancedAppearanceSection.swift
|
||||
│ │ │ ├── AdvancedDisplaySection.swift
|
||||
│ │ │ ├── FontSection.swift
|
||||
│ │ │ ├── NightModeSection.swift
|
||||
│ │ │ ├── OverlaySection.swift
|
||||
│ │ │ └── TimePickerView.swift
|
||||
│ │ │ ├── Models/
|
||||
│ │ │ │ └── ClockStyle.swift
|
||||
│ │ │ ├── State/
|
||||
│ │ │ │ └── ClockViewModel.swift
|
||||
│ │ │ ├── Services/
|
||||
│ │ │ │ ├── AmbientLightService.swift
|
||||
│ │ │ │ └── BatteryService.swift
|
||||
│ │ │ └── Views/
|
||||
│ │ │ ├── ClockView.swift
|
||||
│ │ │ ├── ClockSettingsView.swift
|
||||
│ │ │ └── Components/
|
||||
│ │ │ ├── TimeDisplayView.swift
|
||||
│ │ │ ├── TimeSegment.swift
|
||||
│ │ │ ├── DigitView.swift
|
||||
│ │ │ ├── ColonView.swift
|
||||
│ │ │ ├── DotCircle.swift
|
||||
│ │ │ ├── BatteryOverlayView.swift
|
||||
│ │ │ ├── DateOverlayView.swift
|
||||
│ │ │ ├── TopOverlayView.swift
|
||||
│ │ │ ├── ClockDisplayContainer.swift
|
||||
│ │ │ ├── ClockOverlayContainer.swift
|
||||
│ │ │ ├── ClockGestureHandler.swift
|
||||
│ │ │ ├── ClockTabBarManager.swift
|
||||
│ │ │ ├── ClockToolbar.swift
|
||||
│ │ │ ├── FullScreenHintView.swift
|
||||
│ │ │ └── Settings/
|
||||
│ │ │ ├── BasicAppearanceSection.swift
|
||||
│ │ │ ├── BasicDisplaySection.swift
|
||||
│ │ │ ├── AdvancedAppearanceSection.swift
|
||||
│ │ │ ├── AdvancedDisplaySection.swift
|
||||
│ │ │ ├── FontSection.swift
|
||||
│ │ │ ├── NightModeSection.swift
|
||||
│ │ │ ├── OverlaySection.swift
|
||||
│ │ │ └── TimePickerView.swift
|
||||
│ │ ├── Alarms/
|
||||
│ │ │ ├── AlarmView.swift # Main alarm management view
|
||||
│ │ │ ├── AddAlarmView.swift # Alarm creation interface
|
||||
│ │ │ └── Components/
|
||||
│ │ │ ├── AlarmRowView.swift # Individual alarm row component
|
||||
│ │ │ ├── TimePickerSection.swift # Time selection component
|
||||
│ │ │ ├── TimeUntilAlarmSection.swift # Time calculation display
|
||||
│ │ │ ├── SoundSelectionView.swift # Sound selection with preview
|
||||
│ │ │ ├── LabelEditView.swift # Label editing interface
|
||||
│ │ │ └── SnoozeSelectionView.swift # Snooze duration selection
|
||||
│ │ │ ├── Models/
|
||||
│ │ │ │ └── Alarm.swift
|
||||
│ │ │ ├── State/
|
||||
│ │ │ │ └── AlarmViewModel.swift
|
||||
│ │ │ ├── Services/
|
||||
│ │ │ │ ├── AlarmService.swift
|
||||
│ │ │ │ ├── AlarmSoundService.swift
|
||||
│ │ │ │ ├── FocusModeService.swift
|
||||
│ │ │ │ ├── NotificationService.swift
|
||||
│ │ │ │ └── NotificationDelegate.swift
|
||||
│ │ │ └── Views/
|
||||
│ │ │ ├── AlarmView.swift
|
||||
│ │ │ ├── AddAlarmView.swift
|
||||
│ │ │ ├── EditAlarmView.swift
|
||||
│ │ │ └── Components/
|
||||
│ │ │ ├── AlarmRowView.swift
|
||||
│ │ │ ├── EmptyAlarmsView.swift
|
||||
│ │ │ ├── LabelEditView.swift
|
||||
│ │ │ ├── NotificationMessageEditView.swift
|
||||
│ │ │ ├── SnoozeSelectionView.swift
|
||||
│ │ │ ├── SoundSelectionView.swift
|
||||
│ │ │ ├── TimePickerSection.swift
|
||||
│ │ │ └── TimeUntilAlarmSection.swift
|
||||
│ │ └── Noise/
|
||||
│ │ ├── NoiseView.swift # Main white noise player interface
|
||||
│ │ └── Components/
|
||||
│ │ ├── SoundCategoryView.swift # Advanced grid-based sound selection
|
||||
│ │ └── SoundControlView.swift # Playback controls component
|
||||
│ ├── Services/
|
||||
│ │ ├── AlarmService.swift # Alarm management service with Focus mode integration
|
||||
│ │ ├── NotificationService.swift # Notification handling service
|
||||
│ │ ├── FocusModeService.swift # Focus mode integration and notification management
|
||||
│ │ ├── BatteryService.swift # Battery monitoring and state management service
|
||||
│ │ └── AlarmSoundService.swift # Alarm-specific sound management service
|
||||
│ │ └── Views/
|
||||
│ │ ├── NoiseView.swift
|
||||
│ │ └── Components/
|
||||
│ │ ├── SoundCategoryView.swift
|
||||
│ │ └── SoundControlView.swift
|
||||
│ └── Resources/
|
||||
│ ├── LaunchScreen.storyboard # Branded native launch screen
|
||||
│ ├── sounds.json # Ambient sound configuration and definitions
|
||||
│ ├── alarm-sounds.json # Alarm sound configuration and definitions
|
||||
│ ├── Ambient.bundle/ # Ambient sound category
|
||||
@ -557,6 +590,10 @@ The following changes **automatically require** PRD updates:
|
||||
- Audio playback with background support
|
||||
- Wake lock management
|
||||
- Modern @Observable state management
|
||||
- **Bedrock**: Local Swift package for design system and settings UI
|
||||
- App-wide theming with color providers
|
||||
- Branded launch experience and icon tooling
|
||||
- Reusable settings components (cards, toggles, sliders)
|
||||
- **SwiftUI**: Native iOS UI framework with latest features
|
||||
- **AVFoundation**: Audio playback with modern async patterns and background support
|
||||
- **UserNotifications**: Alarm notifications with rich content support
|
||||
@ -566,6 +603,10 @@ The following changes **automatically require** PRD updates:
|
||||
- **UIKit**: UIFont integration for precise text measurement and font customization
|
||||
- **UIApplication**: Screen wake lock management and idle timer control
|
||||
|
||||
### Build Configuration
|
||||
- **xcconfig**: Centralized identifiers (bundle IDs, team ID, app groups)
|
||||
- **Info.plist bridge**: App identifiers exposed via AppIdentifiers.swift
|
||||
|
||||
### Advanced Font and Typography Utilities
|
||||
- **FontFamily enum**: Type-safe font family selection with allCases support
|
||||
- **Font.Weight extension**: Enhanced weight enum with allCases and uiFontWeight properties
|
||||
|
||||
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 */
|
||||
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */; };
|
||||
EAC051B12F2E64AB007F87EA /* Bedrock in Frameworks */ = {isa = PBXBuildFile; productRef = EAC051B02F2E64AB007F87EA /* Bedrock */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -31,6 +32,9 @@
|
||||
EA384AFB2E6E6B6000CA7D50 /* TheNoiseClock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TheNoiseClock.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EA384B082E6E6B6100CA7D50 /* TheNoiseClockTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TheNoiseClockTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EA384B122E6E6B6100CA7D50 /* TheNoiseClockUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TheNoiseClockUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EAD6E3AE5A7F4D3DB37CF6D1 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TheNoiseClock/Configuration/Base.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TheNoiseClock/Configuration/Debug.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TheNoiseClock/Configuration/Release.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
@ -70,6 +74,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EA384E832E6F806200CA7D50 /* AudioPlaybackKit in Frameworks */,
|
||||
EAC051B12F2E64AB007F87EA /* Bedrock in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -131,6 +136,7 @@
|
||||
name = TheNoiseClock;
|
||||
packageProductDependencies = (
|
||||
EA384D3D2E6F554D00CA7D50 /* AudioPlaybackKit */,
|
||||
EAC051B02F2E64AB007F87EA /* Bedrock */,
|
||||
);
|
||||
productName = TheNoiseClock;
|
||||
productReference = EA384AFB2E6E6B6000CA7D50 /* TheNoiseClock.app */;
|
||||
@ -216,6 +222,7 @@
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
packageReferences = (
|
||||
EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */,
|
||||
EAC051AF2F2E64AB007F87EA /* XCLocalSwiftPackageReference "../Bedrock" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = EA384AFC2E6E6B6000CA7D50 /* Products */;
|
||||
@ -293,6 +300,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
EA384B1A2E6E6B6100CA7D50 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
@ -326,7 +334,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@ -357,6 +365,7 @@
|
||||
};
|
||||
EA384B1B2E6E6B6100CA7D50 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
@ -390,7 +399,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@ -414,18 +423,19 @@
|
||||
};
|
||||
EA384B1D2E6E6B6100CA7D50 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = TheNoiseClock/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -433,7 +443,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClock;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -447,18 +457,19 @@
|
||||
};
|
||||
EA384B1E2E6E6B6100CA7D50 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = TheNoiseClock/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -466,7 +477,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClock;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -480,15 +491,16 @@
|
||||
};
|
||||
EA384B202E6E6B6100CA7D50 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -502,15 +514,16 @@
|
||||
};
|
||||
EA384B212E6E6B6100CA7D50 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -524,13 +537,14 @@
|
||||
};
|
||||
EA384B232E6E6B6100CA7D50 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3AF5A7F4D3DB37CF6D1 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockUITests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -544,13 +558,14 @@
|
||||
};
|
||||
EA384B242E6E6B6100CA7D50 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EAD6E3B05A7F4D3DB37CF6D1 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.TheNoiseClockUITests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -608,6 +623,10 @@
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = AudioPlaybackKit;
|
||||
};
|
||||
EAC051AF2F2E64AB007F87EA /* XCLocalSwiftPackageReference "../Bedrock" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = ../Bedrock;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@ -616,6 +635,10 @@
|
||||
package = EA384D3C2E6F554D00CA7D50 /* XCLocalSwiftPackageReference "AudioPlaybackKit" */;
|
||||
productName = AudioPlaybackKit;
|
||||
};
|
||||
EAC051B02F2E64AB007F87EA /* Bedrock */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Bedrock;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = EA384AF32E6E6B6000CA7D50 /* Project object */;
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<key>TheNoiseClock.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Bedrock
|
||||
|
||||
/// Main tab navigation coordinator
|
||||
struct ContentView: View {
|
||||
@ -34,8 +35,8 @@ struct ContentView: View {
|
||||
Label("Noise", systemImage: "waveform")
|
||||
}
|
||||
}
|
||||
.accentColor(UIConstants.Colors.accentColor)
|
||||
.preferredColorScheme(.dark)
|
||||
.accentColor(AppAccent.primary)
|
||||
.background(Color.Branding.primary.ignoresSafeArea())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Bedrock
|
||||
|
||||
/// App entry point and configuration
|
||||
@main
|
||||
@ -20,7 +21,15 @@ struct TheNoiseClockApp: App {
|
||||
// MARK: - Body
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
ZStack {
|
||||
Color.Branding.primary
|
||||
.ignoresSafeArea()
|
||||
|
||||
AppLaunchView(config: .noiseClock) {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@ -1,48 +1,9 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"filename" : "AppIcon-1024px.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
|
||||
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 {
|
||||
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 Bedrock
|
||||
|
||||
/// Main clock display view with settings and display mode
|
||||
struct ClockView: View {
|
||||
@ -46,9 +47,10 @@ struct ClockView: View {
|
||||
ClockSettingsView(style: viewModel.style) { newStyle in
|
||||
viewModel.updateStyle(newStyle)
|
||||
}
|
||||
.presentationDetents(UIDevice.current.userInterfaceIdiom == .pad ? [.large] : [.medium, .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.hidden)
|
||||
.presentationBackgroundInteraction(.enabled)
|
||||
.presentationBackground(AppSurface.overlay)
|
||||
}
|
||||
.overlay {
|
||||
// Toolbar overlay
|
||||
@ -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 Bedrock
|
||||
|
||||
struct TimePickerView: View {
|
||||
@Binding var timeString: String
|
||||
@ -14,6 +15,7 @@ struct TimePickerView: View {
|
||||
var body: some View {
|
||||
DatePicker("", selection: $selectedTime, displayedComponents: .hourAndMinute)
|
||||
.labelsHidden()
|
||||
.tint(AppAccent.primary)
|
||||
.onAppear {
|
||||
updateSelectedTimeFromString()
|
||||
}
|
||||
@ -6,5 +6,11 @@
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</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>
|
||||
</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