Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
267f3ac987
commit
05cd4f10e6
@ -24,6 +24,18 @@ class ClockStyle: Codable, Equatable {
|
||||
var stretched: Bool = true
|
||||
var backgroundHex: String = AppConstants.Defaults.backgroundColorHex
|
||||
|
||||
// MARK: - Color Theme Settings
|
||||
var selectedColorTheme: String = "Custom" // Custom, Red, Orange, Yellow, Green, Blue, Purple, Pink, White
|
||||
|
||||
// MARK: - Night Mode Settings
|
||||
var nightModeEnabled: Bool = false
|
||||
var autoNightMode: Bool = false
|
||||
var scheduledNightMode: Bool = false
|
||||
var nightModeStartTime: String = "22:00" // 10:00 PM
|
||||
var nightModeEndTime: String = "06:00" // 6:00 AM
|
||||
var autoBrightness: Bool = true // Automatically dim brightness in night mode
|
||||
var ambientLightThreshold: Double = 0.3 // Threshold for ambient light detection (0.0-1.0)
|
||||
|
||||
// MARK: - Font Settings
|
||||
var fontFamily: String = "System" // System, San Francisco, etc.
|
||||
var fontWeight: String = "Bold" // Ultra Light, Thin, Light, Regular, Medium, Semibold, Bold, Heavy, Black
|
||||
@ -54,6 +66,14 @@ class ClockStyle: Codable, Equatable {
|
||||
case digitScale
|
||||
case stretched
|
||||
case backgroundHex
|
||||
case selectedColorTheme
|
||||
case nightModeEnabled
|
||||
case autoNightMode
|
||||
case scheduledNightMode
|
||||
case nightModeStartTime
|
||||
case nightModeEndTime
|
||||
case autoBrightness
|
||||
case ambientLightThreshold
|
||||
case fontFamily
|
||||
case fontWeight
|
||||
case fontDesign
|
||||
@ -83,6 +103,14 @@ class ClockStyle: Codable, Equatable {
|
||||
self.digitScale = try container.decodeIfPresent(Double.self, forKey: .digitScale) ?? self.digitScale
|
||||
self.stretched = try container.decodeIfPresent(Bool.self, forKey: .stretched) ?? self.stretched
|
||||
self.backgroundHex = try container.decodeIfPresent(String.self, forKey: .backgroundHex) ?? self.backgroundHex
|
||||
self.selectedColorTheme = try container.decodeIfPresent(String.self, forKey: .selectedColorTheme) ?? self.selectedColorTheme
|
||||
self.nightModeEnabled = try container.decodeIfPresent(Bool.self, forKey: .nightModeEnabled) ?? self.nightModeEnabled
|
||||
self.autoNightMode = try container.decodeIfPresent(Bool.self, forKey: .autoNightMode) ?? self.autoNightMode
|
||||
self.scheduledNightMode = try container.decodeIfPresent(Bool.self, forKey: .scheduledNightMode) ?? self.scheduledNightMode
|
||||
self.nightModeStartTime = try container.decodeIfPresent(String.self, forKey: .nightModeStartTime) ?? self.nightModeStartTime
|
||||
self.nightModeEndTime = try container.decodeIfPresent(String.self, forKey: .nightModeEndTime) ?? self.nightModeEndTime
|
||||
self.autoBrightness = try container.decodeIfPresent(Bool.self, forKey: .autoBrightness) ?? self.autoBrightness
|
||||
self.ambientLightThreshold = try container.decodeIfPresent(Double.self, forKey: .ambientLightThreshold) ?? self.ambientLightThreshold
|
||||
self.fontFamily = try container.decodeIfPresent(String.self, forKey: .fontFamily) ?? self.fontFamily
|
||||
self.fontWeight = try container.decodeIfPresent(String.self, forKey: .fontWeight) ?? self.fontWeight
|
||||
self.fontDesign = try container.decodeIfPresent(String.self, forKey: .fontDesign) ?? self.fontDesign
|
||||
@ -107,6 +135,14 @@ class ClockStyle: Codable, Equatable {
|
||||
try container.encode(digitScale, forKey: .digitScale)
|
||||
try container.encode(stretched, forKey: .stretched)
|
||||
try container.encode(backgroundHex, forKey: .backgroundHex)
|
||||
try container.encode(selectedColorTheme, forKey: .selectedColorTheme)
|
||||
try container.encode(nightModeEnabled, forKey: .nightModeEnabled)
|
||||
try container.encode(autoNightMode, forKey: .autoNightMode)
|
||||
try container.encode(scheduledNightMode, forKey: .scheduledNightMode)
|
||||
try container.encode(nightModeStartTime, forKey: .nightModeStartTime)
|
||||
try container.encode(nightModeEndTime, forKey: .nightModeEndTime)
|
||||
try container.encode(autoBrightness, forKey: .autoBrightness)
|
||||
try container.encode(ambientLightThreshold, forKey: .ambientLightThreshold)
|
||||
try container.encode(fontFamily, forKey: .fontFamily)
|
||||
try container.encode(fontWeight, forKey: .fontWeight)
|
||||
try container.encode(fontDesign, forKey: .fontDesign)
|
||||
@ -144,6 +180,227 @@ class ClockStyle: Codable, Equatable {
|
||||
_cachedBackgroundColor = nil
|
||||
}
|
||||
|
||||
/// Apply a predefined color theme
|
||||
func applyColorTheme(_ theme: String) {
|
||||
selectedColorTheme = theme
|
||||
|
||||
switch theme {
|
||||
case "Red":
|
||||
digitColorHex = "#FF3B30"
|
||||
backgroundHex = "#000000"
|
||||
case "Orange":
|
||||
digitColorHex = "#FF9500"
|
||||
backgroundHex = "#000000"
|
||||
case "Yellow":
|
||||
digitColorHex = "#FFCC00"
|
||||
backgroundHex = "#000000"
|
||||
case "Green":
|
||||
digitColorHex = "#34C759"
|
||||
backgroundHex = "#000000"
|
||||
case "Blue":
|
||||
digitColorHex = "#007AFF"
|
||||
backgroundHex = "#000000"
|
||||
case "Purple":
|
||||
digitColorHex = "#AF52DE"
|
||||
backgroundHex = "#000000"
|
||||
case "Pink":
|
||||
digitColorHex = "#FF2D92"
|
||||
backgroundHex = "#000000"
|
||||
case "White":
|
||||
digitColorHex = "#FFFFFF"
|
||||
backgroundHex = "#000000"
|
||||
default:
|
||||
// Custom theme - don't change colors
|
||||
break
|
||||
}
|
||||
|
||||
clearColorCache()
|
||||
}
|
||||
|
||||
/// Get available color themes
|
||||
static func availableColorThemes() -> [(String, String)] {
|
||||
return [
|
||||
("Custom", "Custom"),
|
||||
("Red", "Red"),
|
||||
("Orange", "Orange"),
|
||||
("Yellow", "Yellow"),
|
||||
("Green", "Green"),
|
||||
("Blue", "Blue"),
|
||||
("Purple", "Purple"),
|
||||
("Pink", "Pink"),
|
||||
("White", "White")
|
||||
]
|
||||
}
|
||||
|
||||
/// Check if night mode should be active based on current settings
|
||||
var isNightModeActive: Bool {
|
||||
if nightModeEnabled {
|
||||
return true
|
||||
}
|
||||
|
||||
if scheduledNightMode {
|
||||
return isWithinScheduledNightMode()
|
||||
}
|
||||
|
||||
if autoNightMode {
|
||||
return isAmbientLightLow()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/// Check if current time is within scheduled night mode hours
|
||||
private func isWithinScheduledNightMode() -> Bool {
|
||||
let now = Date()
|
||||
let calendar = Calendar.current
|
||||
let currentTime = calendar.dateComponents([.hour, .minute], from: now)
|
||||
|
||||
let startComponents = parseTimeString(nightModeStartTime)
|
||||
let endComponents = parseTimeString(nightModeEndTime)
|
||||
|
||||
guard let startHour = startComponents.hour,
|
||||
let startMinute = startComponents.minute,
|
||||
let endHour = endComponents.hour,
|
||||
let endMinute = endComponents.minute else {
|
||||
return false
|
||||
}
|
||||
|
||||
let currentMinutes = (currentTime.hour ?? 0) * 60 + (currentTime.minute ?? 0)
|
||||
let startMinutes = startHour * 60 + startMinute
|
||||
let endMinutes = endHour * 60 + endMinute
|
||||
|
||||
// Handle overnight schedules (e.g., 22:00 to 06:00)
|
||||
if startMinutes > endMinutes {
|
||||
return currentMinutes >= startMinutes || currentMinutes < endMinutes
|
||||
} else {
|
||||
return currentMinutes >= startMinutes && currentMinutes < endMinutes
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse time string in HH:mm format
|
||||
private func parseTimeString(_ timeString: String) -> (hour: Int?, minute: Int?) {
|
||||
let components = timeString.split(separator: ":")
|
||||
guard components.count == 2,
|
||||
let hour = Int(components[0]),
|
||||
let minute = Int(components[1]) else {
|
||||
return (nil, nil)
|
||||
}
|
||||
return (hour, minute)
|
||||
}
|
||||
|
||||
/// Get the effective digit color considering night mode
|
||||
var effectiveDigitColor: Color {
|
||||
if isNightModeActive {
|
||||
return Color(hex: "#FF3B30") ?? .red // Red for night mode
|
||||
}
|
||||
return digitColor
|
||||
}
|
||||
|
||||
/// Get the effective background color considering night mode
|
||||
var effectiveBackgroundColor: Color {
|
||||
if isNightModeActive {
|
||||
return Color(hex: "#000000") ?? .black // Black background for night mode
|
||||
}
|
||||
return backgroundColor
|
||||
}
|
||||
|
||||
/// Check if ambient light is low enough to trigger night mode
|
||||
private func isAmbientLightLow() -> Bool {
|
||||
// Use screen brightness as a proxy for ambient light
|
||||
// In a real implementation, you'd use the ambient light sensor
|
||||
let currentBrightness = UIScreen.main.brightness
|
||||
return currentBrightness < ambientLightThreshold
|
||||
}
|
||||
|
||||
/// Get the effective brightness considering color theme and night mode
|
||||
var effectiveBrightness: Double {
|
||||
if !autoBrightness {
|
||||
return 1.0 // Full brightness when auto-brightness is disabled
|
||||
}
|
||||
|
||||
if isNightModeActive {
|
||||
// Dim the display to 30% brightness in night mode
|
||||
return 0.3
|
||||
}
|
||||
|
||||
// Color-aware brightness adaptation
|
||||
return getColorAwareBrightness()
|
||||
}
|
||||
|
||||
/// Get brightness based on color theme and ambient light
|
||||
private func getColorAwareBrightness() -> Double {
|
||||
let baseBrightness = getBaseBrightnessForColor()
|
||||
let ambientFactor = getAmbientLightFactor()
|
||||
|
||||
// Combine color-based brightness with ambient light factor
|
||||
return max(0.2, min(1.0, baseBrightness * ambientFactor))
|
||||
}
|
||||
|
||||
/// Get base brightness recommendation for current color theme
|
||||
private func getBaseBrightnessForColor() -> Double {
|
||||
switch selectedColorTheme {
|
||||
case "Red", "Orange":
|
||||
return 0.6 // Warmer colors work well at lower brightness
|
||||
case "Yellow", "White":
|
||||
return 0.8 // Bright colors can be brighter
|
||||
case "Green", "Blue":
|
||||
return 0.7 // Cool colors at medium brightness
|
||||
case "Purple", "Pink":
|
||||
return 0.65 // Vibrant colors at slightly lower brightness
|
||||
default:
|
||||
// For custom colors, analyze the actual color
|
||||
return getBrightnessForCustomColor()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get brightness for custom colors based on color properties
|
||||
private func getBrightnessForCustomColor() -> Double {
|
||||
guard let color = Color(hex: digitColorHex) else { return 0.7 }
|
||||
|
||||
// Convert to UIColor to analyze brightness
|
||||
let uiColor = UIColor(color)
|
||||
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
|
||||
uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
||||
|
||||
// Calculate perceived brightness (luminance)
|
||||
let brightness = 0.299 * red + 0.587 * green + 0.114 * blue
|
||||
|
||||
// Map brightness to recommended display brightness
|
||||
if brightness > 0.8 {
|
||||
return 0.8 // Very bright colors
|
||||
} else if brightness > 0.6 {
|
||||
return 0.7 // Medium bright colors
|
||||
} else if brightness > 0.4 {
|
||||
return 0.6 // Darker colors
|
||||
} else {
|
||||
return 0.5 // Very dark colors
|
||||
}
|
||||
}
|
||||
|
||||
/// Get ambient light factor (0.5 to 1.0)
|
||||
private func getAmbientLightFactor() -> Double {
|
||||
let currentBrightness = UIScreen.main.brightness
|
||||
|
||||
// Map screen brightness to ambient light factor
|
||||
if currentBrightness < 0.2 {
|
||||
return 0.5 // Very dark environment
|
||||
} else if currentBrightness < 0.4 {
|
||||
return 0.7 // Dark environment
|
||||
} else if currentBrightness < 0.6 {
|
||||
return 0.85 // Medium environment
|
||||
} else {
|
||||
return 1.0 // Bright environment
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the night mode red tint intensity
|
||||
var nightModeTintIntensity: Double {
|
||||
if isNightModeActive {
|
||||
return 0.8 // Strong red tint for night mode
|
||||
}
|
||||
return 0.0 // No tint
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
static func == (lhs: ClockStyle, rhs: ClockStyle) -> Bool {
|
||||
lhs.use24Hour == rhs.use24Hour &&
|
||||
@ -154,6 +411,14 @@ class ClockStyle: Codable, Equatable {
|
||||
lhs.digitScale == rhs.digitScale &&
|
||||
lhs.stretched == rhs.stretched &&
|
||||
lhs.backgroundHex == rhs.backgroundHex &&
|
||||
lhs.selectedColorTheme == rhs.selectedColorTheme &&
|
||||
lhs.nightModeEnabled == rhs.nightModeEnabled &&
|
||||
lhs.autoNightMode == rhs.autoNightMode &&
|
||||
lhs.scheduledNightMode == rhs.scheduledNightMode &&
|
||||
lhs.nightModeStartTime == rhs.nightModeStartTime &&
|
||||
lhs.nightModeEndTime == rhs.nightModeEndTime &&
|
||||
lhs.autoBrightness == rhs.autoBrightness &&
|
||||
lhs.ambientLightThreshold == rhs.ambientLightThreshold &&
|
||||
lhs.fontFamily == rhs.fontFamily &&
|
||||
lhs.fontWeight == rhs.fontWeight &&
|
||||
lhs.fontDesign == rhs.fontDesign &&
|
||||
|
||||
76
TheNoiseClock/Services/AmbientLightService.swift
Normal file
76
TheNoiseClock/Services/AmbientLightService.swift
Normal file
@ -0,0 +1,76 @@
|
||||
//
|
||||
// AmbientLightService.swift
|
||||
// TheNoiseClock
|
||||
//
|
||||
// Created by Matt Bruce on 9/10/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Observation
|
||||
|
||||
/// Service for monitoring ambient light and managing brightness
|
||||
@Observable
|
||||
class AmbientLightService {
|
||||
|
||||
// MARK: - Properties
|
||||
private(set) var currentBrightness: Double = 1.0
|
||||
private(set) var isMonitoring = false
|
||||
|
||||
// Timer for periodic brightness checks
|
||||
private var brightnessTimer: Timer?
|
||||
|
||||
// MARK: - Singleton
|
||||
static let shared = AmbientLightService()
|
||||
|
||||
private init() {
|
||||
// Private initializer for singleton
|
||||
}
|
||||
|
||||
// MARK: - Public Interface
|
||||
|
||||
/// Start monitoring ambient light and brightness
|
||||
func startMonitoring() {
|
||||
guard !isMonitoring else { return }
|
||||
|
||||
isMonitoring = true
|
||||
updateCurrentBrightness()
|
||||
|
||||
// Check brightness every 5 seconds
|
||||
brightnessTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
|
||||
self?.updateCurrentBrightness()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop monitoring ambient light and brightness
|
||||
func stopMonitoring() {
|
||||
guard isMonitoring else { return }
|
||||
|
||||
isMonitoring = false
|
||||
brightnessTimer?.invalidate()
|
||||
brightnessTimer = nil
|
||||
}
|
||||
|
||||
/// Set screen brightness (0.0 to 1.0)
|
||||
func setBrightness(_ brightness: Double) {
|
||||
let clampedBrightness = max(0.0, min(1.0, brightness))
|
||||
UIScreen.main.brightness = clampedBrightness
|
||||
currentBrightness = clampedBrightness
|
||||
}
|
||||
|
||||
/// Get current screen brightness
|
||||
func getCurrentBrightness() -> Double {
|
||||
return UIScreen.main.brightness
|
||||
}
|
||||
|
||||
/// Check if ambient light is below threshold
|
||||
func isAmbientLightLow(threshold: Double) -> Bool {
|
||||
return currentBrightness < threshold
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func updateCurrentBrightness() {
|
||||
currentBrightness = UIScreen.main.brightness
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,9 @@ class ClockViewModel {
|
||||
// Wake lock service
|
||||
private let wakeLockService = WakeLockService.shared
|
||||
|
||||
// Ambient light service
|
||||
private let ambientLightService = AmbientLightService.shared
|
||||
|
||||
// Timer management
|
||||
private var secondTimer: Timer.TimerPublisher?
|
||||
private var minuteTimer: Timer.TimerPublisher?
|
||||
@ -47,10 +50,12 @@ class ClockViewModel {
|
||||
init() {
|
||||
loadStyle()
|
||||
setupTimers()
|
||||
startAmbientLightMonitoring()
|
||||
}
|
||||
|
||||
deinit {
|
||||
stopTimers()
|
||||
stopAmbientLightMonitoring()
|
||||
}
|
||||
|
||||
// MARK: - Public Interface
|
||||
@ -68,6 +73,7 @@ class ClockViewModel {
|
||||
saveStyle()
|
||||
updateTimersIfNeeded()
|
||||
updateWakeLockState()
|
||||
updateBrightness() // Update brightness when style changes
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
@ -104,7 +110,15 @@ class ClockViewModel {
|
||||
if self.style.randomizeColor {
|
||||
self.style.digitColorHex = Color.randomBrightColorHex()
|
||||
self.saveStyle()
|
||||
self.updateBrightness() // Update brightness when color changes
|
||||
}
|
||||
|
||||
// Check for night mode state changes (scheduled night mode)
|
||||
// Force a UI update by updating currentTime slightly
|
||||
self.currentTime = Date()
|
||||
|
||||
// Update brightness if night mode is active
|
||||
self.updateBrightness()
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,4 +162,22 @@ class ClockViewModel {
|
||||
wakeLockService.disableWakeLock()
|
||||
}
|
||||
}
|
||||
|
||||
/// Start ambient light monitoring
|
||||
private func startAmbientLightMonitoring() {
|
||||
ambientLightService.startMonitoring()
|
||||
}
|
||||
|
||||
/// Stop ambient light monitoring
|
||||
private func stopAmbientLightMonitoring() {
|
||||
ambientLightService.stopMonitoring()
|
||||
}
|
||||
|
||||
/// Update brightness based on color theme and night mode settings
|
||||
private func updateBrightness() {
|
||||
if style.autoBrightness {
|
||||
let targetBrightness = style.effectiveBrightness
|
||||
ambientLightService.setBrightness(targetBrightness)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ struct ClockSettingsView: View {
|
||||
backgroundColor: $backgroundColor,
|
||||
onCommit: onCommit
|
||||
)
|
||||
NightModeSection(style: $style)
|
||||
DisplaySection(style: $style)
|
||||
OverlaySection(style: $style)
|
||||
}
|
||||
@ -122,6 +123,27 @@ private struct AppearanceSection: View {
|
||||
|
||||
var body: some View {
|
||||
Section(header: Text("Appearance")) {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
ColorPicker("Background Color", selection: $backgroundColor, supportsOpacity: true)
|
||||
ColorPicker("Digit Color", selection: $digitColor, supportsOpacity: false)
|
||||
Toggle("Randomize Color (every minute)", isOn: $style.randomizeColor)
|
||||
@ -153,13 +175,89 @@ private struct AppearanceSection: View {
|
||||
}
|
||||
.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 "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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct OverlaySection: View {
|
||||
@ -199,12 +297,71 @@ private struct DisplaySection: View {
|
||||
Toggle("Keep Awake in Display Mode", isOn: $style.keepAwake)
|
||||
}
|
||||
|
||||
Section(header: Text("Auto Brightness"), footer: Text("Automatically adjust display brightness based on color theme and ambient light. Works with all color themes and night mode.")) {
|
||||
Toggle("Auto Brightness", isOn: $style.autoBrightness)
|
||||
|
||||
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. When enabled, audio may be paused during Focus mode.")) {
|
||||
Toggle("Respect Focus Modes", isOn: $style.respectFocusModes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TimePickerView: View {
|
||||
@Binding var timeString: String
|
||||
@State private var selectedTime = Date()
|
||||
|
||||
var body: some View {
|
||||
DatePicker("", selection: $selectedTime, displayedComponents: .hourAndMinute)
|
||||
.labelsHidden()
|
||||
.onAppear {
|
||||
updateSelectedTimeFromString()
|
||||
}
|
||||
.onChange(of: selectedTime) { _, newTime in
|
||||
updateStringFromTime(newTime)
|
||||
}
|
||||
.onChange(of: timeString) { _, _ in
|
||||
updateSelectedTimeFromString()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSelectedTimeFromString() {
|
||||
let components = timeString.split(separator: ":")
|
||||
guard components.count == 2,
|
||||
let hour = Int(components[0]),
|
||||
let minute = Int(components[1]) else {
|
||||
return
|
||||
}
|
||||
|
||||
let calendar = Calendar.current
|
||||
let now = Date()
|
||||
let dateComponents = calendar.dateComponents([.year, .month, .day], from: now)
|
||||
|
||||
var newComponents = dateComponents
|
||||
newComponents.hour = hour
|
||||
newComponents.minute = minute
|
||||
|
||||
if let newDate = calendar.date(from: newComponents) {
|
||||
selectedTime = newDate
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStringFromTime(_ time: Date) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm"
|
||||
timeString = formatter.string(from: time)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
#Preview {
|
||||
ClockSettingsView(
|
||||
|
||||
@ -17,7 +17,7 @@ struct ClockView: View {
|
||||
// MARK: - Body
|
||||
var body: some View {
|
||||
ZStack {
|
||||
viewModel.style.backgroundColor
|
||||
viewModel.style.effectiveBackgroundColor
|
||||
.ignoresSafeArea()
|
||||
|
||||
GeometryReader { geometry in
|
||||
|
||||
@ -34,7 +34,7 @@ struct ClockDisplayContainer: View {
|
||||
date: currentTime,
|
||||
use24Hour: style.use24Hour,
|
||||
showSeconds: style.showSeconds,
|
||||
digitColor: style.digitColor,
|
||||
digitColor: style.effectiveDigitColor,
|
||||
glowIntensity: style.glowIntensity,
|
||||
manualScale: style.digitScale,
|
||||
stretched: style.stretched,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user