Jonas Björkert 2 سال پیش
والد
کامیت
3ac38e1567

+ 31 - 3
LoopFollow.xcodeproj/project.pbxproj

@@ -64,6 +64,9 @@
 		DDF2C0142BEFD468007A20E6 /* blacklisted-versions.json in Resources */ = {isa = PBXBuildFile; fileRef = DDF2C0132BEFD468007A20E6 /* blacklisted-versions.json */; };
 		DDF699942C555B310058A8D9 /* ViewControllerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF699932C555B310058A8D9 /* ViewControllerManager.swift */; };
 		DDF699962C5582290058A8D9 /* TextFieldWithToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF699952C5582290058A8D9 /* TextFieldWithToolBar.swift */; };
+		DDF699992C5AA3060058A8D9 /* TempTargetPresetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF699982C5AA3060058A8D9 /* TempTargetPresetManager.swift */; };
+		DDF6999B2C5AA32E0058A8D9 /* TempTargetPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6999A2C5AA32E0058A8D9 /* TempTargetPreset.swift */; };
+		DDF6999E2C5AAA640058A8D9 /* ErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6999D2C5AAA640058A8D9 /* ErrorMessageView.swift */; };
 		DDF9676E2AD08C6E00C5EB95 /* SiteChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF9676D2AD08C6E00C5EB95 /* SiteChange.swift */; };
 		FC16A97A24996673003D6245 /* NightScout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC16A97924996673003D6245 /* NightScout.swift */; };
 		FC16A97B249966A3003D6245 /* AlarmSound.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC7CE589248ABEA3001F83B8 /* AlarmSound.swift */; };
