Przeglądaj źródła

Merge branch 'dnzxy:core-data-sync-trio' into APNsPumpNotifications

kskandis 1 rok temu
rodzic
commit
0e1d26fadc

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

@@ -10,6 +10,7 @@ extension BasalProfileEditor {
         var items: [Item] = []
         var total: Decimal = 0.0
         var showAlert: Bool = false
+        var chartData: [BasalProfile]? = []
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
@@ -131,5 +132,33 @@ extension BasalProfileEditor {
 
             return (0 ..< timeValues.count).filter { !usedIndicesByOtherItems.contains($0) }
         }
+
+        func caluclateChartData() {
+            DispatchQueue.main.async {
+                var basals: [BasalProfile] = []
+                let tzOffset = TimeZone.current.secondsFromGMT() * -1
+
+                basals.append(contentsOf: self.items.enumerated().map { index, item in
+                    let startDate = Date(timeIntervalSinceReferenceDate: self.timeValues[item.timeIndex])
+                    var endDate = Date(timeIntervalSinceReferenceDate: self.timeValues.last!).addingTimeInterval(30 * 60)
+                    if self.items.count > index + 1 {
+                        let nextItem = self.items[index + 1]
+                        endDate = Date(timeIntervalSinceReferenceDate: self.timeValues[nextItem.timeIndex])
+                    }
+
+                    return BasalProfile(
+                        amount: Double(self.rateValues[item.rateIndex]),
+                        isOverwritten: false,
+                        startDate: startDate.addingTimeInterval(TimeInterval(tzOffset)),
+                        endDate: endDate.addingTimeInterval(TimeInterval(tzOffset))
+                    )
+                })
+                basals.sort(by: {
+                    $0.startDate > $1.startDate
+                })
+
+                self.chartData = basals
+            }
+        }
     }
 }

+ 95 - 22
FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -1,3 +1,4 @@
+import Charts
 import SwiftUI
 import Swinject
 
