Преглед изворни кода

Merge branch 'core-data-sync-trio' of https://github.com/kskandis/Open-iAPS into APNsPumpNotifications

kskandis пре 1 година
родитељ
комит
7a56c6ac1c

+ 2 - 2
FreeAPS/Sources/Models/DecimalPickerSettings.swift

@@ -33,8 +33,8 @@ class PickerSettingsProvider: ObservableObject {
 }
 }
 
 
 struct DecimalPickerSettings {
 struct DecimalPickerSettings {
-    var lowGlucose = PickerSetting(value: 72, step: 1, min: 40, max: 100, type: PickerSetting.PickerSettingType.glucose)
-    var highGlucose = PickerSetting(value: 270, step: 1, min: 100, max: 500, type: PickerSetting.PickerSettingType.glucose)
+    var lowGlucose = PickerSetting(value: 70, step: 5, min: 40, max: 100, type: PickerSetting.PickerSettingType.glucose)
+    var highGlucose = PickerSetting(value: 180, step: 5, min: 100, max: 400, type: PickerSetting.PickerSettingType.glucose)
     var carbsRequiredThreshold = PickerSetting(value: 10, step: 1, min: 0, max: 100, type: PickerSetting.PickerSettingType.gramms)
     var carbsRequiredThreshold = PickerSetting(value: 10, step: 1, min: 0, max: 100, type: PickerSetting.PickerSettingType.gramms)
     var individualAdjustmentFactor = PickerSetting(
     var individualAdjustmentFactor = PickerSetting(
         value: 0.5,
         value: 0.5,

+ 15 - 0
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -114,7 +114,22 @@ extension BasalProfileEditor {
                 if self.items != sorted {
                 if self.items != sorted {
                     self.items = sorted
                     self.items = sorted
                 }
                 }
+                self.calcTotal()
             }
             }
         }
         }
+
+        func availableTimeIndices(_ itemIndex: Int) -> [Int] {
+            // avoid index out of range issues
+            guard itemIndex >= 0 && itemIndex < items.count else {
+                return []
+            }
+            
+            let usedIndicesByOtherItems = items
+                .enumerated()
+                .filter { $0.offset != itemIndex }
+                .map { $0.element.timeIndex }
+            
+            return (0 ..< timeValues.count).filter { !usedIndicesByOtherItems.contains($0) }
+        }
     }
     }
 }
 }

+ 4 - 6
FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -84,9 +84,7 @@ extension BasalProfileEditor {
                     dismissButton: .default(Text("Close"))
                     dismissButton: .default(Text("Close"))
                 )
                 )
             }
             }
-            .onChange(of: state.items) { _ in
-                state.calcTotal()
-            }
+            .onChange(of: state.items) { state.calcTotal() }
             .scrollContentBackground(.hidden).background(color)
             .scrollContentBackground(.hidden).background(color)
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Basal Profile")
             .navigationTitle("Basal Profile")
@@ -118,12 +116,12 @@ extension BasalProfileEditor {
                             ).tag(i)
                             ).tag(i)
                         }
                         }
                     }
                     }