@@ -279,6 +282,9 @@
 		DDF2C0132BEFD468007A20E6 /* blacklisted-versions.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blacklisted-versions.json"; sourceTree = "<group>"; };
 		DDF699932C555B310058A8D9 /* ViewControllerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerManager.swift; sourceTree = "<group>"; };
 		DDF699952C5582290058A8D9 /* TextFieldWithToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithToolBar.swift; sourceTree = "<group>"; };
+		DDF699982C5AA3060058A8D9 /* TempTargetPresetManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetPresetManager.swift; sourceTree = "<group>"; };
+		DDF6999A2C5AA32E0058A8D9 /* TempTargetPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetPreset.swift; sourceTree = "<group>"; };
+		DDF6999D2C5AAA640058A8D9 /* ErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageView.swift; sourceTree = "<group>"; };
 		DDF9676D2AD08C6E00C5EB95 /* SiteChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteChange.swift; sourceTree = "<group>"; };
 		ECA3EFB4037410B4973BB632 /* Pods-LoopFollow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LoopFollow.debug.xcconfig"; path = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow.debug.xcconfig"; sourceTree = "<group>"; };
 		FC16A97924996673003D6245 /* NightScout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightScout.swift; sourceTree = "<group>"; };
@@ -484,6 +490,7 @@
 		DD0C0C6E2C4AFFB800DBADDF /* Remote */ = {
 			isa = PBXGroup;
 			children = (
+				DDF699972C5AA2E50058A8D9 /* TempTargetPreset */,
 				DD0C0C6F2C4AFFE800DBADDF /* RemoteViewController.swift */,
 				DD0C0C712C4B000800DBADDF /* RemoteView.swift */,
 				DDD10F022C518A6500D76A8E /* TreatmentResponse.swift */,
@@ -558,6 +565,23 @@
 			path = Scripts;
 			sourceTree = "<group>";
 		};
+		DDF699972C5AA2E50058A8D9 /* TempTargetPreset */ = {
+			isa = PBXGroup;
+			children = (
+				DDF699982C5AA3060058A8D9 /* TempTargetPresetManager.swift */,
+				DDF6999A2C5AA32E0058A8D9 /* TempTargetPreset.swift */,
+			);
+			path = TempTargetPreset;
+			sourceTree = "<group>";
+		};
+		DDF6999C2C5AAA4C0058A8D9 /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				DDF6999D2C5AAA640058A8D9 /* ErrorMessageView.swift */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
 		FC16A97624995FEE003D6245 /* Application */ = {
 			isa = PBXGroup;
 			children = (
@@ -731,7 +755,7 @@
 				FCC68871248A736700A0279D /* ViewControllers */,
 				DD98F54224BCEF190007425A /* Extensions */,
 				FC16A9782499657E003D6245 /* Controllers */,
-				FCC688542489367300A0279D /* helpers */,
+				FCC688542489367300A0279D /* Helpers */,
 				FCC688512489363F00A0279D /* Storage */,
 				FC16A97624995FEE003D6245 /* Application */,
 			);
@@ -776,9 +800,10 @@
 			path = Storage;
 			sourceTree = "<group>";
 		};
-		FCC688542489367300A0279D /* helpers */ = {
+		FCC688542489367300A0279D /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
+				DDF6999C2C5AAA4C0058A8D9 /* Views */,
 				FCC6886E2489A53800A0279D /* AppConstants.swift */,
 				FCC6886A24898FD800A0279D /* ObservationToken.swift */,
 				FCC6886C2489909D00A0279D /* AnyConvertible.swift */,
@@ -798,7 +823,7 @@
 				DDF2C0112BEFB733007A20E6 /* AppVersionManager.swift */,
 				DDF699952C5582290058A8D9 /* TextFieldWithToolBar.swift */,
 			);
-			path = helpers;
+			path = Helpers;
 			sourceTree = "<group>";
 		};
 		FCC68871248A736700A0279D /* ViewControllers */ = {
@@ -1075,6 +1100,7 @@
 			files = (
 				FCC68850248935D800A0279D /* AlarmViewController.swift in Sources */,
 				FC7CE59F248D8D23001F83B8 /* SnoozeViewController.swift in Sources */,
+				DDF6999B2C5AA32E0058A8D9 /* TempTargetPreset.swift in Sources */,
 				DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */,
 				FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */,
 				DDF699942C555B310058A8D9 /* ViewControllerManager.swift in Sources */,
@@ -1107,11 +1133,13 @@
 				DD7E19882ACDA5DA00DBD158 /* Notes.swift in Sources */,
 				FCEF87AC24A141A700AE6FA0 /* Localizer.swift in Sources */,
 				FC1BDD3224A2585C001B652C /* DataStructs.swift in Sources */,
+				DDF6999E2C5AAA640058A8D9 /* ErrorMessageView.swift in Sources */,
 				FCC6886524898EEE00A0279D /* UserDefaults.swift in Sources */,
 				FC16A97F249969E2003D6245 /* Graphs.swift in Sources */,
 				FC8589BF252B54F500C8FC73 /* Mobileprovision.swift in Sources */,
 				DD493AE12ACF22FE009A6922 /* Profile.swift in Sources */,
 				DD493ADF2ACF22BB009A6922 /* SAge.swift in Sources */,
+				DDF699992C5AA3060058A8D9 /* TempTargetPresetManager.swift in Sources */,
 				DD0C0C722C4B000800DBADDF /* RemoteView.swift in Sources */,
 				DD493ADB2ACF21A3009A6922 /* Bolus.swift in Sources */,
 				DD152D3B24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift in Sources */,

LoopFollow/helpers/AnyConvertible.swift → LoopFollow/Helpers/AnyConvertible.swift


LoopFollow/helpers/AppConstants.swift → LoopFollow/Helpers/AppConstants.swift


LoopFollow/helpers/AppVersionManager.swift → LoopFollow/Helpers/AppVersionManager.swift


LoopFollow/helpers/BackgroundTaskAudio.swift → LoopFollow/Helpers/BackgroundTaskAudio.swift


LoopFollow/helpers/BuildDetails.swift → LoopFollow/Helpers/BuildDetails.swift


LoopFollow/helpers/Chart.swift → LoopFollow/Helpers/Chart.swift


LoopFollow/helpers/DataStructs.swift → LoopFollow/Helpers/DataStructs.swift


LoopFollow/helpers/DateTime.swift → LoopFollow/Helpers/DateTime.swift


LoopFollow/helpers/DictionaryKeyPath.swift → LoopFollow/Helpers/DictionaryKeyPath.swift


LoopFollow/helpers/GitHubService.swift → LoopFollow/Helpers/GitHubService.swift


LoopFollow/helpers/Globals.swift → LoopFollow/Helpers/Globals.swift


LoopFollow/helpers/GlucoseConversion.swift → LoopFollow/Helpers/GlucoseConversion.swift


LoopFollow/helpers/Localizer.swift → LoopFollow/Helpers/Localizer.swift


LoopFollow/helpers/Mobileprovision.swift → LoopFollow/Helpers/Mobileprovision.swift


LoopFollow/helpers/NightscoutUtils.swift → LoopFollow/Helpers/NightscoutUtils.swift


LoopFollow/helpers/ObservationToken.swift → LoopFollow/Helpers/ObservationToken.swift


LoopFollow/helpers/TextFieldWithToolBar.swift → LoopFollow/Helpers/TextFieldWithToolBar.swift


+ 43 - 0
LoopFollow/Helpers/Views/ErrorMessageView.swift

@@ -0,0 +1,43 @@
+//
+//  ErrorMessageView.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-31.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+
+struct ErrorMessageView: View {
+    var message: String
+    var buttonTitle: String?
+    var buttonAction: (() -> Void)?
+
+    var body: some View {
+        VStack(spacing: 20) {
+            Text(message)
+                .foregroundColor(.red)
+                .multilineTextAlignment(.center)
+                .padding()
+                .background(
+                    RoundedRectangle(cornerRadius: 10)
+                        .fill(Color.white)
+                        .shadow(color: .gray, radius: 5, x: 0, y: 2)
+                )
+                .padding()
+
+            if let buttonTitle = buttonTitle, let buttonAction = buttonAction {
+                Button(action: buttonAction) {
+                    Text(buttonTitle)
+                        .frame(maxWidth: .infinity)
+                        .padding()
+                        .background(Color.blue)
+                        .foregroundColor(.white)
+                        .cornerRadius(8)
+                }
+            }
+        }
+        .padding()
+    }
+}

LoopFollow/helpers/carbBolusArrays.swift → LoopFollow/Helpers/carbBolusArrays.swift


+ 69 - 33
LoopFollow/Remote/RemoteView.swift

@@ -22,6 +22,10 @@ struct RemoteView: View {
     @State private var isLoading: Bool = false
     @State private var statusMessage: String? = nil
 
+    @State private var showPresetSheet: Bool = false
+    @State private var presetName = ""
+    @ObservedObject var presetManager = TempTargetPresetManager.shared
+
     var onCancelExistingTarget: (@escaping (Bool) -> Void) -> Void
     var sendTempTarget: (HKQuantity, HKQuantity, @escaping (Bool) -> Void) -> Void
 
@@ -94,6 +98,45 @@ struct RemoteView: View {
                                 .buttonStyle(BorderlessButtonStyle())
                                 .font(.callout)
                                 .controlSize(.mini)
+
+                                Spacer() // Pushes the next button to the right
+
+                                Button {
+                                    showPresetSheet = true
+                                } label: {
+                                    Text("Save as Preset")
+                                }
+                                .disabled(isButtonDisabled)
+                                .buttonStyle(BorderlessButtonStyle())
+                                .font(.callout)
+                                .controlSize(.mini)
+                            }
+                        }
+
+                        if !presetManager.presets.isEmpty {
+                            Section(header: Text("Presets")) {
+                                ForEach(presetManager.presets) { preset in
+                                    HStack {
+                                        Text(preset.name)
+                                        Spacer()
+                                    }
+                                    .contentShape(Rectangle())
+                                    .onTapGesture {
+                                        alertType = .confirmCommand
+                                        newHKTarget = preset.target
+                                        duration = preset.duration
+                                        showAlert = true
+                                    }
+                                    .swipeActions {
+                                        Button(role: .destructive) {
+                                            if let index = presetManager.presets.firstIndex(where: { $0.id == preset.id }) {
+                                                presetManager.deletePreset(at: index)
+                                            }
+                                        } label: {
+                                            Label("Delete", systemImage: "trash")
+                                        }
+                                    }
+                                }
                             }
                         }
                     }
@@ -138,6 +181,32 @@ struct RemoteView: View {
                     return Alert(title: Text("Unknown Alert"))
                 }
             }
