ソースを参照

Changed picker style

Jonas Björkert 1 年間 前
コミット
ebd2d68a88

+ 4 - 0
LoopFollow.xcodeproj/project.pbxproj

@@ -18,6 +18,7 @@
 		DD0650F32DCE9B3D004D3B41 /* MissedReadingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650F22DCE9B3D004D3B41 /* MissedReadingEditor.swift */; };
 		DD0650F52DCF303F004D3B41 /* AlarmStepperSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650F42DCF303F004D3B41 /* AlarmStepperSection.swift */; };
 		DD0650F72DCFDA26004D3B41 /* InfoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650F62DCFDA26004D3B41 /* InfoBanner.swift */; };
+		DD0650F92DCFE7BE004D3B41 /* FastDropAlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650F82DCFE7BE004D3B41 /* FastDropAlarmEditor.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 */; };
@@ -337,6 +338,7 @@
 		DD0650F22DCE9B3D004D3B41 /* MissedReadingEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissedReadingEditor.swift; sourceTree = "<group>"; };
 		DD0650F42DCF303F004D3B41 /* AlarmStepperSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmStepperSection.swift; sourceTree = "<group>"; };
 		DD0650F62DCFDA26004D3B41 /* InfoBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBanner.swift; sourceTree = "<group>"; };
+		DD0650F82DCFE7BE004D3B41 /* FastDropAlarmEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastDropAlarmEditor.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>"; };
@@ -929,6 +931,7 @@
 				DDC7E53C2DBD8A1600EB1127 /* BuildExpireAlarmEditor.swift */,
 				DDC7E53E2DBD8A1600EB1127 /* LowBgAlarmEditor.swift */,
 				DD0650F22DCE9B3D004D3B41 /* MissedReadingEditor.swift */,
+				DD0650F82DCFE7BE004D3B41 /* FastDropAlarmEditor.swift */,
 			);
 			path = Editors;
 			sourceTree = "<group>";
@@ -1665,6 +1668,7 @@
 				FC16A97D24996747003D6245 /* SpeakBG.swift in Sources */,
 				DDFD5C532CB167DA00D3FD68 /* TRCCommandType.swift in Sources */,
 				DD16AF112C997B4600FB655A /* LoadingButtonView.swift in Sources */,
+				DD0650F92DCFE7BE004D3B41 /* FastDropAlarmEditor.swift in Sources */,
 				DD5817172D2710E90041FB98 /* BLEDeviceSelectionView.swift in Sources */,
 				FC16A97B249966A3003D6245 /* AlarmSound.swift in Sources */,
 				DDBE3ABD2CB5A961006B37DC /* OverrideView.swift in Sources */,

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

@@ -22,6 +22,8 @@ struct AlarmEditor: View {
             LowBgAlarmEditor(alarm: $alarm)
         case .missedReading:
             MissedReadingEditor(alarm: $alarm)
+        case .fastDrop:
+            FastDropAlarmEditor(alarm: $alarm)
 
             // TODO: add other condition types here
 

+ 16 - 65
LoopFollow/Alarm/AlarmEditing/Components/AlarmBGSection.swift

@@ -10,40 +10,21 @@ import SwiftUI
 import HealthKit
 
 struct AlarmBGSection: View {
-    let header: String?
-    let footer: String?
-    let title: String
-    let range: ClosedRange<Double>
+    let header:  String?
+    let footer:  String?
+    let title:   String
+    let range:   ClosedRange<Double>
     @Binding var value: Double
 
-    @State private var showPicker = false
-
-    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 displayValue: String {
-        let formatted = Localizer.formatQuantity(value)
-        return "\(formatted) \(unit.localizedShortUnitString)"
+        "\(Localizer.formatQuantity(value)) \(unit.localizedShortUnitString)"
     }
 
-    private var pickerValues: [Double] {
-        let step : Double = unit == .millimolesPerLiter ? 18.0 * 0.1 : 1.0
-
-        return stride(from: range.lowerBound, through: range.upperBound, by: step)
-            .map { $0 }
+    private var allValues: [Double] {
+        let step = unit == .millimolesPerLiter ? 18.0 * 0.1 : 1
+        return Array(stride(from: range.lowerBound, through: range.upperBound, by: step))
     }
 
     var body: some View {
@@ -51,47 +32,17 @@ struct AlarmBGSection: View {
             header: header.map(Text.init),
             footer: footer.map(Text.init)
         ) {
-            HStack {
-                Text(title)
-                Spacer()
-                Text(displayValue)
-                    .foregroundColor(.secondary)
-            }
-            .contentShape(Rectangle())
-            .onTapGesture { showPicker = true }
-        }
-        .sheet(isPresented: $showPicker) {
-            NavigationStack {
-                VStack {
-                    Picker("", selection: bindingForPicker) {
-                        ForEach(pickerValues, id: \.self) { v in
-                            Text("\(Localizer.formatQuantity(v)) \(unit.localizedShortUnitString)")
-                                .tag(v)
-                        }
-                    }
-                    .pickerStyle(.wheel)
+            Picker(
+                selection: $value,
+                label: HStack {
+                    Text(title)
                 }
-                .navigationTitle(title)
-                .toolbar {
-                    ToolbarItem(placement: .confirmationAction) {
-                        Button("Done") { showPicker = false }
-                    }
+            ) {
+                ForEach(allValues, id: \.self) { v in
+                    Text("\(Localizer.formatQuantity(v)) \(unit.localizedShortUnitString)")
+                        .tag(v)
                 }
             }
-            .presentationDetents([.fraction(0.35), .medium])
-            .presentationDragIndicator(.visible)
         }
     }
-
-    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
-            }
-        )
-    }
 }

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

@@ -102,17 +102,17 @@ struct AlarmAudioSection: View {
             }
 
             // ——— Play / Repeat Toggles ———
-            VStack(alignment: .leading, spacing: 8) {
+            HStack() {
                 Text("Play")
                     .font(.subheadline)
                     .foregroundColor(.secondary)
-
+                Spacer()
                 Picker("", selection: $alarm.playSoundOption) {
                     ForEach(PlaySoundOption.allCases, id: \.self) { opt in
                         Text(opt.rawValue.capitalized).tag(opt)
                     }
                 }
-                .pickerStyle(.segmented)
+                .pickerStyle(.menu)
             }
 
             VStack(alignment: .leading, spacing: 8) {
@@ -125,7 +125,7 @@ struct AlarmAudioSection: View {
                         Text(opt.rawValue.capitalized).tag(opt)
                     }
                 }
-                .pickerStyle(.segmented)
+                .pickerStyle(.menu)
             }
         }
     }

