Browse Source

Low bg alarm in progress

Jonas Björkert 1 year ago
parent
commit
fc3188fee4

+ 4 - 4
LoopFollow.xcodeproj/project.pbxproj

@@ -10,7 +10,7 @@
 		3F1335F351590E573D8E6962 /* Pods_LoopFollow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */; };
 		3F1335F351590E573D8E6962 /* Pods_LoopFollow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */; };
 		DD0247592DB2E89600FCADF6 /* AlarmCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */; };
 		DD0247592DB2E89600FCADF6 /* AlarmCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */; };
 		DD0247712DB4337700FCADF6 /* BuildExpireCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD02475B2DB2E8FB00FCADF6 /* BuildExpireCondition.swift */; };
 		DD0247712DB4337700FCADF6 /* BuildExpireCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD02475B2DB2E8FB00FCADF6 /* BuildExpireCondition.swift */; };
-		DD0650A92DCA8A10004D3B41 /* AlarmThresholdPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650A82DCA8A10004D3B41 /* AlarmThresholdPickerSection.swift */; };
+		DD0650A92DCA8A10004D3B41 /* AlarmBGSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650A82DCA8A10004D3B41 /* AlarmBGSection.swift */; };
 		DD07B5C929E2F9C400C6A635 /* NightscoutUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */; };
 		DD07B5C929E2F9C400C6A635 /* NightscoutUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */; };
 		DD0C0C602C415B9D00DBADDF /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0C0C5F2C415B9D00DBADDF /* ProfileManager.swift */; };
 		DD0C0C602C415B9D00DBADDF /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0C0C5F2C415B9D00DBADDF /* ProfileManager.swift */; };
 		DD0C0C622C4175FD00DBADDF /* NSProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0C0C612C4175FD00DBADDF /* NSProfile.swift */; };
 		DD0C0C622C4175FD00DBADDF /* NSProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0C0C612C4175FD00DBADDF /* NSProfile.swift */; };
@@ -323,7 +323,7 @@
 		DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmCondition.swift; sourceTree = "<group>"; };
 		DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmCondition.swift; sourceTree = "<group>"; };
 		DD02475B2DB2E8FB00FCADF6 /* BuildExpireCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildExpireCondition.swift; sourceTree = "<group>"; };
 		DD02475B2DB2E8FB00FCADF6 /* BuildExpireCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildExpireCondition.swift; sourceTree = "<group>"; };
 		DD0247612DB2EB9A00FCADF6 /* LoopFollowTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoopFollowTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		DD0247612DB2EB9A00FCADF6 /* LoopFollowTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoopFollowTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-		DD0650A82DCA8A10004D3B41 /* AlarmThresholdPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmThresholdPickerSection.swift; sourceTree = "<group>"; };
+		DD0650A82DCA8A10004D3B41 /* AlarmBGSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmBGSection.swift; sourceTree = "<group>"; };
 		DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutUtils.swift; sourceTree = "<group>"; };
 		DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutUtils.swift; sourceTree = "<group>"; };
 		DD0C0C5F2C415B9D00DBADDF /* ProfileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManager.swift; sourceTree = "<group>"; };
 		DD0C0C5F2C415B9D00DBADDF /* ProfileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManager.swift; sourceTree = "<group>"; };
 		DD0C0C612C4175FD00DBADDF /* NSProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSProfile.swift; sourceTree = "<group>"; };
 		DD0C0C612C4175FD00DBADDF /* NSProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSProfile.swift; sourceTree = "<group>"; };
@@ -898,7 +898,7 @@
 		DDC7E53B2DBD8A1600EB1127 /* Components */ = {
 		DDC7E53B2DBD8A1600EB1127 /* Components */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				DD0650A82DCA8A10004D3B41 /* AlarmThresholdPickerSection.swift */,
+				DD0650A82DCA8A10004D3B41 /* AlarmBGSection.swift */,
 				DDC7E5392DBD8A1600EB1127 /* AlarmEditorFields.swift */,
 				DDC7E5392DBD8A1600EB1127 /* AlarmEditorFields.swift */,
 				DDC7E53A2DBD8A1600EB1127 /* SoundFile.swift */,
 				DDC7E53A2DBD8A1600EB1127 /* SoundFile.swift */,
 			);
 			);