+            .sheet(isPresented: $showPresetSheet) {
+                VStack {
+                    Text("Save Preset")
+                        .font(.headline)
+                        .padding()
+                    TextField("Preset Name", text: $presetName)
+                        .textFieldStyle(RoundedBorderTextFieldStyle())
+                        .padding()
+                    HStack {
+                        Button("Cancel") {
+                            showPresetSheet = false
+                        }
+                        .padding()
+                        Spacer()
+                        Button("Save") {
+                            presetManager.addPreset(name: presetName, target: newHKTarget, duration: duration)
+                            presetName = "" // Clear the preset name after saving
+                            showPresetSheet = false
+                        }
+                        .disabled(presetName.isEmpty)
+                        .padding()
+                    }
+                    Spacer()
+                }
+                .padding()
+            }
         }
     }
 
@@ -174,36 +243,3 @@ struct RemoteView: View {
         }
     }
 }
-
-struct ErrorMessageView: View {
-    var message: String
-    var buttonTitle: String?
-    var buttonAction: (() -> Void)?
-
-    var body: some View {
-        VStack(spacing: 20) {
-            Text(message)
-                .foregroundColor(.red)
-                .multilineTextAlignment(.center)
-                .padding()
-                .background(
-                    RoundedRectangle(cornerRadius: 10)
-                        .fill(Color.white)
-                        .shadow(color: .gray, radius: 5, x: 0, y: 2)
-                )
-                .padding()
-
-            if let buttonTitle = buttonTitle, let buttonAction = buttonAction {
-                Button(action: buttonAction) {
-                    Text(buttonTitle)
-                        .frame(maxWidth: .infinity)
-                        .padding()
-                        .background(Color.blue)
-                        .foregroundColor(.white)
-                        .cornerRadius(8)
-                }
-            }
-        }
-        .padding()
-    }
-}

