文件
AUSleeping-IOS/AreYouSleeping/Store.swift
T

142 行
6.0 KiB
Swift
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import Foundation
/// 全局数据中心(基于 UserDefaults),等价于 Android SharedPreferences
@MainActor
final class Store: ObservableObject {
static let shared = Store()
// MARK: - 设置项
@Published var bedtimeMinutes: Int = 23 * 60
@Published var waketimeMinutes: Int = 7 * 60
@Published var supervisionEnabled: Bool = true
@Published var darkMode: Bool = false
@Published var leadMinutes: Int = 30
@Published var nagMinutes: Int = 10
@Published var navStyle: Int = 0 // 0=默认, 1=无边框
@Published var fontScale: Double = 1.0 // 0.85~1.3
@Published var fontStyle: Int = 0 // 0=默认, 1=圆体, 2=等宽
@Published var cardRadius: Int = 18 // 4/18/28
@Published var blurStrength: Int = 1 // 0=无, 1=轻, 2=强
@Published var bedtimeReminderEnabled: Bool = true
@Published var backgroundRunning: Bool = true
@Published var controlCodeEnabled: Bool = true
// MARK: - 控制码 & 账户
@Published var controlCode: String = ""
@Published var nickname: String = "未命名"
@Published var agreed: Bool = false
// MARK: - 运行状态
@Published var isSleeping: Bool = false
@Published var sleepStartMillis: Double = 0
@Published var lastNagMillis: Double = 0
// MARK: - 屏蔽应用(Screen Time API
@Published var blockedAppCount: Int = 0 // 已选中的应用数
@Published var familyControlsAuthorized: Bool = false
// MARK: - 睡眠记录
@Published var recordLines: Set<String> = []
private let defaults = UserDefaults.standard
private init() { load() }
func load() {
let d = defaults
bedtimeMinutes = d.integer(forKey: "bed")
if bedtimeMinutes == 0 { bedtimeMinutes = 23 * 60 }
waketimeMinutes = d.integer(forKey: "wake")
if waketimeMinutes == 0 { waketimeMinutes = 7 * 60 }
supervisionEnabled = d.object(forKey: "sup") as? Bool ?? true
darkMode = d.bool(forKey: "dark")
leadMinutes = d.integer(forKey: "lead"); if leadMinutes == 0 { leadMinutes = 30 }
nagMinutes = d.integer(forKey: "nag"); if nagMinutes == 0 { nagMinutes = 10 }
navStyle = d.integer(forKey: "nav_style")
fontScale = d.double(forKey: "font_scale"); if fontScale == 0 { fontScale = 1.0 }
fontStyle = d.integer(forKey: "font_style")
cardRadius = d.integer(forKey: "card_radius"); if cardRadius == 0 { cardRadius = 18 }
blurStrength = d.integer(forKey: "blur_strength"); if blurStrength == 0 { blurStrength = 1 }
bedtimeReminderEnabled = d.object(forKey: "bed_reminder") as? Bool ?? true
backgroundRunning = d.object(forKey: "bg_run") as? Bool ?? true
controlCodeEnabled = d.object(forKey: "control_code_enabled") as? Bool ?? true
controlCode = d.string(forKey: "control_code") ?? ""
nickname = d.string(forKey: "nickname") ?? "未命名"
agreed = d.bool(forKey: "agreed")
isSleeping = d.bool(forKey: "sleeping")
sleepStartMillis = d.double(forKey: "start")
lastNagMillis = d.double(forKey: "lastNag")
blockedAppCount = d.integer(forKey: "blocked_count")
recordLines = Set(d.stringArray(forKey: "records") ?? [])
}
func save() {
let d = defaults
d.set(bedtimeMinutes, forKey: "bed")
d.set(waketimeMinutes, forKey: "wake")
d.set(supervisionEnabled, forKey: "sup")
d.set(darkMode, forKey: "dark")
d.set(leadMinutes, forKey: "lead")
d.set(nagMinutes, forKey: "nag")
d.set(navStyle, forKey: "nav_style")
d.set(fontScale, forKey: "font_scale")
d.set(fontStyle, forKey: "font_style")
d.set(cardRadius, forKey: "card_radius")
d.set(blurStrength, forKey: "blur_strength")
d.set(bedtimeReminderEnabled, forKey: "bed_reminder")
d.set(backgroundRunning, forKey: "bg_run")
d.set(controlCodeEnabled, forKey: "control_code_enabled")
d.set(controlCode, forKey: "control_code")
d.set(nickname, forKey: "nickname")
d.set(agreed, forKey: "agreed")
d.set(isSleeping, forKey: "sleeping")
d.set(sleepStartMillis, forKey: "start")
d.set(lastNagMillis, forKey: "lastNag")
d.set(blockedAppCount, forKey: "blocked_count")
d.set(Array(recordLines), forKey: "records")
}
// MARK: - 控制码生成
private static let codeChars = Array("ABCDEFGHJKLMNPQRSTUVWXYZ23456789")
func ensureControlCode() {
guard controlCode.isEmpty else { return }
let raw = (0..<10).map { _ in
String(Store.codeChars[Int.random(in: 0..<Store.codeChars.count)])
}.joined()
controlCode = raw.prefix(4) + "-" + raw.dropFirst(4).prefix(4) + "-" + raw.dropFirst(8)
save()
}
// MARK: - 睡眠状态
func startSleep() {
isSleeping = true
sleepStartMillis = Date().timeIntervalSince1970 * 1000
save()
}
func endSleep() {
guard isSleeping, sleepStartMillis > 0 else { return }
let endMs = Date().timeIntervalSince1970 * 1000
let hours = max(0, (endMs - sleepStartMillis) / 3_600_000)
let bedMin = TimeUtil.minuteOfDay(Date(timeIntervalSince1970: sleepStartMillis / 1000))
let wakeMin = TimeUtil.minuteOfDay(Date(timeIntervalSince1970: endMs / 1000))
let offset = bedMin < waketimeMinutes ? -1 : 0
let night = TimeUtil.dateStr(Date(timeIntervalSince1970: sleepStartMillis / 1000), offsetDays: offset)
let rec = SleepRecord(nightDate: night, bedMinutes: bedMin, wakeMinutes: wakeMin, sleepHours: hours)
recordLines = recordLines.filter { !$0.hasPrefix(night + "|") }
recordLines.insert(rec.serialize())
isSleeping = false
sleepStartMillis = 0
save()
}
func recentRecords(_ n: Int) -> [SleepRecord] {
recordLines
.compactMap(SleepRecord.parse)
.sorted { $0.nightDate > $1.nightDate }
.prefix(n)
.map { $0 }
}
}