polscm32 1 rok temu
rodzic
commit
da4d777114

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -247,6 +247,7 @@
 		581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581516A82BCEEDF800BF67D7 /* NSPredicates.swift */; };
 		581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581AC4382BE22ED10038760C /* JSONConverter.swift */; };
 		58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58237D9D2BCF0A6B00A47A79 /* PopupView.swift */; };
+		5825A1BE2C97335C0046467E /* EditTempTargetForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */; };
 		582DF9752C8CDB92001F516D /* GlucoseChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9742C8CDB92001F516D /* GlucoseChartView.swift */; };
 		582DF9772C8CDBE7001F516D /* InsulinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9762C8CDBE7001F516D /* InsulinView.swift */; };
 		582DF9792C8CE1E5001F516D /* MainChartHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9782C8CE1E5001F516D /* MainChartHelper.swift */; };
@@ -900,6 +901,7 @@
 		581516A82BCEEDF800BF67D7 /* NSPredicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPredicates.swift; sourceTree = "<group>"; };
 		581AC4382BE22ED10038760C /* JSONConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONConverter.swift; sourceTree = "<group>"; };
 		58237D9D2BCF0A6B00A47A79 /* PopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupView.swift; sourceTree = "<group>"; };
+		5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditTempTargetForm.swift; sourceTree = "<group>"; };
 		582DF9742C8CDB92001F516D /* GlucoseChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseChartView.swift; sourceTree = "<group>"; };
 		582DF9762C8CDBE7001F516D /* InsulinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinView.swift; sourceTree = "<group>"; };
 		582DF9782C8CE1E5001F516D /* MainChartHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartHelper.swift; sourceTree = "<group>"; };
@@ -2677,6 +2679,7 @@
 				DDD163192C4C695E00CD525A /* EditOverrideForm.swift */,
 				DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */,
 				58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */,