@@ -1725,7 +1725,7 @@
 				DD0C0C6B2C48562000DBADDF /* InsulinMetric.swift in Sources */,
 				DD0C0C6B2C48562000DBADDF /* InsulinMetric.swift in Sources */,
 				DD493AD92ACF2171009A6922 /* Carbs.swift in Sources */,
 				DD493AD92ACF2171009A6922 /* Carbs.swift in Sources */,
 				DD493AE92ACF2445009A6922 /* BGData.swift in Sources */,
 				DD493AE92ACF2445009A6922 /* BGData.swift in Sources */,
-				DD0650A92DCA8A10004D3B41 /* AlarmThresholdPickerSection.swift in Sources */,
+				DD0650A92DCA8A10004D3B41 /* AlarmBGSection.swift in Sources */,
 				FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */,
 				FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */,
 				DD4AFB6B2DB6BF2A00BB593F /* Binding+Optional.swift in Sources */,
 				DD4AFB6B2DB6BF2A00BB593F /* Binding+Optional.swift in Sources */,
 				DD608A082C1F584900F91132 /* DeviceStatusLoop.swift in Sources */,
 				DD608A082C1F584900F91132 /* DeviceStatusLoop.swift in Sources */,

+ 2 - 2
LoopFollow/Alarm/Alarm.swift

