Jonas Björkert 1 rok temu
rodzic
commit
002b328fb3

+ 4 - 0
LoopFollow.xcodeproj/project.pbxproj

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

+ 92 - 0
LoopFollow/Alarm/AlarmEditing/Components/AlarmThresholdPickerSection.swift

@@ -0,0 +1,92 @@
+//
+//  AlarmThresholdPickerSection.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2025-05-06.
+//  Copyright © 2025 Jon Fawcett. All rights reserved.
+//
+
+import SwiftUI
+import HealthKit
+
+struct AlarmThresholdRow: View {
+    // ── Public API ──────────────────────────────────────────────────────────────
+    let title: String
+    let range: ClosedRange<Double>
+    let step: Double          // 1 for mg/dL, 0.1 for mmol/L
+    @Binding var value: Double   // **stored in mg/dL**
+
+    // ── Private state ──────────────────────────────────────────────────────────
+    @State private var showPicker = false
+
+    // Preferred unit – mmol/L or mg/dL
+    private var unit: HKUnit { UserDefaultsRepository.getPreferredUnit() }
+
+    private var displayValue: String {
+        format(value, in: unit)
+    }
+
+    // Generate all selectable display values for the picker
+    private var pickerValues: [Double] {
+        stride(from: range.lowerBound, through: range.upperBound, by: step)
+            .map { $0 }
+    }
+
+    var body: some View {
+        Section {
+            // Collapsed row
+            HStack {
+                Text(title)
+                Spacer()
+                Text(displayValue)
+                    .foregroundColor(.secondary)
+            }
+            .contentShape(Rectangle())
+            .onTapGesture { showPicker = true }
+        }
+        .sheet(isPresented: $showPicker) {
+            // Expanded wheel picker
+            NavigationStack {
+                VStack {
+                    Picker("", selection: bindingForPicker) {
+                        ForEach(pickerValues, id: \.self) { v in
+                            Text(format(v, in: unit))
+                                .tag(v)
+                        }
+                    }
+                    .pickerStyle(.wheel)
+                }
+                .navigationTitle(title)
+                .toolbar {
+                    ToolbarItem(placement: .confirmationAction) {
+                        Button("Done") { showPicker = false }
+                    }
+                }
+            }   .presentationDetents([.fraction(0.35), .medium])   // 35 % or medium height
+                .presentationDragIndicator(.visible)               // the little grab-bar
+        }
+    }
+
+    // MARK: – Helpers
+    private func format(_ mgdl: Double, in unit: HKUnit) -> String {
+        if unit == .millimolesPerLiter {
+            let mmol = mgdl / 18.0
+            return String(format: "%.1f mmol/L", mmol)
+        } else {
+            return String(format: "%.0f mg/dL", mgdl)
+        }
+    }
+
+    // Bind the picker directly to `value`, snapping to the closest choice
+    private var bindingForPicker: Binding<Double> {
+        Binding(
+            get: {
+                // pick the closest representable value
+                pickerValues.min(by: { abs($0 - value) < abs($1 - value) }) ?? value
+            },
+            set: { newVal in
+                value = newVal    // stored in mg/dL
+            }
+        )
+    }
+}

+ 10 - 14
LoopFollow/Alarm/AlarmEditing/Editors/LowBgAlarmEditor.swift

@@ -13,23 +13,19 @@ struct LowBgAlarmEditor: View {
     @Binding var alarm: Alarm
 
     var body: some View {
-        Form {/*
-            AlarmNameField(alarm: $alarm)
-            EnabledToggle(alarm: $alarm)
-            ValueStepper(
-                title: "BG Below",
+        Form {
+            AlarmGeneralSection(alarm: $alarm)
+
+            AlarmThresholdRow(
+                title: "BG",
+                range: 40...150,
+                step: UserDefaultsRepository.getPreferredUnit() == .millimolesPerLiter ? 18.0 * 0.1 : 1.0,
                 value: Binding(
-                    get: { Double(alarm.threshold ?? 0) },
+                    get: { Double(alarm.threshold ?? 80) },
                     set: { alarm.threshold = Float($0) }
-                ),
-                range: 0...500, step: 1,
-                formatter: { "\(Int($0))" }
+                )
             )
-            DayNightToggle(alarm: $alarm)
-            SoundPicker(alarm: $alarm)
-            SnoozeDatePicker(alarm: $alarm)
-            SnoozeDurationStepper(alarm: $alarm)*/
         }
-        .navigationTitle("Low BG Alert")
+        .navigationTitle(alarm.type.rawValue)
     }
 }

+ 2 - 2
LoopFollow/Alarm/AlarmType.swift

@@ -14,8 +14,8 @@ enum AlarmType: String, CaseIterable, Codable {
     case iob = "IOB Alert"
     case bolus = "Bolus Alert"
     case cob = "COB Alert"
-    case low = "Low Alert"
-    case high = "High Alert"
+    case low = "Low BG Alert"
+    case high = "High BG Alert"
     case fastDrop = "Fast Drop Alert"
     case fastRise = "Fast Rise Alert"
     case missedReading = "Missed Reading Alert"

+ 2 - 2
LoopFollow/Task/AlarmTask.swift

@@ -22,10 +22,10 @@ extension MainViewController {
             let alarmData = AlarmData(
                 bgReadings: self.bgData
                     .suffix(5)
-                    .map { GlucoseValue(sgv: $0.sgv, date: Date(timeIntervalSince1970: $0.date)) },
+                    .map { GlucoseValue(sgv: $0.sgv, date: Date(timeIntervalSince1970: $0.date)) }, /// These are oldest .. newest
                 predictionData: self.predictionData
                     .prefix(5)
-                    .map { GlucoseValue(sgv: $0.sgv, date: Date(timeIntervalSince1970: $0.date)) },
+                    .map { GlucoseValue(sgv: $0.sgv, date: Date(timeIntervalSince1970: $0.date)) }, /// These are oldest .. newest, Predictions not currently available for Trio
                 expireDate: Storage.shared.expirationDate.value
             )