Jonas Björkert 1 год назад
Родитель
Сommit
70b5800b3f

+ 8 - 0
LoopFollow.xcodeproj/project.pbxproj

@@ -107,6 +107,8 @@
 		DD7F4C092DD504A700D449E9 /* OverrideStartCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C082DD504A700D449E9 /* OverrideStartCondition.swift */; };
 		DD7F4C0B2DD51C5500D449E9 /* OverrideEndCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C0A2DD51C5500D449E9 /* OverrideEndCondition.swift */; };
 		DD7F4C0D2DD51C8100D449E9 /* OverrideEndAlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C0C2DD51C8100D449E9 /* OverrideEndAlarmEditor.swift */; };
+		DD7F4C0F2DD51EC200D449E9 /* TempTargetStartCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C0E2DD51EC200D449E9 /* TempTargetStartCondition.swift */; };
+		DD7F4C112DD51ED900D449E9 /* TempTargetStartAlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C102DD51ED900D449E9 /* TempTargetStartAlarmEditor.swift */; };
 		DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */; };
 		DD85E9952D739CFE001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */; };
 		DD91E4DD2BDEC3F8002D9E97 /* GlucoseConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */; };
@@ -438,6 +440,8 @@
 		DD7F4C082DD504A700D449E9 /* OverrideStartCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStartCondition.swift; sourceTree = "<group>"; };
 		DD7F4C0A2DD51C5500D449E9 /* OverrideEndCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideEndCondition.swift; sourceTree = "<group>"; };
 		DD7F4C0C2DD51C8100D449E9 /* OverrideEndAlarmEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideEndAlarmEditor.swift; sourceTree = "<group>"; };
+		DD7F4C0E2DD51EC200D449E9 /* TempTargetStartCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetStartCondition.swift; sourceTree = "<group>"; };
+		DD7F4C102DD51ED900D449E9 /* TempTargetStartAlarmEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetStartAlarmEditor.swift; sourceTree = "<group>"; };
 		DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EKEventStore+Extensions.swift"; sourceTree = "<group>"; };
 		DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnipodDashHeartbeatBluetoothTransmitter.swift; sourceTree = "<group>"; };
 		DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseConversion.swift; sourceTree = "<group>"; };
@@ -706,6 +710,7 @@
 		DD02475A2DB2E8CE00FCADF6 /* AlarmCondition */ = {
 			isa = PBXGroup;
 			children = (
+				DD7F4C0E2DD51EC200D449E9 /* TempTargetStartCondition.swift */,
 				DD7F4C0A2DD51C5500D449E9 /* OverrideEndCondition.swift */,
 				DD7F4C042DD4BBE200D449E9 /* NotLoopingCondition.swift */,
 				DD7F4C082DD504A700D449E9 /* OverrideStartCondition.swift */,
@@ -960,6 +965,7 @@
 		DDC7E53F2DBD8A1600EB1127 /* Editors */ = {
 			isa = PBXGroup;
 			children = (
+				DD7F4C102DD51ED900D449E9 /* TempTargetStartAlarmEditor.swift */,
 				DD7F4C0C2DD51C8100D449E9 /* OverrideEndAlarmEditor.swift */,
 				DD7F4C062DD5042F00D449E9 /* OverrideStartAlarmEditor.swift */,
 				DD7F4C022DD4B92E00D449E9 /* NotLoopingAlarmEditor.swift */,
@@ -1698,6 +1704,7 @@
 				DD4878202C7DAF890048F05C /* PushMessage.swift in Sources */,
 				DD0247712DB4337700FCADF6 /* BuildExpireCondition.swift in Sources */,
 				DDF6999B2C5AA32E0058A8D9 /* TempTargetPreset.swift in Sources */,
+				DD7F4C0F2DD51EC200D449E9 /* TempTargetStartCondition.swift in Sources */,
 				DD9ACA0E2D340BFF00415D8A /* AlarmTask.swift in Sources */,
 				DDCF9A822D85FD15004DF4DD /* AlarmType.swift in Sources */,
 				DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */,
@@ -1856,6 +1863,7 @@
 				DD0C0C642C45A59400DBADDF /* HKUnit+Extensions.swift in Sources */,
 				DDCF979A24C14DB4002C9752 /* WatchSettingsViewController.swift in Sources */,
 				DD493AE52ACF2383009A6922 /* Treatments.swift in Sources */,
+				DD7F4C112DD51ED900D449E9 /* TempTargetStartAlarmEditor.swift in Sources */,
 				DD50C7552D0862770057AE6F /* ContactImageUpdater.swift in Sources */,
 				DD0650F52DCF303F004D3B41 /* AlarmStepperSection.swift in Sources */,
 				DDCF9A802D85FD0B004DF4DD /* Alarm.swift in Sources */,

+ 26 - 0
LoopFollow/Alarm/AlarmCondition/TempTargetStartCondition.swift

@@ -0,0 +1,26 @@
+//
+//  TempTargetStartCondition.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2025-05-14.
+//  Copyright © 2025 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+
+/// Fires once when a temp-target is activated.
+struct TempTargetStartCondition: AlarmCondition {
+    static let type: AlarmType = .tempTargetStart
+    init() {}
+
+    func evaluate(alarm _: Alarm, data: AlarmData) -> Bool {
+        guard let startTS = data.latestTempTargetStart, startTS > 0 else { return false }
+        guard Date().timeIntervalSince1970 - startTS <= 15 * 60 else { return false }
+
+        let last = Storage.shared.lastTempTargetStartNotified.value ?? 0
+        guard startTS > last else { return false }
+
+        Storage.shared.lastTempTargetStartNotified.value = startTS
+        return true
+    }
+}

+ 1 - 0
LoopFollow/Alarm/AlarmData.swift

@@ -15,6 +15,7 @@ struct AlarmData: Codable {
     let lastLoopTime: TimeInterval?
     let latestOverrideStart: TimeInterval?
     let latestOverrideEnd: TimeInterval?
+    let latestTempTargetStart: TimeInterval?
 }
 
 /*

+ 2 - 0
LoopFollow/Alarm/AlarmEditing/AlarmEditor.swift

@@ -57,6 +57,8 @@ struct AlarmEditor: View {
             OverrideStartAlarmEditor(alarm: $alarm)
         case .overrideEnd:
             OverrideEndAlarmEditor(alarm: $alarm)
+        case .tempTargetStart:
+            TempTargetStartAlarmEditor(alarm: $alarm)
         /* TODO: add other condition types here */
         default:
             Text("No editor for \(alarm.type.rawValue)")

+ 26 - 0
LoopFollow/Alarm/AlarmEditing/Editors/TempTargetStartAlarmEditor.swift

@@ -0,0 +1,26 @@
+//
+//  TempTargetStartAlarmEditor.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2025-05-14.
+//  Copyright © 2025 Jon Fawcett. All rights reserved.
+//
+
+import SwiftUI
+
+struct TempTargetStartAlarmEditor: View {
+    @Binding var alarm: Alarm
+
+    var body: some View {
+        Form {
+            InfoBanner(text: "Alerts when a temp target starts.", alarmType: alarm.type)
+
+            AlarmGeneralSection(alarm: $alarm)
+
+            AlarmActiveSection(alarm: $alarm)
+            AlarmAudioSection(alarm: $alarm, hideRepeat: true)
+            AlarmSnoozeSection(alarm: $alarm, range: 10 ... 60, step: 5)
+        }
+        .navigationTitle(alarm.type.rawValue)
+    }
+}

+ 1 - 0
LoopFollow/Alarm/AlarmManager.swift

@@ -24,6 +24,7 @@ class AlarmManager {
             NotLoopingCondition.self,
             OverrideStartCondition.self,
             OverrideEndCondition.self,
+            TempTargetStartCondition.self,
             // TODO: add other condition types here
         ]
     ) {

+ 1 - 0
LoopFollow/Storage/Storage.swift

@@ -55,6 +55,7 @@ class Storage {
 
     var lastOverrideStartNotified = StorageValue<TimeInterval?>(key: "lastOverrideStartNotified", defaultValue: nil)
     var lastOverrideEndNotified = StorageValue<TimeInterval?>(key: "lastOverrideEndNotified", defaultValue: nil)
+    var lastTempTargetStartNotified = StorageValue<TimeInterval?>(key: "lastTempTargetStartNotified", defaultValue: nil)
 
     static let shared = Storage()
     private init() {}

+ 2 - 0
LoopFollow/Task/AlarmTask.swift

@@ -21,6 +21,7 @@ extension MainViewController {
         DispatchQueue.main.async {
             let latestOverrideStart = self.overrideGraphData.last?.date
             let latestOverrideEnd = self.overrideGraphData.last?.endDate
+            let latestTempTargetStart = self.tempTargetGraphData.last?.date
 
             let alarmData = AlarmData(
                 bgReadings: self.bgData
@@ -33,6 +34,7 @@ extension MainViewController {
                 lastLoopTime: UserDefaultsRepository.alertLastLoopTime.value,
                 latestOverrideStart: latestOverrideStart,
                 latestOverrideEnd: latestOverrideEnd,
+                latestTempTargetStart: latestTempTargetStart
             )
 
             let finalAlarmData: AlarmData