@@ -7,6 +8,9 @@ extension BasalProfileEditor {
         @State var state = StateModel()
         @State private var editMode = EditMode.inactive
 
+        let chartScale = Calendar.current
+            .date(from: DateComponents(year: 2001, month: 01, day: 01, hour: 0, minute: 0, second: 0))
+
         @Environment(\.colorScheme) var colorScheme
         var color: LinearGradient {
             colorScheme == .dark ? LinearGradient(
@@ -38,11 +42,92 @@ extension BasalProfileEditor {
             return formatter
         }
 
-        var body: some View {
-            Form {
+        var basalScheduleChart: some View {
+            Chart {
+                ForEach(state.chartData!, id: \.self) { profile in
+                    RectangleMark(
+                        xStart: .value("start", profile.startDate),
+                        xEnd: .value("end", profile.endDate!),
+                        yStart: .value("rate-start", profile.amount),
+                        yEnd: .value("rate-end", 0)
+                    ).foregroundStyle(
+                        .linearGradient(
+                            colors: [
+                                Color.insulin.opacity(0.6),
+                                Color.insulin.opacity(0.1)
+                            ],
+                            startPoint: .bottom,
+                            endPoint: .top
+                        )
+                    ).alignsMarkStylesWithPlotArea()
+
+                    LineMark(x: .value("End Date", profile.endDate!), y: .value("Amount", profile.amount))
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+
+                    LineMark(x: .value("Start Date", profile.startDate), y: .value("Amount", profile.amount))
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                }
+            }
+            .chartXAxis {
+                AxisMarks(values: .automatic(desiredCount: 6)) { _ in
+                    AxisValueLabel(format: .dateTime.hour())
+                    AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1, dash: [2, 4]))
+                }
+            }
+            .chartYAxis {
+                AxisMarks(values: .automatic(desiredCount: 2)) { _ in
+                    AxisValueLabel()
+                    AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1, dash: [2, 4]))
+                }
+            }
+            .chartXScale(
+                domain: Calendar.current.startOfDay(for: chartScale!) ... Calendar.current.startOfDay(for: chartScale!)
+                    .addingTimeInterval(60 * 60 * 24)
+            )
+        }
+
+        var saveButton: some View {
+            ZStack {
                 let shouldDisableButton = state.syncInProgress || state.items.isEmpty || !state.hasChanges
 
+                Rectangle()
+                    .frame(width: UIScreen.main.bounds.width, height: 65)
+                    .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                    .background(.thinMaterial)
+                    .opacity(0.8)
+                    .clipShape(Rectangle())
+
+                Group {
+                    HStack {
+                        Button {
+                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                            impactHeavy.impactOccurred()
+                            state.save()
+                        } label: {
+                            HStack {
+                                if state.syncInProgress {
+                                    ProgressView().padding(.trailing, 10)
+                                }
+                                Text(state.syncInProgress ? "Saving..." : "Save")
+                            }.padding(10)
+                        }
+                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                        .disabled(shouldDisableButton)
+                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                        .tint(.white)
+                        .clipShape(RoundedRectangle(cornerRadius: 8))
+                    }
+                }.padding(5)
+            }
+        }
+
+        var body: some View {
+            Form {
                 Section(header: Text("Schedule")) {
+                    if !state.items.isEmpty {
+                        basalScheduleChart.padding(.vertical)
+                    }
+
                     list
                 }.listRowBackground(Color.chart)
 
@@ -58,25 +143,8 @@ extension BasalProfileEditor {
                             .foregroundColor(.secondary)
                     }
                 }.listRowBackground(Color.chart)
-
-                Section {
-                    HStack {
-                        if state.syncInProgress {
-                            ProgressView().padding(.trailing, 10)
-                        }
-                        Button {
-                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
-                            impactHeavy.impactOccurred()
-                            state.save()
-                        } label: {
-                            Text(state.syncInProgress ? "Saving..." : "Save")
-                        }
-                        .disabled(shouldDisableButton)
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .tint(.white)
-                    }
-                }.listRowBackground(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
             }
+            .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .alert(isPresented: $state.showAlert) {
                 Alert(
                     title: Text("Unable to Save"),
@@ -84,9 +152,11 @@ extension BasalProfileEditor {
                     dismissButton: .default(Text("Close"))
                 )
             }
-            .onChange(of: state.items) { state.calcTotal() }
+            .onChange(of: state.items) {
+                state.calcTotal()
+                state.caluclateChartData()
+            }
             .scrollContentBackground(.hidden).background(color)
-            .onAppear(perform: configureView)
             .navigationTitle("Basal Profile")
             .navigationBarTitleDisplayMode(.automatic)
             .toolbar(content: {
@@ -99,7 +169,9 @@ extension BasalProfileEditor {
             })
             .environment(\.editMode, $editMode)
             .onAppear {
+                configureView()
                 state.validate()
+                state.caluclateChartData()
             }
         }
 
@@ -183,6 +255,7 @@ extension BasalProfileEditor {
             state.items.remove(atOffsets: offsets)
             state.validate()
             state.calcTotal()
+            state.caluclateChartData()
         }
     }
 }

+ 43 - 26
FreeAPS/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift

@@ -38,10 +38,50 @@ extension CarbRatioEditor {
             return formatter
         }
 
-        var body: some View {
-            Form {
+        var saveButton: some View {
+            ZStack {
                 let shouldDisableButton = state.shouldDisplaySaving || state.items.isEmpty || !state.hasChanges
 
+                Rectangle()
+                    .frame(width: UIScreen.main.bounds.width, height: 65)
+                    .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                    .background(.thinMaterial)
+                    .opacity(0.8)
+                    .clipShape(Rectangle())
+
+                Group {
+                    HStack {
+                        HStack {
+                            Button {
+                                let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                                impactHeavy.impactOccurred()
+                                state.save()
+
+                                // deactivate saving display after 1.25 seconds
+                                DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
+                                    state.shouldDisplaySaving = false
+                                }
+                            } label: {
+                                HStack {
+                                    if state.shouldDisplaySaving {
+                                        ProgressView().padding(.trailing, 10)
+                                    }
+                                    Text(state.shouldDisplaySaving ? "Saving..." : "Save")
+                                }.padding(10)
+                            }
+                        }
+                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                        .disabled(shouldDisableButton)
+                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                        .tint(.white)
+                        .clipShape(RoundedRectangle(cornerRadius: 8))
+                    }
+                }.padding(5)
+            }
+        }
+
+        var body: some View {
+            Form {
                 if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
                     Section(header: Text("Autotune")) {
                         HStack {
@@ -56,31 +96,8 @@ extension CarbRatioEditor {
                 Section(header: Text("Schedule")) {
                     list
                 }.listRowBackground(Color.chart)
-
-                Section {
-                    HStack {
-                        if state.shouldDisplaySaving {
-                            ProgressView().padding(.trailing, 10)
-                        }
-
-                        Button {
-                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
-                            impactHeavy.impactOccurred()
-                            state.save()
-
-                            // deactivate saving display after 1.25 seconds
-                            DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
-                                state.shouldDisplaySaving = false
-                            }
-                        } label: {
-                            Text(state.shouldDisplaySaving ? "Saving..." : "Save")
-                        }
-                        .disabled(shouldDisableButton)
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .tint(.white)
-                    }
-                }.listRowBackground(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
             }
+            .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .scrollContentBackground(.hidden).background(color)
             .onAppear(perform: configureView)
             .navigationTitle("Carb Ratios")

+ 8 - 10
FreeAPS/Sources/Modules/Home/View/Chart/BasalChart.swift

@@ -74,17 +74,15 @@ extension MainChartView {
                     yStart: .value("rate-start", 0),
                     yEnd: .value("rate-end", basal.rate)
                 ).foregroundStyle(
-                    LinearGradient(
-                        gradient: Gradient(
-                            colors: [
-                                Color.insulin.opacity(0.6),
-                                Color.insulin.opacity(0.1)
-                            ]
-                        ),
-                        startPoint: .top,
-                        endPoint: .bottom
+                    .linearGradient(
+                        colors: [
+                            Color.insulin.opacity(0.6),
+                            Color.insulin.opacity(0.1)
+                        ],
+                        startPoint: .bottom,
+                        endPoint: .top
                     )
-                )
+                ).alignsMarkStylesWithPlotArea()
 
                 LineMark(x: .value("Start Date", basal.start), y: .value("Amount", basal.rate))
                     .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)

+ 42 - 26
FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -39,10 +39,49 @@ extension ISFEditor {
             return formatter
         }
 
-        var body: some View {
-            Form {
+        var saveButton: some View {
+            ZStack {
                 let shouldDisableButton = state.items.isEmpty || !state.hasChanges
 
+                Rectangle()
+                    .frame(width: UIScreen.main.bounds.width, height: 65)
+                    .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                    .background(.thinMaterial)
+                    .opacity(0.8)
+                    .clipShape(Rectangle())
+
+                Group {
+                    HStack {
+                        HStack {
+                            if state.shouldDisplaySaving {
+                                ProgressView().padding(.trailing, 10)
+                            }
+
+                            Button {
+                                let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                                impactHeavy.impactOccurred()
+                                state.save()
+
+                                // deactivate saving display after 1.25 seconds
+                                DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
+                                    state.shouldDisplaySaving = false
+                                }
+                            } label: {
+                                Text(state.shouldDisplaySaving ? "Saving..." : "Save").padding(10)
+                            }
+                        }
+                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                        .disabled(shouldDisableButton)
+                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                        .tint(.white)
+                        .clipShape(RoundedRectangle(cornerRadius: 8))
+                    }
+                }.padding(5)
+            }
+        }
+
+        var body: some View {
+            Form {
                 if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
                     Section(header: Text("Autotune")) {
                         HStack {
@@ -99,31 +138,8 @@ extension ISFEditor {
                 Section(header: Text("Schedule")) {
                     list
                 }.listRowBackground(Color.chart)
-
-                Section {
-                    HStack {
-                        if state.shouldDisplaySaving {
-                            ProgressView().padding(.trailing, 10)
-                        }
-
-                        Button {
-                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
-                            impactHeavy.impactOccurred()
-                            state.save()
-
-                            // deactivate saving display after 1.25 seconds
-                            DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
-                                state.shouldDisplaySaving = false
-                            }
-                        } label: {
-                            Text(state.shouldDisplaySaving ? "Saving..." : "Save")
-                        }
-                        .disabled(shouldDisableButton)
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .tint(.white)
-                    }
-                }.listRowBackground(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
             }
+            .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .scrollContentBackground(.hidden).background(color)
             .onAppear(perform: configureView)
             .navigationTitle("Insulin Sensitivities")

+ 38 - 21
FreeAPS/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift

@@ -32,38 +32,55 @@ extension TargetsEditor {
             return formatter
         }
 
-        var body: some View {
-            Form {
+        var saveButton: some View {
+            ZStack {
                 let shouldDisableButton = state.shouldDisplaySaving || state.items.isEmpty || !state.hasChanges
 
-                Section(header: Text("Schedule")) {
-                    list
-                }.listRowBackground(Color.chart)
+                Rectangle()
+                    .frame(width: UIScreen.main.bounds.width, height: 65)
+                    .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                    .background(.thinMaterial)
+                    .opacity(0.8)
+                    .clipShape(Rectangle())
 
-                Section {
+                Group {
                     HStack {
-                        if state.shouldDisplaySaving {
-                            ProgressView().padding(.trailing, 10)
-                        }
-
-                        Button {
-                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
-                            impactHeavy.impactOccurred()
-                            state.save()
+                        HStack {
+                            Button {
+                                let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                                impactHeavy.impactOccurred()
+                                state.save()
 
-                            // deactivate saving display after 1.25 seconds
-                            DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
-                                state.shouldDisplaySaving = false
+                                // deactivate saving display after 1.25 seconds
+                                DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
+                                    state.shouldDisplaySaving = false
+                                }
+                            } label: {
+                                HStack {
+                                    if state.shouldDisplaySaving {
+                                        ProgressView().padding(.trailing, 10)
+                                    }
+                                    Text(state.shouldDisplaySaving ? "Saving..." : "Save")
+                                }.padding(10)
                             }
-                        } label: {
-                            Text(state.shouldDisplaySaving ? "Saving..." : "Save")
                         }
+                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
                         .disabled(shouldDisableButton)
-                        .frame(maxWidth: .infinity, alignment: .center)
+                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
                         .tint(.white)
+                        .clipShape(RoundedRectangle(cornerRadius: 8))
                     }
-                }.listRowBackground(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                }.padding(5)
+            }
+        }
+
+        var body: some View {
+            Form {
+                Section(header: Text("Schedule")) {
+                    list
+                }.listRowBackground(Color.chart)
             }
+            .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .scrollContentBackground(.hidden).background(color)
             .onAppear(perform: configureView)
             .navigationTitle("Target Glucose")