+ 48 - 0
LoopFollow/Remote/TempTargetPreset/TempTargetPreset.swift

@@ -0,0 +1,48 @@
+//
+//  TempTargetPreset.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-31.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import HealthKit
+
+struct TempTargetPreset: Identifiable, Codable {
+    var id: UUID
+    var name: String
+    var target: HKQuantity
+    var duration: HKQuantity
+
+    enum CodingKeys: String, CodingKey {
+        case id, name, targetValue, durationValue
+    }
+
+    init(id: UUID = UUID(), name: String, target: HKQuantity, duration: HKQuantity) {
+        self.id = id
+        self.name = name
+        self.target = target
+        self.duration = duration
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        id = try container.decode(UUID.self, forKey: .id)
+        name = try container.decode(String.self, forKey: .name)
+
+        let targetValue = try container.decode(Double.self, forKey: .targetValue)
+        target = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: targetValue)
+
+        let durationValue = try container.decode(Double.self, forKey: .durationValue)
+        duration = HKQuantity(unit: .minute(), doubleValue: durationValue)
+    }
+
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(id, forKey: .id)
+        try container.encode(name, forKey: .name)
+        try container.encode(target.doubleValue(for: .milligramsPerDeciliter), forKey: .targetValue)
+        try container.encode(duration.doubleValue(for: .minute()), forKey: .durationValue)
+    }
+}

+ 51 - 0
LoopFollow/Remote/TempTargetPreset/TempTargetPresetManager.swift

@@ -0,0 +1,51 @@
+//
+//  TempTargetPresetManager.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-31.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import HealthKit
+import Combine
+
+class TempTargetPresetManager: ObservableObject {
+    static let shared = TempTargetPresetManager()
+
+    @Published var presets: [TempTargetPreset] = []
+
+    private let presetsKey = "tempTargetPresets"
+
+    private init() {
+        loadPresets()
+    }
+
+    func loadPresets() {
+        if let data = UserDefaults.standard.data(forKey: presetsKey) {
+            if let decodedPresets = try? JSONDecoder().decode([TempTargetPreset].self, from: data) {
+                self.presets = decodedPresets
+            }
+        }
+    }
+
+    func savePresets() {
+        if let encodedData = try? JSONEncoder().encode(presets) {
+            UserDefaults.standard.set(encodedData, forKey: presetsKey)
+        }
+    }
+
+    func addPreset(name: String, target: HKQuantity, duration: HKQuantity) {
+        if let index = presets.firstIndex(where: { $0.name == name }) {
+            presets[index] = TempTargetPreset(name: name, target: target, duration: duration)
+        } else {
+            presets.append(TempTargetPreset(name: name, target: target, duration: duration))
+        }
+        savePresets()
+    }
+
+    func deletePreset(at index: Int) {
+        presets.remove(at: index)
+        savePresets()
+    }
+}