Ivan Valkou 5 лет назад
Родитель
Сommit
3aa4acec68

+ 1 - 0
FreeAPS/Resources/json/defaults/freeaps/temptargets_presets.json

@@ -0,0 +1 @@
+[]

+ 1 - 0
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -77,5 +77,6 @@ extension OpenAPS {
         static let settings = "freeaps/freeaps_settings.json"
         static let announcements = "freeaps/announcements.json"
         static let announcementsEnacted = "freeaps/announcements_enacted.json"
+        static let tempTargetsPresets = "freeaps/temptargets_presets.json"
     }
 }

+ 20 - 2
FreeAPS/Sources/APS/Storage/TempTargetsStorage.swift

@@ -11,6 +11,8 @@ protocol TempTargetsStorage {
     func syncDate() -> Date
     func recent() -> [TempTarget]
     func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment]
+    func storePresets(_ targets: [TempTarget])
+    func presets() -> [TempTarget]
 }
 
 final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
@@ -23,13 +25,20 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func storeTempTargets(_ targets: [TempTarget]) {
+        storeTempTargets(targets, isPresets: false)
+    }
+
+    private func storeTempTargets(_ targets: [TempTarget], isPresets: Bool) {
         processQueue.sync {
-            let file = OpenAPS.Settings.tempTargets
+            let file = isPresets ? OpenAPS.FreeAPS.tempTargetsPresets : OpenAPS.Settings.tempTargets
             var uniqEvents: [TempTarget] = []
             try? self.storage.transaction { storage in
                 try storage.append(targets, to: file, uniqBy: \.createdAt)
                 uniqEvents = try storage.retrieve(file, as: [TempTarget].self)
-                    .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
+                    .filter {
+                        guard !isPresets else { return true }
+                        return $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date()
+                    }
                     .sorted { $0.createdAt > $1.createdAt }
                 try storage.save(Array(uniqEvents), as: file)
             }
@@ -76,4 +85,13 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         }
         return Array(Set(treatments).subtracting(Set(uploaded)))
     }
+
+    func storePresets(_ targets: [TempTarget]) {
+        try? storage.remove(OpenAPS.FreeAPS.tempTargetsPresets)
+        storeTempTargets(targets, isPresets: true)
+    }
+
+    func presets() -> [TempTarget] {
+        (try? storage.retrieve(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self))?.reversed() ?? []
+    }
 }

+ 26 - 0
FreeAPS/Sources/Models/BloodGlucose.swift

@@ -34,3 +34,29 @@ enum GlucoseUnits: String, JSON {
 
     static let exchangeRate: Decimal = 0.0555
 }
+
+extension Int {
+    var asMmolL: Decimal {
+        Decimal(self) * GlucoseUnits.exchangeRate
+    }
+}
+
+extension Decimal {
+    var asMmolL: Decimal {
+        self * GlucoseUnits.exchangeRate
+    }
+
+    var asMgdL: Decimal {
+        self / GlucoseUnits.exchangeRate
+    }
+}
+
+extension Double {
+    var asMmolL: Decimal {
+        Decimal(self) * GlucoseUnits.exchangeRate
+    }
+
+    var asMgdL: Decimal {
+        Decimal(self) / GlucoseUnits.exchangeRate
+    }
+}

+ 6 - 2
FreeAPS/Sources/Models/TempTarget.swift

@@ -1,19 +1,23 @@
 import Foundation
 
