Files
Gaze/Gaze/Models/AppSettings.swift
2026-01-12 00:40:04 -05:00

157 lines
5.9 KiB
Swift

//
// AppSettings.swift
// Gaze
//
// Created by Mike Freno on 1/7/26.
//
import Foundation
// MARK: - Reminder Size
enum ReminderSize: String, Codable, CaseIterable {
case small
case medium
case large
var percentage: Double {
switch self {
case .small: return 1.5
case .medium: return 2.5
case .large: return 5.0
}
}
var displayName: String {
switch self {
case .small: return "Small"
case .medium: return "Medium"
case .large: return "Large"
}
}
}
// MARK: - Centralized Configuration System
/// Unified configuration class that manages all app settings in a centralized way
struct AppSettings: Codable, Equatable, Hashable {
// Timer configurations
var lookAwayTimer: TimerConfiguration
var lookAwayCountdownSeconds: Int
var blinkTimer: TimerConfiguration
var postureTimer: TimerConfiguration
var userTimers: [UserTimer]
var subtleReminderSize: ReminderSize
// App state and behavior
var hasCompletedOnboarding: Bool
var launchAtLogin: Bool
var playSounds: Bool
// App Store detection (cached at launch, not persisted)
var isAppStoreVersion: Bool
init(
lookAwayTimer: TimerConfiguration = TimerConfiguration(
enabled: true, intervalSeconds: 20 * 60),
lookAwayCountdownSeconds: Int = 20,
blinkTimer: TimerConfiguration = TimerConfiguration(
enabled: false, intervalSeconds: 7 * 60),
postureTimer: TimerConfiguration = TimerConfiguration(
enabled: true, intervalSeconds: 30 * 60),
userTimers: [UserTimer] = [],
subtleReminderSize: ReminderSize = .medium,
hasCompletedOnboarding: Bool = false,
launchAtLogin: Bool = false,
playSounds: Bool = true,
isAppStoreVersion: Bool = true
) {
self.lookAwayTimer = lookAwayTimer
self.lookAwayCountdownSeconds = lookAwayCountdownSeconds
self.blinkTimer = blinkTimer
self.postureTimer = postureTimer
self.userTimers = userTimers
self.subtleReminderSize = subtleReminderSize
self.hasCompletedOnboarding = hasCompletedOnboarding
self.launchAtLogin = launchAtLogin
self.playSounds = playSounds
self.isAppStoreVersion = isAppStoreVersion
}
static var defaults: AppSettings {
AppSettings(
lookAwayTimer: TimerConfiguration(enabled: true, intervalSeconds: 20 * 60),
lookAwayCountdownSeconds: 20,
blinkTimer: TimerConfiguration(enabled: false, intervalSeconds: 7 * 60),
postureTimer: TimerConfiguration(enabled: true, intervalSeconds: 30 * 60),
userTimers: [],
subtleReminderSize: .medium,
hasCompletedOnboarding: false,
launchAtLogin: false,
playSounds: true,
isAppStoreVersion: false
)
}
/// Manual Equatable implementation required because isAppStoreVersion
/// is excluded from Codable persistence but included in equality checks
static func == (lhs: AppSettings, rhs: AppSettings) -> Bool {
lhs.lookAwayTimer == rhs.lookAwayTimer
&& lhs.lookAwayCountdownSeconds == rhs.lookAwayCountdownSeconds
&& lhs.blinkTimer == rhs.blinkTimer
&& lhs.postureTimer == rhs.postureTimer
&& lhs.userTimers == rhs.userTimers
&& lhs.subtleReminderSize == rhs.subtleReminderSize
&& lhs.hasCompletedOnboarding == rhs.hasCompletedOnboarding
&& lhs.launchAtLogin == rhs.launchAtLogin
&& lhs.playSounds == rhs.playSounds
&& lhs.isAppStoreVersion == rhs.isAppStoreVersion
}
// MARK: - Custom Codable Implementation
enum CodingKeys: String, CodingKey {
case lookAwayTimer
case lookAwayCountdownSeconds
case blinkTimer
case postureTimer
case userTimers
case subtleReminderSize
case hasCompletedOnboarding
case launchAtLogin
case playSounds
// isAppStoreVersion is intentionally excluded from persistence
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
lookAwayTimer = try container.decode(TimerConfiguration.self, forKey: .lookAwayTimer)
lookAwayCountdownSeconds = try container.decode(Int.self, forKey: .lookAwayCountdownSeconds)
blinkTimer = try container.decode(TimerConfiguration.self, forKey: .blinkTimer)
postureTimer = try container.decode(TimerConfiguration.self, forKey: .postureTimer)
userTimers = try container.decode([UserTimer].self, forKey: .userTimers)
subtleReminderSize = try container.decode(ReminderSize.self, forKey: .subtleReminderSize)
hasCompletedOnboarding = try container.decode(Bool.self, forKey: .hasCompletedOnboarding)
launchAtLogin = try container.decode(Bool.self, forKey: .launchAtLogin)
playSounds = try container.decode(Bool.self, forKey: .playSounds)
// isAppStoreVersion is not persisted, will be set at launch
isAppStoreVersion = false
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(lookAwayTimer, forKey: .lookAwayTimer)
try container.encode(lookAwayCountdownSeconds, forKey: .lookAwayCountdownSeconds)
try container.encode(blinkTimer, forKey: .blinkTimer)
try container.encode(postureTimer, forKey: .postureTimer)
try container.encode(userTimers, forKey: .userTimers)
try container.encode(subtleReminderSize, forKey: .subtleReminderSize)
try container.encode(hasCompletedOnboarding, forKey: .hasCompletedOnboarding)
try container.encode(launchAtLogin, forKey: .launchAtLogin)
try container.encode(playSounds, forKey: .playSounds)
// isAppStoreVersion is intentionally not persisted
}
}