-                    .onChange(of: state.items[index].rateIndex, perform: { _ in state.calcTotal() })
+                    .onChange(of: state.items[index].rateIndex, { state.calcTotal() })
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
 
 
                 Section {
                 Section {
                     Picker(selection: $state.items[index].timeIndex, label: Text("Time")) {
                     Picker(selection: $state.items[index].timeIndex, label: Text("Time")) {
-                        ForEach(0 ..< state.timeValues.count, id: \.self) { i in
+                        ForEach(state.availableTimeIndices(index), id: \.self) { i in
                             Text(
                             Text(
                                 self.dateFormatter
                                 self.dateFormatter
                                     .string(from: Date(
                                     .string(from: Date(
@@ -133,7 +131,7 @@ extension BasalProfileEditor {
                             ).tag(i)
                             ).tag(i)
                         }
                         }
                     }
                     }
-                    .onChange(of: state.items[index].timeIndex, perform: { _ in state.calcTotal() })
+                    .onChange(of: state.items[index].timeIndex, { state.calcTotal() })
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
             }
             }
             .padding(.top)
             .padding(.top)

+ 29 - 13
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -241,9 +241,10 @@ extension Bolus {
             let now = Date()
             let now = Date()
             let calendar = Calendar.current
             let calendar = Calendar.current
             let dateFormatter = DateFormatter()
             let dateFormatter = DateFormatter()
-            dateFormatter.dateFormat = "HH:mm"
             dateFormatter.timeZone = TimeZone.current
             dateFormatter.timeZone = TimeZone.current
 
 
+            let regexWithSeconds = #"^\d{2}:\d{2}:\d{2}$"#
+
             let entries: [(start: String, value: Decimal)]
             let entries: [(start: String, value: Decimal)]
 
 
             switch type {
             switch type {
@@ -262,6 +263,13 @@ extension Bolus {
             }
             }
 
 
             for (index, entry) in entries.enumerated() {
             for (index, entry) in entries.enumerated() {
+                // Dynamically set the format based on whether it matches the regex
+                if entry.start.range(of: regexWithSeconds, options: .regularExpression) != nil {
+                    dateFormatter.dateFormat = "HH:mm:ss"
+                } else {
+                    dateFormatter.dateFormat = "HH:mm"
+                }
+
                 guard let entryTime = dateFormatter.date(from: entry.start) else {
                 guard let entryTime = dateFormatter.date(from: entry.start) else {
                     print("Invalid entry start time: \(entry.start)")
                     print("Invalid entry start time: \(entry.start)")
                     continue
                     continue
@@ -271,21 +279,30 @@ extension Bolus {
                 let entryStartTime = calendar.date(
                 let entryStartTime = calendar.date(
                     bySettingHour: entryComponents.hour!,
                     bySettingHour: entryComponents.hour!,
                     minute: entryComponents.minute!,
                     minute: entryComponents.minute!,
-                    second: entryComponents.second!,
+                    second: entryComponents.second ?? 0, // Set seconds to 0 if not provided
                     of: now
                     of: now
                 )!
                 )!
 
 
                 let entryEndTime: Date
                 let entryEndTime: Date
-                if index < entries.count - 1,
-                   let nextEntryTime = dateFormatter.date(from: entries[index + 1].start)
-                {
-                    let nextEntryComponents = calendar.dateComponents([.hour, .minute, .second], from: nextEntryTime)
-                    entryEndTime = calendar.date(
-                        bySettingHour: nextEntryComponents.hour!,
-                        minute: nextEntryComponents.minute!,
-                        second: nextEntryComponents.second!,
-                        of: now
-                    )!
+                if index < entries.count - 1 {
+                    // Dynamically set the format again for the next element
+                    if entries[index + 1].start.range(of: regexWithSeconds, options: .regularExpression) != nil {
+                        dateFormatter.dateFormat = "HH:mm:ss"
+                    } else {
+                        dateFormatter.dateFormat = "HH:mm"
+                    }
+
+                    if let nextEntryTime = dateFormatter.date(from: entries[index + 1].start) {
+                        let nextEntryComponents = calendar.dateComponents([.hour, .minute, .second], from: nextEntryTime)
+                        entryEndTime = calendar.date(
+                            bySettingHour: nextEntryComponents.hour!,
+                            minute: nextEntryComponents.minute!,
+                            second: nextEntryComponents.second ?? 0,
+                            of: now
+                        )!
+                    } else {
+                        entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
+                    }
                 } else {
                 } else {
                     entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
                     entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
                 }
                 }
@@ -359,7 +376,6 @@ extension Bolus {
             insulinCalculated = min(insulinCalculated, maxBolus)
             insulinCalculated = min(insulinCalculated, maxBolus)
 
 
             guard let apsManager = apsManager else {
             guard let apsManager = apsManager else {
-                debug(.apsManager, "APSManager could not be gracefully unwrapped")
                 return insulinCalculated
                 return insulinCalculated
             }
             }
 
 

+ 7 - 8
FreeAPS/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsStateModel.swift

@@ -30,20 +30,19 @@ extension GlucoseNotificationSettings {
             subscribeSetting(\.useAlarmSound, on: $useAlarmSound) { useAlarmSound = $0 }
             subscribeSetting(\.useAlarmSound, on: $useAlarmSound) { useAlarmSound = $0 }
             subscribeSetting(\.addSourceInfoToGlucoseNotifications, on: $addSourceInfoToGlucoseNotifications) {
             subscribeSetting(\.addSourceInfoToGlucoseNotifications, on: $addSourceInfoToGlucoseNotifications) {
                 addSourceInfoToGlucoseNotifications = $0 }
                 addSourceInfoToGlucoseNotifications = $0 }
+
             subscribeSetting(\.lowGlucose, on: $lowGlucose, initial: {
             subscribeSetting(\.lowGlucose, on: $lowGlucose, initial: {
-                let value = max(min($0, 400), 40)
-                lowGlucose = value
+                lowGlucose = $0
             }, map: {
             }, map: {
-                guard units == .mmolL else { return $0 }
-                return $0.asMgdL
+                let clampedValue = max(min($0, 400), 40)
+                return clampedValue
             })
             })
 
 
             subscribeSetting(\.highGlucose, on: $highGlucose, initial: {
             subscribeSetting(\.highGlucose, on: $highGlucose, initial: {
-                let value = max(min($0, 400), 40)
-                highGlucose = value
+                highGlucose = $0
             }, map: {
             }, map: {
-                guard units == .mmolL else { return $0 }
-                return $0.asMgdL
+                let clampedValue = max(min($0, 400), 40)
+                return clampedValue
             })
             })
         }
         }
     }
     }

+ 106 - 40
FreeAPS/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift

@@ -15,6 +15,8 @@ extension GlucoseNotificationSettings {
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
         @State private var booleanPlaceholder: Bool = false
         @State var notificationsDisabled = false
         @State var notificationsDisabled = false
+        @State private var displayPickerLowGlucose: Bool = false
+        @State private var displayPickerHighGlucose: Bool = false
 
 
         private var glucoseFormatter: NumberFormatter {
         private var glucoseFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -198,46 +200,7 @@ extension GlucoseNotificationSettings {
                     verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
                     verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
                 )
                 )
 
 
-                Section {
-                    HStack {
-                        Text("Low Glucose Alarm Limit")
-                        Spacer()
-                        TextFieldWithToolBar(text: $state.lowGlucose, placeholder: "0", numberFormatter: glucoseFormatter)
-                        Text(state.units.rawValue).foregroundColor(.secondary)
-                    }.padding(.top)
-
-                    HStack {
-                        Text("High Glucose Alarm Limit")
-                        Spacer()
-                        TextFieldWithToolBar(text: $state.highGlucose, placeholder: "0", numberFormatter: glucoseFormatter)
-                        Text(state.units.rawValue).foregroundColor(.secondary)
-                    }
-
-                    HStack(alignment: .top) {
-                        Text(
-                            "Set the lower and upper limit for glucose alarms. See hint for more details."
-                        )
-                        .font(.footnote)
-                        .foregroundColor(.secondary)
-                        .lineLimit(nil)
-                        Spacer()
-                        Button(
-                            action: {
-                                hintLabel = "Low and High Glucose Alarm Limits"
-                                selectedVerboseHint =
-                                    "These two settings limit the range outside of which you will be notified via push notifications. If your CGM readings are below 'Low' or above 'High', you will receive a glucose alarm."
-                                shouldDisplayHint.toggle()
-                            },
-                            label: {
-                                HStack {
-                                    Image(systemName: "questionmark.circle")
-                                }
-                            }
-                        ).buttonStyle(BorderlessButtonStyle())
-                    }.padding(.vertical)
-//                    }
-                        .listRowBackground(Color.chart)
-                }
+                self.lowAndHighGlucoseAlertSection
             }
             }
             .sheet(isPresented: $shouldDisplayHint) {
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                 SettingInputHintView(
@@ -262,5 +225,108 @@ extension GlucoseNotificationSettings {
             .navigationBarTitle("Trio Notifications")
             .navigationBarTitle("Trio Notifications")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
         }
         }
+
+        var lowAndHighGlucoseAlertSection: some View {
+            Section {
+                VStack {
+                    VStack {
+                        HStack {
+                            Text("Low Glucose Alarm Limit")
+
+                            Spacer()
+
+                            Group {
+                                Text(
+                                    state.units == .mgdL ? state.lowGlucose.description : state.lowGlucose.formattedAsMmolL
+                                )
+                                .foregroundColor(!displayPickerLowGlucose ? .primary : .accentColor)
+
+                                Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                            }
+                        }
+                        .onTapGesture {
+                            displayPickerLowGlucose.toggle()
+                        }
+                    }
+                    .padding(.top)
+
+                    if displayPickerLowGlucose {
+                        let setting = PickerSettingsProvider.shared.settings.lowGlucose
+
+                        Picker(selection: $state.lowGlucose, label: Text("")) {
+                            ForEach(
+                                PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
+                                id: \.self
+                            ) { value in
+                                let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
+                                Text(displayValue).tag(value)
+                            }
+                        }
+                        .pickerStyle(WheelPickerStyle())
+                        .frame(maxWidth: .infinity)
+                    }
+
+                    VStack {
+                        HStack {
+                            Text("High Glucose Alarm Limit")
+
+                            Spacer()
+
+                            Group {
+                                Text(
+                                    state.units == .mgdL ? state.highGlucose.description : state.highGlucose.formattedAsMmolL
+                                )
+                                .foregroundColor(!displayPickerHighGlucose ? .primary : .accentColor)
+
+                                Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                            }
+                        }
+                        .onTapGesture {
+                            displayPickerHighGlucose.toggle()
+                        }
+                    }
+                    .padding(.top)
+
+                    if displayPickerHighGlucose {
+                        let setting = PickerSettingsProvider.shared.settings.highGlucose
+                        Picker(selection: $state.highGlucose, label: Text("")) {
+                            ForEach(
+                                PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
+                                id: \.self
+                            ) { value in
+                                let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
+                                Text(displayValue).tag(value)
+                            }
+                        }
+                        .pickerStyle(WheelPickerStyle())
+                        .frame(maxWidth: .infinity)
+                    }
+
+                    HStack(alignment: .top) {
+                        Text(
+                            "Set the lower and upper limit for glucose alarms. See hint for more details."
+                        )
+                        .lineLimit(nil)
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+
+                        Spacer()
+                        Button(
+                            action: {
+                                hintLabel = "Low and High Glucose Alarm Limits"
+                                selectedVerboseHint =
+                                    "These two settings limit the range outside of which you will be notified via push notifications. If your CGM readings are below 'Low' or above 'High', you will receive an alarm via push notification."
+                                shouldDisplayHint.toggle()
+                            },
+                            label: {
+                                HStack {
+                                    Image(systemName: "questionmark.circle")
+                                }
+                            }
+                        ).buttonStyle(BorderlessButtonStyle())
+                    }.padding(.top)
+                }.padding(.bottom)
+            }.listRowBackground(Color.chart)
+        }
     }
     }
 }
 }

+ 14 - 24
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -43,37 +43,27 @@ struct MainChartView: View {
     }
     }
 
 
     private var selectedGlucose: GlucoseStored? {
     private var selectedGlucose: GlucoseStored? {
-        if let selection = selection {
-            let lowerBound = selection.addingTimeInterval(-150)
-            let upperBound = selection.addingTimeInterval(150)
-            return state.glucoseFromPersistence.first { $0.date ?? now >= lowerBound && $0.date ?? now <= upperBound }
-        } else {
-            return nil
+        guard let selection = selection else { return nil }
+        let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
+        return state.glucoseFromPersistence.first { $0.date.map(range.contains) ?? false }
+    }
+
+    private func findDetermination(in range: ClosedRange<Date>) -> OrefDetermination? {
+        state.enactedAndNonEnactedDeterminations.first {
+            $0.deliverAt ?? now >= range.lowerBound && $0.deliverAt ?? now <= range.upperBound
         }
         }
     }
     }
 
 
     var selectedCOBValue: OrefDetermination? {
     var selectedCOBValue: OrefDetermination? {
-        if let selection = selection {
-            let lowerBound = selection.addingTimeInterval(-120)
-            let upperBound = selection.addingTimeInterval(120)
-            return state.enactedAndNonEnactedDeterminations.first {
-                $0.deliverAt ?? now >= lowerBound && $0.deliverAt ?? now <= upperBound
-            }
-        } else {
-            return nil
-        }
+        guard let selection = selection else { return nil }
+        let range = selection.addingTimeInterval(-120) ... selection.addingTimeInterval(120)
+        return findDetermination(in: range)
     }
     }
 
 
     var selectedIOBValue: OrefDetermination? {
     var selectedIOBValue: OrefDetermination? {
-        if let selection = selection {
-            let lowerBound = selection.addingTimeInterval(-120)
-            let upperBound = selection.addingTimeInterval(120)
-            return state.enactedAndNonEnactedDeterminations.first {
-                $0.deliverAt ?? now >= lowerBound && $0.deliverAt ?? now <= upperBound
-            }
-        } else {
-            return nil
-        }
+        guard let selection = selection else { return nil }
+        let range = selection.addingTimeInterval(-120) ... selection.addingTimeInterval(120)
+        return findDetermination(in: range)
     }
     }
 
 
     var body: some View {
     var body: some View {

+ 2 - 2
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -258,8 +258,8 @@ final class BaseCalendarManager: CalendarManager, Injectable {
 
 
             let directionText = lastGlucoseObject.directionEnum?.symbol ?? "↔︎"
             let directionText = lastGlucoseObject.directionEnum?.symbol ?? "↔︎"
 
 
-            let deltaValue = settingsManager.settings.units == .mmolL ? Int(delta.asMmolL) : Int(delta)
-            let deltaText = deltaFormatter.string(from: NSNumber(value: deltaValue)) ?? "--"
+            let deltaValue = settingsManager.settings.units == .mmolL ? delta.asMmolL : delta
+            let deltaText = deltaFormatter.string(from: deltaValue as NSNumber) ?? "--"
 
 
             let iobText = iobFormatter.string(from: (determinationObject.iob ?? 0) as NSNumber) ?? ""
             let iobText = iobFormatter.string(from: (determinationObject.iob ?? 0) as NSNumber) ?? ""
             let cobText = cobFormatter.string(from: determinationObject.cob as NSNumber) ?? ""
             let cobText = cobFormatter.string(from: determinationObject.cob as NSNumber) ?? ""

+ 0 - 1
Model/CoreDataStack.swift

@@ -18,7 +18,6 @@ class CoreDataStack: ObservableObject {
             object: nil,
             object: nil,
             queue: nil
             queue: nil
         ) { _ in
         ) { _ in
-            debugPrint("Received a persistent store remote change notification")
             Task {
             Task {
                 await self.fetchPersistentHistory()
                 await self.fetchPersistentHistory()
             }
             }