+				5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -3153,6 +3156,7 @@
 				38E44537274E411700EC9A94 /* Disk+Helpers.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
+				5825A1BE2C97335C0046467E /* EditTempTargetForm.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,

+ 36 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -356,6 +356,22 @@ extension Home {
             }
         }
 
+        @MainActor func cancelTempTarget(withID id: NSManagedObjectID) async {
+            do {
+                let profileToCancel = try viewContext.existingObject(with: id) as? TempTargetStored
+                profileToCancel?.enabled = false
+
+                await saveToTempTargetRunStored(withID: id)
+
+                guard viewContext.hasChanges else { return }
+                try viewContext.save()
+
+                Foundation.NotificationCenter.default.post(name: .didUpdateTempTargetConfiguration, object: nil)
+            } catch {
+                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel Profile")
+            }
+        }
+
         func calculateTINS() -> String {
             let startTime = calculateStartTime(hours: Int(hours))
 
@@ -916,6 +932,26 @@ extension Home.StateModel {
         overrideRunStored = objects
     }
 
+    @MainActor func saveToTempTargetRunStored(withID id: NSManagedObjectID) async {
+        await viewContext.perform {
+            do {
+                guard let object = try self.viewContext.existingObject(with: id) as? TempTargetStored else { return }
+
+                let newTempTargetRunStored = TempTargetRunStored(context: self.viewContext)
+                newTempTargetRunStored.id = UUID()
+                newTempTargetRunStored.name = object.name
+                newTempTargetRunStored.startDate = object.date ?? .distantPast
+                newTempTargetRunStored.endDate = Date()
+                newTempTargetRunStored.target = object.target ?? 0
+                newTempTargetRunStored.tempTarget = object
+                newTempTargetRunStored.isUploadedToNS = false
+
+            } catch {
+                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to initialize a new Override Run Object")
+            }
+        }
+    }
+
     @MainActor func saveToOverrideRunStored(withID id: NSManagedObjectID) async {
         await viewContext.perform {
             do {

+ 20 - 11
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -11,6 +11,7 @@ extension Home {
         @StateObject var state = StateModel()
         @State var isStatusPopupPresented = false
         @State var showCancelAlert = false
+        @State var showTempTargetCancelAlert = false
         @State var isMenuPresented = false
         @State var showTreatments = false
         @State var selectedTab: Int = 0
@@ -50,16 +51,6 @@ extension Home {
             fetchLimit: 1
         )) var latestTempTarget: FetchedResults<TempTargetStored>
 
-//        @FetchRequest(
-//            entity: TempTargets.entity(),
-//            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
-//        ) var sliderTTpresets: FetchedResults<TempTargets>
-//
-//        @FetchRequest(
-//            entity: TempTargetsSlider.entity(),
-//            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
-//        ) var enactedSliderTT: FetchedResults<TempTargetsSlider>
-
         var bolusProgressFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
@@ -268,7 +259,7 @@ extension Home {
             } else {
                 /// Do not show the Override anymore
                 Task {
-                    guard let objectID = self.latestOverride.first?.objectID else { return }
+                    guard let objectID = self.latestTempTarget.first?.objectID else { return }
                     await state.cancelOverride(withID: objectID)
                 }
             }
@@ -614,6 +605,24 @@ extension Home {
                                     .font(.subheadline)
                                 Spacer()
                             }.padding(.horizontal, 10)
+                                .alert(
+                                    "Return to Normal?", isPresented: $showTempTargetCancelAlert,
+                                    actions: {
+                                        Button("No", role: .cancel) {}
+                                        Button("Yes", role: .destructive) {
+                                            Task {
+                                                guard let objectID = latestTempTarget.first?.objectID else { return }
+                                                await state.cancelTempTarget(withID: objectID)
+                                            }
+                                        }
+                                    }, message: { Text("This will change settings back to your normal profile.") }
+                                )
+                                .onTapGesture {
+                                    if !latestTempTarget.isEmpty {
+                                        showTempTargetCancelAlert = true
+                                    }
+                                }
+
                         }.padding(.horizontal, 10).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 10))
                     }
                 }

+ 60 - 3
FreeAPS/Sources/Modules/OverrideConfig/OverrideStateModel.swift

@@ -36,6 +36,7 @@ extension OverrideConfig {
         @Published var currentActiveOverride: OverrideStored?
         @Published var currentActiveTempTarget: TempTargetStored?
         @Published var showOverrideEditSheet = false
+        @Published var showTempTargetEditSheet = false
         @Published var showInvalidTargetAlert = false
 
         var units: GlucoseUnits = .mgdL
@@ -541,6 +542,7 @@ extension OverrideConfig.StateModel {
         newTempTarget.isPreset = false
 
         // disable all TempTargets
+        await disableAllActiveOverrides(createOverrideRunEntry: true)
 
         // Save Temp Target to Core Data
         do {
@@ -571,8 +573,6 @@ extension OverrideConfig.StateModel {
         newTempTarget.target = tempTargetTarget as NSDecimalNumber
         newTempTarget.isPreset = true
 
-        // disable all TempTargets
-
         // Save Temp Target to Core Data
         do {
             guard coredataContext.hasChanges else { return }
@@ -662,7 +662,7 @@ extension OverrideConfig.StateModel {
                         newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
                         newTempTargetRunStored.endDate = Date()
                         newTempTargetRunStored
-                            .target = canceledTempTarget.target?.decimalValue ?? 0
+                            .target = canceledTempTarget.target ?? 0
                         newTempTargetRunStored.tempTarget = canceledTempTarget
                         newTempTargetRunStored.isUploadedToNS = false
                     }
@@ -690,6 +690,63 @@ extension OverrideConfig.StateModel {
         }
     }
 
+    @MainActor func duplicateTempTargetPresetAndCancelPreviousTempTarget() async {
+        // We get the current active Preset by using currentActiveTempTarget which can either be a Preset or a custom Override
+        guard let tempTargetPresetToDuplicate = currentActiveTempTarget,
+              tempTargetPresetToDuplicate.isPreset == true else { return }
+
+        // Copy the current TempTarget-Preset to not edit the underlying Preset
+        let duplidateId = await copyRunningTempTarget(tempTargetPresetToDuplicate)
+
+        // Cancel the duplicated Temp Target
+        /// As we are on the Main Thread already we don't need to cancel via the objectID in this case
+        do {
+            try await viewContext.perform {
+                tempTargetPresetToDuplicate.enabled = false
+
+                guard self.viewContext.hasChanges else { return }
+                try self.viewContext.save()
+            }
+
+            if let tempTargetToEdit = try viewContext.existingObject(with: duplidateId) as? TempTargetStored
+            {
+                currentActiveTempTarget = tempTargetToEdit
+                activeTempTargetName = tempTargetToEdit.name ?? "Custom Temp Target"
+            }
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel previous override with error: \(error.localizedDescription)"
+            )
+        }
+    }
+
+    // Copy the current Temp Target if it is a RUNNING Preset
+    /// otherwise we would edit the Preset
+    @MainActor func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID {
+        let newTempTarget = TempTargetStored(context: viewContext)
+        newTempTarget.date = tempTarget.date
+        newTempTarget.id = tempTarget.id
+        newTempTarget.enabled = tempTarget.enabled
+        newTempTarget.duration = tempTarget.duration
+        newTempTarget.isUploadedToNS = true // to avoid getting duplicates on NS
+        newTempTarget.name = tempTarget.name
+        newTempTarget.target = tempTarget.target
+        newTempTarget.isPreset = false // no Preset
+
+        await viewContext.perform {
+            do {
+                guard self.viewContext.hasChanges else { return }
+                try self.viewContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to copy Temp Target with error: \(error.userInfo)"
+                )
+            }
+        }
+
+        return newTempTarget.objectID
+    }
+
     // Deletion of Temp Targets
     func invokeTempTargetPresetDeletion(_ objectID: NSManagedObjectID) async {
         await deleteOverridePreset(objectID)

+ 184 - 0
FreeAPS/Sources/Modules/OverrideConfig/View/EditTempTargetForm.swift

@@ -0,0 +1,184 @@
+import Foundation
+import SwiftUI
+
+struct EditTempTargetForm: View {
+    @ObservedObject var tempTarget: TempTargetStored
+    @Environment(\.presentationMode) var presentationMode
+    @Environment(\.colorScheme) var colorScheme
+    @StateObject var state: OverrideConfig.StateModel
+
+    @State private var name: String
+    @State private var target: Decimal
+    @State private var duration: Decimal
+    @State private var date: Date
+
+    @State private var hasChanges = false
+    @State private var showAlert = false
+
+    init(tempTargetToEdit: TempTargetStored, state: OverrideConfig.StateModel) {
+        tempTarget = tempTargetToEdit
+        _state = StateObject(wrappedValue: state)
+        _name = State(initialValue: tempTargetToEdit.name ?? "")
+        _target = State(initialValue: tempTargetToEdit.target?.decimalValue ?? 0)
+        _duration = State(initialValue: tempTargetToEdit.duration?.decimalValue ?? 0)
+        _date = State(initialValue: tempTargetToEdit.date ?? Date())
+    }
+
+    var color: LinearGradient {
+        colorScheme == .dark ? LinearGradient(
+            gradient: Gradient(colors: [
+                Color.bgDarkBlue,
+                Color.bgDarkerDarkBlue
+            ]),
+            startPoint: .top,
+            endPoint: .bottom
+        ) :
+            LinearGradient(
+                gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
+                startPoint: .top,
+                endPoint: .bottom
+            )
+    }
+
+    private var formatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }
+
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        if state.units == .mmolL {
+            formatter.maximumFractionDigits = 1
+        } else {
+            formatter.maximumFractionDigits = 0
+        }
+        formatter.roundingMode = .halfUp
+        return formatter
+    }
+
+    var body: some View {
+        NavigationView {
+            Form {
+                editTempTarget()
+
+                saveButton
+
+            }.scrollContentBackground(.hidden)
+                .background(color)
+                .navigationTitle("Edit Temp Target")
+                .navigationBarTitleDisplayMode(.inline)
+                .navigationBarItems(leading: Button("Close") {
+                    presentationMode.wrappedValue.dismiss()
+                })
+                .onDisappear {
+                    if !hasChanges {
+                        // Reset UI changes
+                        resetValues()
+                    }
+                }
+                .alert(isPresented: $state.showInvalidTargetAlert) {
+                    Alert(
+                        title: Text("Invalid Input"),
+                        message: Text("\(state.alertMessage)"),
+                        dismissButton: .default(Text("OK")) { state.showInvalidTargetAlert = false }
+                    )
+                }
+        }
+    }
+
+    @ViewBuilder private func editTempTarget() -> some View {
+        Section {
+            VStack {
+                TextField("Name", text: $name)
+                    .onChange(of: name) { _ in hasChanges = true }
+            }
+        } header: {
+            Text("Name")
+        }.listRowBackground(Color.chart)
+
+        Section {
+            HStack {
+                Text("Target")
+                Spacer()
+                TextFieldWithToolBar(
+                    text: Binding(
+                        get: { target },
+                        set: {
+                            target = $0
+                            hasChanges = true
+                        }
+                    ),
+                    placeholder: "0",
+                    numberFormatter: glucoseFormatter
+                )
+                Text(state.units.rawValue).foregroundColor(.secondary)
+            }
+            HStack {
+                Text("Duration")
+                Spacer()
+                TextFieldWithToolBar(
+                    text: Binding(
+                        get: { duration },
+                        set: {
+                            duration = $0
+                            hasChanges = true
+                        }
+                    ),
+                    placeholder: "0",
+                    numberFormatter: formatter
+                )
+                Text("minutes").foregroundColor(.secondary)
+            }
+            DatePicker("Date", selection: $date)
+                .onChange(of: date) { _ in hasChanges = true }
+        }.listRowBackground(Color.chart)
+    }
+
+    private var saveButton: some View {
+        HStack {
+            Spacer()
+            Button(action: {
+                if !state.isInputInvalid(target: target) {
+                    saveChanges()
+
+                    do {
+                        guard let moc = tempTarget.managedObjectContext else { return }
+                        guard moc.hasChanges else { return }
+                        try moc.save()
+
+                        // Update View
+                        hasChanges = false
+                        presentationMode.wrappedValue.dismiss()
+                    } catch {
+                        debugPrint("Failed to edit Temp Target")
+                    }
+                }
+            }, label: {
+                Text("Save")
+            })
+                .disabled(!hasChanges)
+                .frame(maxWidth: .infinity, alignment: .center)
+                .tint(.white)
+
+            Spacer()
+        }.listRowBackground(hasChanges ? Color(.systemBlue) : Color(.systemGray4))
+    }
+
+    private func saveChanges() {
+        tempTarget.name = name
+        tempTarget.target = NSDecimalNumber(decimal: target)
+        tempTarget.duration = NSDecimalNumber(decimal: duration)
+        tempTarget.date = date
+        tempTarget.isUploadedToNS = false
+    }
+
+    private func resetValues() {
+        name = tempTarget.name ?? ""
+        target = tempTarget.target?.decimalValue ?? 0
+        duration = tempTarget.duration?.decimalValue ?? 0
+        date = tempTarget.date ?? Date()
+    }
+}

+ 29 - 18
FreeAPS/Sources/Modules/OverrideConfig/View/OverrideRootView.swift

@@ -128,6 +128,17 @@ extension OverrideConfig {
                     }) {
                         AddTempTargetForm(state: state)
                     }
+                    .sheet(isPresented: $state.showTempTargetEditSheet, onDismiss: {
+                        Task {
+                            await state.resetTempTargetState()
+                            state.showTempTargetEditSheet = false
+                        }
+
+                    }) {
+                        if let tempTarget = selectedTempTarget {
+                            EditTempTargetForm(tempTargetToEdit: tempTarget, state: state)
+                        }
+                    }
             }.background(color)
         }
 
@@ -139,11 +150,11 @@ extension OverrideConfig {
             }
 
             if state.isEnabled, state.activeOverrideName.isNotEmpty {
-                currentActiveOverride
+                currentActiveAdjustment
             }
 
             if state.overridePresets.isNotEmpty || state.currentActiveOverride != nil {
-                cancelOverrideButton
+                cancelAdjustmentButton
             }
         }
 
@@ -153,14 +164,14 @@ extension OverrideConfig {
             } else {
                 defaultText
             }
-//
-//            if state.isEnabled, state.activeOverrideName.isNotEmpty {
-//                currentActiveOverride
-//            }
-//
-//            if state.overridePresets.isNotEmpty || state.currentActiveOverride != nil {
-//                cancelOverrideButton
-//            }
+
+            if state.isTempTargetEnabled, state.activeTempTargetName.isNotEmpty {
+                currentActiveAdjustment
+            }
+
+            if state.tempTargetPresets.isNotEmpty || state.currentActiveTempTarget != nil {
+                cancelAdjustmentButton
+            }
         }
 
         private var defaultText: some View {
@@ -233,14 +244,14 @@ extension OverrideConfig {
                             Button(action: {
                                 // Set the selected Temp Target to the chosen Preset and pass it to the Edit Sheet
                                 selectedTempTarget = preset
-//                                state.showOverrideEditSheet = true
+                                state.showTempTargetEditSheet = true
                             }, label: {
                                 Label("Edit", systemImage: "pencil")
                                     .tint(.blue)
                             })
                         }
                 }
-                .onMove(perform: state.reorderOverride)
+//                .onMove(perform: state.reorderOverride)
                 .listRowBackground(Color.chart)
             } header: {
                 Text("Presets")
@@ -252,7 +263,7 @@ extension OverrideConfig {
             }
         }
 
-        private var currentActiveOverride: some View {
+        private var currentActiveAdjustment: some View {
             switch state.selectedTab {
             case .overrides:
                 Section {
@@ -282,7 +293,7 @@ extension OverrideConfig {
             case .tempTargets:
                 Section {
                     HStack {
-                        Text("\(state.activeOverrideName) is running")
+                        Text("\(state.activeTempTargetName) is running")
 
                         Spacer()
                         Image(systemName: "square.and.pencil")
@@ -293,13 +304,13 @@ extension OverrideConfig {
                         Task {
                             /// To avoid editing the Preset when a Preset-Override is running we first duplicate the Preset-Override as a non-Preset Override
                             /// The currentActiveOverride variable in the State will update automatically via MOC notification
-                            await state.duplicateOverridePresetAndCancelPreviousOverride()
+                            await state.duplicateTempTargetPresetAndCancelPreviousTempTarget()
 
                             /// selectedOverride is used for passing the chosen Override to the EditSheet so we have to set the updated currentActiveOverride to be the selectedOverride
-                            selectedOverride = state.currentActiveOverride
+                            selectedTempTarget = state.currentActiveTempTarget
 
                             /// Now we can show the Edit sheet
-                            state.showOverrideEditSheet = true
+                            state.showTempTargetEditSheet = true
                         }
                     }
                 }
@@ -307,7 +318,7 @@ extension OverrideConfig {
             }
         }
 
-        private var cancelOverrideButton: some View {
+        private var cancelAdjustmentButton: some View {
             switch state.selectedTab {
             case .overrides:
                 Button(action: {

+ 1 - 0
Model/Helper/CustomNotification.swift

@@ -6,5 +6,6 @@ extension Notification.Name {
     static let didPerformBatchDelete = Notification.Name("didPerformBatchDelete")
     static let didUpdateDetermination = Notification.Name("didUpdateDetermination")
     static let didUpdateOverrideConfiguration = Notification.Name("didUpdateOverrideConfiguration")
+    static let didUpdateTempTargetConfiguration = Notification.Name("didUpdateTempTargetConfiguration")
     static let didUpdateCobIob = Notification.Name("didUpdateCobIob")
 }

+ 1 - 1
TempTargetRunStored+CoreDataProperties.swift

@@ -7,7 +7,7 @@ public extension TempTargetRunStored {
     }
 
     @NSManaged var startDate: Date?
-    @NSManaged var target: Decimal
+    @NSManaged var target: NSDecimalNumber?
     @NSManaged var id: UUID?
     @NSManaged var endDate: Date?
     @NSManaged var isUploadedToNS: Bool