-struct TempTarget: JSON {
+struct TempTarget: JSON, Identifiable {
     var id = UUID().uuidString
-    let createdAt: Date
+    let name: String
+    var createdAt: Date
     let targetTop: Decimal
     let targetBottom: Decimal
     let duration: Decimal
     let enteredBy: String?
 
     static let manual = "freeaps-x://manual"
+    static let custom = "Custom"
+    static let cancel = "Cancel"
 }
 
 extension TempTarget {
     private enum CodingKeys: String, CodingKey {
         case id = "_id"
+        case name
         case createdAt = "created_at"
         case targetTop
         case targetBottom

+ 60 - 4
FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetViewModel.swift

@@ -9,24 +9,29 @@ extension AddTempTarget {
         @Published var high: Decimal = 0
         @Published var duration: Decimal = 0
         @Published var date = Date()
+        @Published var newPresetName = ""
+        @Published var presets: [TempTarget] = []
 
         private(set) var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
             units = settingsManager.settings.units
+            presets = storage.presets()
         }
 
-        func add() {
+        func enact() {
             var lowTarget = low
             var highTarget = high
 
+            highTarget = max(highTarget, lowTarget)
+
             if units == .mmolL {
-                lowTarget = Decimal(Int(lowTarget / GlucoseUnits.exchangeRate))
-                highTarget = Decimal(Int(highTarget / GlucoseUnits.exchangeRate))
+                lowTarget = lowTarget.asMgdL
+                highTarget = highTarget.asMgdL
             }
 
-            highTarget = max(highTarget, lowTarget)
             let entry = TempTarget(
+                name: TempTarget.custom,
                 createdAt: date,
                 targetTop: highTarget,
                 targetBottom: lowTarget,
@@ -37,5 +42,56 @@ extension AddTempTarget {
 
             showModal(for: nil)
         }
+
+        func cancel() {
+            let entry = TempTarget(
+                name: TempTarget.cancel,
+                createdAt: Date(),
+                targetTop: 0,
+                targetBottom: 0,
+                duration: 0,
+                enteredBy: TempTarget.manual
+            )
+            storage.storeTempTargets([entry])
+
+            showModal(for: nil)
+        }
+
+        func save() {
+            var lowTarget = low
+            var highTarget = high
+
+            highTarget = max(highTarget, lowTarget)
+
+            if units == .mmolL {
+                lowTarget = lowTarget.asMgdL
+                highTarget = highTarget.asMgdL
+            }
+
+            let entry = TempTarget(
+                name: newPresetName.isEmpty ? TempTarget.custom : newPresetName,
+                createdAt: Date(),
+                targetTop: highTarget,
+                targetBottom: lowTarget,
+                duration: duration,
+                enteredBy: TempTarget.manual
+            )
+
+            presets.append(entry)
+            storage.storePresets(presets)
+        }
+
+        func enactPreset(id: String) {
+            if var preset = presets.first(where: { $0.id == id }) {
+                preset.createdAt = Date()
+                storage.storeTempTargets([preset])
+                showModal(for: nil)
+            }
+        }
+
+        func removePreset(id: String) {
+            presets = presets.filter { $0.id != id }
+            storage.storePresets(presets)
+        }
     }
 }

+ 87 - 5
FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift

@@ -3,20 +3,32 @@ import SwiftUI
 extension AddTempTarget {
     struct RootView: BaseView {
         @EnvironmentObject var viewModel: ViewModel<Provider>
+        @State private var isPromtPresented = false
+        @State private var isRemoveAlertPresented = false
+        @State private var removeAlert: Alert?
 
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 1
             return formatter
         }
 
         var body: some View {
             Form {
-                Section {
+                if !viewModel.presets.isEmpty {
+                    Section(header: Text("Presets")) {
+                        ForEach(viewModel.presets) { preset in
+                            presetView(for: preset)
+                        }
+                    }
+                }
+
+                Section(header: Text("Custom")) {
                     HStack {
                         Text("Bottom target")
                         Spacer()
-                        DecimalTextField("0", value: $viewModel.low, formatter: formatter, autofocus: true, cleanInput: true)
+                        DecimalTextField("0", value: $viewModel.low, formatter: formatter, cleanInput: true)
                         Text(viewModel.units.rawValue).foregroundColor(.secondary)
                     }
                     HStack {
@@ -32,16 +44,86 @@ extension AddTempTarget {
                         Text("minutes").foregroundColor(.secondary)
                     }
                     DatePicker("Date", selection: $viewModel.date)
+                    Button { isPromtPresented = true }
+                    label: { Text("Save as preset") }
                 }
 
                 Section {
-                    Button { viewModel.add() }
-                    label: { Text("Continue") }
+                    Button { viewModel.enact() }
+                    label: { Text("Enact") }
+                    Button { viewModel.cancel() }
+                    label: { Text("Cancel Temp Target") }
                 }
             }
-            .navigationTitle("Add Temp Target")
+            .popover(isPresented: $isPromtPresented) {
+                Form {
+                    Section(header: Text("Enter preset name")) {
+                        TextField("Name", text: $viewModel.newPresetName)
+                        Button {
+                            viewModel.save()
+                            isPromtPresented = false
+                        }
+                        label: { Text("Save") }
+                        Button { isPromtPresented = false }
+                        label: { Text("Cancel") }
+                    }
+                }
+            }
+            .navigationTitle("Enact Temp Target")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
         }
+
+        private func presetView(for preset: TempTarget) -> some View {
+            var low = preset.targetBottom
+            var high = preset.targetTop
+            if viewModel.units == .mmolL {
+                low = low.asMmolL
+                high = high.asMmolL
+            }
+            return HStack {
+                VStack {
+                    HStack {
+                        Text(preset.name)
+                        Spacer()
+                    }
+                    HStack {
+                        Text(
+                            "\(formatter.string(from: low as NSNumber)!) - \(formatter.string(from: high as NSNumber)!)"
+                        )
+                        .foregroundColor(.secondary)
+                        .font(.caption)
+
+                        Text(viewModel.units.rawValue)
+                            .foregroundColor(.secondary)
+                            .font(.caption)
+                        Text("for \(formatter.string(from: preset.duration as NSNumber)!) min")
+                            .foregroundColor(.secondary)
+                            .font(.caption)
+                        Spacer()
+                    }.padding(.top, 2)
+                }
+                .contentShape(Rectangle())
+                .onTapGesture {
+                    viewModel.enactPreset(id: preset.id)
+                }
+
+                Image(systemName: "xmark.circle").foregroundColor(.secondary)
+                    .contentShape(Rectangle())
+                    .padding(.vertical)
+                    .onTapGesture {
+                        removeAlert = Alert(
+                            title: Text("A you sure?"),
+                            message: Text("Delete preset \"\(preset.name)\""),
+                            primaryButton: .destructive(Text("Delete"), action: { viewModel.removePreset(id: preset.id) }),
+                            secondaryButton: .cancel()
+                        )
+                        isRemoveAlertPresented = true
+                    }
+                    .alert(isPresented: $isRemoveAlertPresented) {
+                        removeAlert!
+                    }
+            }
+        }
     }
 }