+ 3 - 8
LoopFollow/Alarm/AlarmEditing/Components/AlarmStepperSection.swift

@@ -14,7 +14,7 @@ struct AlarmStepperSection: View {
     let title: String
     let range: ClosedRange<Double>
     let step: Double
-    let unitLabel: String
+    let unitLabel: String?
     @Binding var value: Double
 
     init(
@@ -23,7 +23,7 @@ struct AlarmStepperSection: View {
         title: String,
         range: ClosedRange<Double>,
         step: Double,
-        unitLabel: String,
+        unitLabel: String? = nil,
         value: Binding<Double>
     ) {
         self.header = header
@@ -35,18 +35,13 @@ struct AlarmStepperSection: View {
         self._value = value
     }
 
-/**
- header: Text(title),
- footer: Text("Set \(title), \(Int(range.lowerBound))–\(Int(range.upperBound)) \(unitLabel)")
- **/
-
     var body: some View {
         Section(
             header: header.map(Text.init),
             footer: footer.map(Text.init)
         ) {
             Stepper(
-                "\(title): \(Int(value)) \(unitLabel)",
+                "\(title): \(Int(value))\(unitLabel.map { " \($0)" } ?? "")",
                 value: $value,
                 in: range,
                 step: step

+ 92 - 0
LoopFollow/Alarm/AlarmEditing/Editors/FastDropAlarmEditor.swift

@@ -0,0 +1,92 @@
+//
+//  FastDropAlarmEditor.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2025-05-10.
+//  Copyright © 2025 Jon Fawcett. All rights reserved.
+//
+
+import SwiftUI
+
+struct FastDropAlarmEditor: View {
+    @Binding var alarm: Alarm
+
+    @State private var useLimit: Bool = false
+
+    var body: some View {
+        Form {
+            InfoBanner(
+                text: "Alerts when glucose readings drop rapidly. For example, three straight readings each falling by at least the amount you set. Optionally limit alerts to only fire below a certain BG level."
+            )
+            AlarmGeneralSection(alarm: $alarm)
+
+            AlarmBGSection(
+                header: "Rate of Fall",
+                footer: "How much the bg must fall to count as a “fast” drop.",
+                title: "Drop per reading",
+                range: 3...20,
+                value: Binding(
+                    get: { alarm.threshold ?? 4 },
+                    set: { alarm.threshold = $0 }
+                )
+            )
+
+            //TODO: In the migration script, use 1 value less than stored since we are switching from readings to drops
+            AlarmStepperSection(
+                header: "Consecutive Drops",
+                footer: "Number of back-to-back drops—each meeting the rate above—required before an alert fires.",
+                title: "Drops in a row",
+                range: 1...3,
+                step: 1,
+                value: Binding(
+                    get: { Double(alarm.monitoringWindow ?? 2) },
+                    set: { alarm.monitoringWindow = Int($0) }
+                )
+            )
+/*
+            // ────────── BG LIMIT ───────────
+            Section {
+                Toggle("Only alert when below BG limit", isOn: $useLimit)
+                    .onAppear {
+                        useLimit = (alarm.threshold != nil)
+                    }
+                    .onChange(of: useLimit) { newValue in
+                        if !newValue { alarm.threshold = nil }
+                    }
+
+                AlarmBGSection(
+                    header: nil,
+                    footer: "Ignored unless the toggle above is enabled.",
+                    title: "Dropping below",
+                    range: 40...300,
+                    value: Binding(
+                        get: { alarm.threshold ?? 70 },
+                        set: { alarm.threshold = $0 }
+                    )
+                )
+                .disabled(!useLimit)
+                .opacity(useLimit ? 1 : 0.35)
+            }   // Section
+*/
+            // ────────── SNOOZE  ────────────
+            AlarmStepperSection(
+                header: "Default Snooze",
+                footer: "How long to silence this alert after you press Snooze.",
+                title: "Default Snooze",
+                range: 5...60,
+                step: 5,
+                unitLabel: alarm.type.timeUnit.label,
+                value: Binding(
+                    get: { Double(alarm.snoozeDuration) },
+                    set: { alarm.snoozeDuration = Int($0) }
+                )
+            )
+
+            // ────── SOUND / ACTIVE / UNTIL ──────
+            AlarmAudioSection(alarm: $alarm)
+            AlarmActiveSection(alarm: $alarm)
+            AlarmSnoozedUntilSection(alarm: $alarm)
+        }
+        .navigationTitle(alarm.type.rawValue)
+    }
+}

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

@@ -22,6 +22,7 @@ struct LowBgAlarmEditor: View {
 
             AlarmBGSection(
                 header: "Threshold",
+                footer: "bla bla",
                 title: "BG",
                 range: 40...150,
                 value: Binding(