| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693 |
- // LoopFollow
- // Storage+Migrate.swift
- // Created by Jonas Björkert on 2025-05-26.
- import Foundation
- extension Storage {
- func migrate() {
- // Helper: 1-to-1 type -----------------------------------------------------------------
- func move<T: AnyConvertible & Equatable>(
- _ legacy: @autoclosure () -> UserDefaultsValue<T>,
- into newValue: StorageValue<T>
- ) {
- let item = legacy()
- guard item.exists else { return }
- newValue.value = item.value
- item.setNil(key: item.key)
- }
- // Helper: Float → Double ------------------------------------------------------------
- func moveFloatToDouble(
- _ legacy: @autoclosure () -> UserDefaultsValue<Float>,
- into newValue: StorageValue<Double>
- ) {
- let item = legacy()
- guard item.exists else { return }
- newValue.value = Double(item.value)
- item.setNil(key: item.key)
- }
- if !UserDefaultsRepository.backgroundRefresh.value {
- Storage.shared.backgroundRefreshType.value = .none
- UserDefaultsRepository.backgroundRefresh.value = true
- }
- // Remove this in a year later than the release of the new Alarms [BEGIN]
- let legacyColorBGText = UserDefaultsValue<Bool>(key: "colorBGText", default: true)
- if legacyColorBGText.exists {
- Storage.shared.colorBGText.value = legacyColorBGText.value
- legacyColorBGText.setNil(key: "colorBGText")
- }
- let legacyAppBadge = UserDefaultsValue<Bool>(key: "appBadge", default: true)
- if legacyAppBadge.exists {
- Storage.shared.appBadge.value = legacyAppBadge.value
- legacyAppBadge.setNil(key: "appBadge")
- }
- let legacyForceDarkMode = UserDefaultsValue<Bool>(key: "forceDarkMode", default: true)
- if legacyForceDarkMode.exists {
- Storage.shared.forceDarkMode.value = legacyForceDarkMode.value
- legacyForceDarkMode.setNil(key: "forceDarkMode")
- }
- let legacyShowStats = UserDefaultsValue<Bool>(key: "showStats", default: true)
- if legacyShowStats.exists {
- Storage.shared.showStats.value = legacyShowStats.value
- legacyShowStats.setNil(key: "showStats")
- }
- let legacyUseIFCC = UserDefaultsValue<Bool>(key: "useIFCC", default: false)
- if legacyUseIFCC.exists {
- Storage.shared.useIFCC.value = legacyUseIFCC.value
- legacyUseIFCC.setNil(key: "useIFCC")
- }
- let legacyShowSmallGraph = UserDefaultsValue<Bool>(key: "showSmallGraph", default: true)
- if legacyShowSmallGraph.exists {
- Storage.shared.showSmallGraph.value = legacyShowSmallGraph.value
- legacyShowSmallGraph.setNil(key: "showSmallGraph")
- }
- let legacyScreenlockSwitchState = UserDefaultsValue<Bool>(key: "screenlockSwitchState", default: true)
- if legacyScreenlockSwitchState.exists {
- Storage.shared.screenlockSwitchState.value = legacyScreenlockSwitchState.value
- legacyScreenlockSwitchState.setNil(key: "screenlockSwitchState")
- }
- let legacyShowDisplayName = UserDefaultsValue<Bool>(key: "showDisplayName", default: false)
- if legacyShowDisplayName.exists {
- Storage.shared.showDisplayName.value = legacyShowDisplayName.value
- legacyShowDisplayName.setNil(key: "showDisplayName")
- }
- let legacySpeakBG = UserDefaultsValue<Bool>(key: "speakBG", default: false)
- if legacySpeakBG.exists {
- Storage.shared.speakBG.value = legacySpeakBG.value
- legacySpeakBG.setNil(key: "speakBG")
- }
- let legacySpeakBGAlways = UserDefaultsValue<Bool>(key: "speakBGAlways", default: true)
- if legacySpeakBGAlways.exists {
- Storage.shared.speakBGAlways.value = legacySpeakBGAlways.value
- legacySpeakBGAlways.setNil(key: "speakBGAlways")
- }
- let legacySpeakLowBG = UserDefaultsValue<Bool>(key: "speakLowBG", default: false)
- if legacySpeakLowBG.exists {
- Storage.shared.speakLowBG.value = legacySpeakLowBG.value
- legacySpeakLowBG.setNil(key: "speakLowBG")
- }
- let legacySpeakProactiveLowBG = UserDefaultsValue<Bool>(key: "speakProactiveLowBG", default: false)
- if legacySpeakProactiveLowBG.exists {
- Storage.shared.speakProactiveLowBG.value = legacySpeakProactiveLowBG.value
- legacySpeakProactiveLowBG.setNil(key: "speakProactiveLowBG")
- }
- let legacySpeakFastDropDelta = UserDefaultsValue<Float>(key: "speakFastDropDelta", default: 10.0)
- if legacySpeakFastDropDelta.exists {
- Storage.shared.speakFastDropDelta.value = Double(legacySpeakFastDropDelta.value)
- legacySpeakFastDropDelta.setNil(key: "speakFastDropDelta")
- }
- let legacySpeakLowBGLimit = UserDefaultsValue<Float>(key: "speakLowBGLimit", default: 72.0)
- if legacySpeakLowBGLimit.exists {
- Storage.shared.speakLowBGLimit.value = Double(legacySpeakLowBGLimit.value)
- legacySpeakLowBGLimit.setNil(key: "speakLowBGLimit")
- }
- let legacySpeakHighBGLimit = UserDefaultsValue<Float>(key: "speakHighBGLimit", default: 180.0)
- if legacySpeakHighBGLimit.exists {
- Storage.shared.speakHighBGLimit.value = Double(legacySpeakHighBGLimit.value)
- legacySpeakHighBGLimit.setNil(key: "speakHighBGLimit")
- }
- let legacySpeakHighBG = UserDefaultsValue<Bool>(key: "speakHighBG", default: false)
- if legacySpeakHighBG.exists {
- Storage.shared.speakHighBG.value = legacySpeakHighBG.value
- legacySpeakHighBG.setNil(key: "speakHighBG")
- }
- let legacySpeakLanguage = UserDefaultsValue<String>(key: "speakLanguage", default: "en")
- if legacySpeakLanguage.exists {
- Storage.shared.speakLanguage.value = legacySpeakLanguage.value
- legacySpeakLanguage.setNil(key: "speakLanguage")
- }
- // ── General (done earlier, but safe to repeat) ──
- move(UserDefaultsValue<Bool>(key: "colorBGText", default: true), into: Storage.shared.colorBGText)
- move(UserDefaultsValue<Bool>(key: "appBadge", default: true), into: appBadge)
- move(UserDefaultsValue<Bool>(key: "forceDarkMode", default: false), into: forceDarkMode)
- move(UserDefaultsValue<Bool>(key: "showStats", default: true), into: showStats)
- move(UserDefaultsValue<Bool>(key: "useIFCC", default: false), into: useIFCC)
- move(UserDefaultsValue<Bool>(key: "showSmallGraph", default: true), into: showSmallGraph)
- move(UserDefaultsValue<Bool>(key: "screenlockSwitchState", default: false), into: screenlockSwitchState)
- move(UserDefaultsValue<Bool>(key: "showDisplayName", default: false), into: showDisplayName)
- // ── Speak-BG ──
- move(UserDefaultsValue<Bool>(key: "speakBG", default: false), into: speakBG)
- move(UserDefaultsValue<Bool>(key: "speakBGAlways", default: true), into: speakBGAlways)
- move(UserDefaultsValue<Bool>(key: "speakLowBG", default: false), into: speakLowBG)
- move(UserDefaultsValue<Bool>(key: "speakProactiveLowBG", default: false), into: speakProactiveLowBG)
- move(UserDefaultsValue<Bool>(key: "speakHighBG", default: false), into: speakHighBG)
- moveFloatToDouble(UserDefaultsValue<Float>(key: "speakLowBGLimit", default: 72.0), into: speakLowBGLimit)
- moveFloatToDouble(UserDefaultsValue<Float>(key: "speakHighBGLimit", default: 180.0), into: speakHighBGLimit)
- moveFloatToDouble(UserDefaultsValue<Float>(key: "speakFastDropDelta", default: 10.0), into: speakFastDropDelta)
- move(UserDefaultsValue<String>(key: "speakLanguage", default: "en"), into: speakLanguage)
- // ── Graph ──
- move(UserDefaultsValue<Bool>(key: "showDots", default: true), into: showDots)
- move(UserDefaultsValue<Bool>(key: "showLines", default: true), into: showLines)
- move(UserDefaultsValue<Bool>(key: "showValues", default: true), into: showValues)
- move(UserDefaultsValue<Bool>(key: "showAbsorption", default: true), into: showAbsorption)
- move(UserDefaultsValue<Bool>(key: "showDIAMarkers", default: true), into: showDIALines)
- move(UserDefaultsValue<Bool>(key: "show30MinLine", default: false), into: show30MinLine)
- move(UserDefaultsValue<Bool>(key: "show90MinLine", default: false), into: show90MinLine)
- move(UserDefaultsValue<Bool>(key: "showMidnightMarkers", default: false), into: showMidnightLines)
- move(UserDefaultsValue<Bool>(key: "smallGraphTreatments", default: true), into: smallGraphTreatments)
- move(UserDefaultsValue<Int>(key: "smallGraphHeight", default: 40), into: smallGraphHeight)
- move(UserDefaultsValue<Double>(key: "predictionToLoad", default: 1.0), into: predictionToLoad)
- move(UserDefaultsValue<Double>(key: "minBasalScale", default: 5.0), into: minBasalScale)
- moveFloatToDouble(UserDefaultsValue<Float>(key: "minBGScale", default: 250.0), into: minBGScale)
- moveFloatToDouble(UserDefaultsValue<Float>(key: "lowLine", default: 70.0), into: lowLine)
- moveFloatToDouble(UserDefaultsValue<Float>(key: "highLine", default: 180.0), into: highLine)
- move(UserDefaultsValue<Int>(key: "downloadDays", default: 1), into: downloadDays)
- // Remove this in a year later than the release of the new Alarms [END]
- // ── Watch / Calendar ────────────────────────────────────────────────
- move(UserDefaultsValue<Bool>(key: "writeCalendarEvent", default: false), into: writeCalendarEvent)
- move(UserDefaultsValue<String>(key: "calendarIdentifier", default: ""), into: calendarIdentifier)
- move(UserDefaultsValue<String>(key: "watchLine1", default: "%BG% %DIRECTION% %DELTA% %MINAGO%"), into: watchLine1)
- move(UserDefaultsValue<String>(key: "watchLine2", default: "C:%COB% I:%IOB% B:%BASAL%"), into: watchLine2)
- // Migration of generic alarm settings
- // ── AlarmConfiguration migration ─────────────────────────────────────────
- do {
- // Work on a mutable copy, then write the whole thing back once.
- var cfg = Storage.shared.alarmConfiguration.value
- let cal = Calendar.current
- /// Copy *one* legacy value → struct field → delete old key
- func move<T: AnyConvertible & Equatable>(
- _ legacy: @autoclosure () -> UserDefaultsValue<T>,
- write: (inout AlarmConfiguration, T) -> Void
- ) {
- let item = legacy()
- guard item.exists else { return }
- write(&cfg, item.value)
- item.setNil(key: item.key)
- }
- // 1. Override-volume toggle
- move(UserDefaultsValue<Bool>(key: "overrideSystemOutputVolume",
- default: cfg.overrideSystemOutputVolume))
- {
- $0.overrideSystemOutputVolume = $1
- }
- // 2. Forced output volume itself.
- // Prefer newer key (“forcedOutputVolume”); otherwise fall back.
- if UserDefaultsValue<Float>(key: "forcedOutputVolume",
- default: cfg.forcedOutputVolume).exists
- {
- move(UserDefaultsValue<Float>(key: "forcedOutputVolume",
- default: cfg.forcedOutputVolume))
- {
- $0.forcedOutputVolume = $1
- }
- } else {
- move(UserDefaultsValue<Float>(key: "systemOutputVolume",
- default: cfg.forcedOutputVolume))
- {
- $0.forcedOutputVolume = $1
- }
- }
- // 3. Play audio during phone calls
- move(UserDefaultsValue<Bool>(key: "alertAudioDuringPhone",
- default: cfg.audioDuringCalls))
- {
- $0.audioDuringCalls = $1
- }
- // 4. Auto-snooze CGM-start alarm
- move(UserDefaultsValue<Bool>(key: "alertAutoSnoozeCGMStart",
- default: cfg.autoSnoozeCGMStart))
- {
- $0.autoSnoozeCGMStart = $1
- }
- // 5. Global “Snooze all” → snoozeUntil
- move(UserDefaultsValue<Date?>(key: "alertSnoozeAllTime",
- default: cfg.snoozeUntil))
- {
- $0.snoozeUntil = $1
- }
- // 6. Global “Mute all” → muteUntil
- move(UserDefaultsValue<Date?>(key: "alertMuteAllTime",
- default: cfg.muteUntil))
- {
- $0.muteUntil = $1
- }
- // 7 & 8. Legacy quiet-hours → day/night start
- // (only if both dates exist and are on the same “reference” day)
- let qStart = UserDefaultsValue<Date?>(key: "quietHourStart", default: nil)
- let qEnd = UserDefaultsValue<Date?>(key: "quietHourEnd", default: nil)
- if let s = qStart.value, let e = qEnd.value {
- let compsStart = cal.dateComponents([.hour, .minute], from: s)
- let compsEnd = cal.dateComponents([.hour, .minute], from: e)
- if let sh = compsStart.hour, let sm = compsStart.minute,
- let eh = compsEnd.hour, let em = compsEnd.minute
- {
- cfg.nightStart = TimeOfDay(hour: sh, minute: sm)
- cfg.dayStart = TimeOfDay(hour: eh, minute: em)
- qStart.setNil(key: qStart.key)
- qEnd.setNil(key: qEnd.key)
- }
- }
- // 9. Legacy “ignore zero BG” flag → ignoreZeroBG
- move(UserDefaultsValue<Bool>(key: "alertIgnoreZero",
- default: cfg.ignoreZeroBG))
- {
- $0.ignoreZeroBG = $1
- }
- // finally persist the whole struct
- Storage.shared.alarmConfiguration.value = cfg
- }
- migrateUrgentLowAlarm()
- migrateLowAlarm()
- migrateHighAlarm()
- migrateUrgentHighAlarm()
- migrateFastDropAlarm()
- migrateFastRiseAlarm()
- migrateMissedReadingAlarm()
- }
- // MARK: - One-off alarm migrations
- /// Reads *all* `alertUrgentLow*` keys, converts them into a single `Alarm`,
- /// saves it to the modern `[Alarm]` store, then deletes the legacy keys.
- private func migrateUrgentLowAlarm() {
- // Did the user ever change that alert? (No key ⇒ nothing to do.)
- guard UserDefaultsValue<Bool>(key: "alertUrgentLowActive", default: false).exists else { return }
- /// Helper: fetch-then-delete a legacy value in one line.
- func take<V: AnyConvertible & Equatable>(_ key: String, default def: V) -> V {
- let box = UserDefaultsValue<V>(key: key, default: def)
- defer { box.setNil(key: key) }
- return box.value
- }
- // Build the new Alarm ------------------------------------------------
- var alarm = Alarm(type: .low)
- alarm.name = "Urgent Low"
- alarm.isEnabled = take("alertUrgentLowActive", default: false)
- alarm.belowBG = Double(take("alertUrgentLowBG", default: 55.0))
- alarm.predictiveMinutes = take("alertUrgentLowPredictiveMinutes", default: 0)
- alarm.snoozeDuration = take("alertUrgentLowSnooze", default: 5)
- alarm.snoozedUntil = take("alertUrgentLowSnoozedTime", default: nil as Date?)
- alarm.soundFile = SoundFile(
- rawValue: take("alertUrgentLowSound",
- default: "Emergency_Alarm_Siren"))
- ?? .emergencyAlarmSiren
- alarm.playSoundOption = PlaySoundOption(
- rawValue: take("alertUrgentLowAudible",
- default: "Always").lowercased()) ?? .always
- alarm.repeatSoundOption = RepeatSoundOption(
- rawValue: take("alertUrgentLowRepeat",
- default: "Always").lowercased()) ?? .always
- // Day / Night active-window (“Pre-Snooze”)
- let autoStr = take("alertUrgentLowAutosnooze", default: "Never").lowercased()
- let dayFlag = take("alertUrgentLowAutosnoozeDay", default: false)
- let nightFlag = take("alertUrgentLowAutosnoozeNight", default: false)
- alarm.activeOption = {
- if dayFlag, !nightFlag { return .day }
- if !dayFlag, nightFlag { return .night }
- switch autoStr {
- case "day", "at day": return .day
- case "night", "at night": return .night
- default: return .always // “Never” → always active
- }
- }()
- // Persist -----------------------------------------------------------------
- Storage.shared.alarms.value.append(alarm)
- }
- // MARK: - Low-BG alarm -------------------------------------------------------
- private func migrateLowAlarm() {
- // Bail if the old keys were never written
- guard UserDefaultsValue<Bool>(key: "alertLowActive", default: false).exists else { return }
- // tiny helper: fetch value → erase key
- func take<V: AnyConvertible & Equatable>(_ key: String, default def: V) -> V {
- let box = UserDefaultsValue<V>(key: key, default: def)
- defer { box.setNil(key: key) }
- return box.value
- }
- // Build the new Alarm ----------------------------------------------------
- var alarm = Alarm(type: .low)
- alarm.name = "Low"
- alarm.isEnabled = take("alertLowActive", default: false)
- alarm.belowBG = Double(take("alertLowBG", default: 70.0))
- // “Persistent ≥ X min” → `persistentMinutes`
- alarm.persistentMinutes = take("alertLowPersistent", default: 0)
- // “Persistence max BG drop” -- ignoring this for now
- // let persistentLowTriggerImmediatelyBG = UserDefaultsRepository.alertLowBG.value - UserDefaultsRepository.alertLowPersistenceMax.value
- // (Float(persistentLowBG) <= UserDefaultsRepository.alertLowBG.value || Float(currentBG) <= persistentLowTriggerImmediatelyBG)
- _ = Double(take("alertLowPersistenceMax", default: 5.0))
- alarm.snoozeDuration = take("alertLowSnooze", default: 5)
- alarm.snoozedUntil = take("alertLowSnoozedTime", default: nil as Date?)
- alarm.soundFile = SoundFile(
- rawValue: take("alertLowSound",
- default: "Indeed")) ?? .indeed
- alarm.playSoundOption = PlaySoundOption(
- rawValue: take("alertLowAudible",
- default: "Always").lowercased()) ?? .always
- alarm.repeatSoundOption = RepeatSoundOption(
- rawValue: take("alertLowRepeat",
- default: "Always").lowercased()) ?? .always
- // activeOption ← legacy “Pre-Snooze” flags / picker
- let autoStr = take("alertLowAutosnooze", default: "Never").lowercased()
- let dayFlag = take("alertLowAutosnoozeDay", default: false)
- let nightFlag = take("alertLowAutosnoozeNight", default: false)
- alarm.activeOption = {
- if dayFlag, !nightFlag { return .day }
- if !dayFlag, nightFlag { return .night }
- switch autoStr {
- case "day", "at day": return .day
- case "night", "at night": return .night
- default: return .always
- }
- }()
- // Done → append to the modern store
- Storage.shared.alarms.value.append(alarm)
- }
- // MARK: - High-BG alarm -----------------------------------------------------
- private func migrateHighAlarm() {
- // Only run if the legacy key ever existed
- guard UserDefaultsValue<Bool>(key: "alertHighActive", default: false).exists else { return }
- /// Fetch → erase helper
- func take<V: AnyConvertible & Equatable>(_ key: String, default def: V) -> V {
- let box = UserDefaultsValue<V>(key: key, default: def)
- defer { box.setNil(key: key) } // remove legacy value
- return box.value
- }
- // ---------- Build Alarm -----------------------------------------------
- var alarm = Alarm(type: .high)
- alarm.name = "High"
- alarm.isEnabled = take("alertHighActive", default: false)
- alarm.aboveBG = Double(take("alertHighBG", default: 180.0))
- alarm.persistentMinutes = take("alertHighPersistent", default: 60)
- alarm.snoozeDuration = take("alertHighSnooze", default: 60)
- alarm.snoozedUntil = take("alertHighSnoozedTime", default: nil as Date?)
- alarm.soundFile = SoundFile(
- rawValue: take("alertHighSound",
- default: "Time_Has_Come")) ?? .timeHasCome
- alarm.playSoundOption = PlaySoundOption(
- rawValue: take("alertHighAudible",
- default: "Always").lowercased()) ?? .always
- alarm.repeatSoundOption = RepeatSoundOption(
- rawValue: take("alertHighRepeat",
- default: "Always").lowercased()) ?? .always
- // ── activeOption derived from “Pre-Snooze” picker & flags
- let autoStr = take("alertHighAutosnooze", default: "Never").lowercased()
- let dayFlag = take("alertHighAutosnoozeDay", default: false)
- let nightFlag = take("alertHighAutosnoozeNight", default: false)
- alarm.activeOption = {
- if dayFlag, !nightFlag { return .day }
- if !dayFlag, nightFlag { return .night }
- switch autoStr {
- case "day", "at day": return .day
- case "night", "at night": return .night
- default: return .always
- }
- }()
- // ---------- Persist & we’re done --------------------------------------
- Storage.shared.alarms.value.append(alarm)
- }
- // MARK: - Urgent-High alarm --------------------------------------------------
- private func migrateUrgentHighAlarm() {
- // run only once, only if the user ever changed that toggle
- guard UserDefaultsValue<Bool>(key: "alertUrgentHighActive", default: false).exists else { return }
- // helper: read-then-erase a legacy value
- func take<V: AnyConvertible & Equatable>(_ key: String, default def: V) -> V {
- let box = UserDefaultsValue<V>(key: key, default: def)
- defer { box.setNil(key: key) } // wipe legacy key
- return box.value
- }
- // ───────── Build the Alarm ────────────────────────────────────────────
- var alarm = Alarm(type: .high) // we map to the existing `.high` type
- alarm.name = "Urgent High"
- alarm.isEnabled = take("alertUrgentHighActive", default: false)
- alarm.aboveBG = Double(take("alertUrgentHighBG", default: 250.0))
- alarm.snoozeDuration = take("alertUrgentHighSnooze", default: 30)
- alarm.snoozedUntil = take("alertUrgentHighSnoozedTime", default: nil as Date?)
- alarm.soundFile = SoundFile(
- rawValue: take("alertUrgentHighSound",
- default: "Pager_Beeps")) ?? .pagerBeeps
- alarm.playSoundOption = PlaySoundOption(
- rawValue: take("alertUrgentHighAudible",
- default: "Always").lowercased()) ?? .always
- alarm.repeatSoundOption = RepeatSoundOption(
- rawValue: take("alertUrgentHighRepeat",
- default: "Always").lowercased()) ?? .always
- // activeOption comes from the old “Pre-Snooze” picker + its day/night flags
- let autoStr = take("alertUrgentHighAutosnooze", default: "Never").lowercased()
- let dayFlag = take("alertUrgentHighAutosnoozeDay", default: false)
- let nightFlag = take("alertUrgentHighAutosnoozeNight", default: false)
- alarm.activeOption = {
- if dayFlag, !nightFlag { return .day }
- if !dayFlag, nightFlag { return .night }
- switch autoStr { // fall back to picker value
- case "day", "at day": return .day
- case "night", "at night": return .night
- default: return .always
- }
- }()
- // ───────── Persist in new storage ─────────────────────────────────────
- Storage.shared.alarms.value.append(alarm)
- }
- // MARK: - Fast-Drop alarm ----------------------------------------------------
- private func migrateFastDropAlarm() {
- guard UserDefaultsValue<Bool>(key: "alertFastDropDeltaActive",
- default: false).exists else { return }
- // helper: read-then-erase
- func take<V: AnyConvertible & Equatable>(_ k: String, default d: V) -> V {
- let box = UserDefaultsValue<V>(key: k, default: d)
- defer { box.setNil(key: k) }
- return box.value
- }
- var alarm = Alarm(type: .fastDrop)
- alarm.name = "Fast Drop"
- alarm.isEnabled = take("alertFastDropDeltaActive", default: false)
- // core trigger parameters
- alarm.delta = Double(take("alertFastDropDelta", default: 10.0))
- alarm.monitoringWindow = take("alertFastDropReadings", default: 3) // store #readings
- if take("alertFastDropUseLimit", default: false) {
- alarm.belowBG = Double(take("alertFastDropBelowBG", default: 120.0))
- }
- // snoozing
- alarm.snoozeDuration = take("alertFastDropDeltaSnooze", default: 10)
- alarm.snoozedUntil = take("alertFastDropSnoozedTime", default: nil as Date?)
- // sound + options
- alarm.soundFile = SoundFile(
- rawValue: take("alertFastDropSound", default: "Big_Clock_Ticking")
- ) ?? .bigClockTicking
- alarm.playSoundOption = PlaySoundOption(
- rawValue: take("alertFastDropAudible", default: "Always").lowercased()
- ) ?? .always
- alarm.repeatSoundOption = RepeatSoundOption(
- rawValue: take("alertFastDropRepeat", default: "Never").lowercased()
- ) ?? .never
- // activeOption from old “Pre-Snooze” picker + day/night flags
- let autoStr = take("alertFastDropAutosnooze", default: "Never").lowercased()
- let dayFlag = take("alertFastDropAutosnoozeDay", default: false)
- let nightFlag = take("alertFastDropAutosnoozeNight", default: false)
- alarm.activeOption = {
- if dayFlag, !nightFlag { return .day }
- if !dayFlag, nightFlag { return .night }
- switch autoStr {
- case "day", "at day": return .day
- case "night", "at night": return .night
- default: return .always
- }
- }()
- Storage.shared.alarms.value.append(alarm)
- }
- // MARK: - Fast-Rise alarm ----------------------------------------------------
- private func migrateFastRiseAlarm() {
- guard UserDefaultsValue<Bool>(key: "alertFastRiseDeltaActive",
- default: false).exists else { return }
- func take<V: AnyConvertible & Equatable>(_ k: String, default d: V) -> V {
- let box = UserDefaultsValue<V>(key: k, default: d)
- defer { box.setNil(key: k) }
- return box.value
- }
- var alarm = Alarm(type: .fastRise)
- alarm.name = "Fast Rise"
- alarm.isEnabled = take("alertFastRiseDeltaActive", default: false)
- alarm.delta = Double(take("alertFastRiseDelta", default: 10.0))
- alarm.monitoringWindow = take("alertFastRiseReadings", default: 3)
- if take("alertFastRiseUseLimit", default: false) {
- alarm.aboveBG = Double(take("alertFastRiseAboveBG", default: 200.0))
- }
- alarm.snoozeDuration = take("alertFastRiseDeltaSnooze", default: 10)
- alarm.snoozedUntil = take("alertFastRiseSnoozedTime", default: nil as Date?)
- alarm.soundFile = SoundFile(
- rawValue: take("alertFastRiseSound",
- default: "Cartoon_Fail_Strings_Trumpet")
- ) ?? .cartoonFailStringsTrumpet
- alarm.playSoundOption = PlaySoundOption(
- rawValue: take("alertFastRiseAudible", default: "Always").lowercased()
- ) ?? .always
- alarm.repeatSoundOption = RepeatSoundOption(
- rawValue: take("alertFastRiseRepeat", default: "Never").lowercased()
- ) ?? .never
- let autoStr = take("alertFastRiseAutosnooze", default: "Never").lowercased()
- let dayFlag = take("alertFastRiseAutosnoozeDay", default: false)
- let nightFlag = take("alertFastRiseAutosnoozeNight", default: false)
- alarm.activeOption = {
- if dayFlag, !nightFlag { return .day }
- if !dayFlag, nightFlag { return .night }
- switch autoStr {
- case "day", "at day": return .day
- case "night", "at night": return .night
- default: return .always
- }
- }()
- Storage.shared.alarms.value.append(alarm)
- }
- // MARK: - Missed-Reading alarm ---------------------------------------------
- private func migrateMissedReadingAlarm() {
- // Run only when the user has ever modified those settings
- guard UserDefaultsValue<Bool>(key: "alertMissedReadingActive",
- default: false).exists else { return }
- // read-then-erase helper
- func take<V: AnyConvertible & Equatable>(_ k: String, default d: V) -> V {
- let b = UserDefaultsValue<V>(key: k, default: d)
- defer { b.setNil(key: k) }
- return b.value
- }
- var alarm = Alarm(type: .missedReading)
- alarm.name = "Missed Reading"
- alarm.isEnabled = take("alertMissedReadingActive", default: false)
- // “No CGM data for X minutes”
- alarm.persistentMinutes = take("alertMissedReading", default: 31)
- // snoozing
- alarm.snoozeDuration = take("alertMissedReadingSnooze", default: 30)
- alarm.snoozedUntil = take("alertMissedReadingSnoozedTime",
- default: nil as Date?)
- // (legacy “is-snoozed” flag is implicit in snoozedUntil)
- // sound
- alarm.soundFile = SoundFile(
- rawValue: take("alertMissedReadingSound",
- default: "Cartoon_Tip_Toe_Sneaky_Walk")
- ) ?? .cartoonTipToeSneakyWalk
- // play / repeat options
- alarm.playSoundOption = PlaySoundOption(
- rawValue: take("alertMissedReadingAudible",
- default: "Always").lowercased()
- ) ?? .always
- alarm.repeatSoundOption = RepeatSoundOption(
- rawValue: take("alertMissedReadingRepeat",
- default: "Never").lowercased()
- ) ?? .never
- // activeOption ← “Pre-Snooze” picker + day/night flags
- let autoStr = take("alertMissedReadingAutosnooze", default: "Never")
- .lowercased()
- let dayFlag = take("alertMissedReadingAutosnoozeDay", default: false)
- let nightFlag = take("alertMissedReadingAutosnoozeNight", default: false)
- alarm.activeOption = {
- if dayFlag, !nightFlag { return .day }
- if !dayFlag, nightFlag { return .night }
- switch autoStr {
- case "day", "at day": return .day
- case "night", "at night": return .night
- default: return .always // “Never” → always on
- }
- }()
- // store in the new world
- Storage.shared.alarms.value.append(alarm)
- }
- }
|