Browse Source

refine therapy settings UI in app (isf)

Marvin Polscheit 7 months ago
parent
commit
16fa86cc43

+ 7 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -1337,6 +1337,7 @@
       }
       }
     },
     },
     " %@/U" : {
     " %@/U" : {
+      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -23143,6 +23144,7 @@
       }
       }
     },
     },
     "Add an entry by tapping 'Add Sensitivity +' in the top right-hand corner of the screen." : {
     "Add an entry by tapping 'Add Sensitivity +' in the top right-hand corner of the screen." : {
+      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -26601,6 +26603,7 @@
       }
       }
     },
     },
     "Add Sensitivity" : {
     "Add Sensitivity" : {
+      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -120510,6 +120513,7 @@
       }
       }
     },
     },
     "Insulin Sensitivities cover 24 hours. You cannot add more rates. Please remove or adjust existing rates to make space." : {
     "Insulin Sensitivities cover 24 hours. You cannot add more rates. Please remove or adjust existing rates to make space." : {
+      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -176625,6 +176629,7 @@
       }
       }
     },
     },
     "Schedule" : {
     "Schedule" : {
+      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -181964,6 +181969,7 @@
       }
       }
     },
     },
     "Set Rate" : {
     "Set Rate" : {
+      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -196386,6 +196392,7 @@
       }
       }
     },
     },
     "Swipe left to delete a single entry. Tap on it, to edit its time or rate." : {
     "Swipe left to delete a single entry. Tap on it, to edit its time or rate." : {
+      "extractionState" : "stale",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {

+ 20 - 0
Trio/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -19,6 +19,7 @@ extension ISFEditor {
 
 
         var items: [Item] = []
         var items: [Item] = []
         var initialItems: [Item] = []
         var initialItems: [Item] = []
+        var therapyItems: [TherapySettingItem] = []
         var shouldDisplaySaving: Bool = false
         var shouldDisplaySaving: Bool = false
 
 
         let context = CoreDataStack.shared.newTaskContext()
         let context = CoreDataStack.shared.newTaskContext()
@@ -42,6 +43,25 @@ extension ISFEditor {
 
 
         private(set) var units: GlucoseUnits = .mgdL
         private(set) var units: GlucoseUnits = .mgdL
 
 
+        // Convert items to TherapySettingItem format
+        func getTherapyItems() -> [TherapySettingItem] {
+            items.map { item in
+                TherapySettingItem(
+                    time: timeValues[item.timeIndex],
+                    value: rateValues[item.rateIndex]
+                )
+            }
+        }
+
+        // Update items from TherapySettingItem format
+        func updateFromTherapyItems(_ therapyItems: [TherapySettingItem]) {
+            items = therapyItems.map { therapyItem in
+                let timeIndex = timeValues.firstIndex(where: { abs($0 - therapyItem.time) < 1 }) ?? 0
+                let rateIndex = rateValues.firstIndex(of: therapyItem.value) ?? 0
+                return Item(rateIndex: rateIndex, timeIndex: timeIndex)
+            }
+        }
+
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
 
 

+ 79 - 133
Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -6,15 +6,17 @@ extension ISFEditor {
     struct RootView: BaseView {
     struct RootView: BaseView {
         let resolver: Resolver
         let resolver: Resolver
         @State var state = StateModel()
         @State var state = StateModel()
-        @State private var editMode = EditMode.inactive
+        @State private var refreshUI = UUID()
+        @State private var now = Date()
+        @Namespace private var bottomID
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
         @Environment(AppState.self) var appState
         @Environment(AppState.self) var appState
 
 
-        private var dateFormatter: DateFormatter {
-            let formatter = DateFormatter()
-            formatter.timeZone = TimeZone(secondsFromGMT: 0)
-            formatter.timeStyle = .short
+        private var numberFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = state.units == .mmolL ? 1 : 0
             return formatter
             return formatter
         }
         }
 
 
@@ -67,143 +69,92 @@ extension ISFEditor {
         }
         }
 
 
         var body: some View {
         var body: some View {
-            Form {
-                if !state.canAdd {
-                    Section {
-                        VStack(alignment: .leading) {
-                            Text(
-                                "Insulin Sensitivities cover 24 hours. You cannot add more rates. Please remove or adjust existing rates to make space."
-                            ).bold()
-                        }
-                    }.listRowBackground(Color.tabBar)
-                }
-
-                Section(header: Text("Schedule")) {
-                    list
-                }.listRowBackground(Color.chart)
+            ScrollViewReader { proxy in
+                VStack(spacing: 0) {
+                    ScrollView {
+                        LazyVStack {
+                            VStack(alignment: .leading, spacing: 0) {
+                                // Chart visualization
+                                if !state.items.isEmpty {
+                                    VStack(alignment: .leading) {
+                                        isfChart
+                                            .frame(height: 180)
+                                            .padding(.horizontal)
+                                    }
+                                    .padding(.vertical)
+                                    .background(Color.chart.opacity(0.65))
+                                    .clipShape(
+                                        .rect(
+                                            topLeadingRadius: 10,
+                                            bottomLeadingRadius: 0,
+                                            bottomTrailingRadius: 0,
+                                            topTrailingRadius: 10
+                                        )
+                                    )
+                                    .padding(.horizontal)
+                                    .padding(.top)
+                                }
 
 
-                Section {} header: {
-                    VStack(alignment: .leading, spacing: 10) {
-                        HStack {
-                            Image(systemName: "note.text.badge.plus").foregroundStyle(.primary)
-                            Text("Add an entry by tapping 'Add Sensitivity +' in the top right-hand corner of the screen.")
-                        }
-                        HStack {
-                            Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
-                            Text("Swipe left to delete a single entry. Tap on it, to edit its time or rate.")
+                                // ISF list
+                                TherapySettingEditorView(
+                                    items: $state.therapyItems,
+                                    unit: state.units == .mgdL ? .mgdLPerUnit : .mmolLPerUnit,
+                                    timeOptions: state.timeValues,
+                                    valueOptions: state.rateValues,
+                                    validateOnDelete: state.validate,
+                                    onItemAdded: {
+                                        withAnimation {
+                                            proxy.scrollTo(bottomID, anchor: .bottom)
+                                        }
+                                    }
+                                )
+                                .padding(.horizontal)
+                                .id(bottomID)
+                            }
                         }
                         }
                     }
                     }
-                    .textCase(nil)
+
+                    saveButton
                 }
                 }
-            }
-            .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
-            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
-            .onAppear(perform: configureView)
-            .navigationTitle("Insulin Sensitivities")
-            .navigationBarTitleDisplayMode(.automatic)
-            .toolbar(content: {
-                ToolbarItem(placement: .topBarTrailing) {
-                    Button(action: { state.add() }) {
-                        HStack {
-                            Text("Add Sensitivity")
-                            Image(systemName: "plus")
-                        }
-                    }.disabled(!state.canAdd)
+                .background(appState.trioBackgroundColor(for: colorScheme))
+                .onAppear(perform: configureView)
+                .navigationTitle("Insulin Sensitivities")
+                .navigationBarTitleDisplayMode(.automatic)
+                .onAppear {
+                    state.validate()
+                    state.therapyItems = state.getTherapyItems()
                 }
                 }
-            })
-            .environment(\.editMode, $editMode)
-            .onAppear {
-                state.validate()
-            }
-        }
-
-        private func pickers(for index: Int) -> some View {
-            Form {
-                Section {
-                    Picker(selection: $state.items[index].rateIndex, label: Text("Rate")) {
-                        ForEach(0 ..< state.rateValues.count, id: \.self) { i in
-                            Text(
-                                state.units == .mgdL ? state.rateValues[i].description : state.rateValues[i]
-                                    .formattedAsMmolL + String(localized: " \(state.units.rawValue)/U")
-                            ).tag(i)
-                        }
-                    }
-                }.listRowBackground(Color.chart)
-
-                Section {
-                    Picker(selection: $state.items[index].timeIndex, label: Text("Time")) {
-                        ForEach(0 ..< state.timeValues.count, id: \.self) { i in
-                            Text(
-                                self.dateFormatter
-                                    .string(from: Date(
-                                        timeIntervalSince1970: state
-                                            .timeValues[i]
-                                    ))
-                            ).tag(i)
-                        }
-                    }
-                }.listRowBackground(Color.chart)
-            }
-            .padding(.top)
-            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
-            .navigationTitle("Set Rate")
-            .navigationBarTitleDisplayMode(.automatic)
-        }
-
-        private var list: some View {
-            List {
-                chart.padding(.vertical)
-                ForEach(state.items.indexed(), id: \.1.id) { index, item in
-                    let displayValue = state.units == .mgdL ? state.rateValues[item.rateIndex].description : state
-                        .rateValues[item.rateIndex].formattedAsMmolL
-
-                    NavigationLink(destination: pickers(for: index)) {
-                        HStack {
-                            Text("Rate").foregroundColor(.secondary)
-
-                            Text(
-                                displayValue + String(localized: " \(state.units.rawValue)/U")
-                            )
-                            Spacer()
-                            Text("starts at").foregroundColor(.secondary)
-                            Text(
-                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: state.timeValues[item.timeIndex])))"
-                            )
-                        }
-                    }
-                    .moveDisabled(true)
+                .onChange(of: state.therapyItems) { _, newItems in
+                    state.updateFromTherapyItems(newItems)
+                    refreshUI = UUID()
                 }
                 }
-                .onDelete(perform: onDelete)
             }
             }
         }
         }
 
 
-        var now = Date()
-
-        var chart: some View {
+        // Chart for visualizing ISF profile
+        private var isfChart: some View {
             Chart {
             Chart {
-                ForEach(state.items.indexed(), id: \.1.id) { index, item in
-                    let displayValue = state.units == .mgdL ? state.rateValues[item.rateIndex].description : state
-                        .rateValues[item.rateIndex].formattedAsMmolL
-
-                    // Convert from string so we know we use the same math as the rest of Trio.
-                    // However, swift doesn't understand languages that use comma as decimal delminator
-                    let displayValueFloat = Double(displayValue.replacingOccurrences(of: ",", with: "."))
+                ForEach(Array(state.items.enumerated()), id: \.element.id) { index, item in
+                    let displayValue = state.rateValues[item.rateIndex]
 
 
                     let startDate = Calendar.current
                     let startDate = Calendar.current
                         .startOfDay(for: now)
                         .startOfDay(for: now)
                         .addingTimeInterval(state.timeValues[item.timeIndex])
                         .addingTimeInterval(state.timeValues[item.timeIndex])
 
 
-                    let endDate = state.items
-                        .count > index + 1 ?
-                        Calendar.current.startOfDay(for: now)
-                        .addingTimeInterval(state.timeValues[state.items[index + 1].timeIndex])
-                        :
-                        Calendar.current.startOfDay(for: now)
-                        .addingTimeInterval(state.timeValues.last! + 30 * 60)
+                    var offset: TimeInterval {
+                        if state.items.count > index + 1 {
+                            return state.timeValues[state.items[index + 1].timeIndex]
+                        } else {
+                            return state.timeValues.last! + 30 * 60
+                        }
+                    }
+
+                    let endDate = Calendar.current.startOfDay(for: now).addingTimeInterval(offset)
+
                     RectangleMark(
                     RectangleMark(
                         xStart: .value("start", startDate),
                         xStart: .value("start", startDate),
                         xEnd: .value("end", endDate),
                         xEnd: .value("end", endDate),
-                        yStart: .value("rate-start", displayValueFloat ?? 0),
+                        yStart: .value("rate-start", displayValue),
                         yEnd: .value("rate-end", 0)
                         yEnd: .value("rate-end", 0)
                     ).foregroundStyle(
                     ).foregroundStyle(
                         .linearGradient(
                         .linearGradient(
@@ -216,13 +167,14 @@ extension ISFEditor {
                         )
                         )
                     ).alignsMarkStylesWithPlotArea()
                     ).alignsMarkStylesWithPlotArea()
 
 
-                    LineMark(x: .value("End Date", startDate), y: .value("ISF", displayValueFloat ?? 0))
+                    LineMark(x: .value("End Date", startDate), y: .value("ISF", displayValue))
                         .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
                         .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
 
 
-                    LineMark(x: .value("Start Date", endDate), y: .value("ISF", displayValueFloat ?? 0))
+                    LineMark(x: .value("Start Date", endDate), y: .value("ISF", displayValue))
                         .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
                         .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
                 }
                 }
             }
             }
+            .id(refreshUI) // Force chart update
             .chartXAxis {
             .chartXAxis {
                 AxisMarks(values: .automatic(desiredCount: 6)) { _ in
                 AxisMarks(values: .automatic(desiredCount: 6)) { _ in
                     AxisValueLabel(format: .dateTime.hour())
                     AxisValueLabel(format: .dateTime.hour())
@@ -230,8 +182,7 @@ extension ISFEditor {
                 }
                 }
             }
             }
             .chartXScale(
             .chartXScale(
-                domain: Calendar.current.startOfDay(for: now) ... Calendar
-                    .current.startOfDay(for: now)
+                domain: Calendar.current.startOfDay(for: now) ... Calendar.current.startOfDay(for: now)
                     .addingTimeInterval(60 * 60 * 24)
                     .addingTimeInterval(60 * 60 * 24)
             )
             )
             .chartYAxis {
             .chartYAxis {
@@ -241,10 +192,5 @@ extension ISFEditor {
                 }
                 }
             }
             }
         }
         }
-
-        private func onDelete(offsets: IndexSet) {
-            state.items.remove(atOffsets: offsets)
-            state.validate()
-        }
     }
     }
 }
 }