@@ -36,8 +36,8 @@ struct Alarm: Identifiable, Codable, Equatable {
     /// Also used as bg limit for drop alarms for example
     /// Also used as bg limit for drop alarms for example
     var threshold: Double?
     var threshold: Double?
 
 
-    /// If the alarm looks at predictions, this is how many predictions to include
-    var predictiveReadings: Int?
+    /// If the alarm looks at predictions, this is how long into the future to look
+    var predictiveMinutes: Int?
 
 
     /// If the alarm acts on delta, the delta is stored here, it can be a delta bgvalue (in mg/Dl)
     /// If the alarm acts on delta, the delta is stored here, it can be a delta bgvalue (in mg/Dl)
     /// If a delta alarm is only active below a bg, that bg is stored in threshold
     /// If a delta alarm is only active below a bg, that bg is stored in threshold

+ 24 - 9
LoopFollow/Alarm/AlarmEditing/Components/AlarmThresholdPickerSection.swift

@@ -1,5 +1,5 @@
 //
 //
-//  AlarmThresholdPickerSection.swift
+//  AlarmBGSection.swift
 //  LoopFollow
 //  LoopFollow
 //
 //
 //  Created by Jonas Björkert on 2025-05-06.
 //  Created by Jonas Björkert on 2025-05-06.
@@ -10,22 +10,35 @@ import SwiftUI
 import HealthKit
 import HealthKit
 
 
 struct AlarmBGSection: View {
 struct AlarmBGSection: View {
-    // ── Public API ──────────────────────────────────────────────────────────────
+    let header: String?
+    let footer: String?
     let title: String
     let title: String
     let range: ClosedRange<Double>
     let range: ClosedRange<Double>
     @Binding var value: Double
     @Binding var value: Double
 
 
-    // ── Private state ──────────────────────────────────────────────────────────
     @State private var showPicker = false
     @State private var showPicker = false
 
 
-    // Preferred unit – mmol/L or mg/dL
+    init(
+        header: String? = nil,
+        footer: String? = nil,
+        title: String,
+        range: ClosedRange<Double>,
+        value: Binding<Double>
+    ) {
+        self.header = header
+        self.footer = footer
+        self.title  = title
+        self.range  = range
+        self._value = value
+    }
+    
     private var unit: HKUnit { UserDefaultsRepository.getPreferredUnit() }
     private var unit: HKUnit { UserDefaultsRepository.getPreferredUnit() }
 
 
     private var displayValue: String {
     private var displayValue: String {
-        Localizer.formatQuantity(value)
+        let formatted = Localizer.formatQuantity(value)
+        return "\(formatted) \(unit.localizedShortUnitString)"
     }
     }
 
 
-    // Generate all selectable display values for the picker
     private var pickerValues: [Double] {
     private var pickerValues: [Double] {
         let step : Double = unit == .millimolesPerLiter ? 18.0 * 0.1 : 1.0
         let step : Double = unit == .millimolesPerLiter ? 18.0 * 0.1 : 1.0
 
 
@@ -34,8 +47,10 @@ struct AlarmBGSection: View {
     }
     }
 
 
     var body: some View {
     var body: some View {
-        Section {
-            // Collapsed row
+        Section(
+            header: header.map(Text.init),
+            footer: footer.map(Text.init)
+        ) {
             HStack {
             HStack {
                 Text(title)
                 Text(title)
                 Spacer()
                 Spacer()
@@ -50,7 +65,7 @@ struct AlarmBGSection: View {
                 VStack {
                 VStack {
                     Picker("", selection: bindingForPicker) {
                     Picker("", selection: bindingForPicker) {
                         ForEach(pickerValues, id: \.self) { v in
                         ForEach(pickerValues, id: \.self) { v in
-                            Text(Localizer.formatQuantity(v))
+                            Text("\(Localizer.formatQuantity(v)) \(unit.localizedShortUnitString)")
                                 .tag(v)
                                 .tag(v)
                         }
                         }
                     }
                     }

+ 1 - 1
LoopFollow/Alarm/AlarmEditing/Components/AlarmEditorFields.swift

@@ -24,7 +24,7 @@ struct AlarmGeneralSection: View {
     }
     }
 }
 }
 
 
-struct AlarmThresholdSection: View {
+struct AlarmStepperSection: View {
     let title: String
     let title: String
     let range: ClosedRange<Double>
     let range: ClosedRange<Double>
     let step: Double
     let step: Double

+ 1 - 1
LoopFollow/Alarm/AlarmEditing/Editors/BuildExpireAlarmEditor.swift

@@ -15,7 +15,7 @@ struct BuildExpireAlarmEditor: View {
         Form {
         Form {
           AlarmGeneralSection(alarm: $alarm)
           AlarmGeneralSection(alarm: $alarm)
 
 
-          AlarmThresholdSection(
+          AlarmStepperSection(
             title: "Expires In",
             title: "Expires In",
             range: 1...14,
             range: 1...14,
             step: 1,
             step: 1,

+ 28 - 0
LoopFollow/Alarm/AlarmEditing/Editors/LowBgAlarmEditor.swift

@@ -17,6 +17,7 @@ struct LowBgAlarmEditor: View {
             AlarmGeneralSection(alarm: $alarm)
             AlarmGeneralSection(alarm: $alarm)
 
 
             AlarmBGSection(
             AlarmBGSection(
+                header: "Threshold",
                 title: "BG",
                 title: "BG",
                 range: 40...150,
                 range: 40...150,
                 value: Binding(
                 value: Binding(
@@ -24,6 +25,33 @@ struct LowBgAlarmEditor: View {
                     set: { alarm.threshold = $0 }
                     set: { alarm.threshold = $0 }
                 )
                 )
             )
             )
+
+            AlarmStepperSection(
+                title: "Predictive",
+                range: 0...60,
+                step: 5,
+                unitLabel: alarm.type.timeUnit.label,
+                value: Binding(
+                    get: { Double(alarm.predictiveMinutes ?? 0) },
+                    set: { alarm.predictiveMinutes = Int($0) }
+                )
+            )
+
+            AlarmStepperSection(
+                title: "Default Snooze",
+                range: 5...30,
+                step: 5,
+                unitLabel: alarm.type.timeUnit.label,
+                value: Binding(
+                    get: { Double(alarm.snoozeDuration) },
+                    set: { alarm.snoozeDuration = Int($0) }
+                )
+            )
+
+            AlarmAudioSection(alarm: $alarm)
+            AlarmActiveSection(alarm: $alarm)
+            AlarmSnoozedUntilSection(alarm: $alarm)
+
         }
         }
         .navigationTitle(alarm.type.rawValue)
         .navigationTitle(alarm.type.rawValue)
     }
     }