Explorar el Código

Working temp targets

Ivan Valkou hace 4 años
padre
commit
4cec3a1604

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -202,6 +202,7 @@
 		38E8755927567CA600975559 /* Decimal+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */; };
 		38E8755B27568A6800975559 /* ConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E8755A27568A6700975559 /* ConfirmationView.swift */; };
 		38E8757927579D9200975559 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5525C9D4D500A708ED /* Publisher.swift */; };
+		38E8757B2757B1C300975559 /* TempTargetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E8757A2757B1C300975559 /* TempTargetsView.swift */; };
 		38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E989DC25F5021400C0CED0 /* PumpStatus.swift */; };
 		38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1B25F52C9300C0CED0 /* Signpost.swift */; };
 		38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1C25F52C9300C0CED0 /* Logger.swift */; };
@@ -586,6 +587,7 @@
 		38E8755527564B5000975559 /* FreeAPSWatch WatchKit Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "FreeAPSWatch WatchKit Extension.entitlements"; sourceTree = "<group>"; };
 		38E8755627564B6100975559 /* FreeAPSWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FreeAPSWatch.entitlements; sourceTree = "<group>"; };
 		38E8755A27568A6700975559 /* ConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationView.swift; sourceTree = "<group>"; };
+		38E8757A2757B1C300975559 /* TempTargetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetsView.swift; sourceTree = "<group>"; };
 		38E989DC25F5021400C0CED0 /* PumpStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpStatus.swift; sourceTree = "<group>"; };
 		38E98A1B25F52C9300C0CED0 /* Signpost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signpost.swift; sourceTree = "<group>"; };
 		38E98A1C25F52C9300C0CED0 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@@ -1373,6 +1375,7 @@
 				38E8754B2755548F00975559 /* WatchStateModel.swift */,
 				38E87549275550BB00975559 /* CarbsView.swift */,
 				38E8755A27568A6700975559 /* ConfirmationView.swift */,
+				38E8757A2757B1C300975559 /* TempTargetsView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -2222,6 +2225,7 @@
 				38E8755127555D0500975559 /* DataFlow.swift in Sources */,
 				38E8753227554D5700975559 /* ComplicationController.swift in Sources */,
 				38E8752A27554D5700975559 /* FreeAPSApp.swift in Sources */,
+				38E8757B2757B1C300975559 /* TempTargetsView.swift in Sources */,
 				38E8753027554D5700975559 /* NotificationView.swift in Sources */,
 				38E8754727554DF100975559 /* Color+Extensions.swift in Sources */,
 				38E8755927567CA600975559 /* Decimal+Extensions.swift in Sources */,

+ 60 - 1
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -15,6 +15,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
     @Injected() private var apsManager: APSManager!
     @Injected() private var storage: FileStorage!
     @Injected() private var carbsStorage: CarbsStorage!
+    @Injected() private var tempTargetsStorage: TempTargetsStorage!
 
     init(resolver: Resolver, session: WCSession = .default) {
         self.session = session
@@ -60,7 +61,19 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
 
             self.state.iob = self.suggestion?.iob
             self.state.cob = self.suggestion?.cob
-
+            self.state.tempTargets = self.tempTargetsStorage.presets()
+                .map { target -> TempTargetWatchPreset in
+                    let untilDate = self.tempTargetsStorage.current().flatMap { currentTarget -> Date? in
+                        guard currentTarget.id == target.id else { return nil }
+                        return currentTarget.createdAt.addingTimeInterval(TimeInterval(currentTarget.duration * 60))
+                    }
+                    return TempTargetWatchPreset(
+                        name: target.displayName,
+                        id: target.id,
+                        description: self.descriptionForTarget(target),
+                        until: untilDate
+                    )
+                }
             self.sendState()
         }
     }
@@ -106,6 +119,23 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
         return (glucoseText, directionText, deltaText)
     }
 
+    private func descriptionForTarget(_ target: TempTarget) -> String {
+        let units = settingsManager.settings.units
+
+        var low = target.targetBottom
+        var high = target.targetTop
+        if units == .mmolL {
+            low = low?.asMmolL
+            high = high?.asMmolL
+        }
+
+        let description =
+            "\(targetFormatter.string(from: (low ?? 0) as NSNumber)!) - \(targetFormatter.string(from: (high ?? 0) as NSNumber)!)" +
+            " for \(targetFormatter.string(from: target.duration as NSNumber)!) min"
+
+        return description
+    }
+
     private var glucoseFormatter: NumberFormatter {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
@@ -126,6 +156,13 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
         return formatter
     }
 
+    private var targetFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
     private var suggestion: Suggestion? {
         storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
     }
@@ -166,6 +203,28 @@ extension BaseWatchManager: WCSessionDelegate {
             return
         }
 
+        if let tempTargetID = message["tempTarget"] as? String {
+            if var preset = tempTargetsStorage.presets().first(where: { $0.id == tempTargetID }) {
+                preset.createdAt = Date()
+                tempTargetsStorage.storeTempTargets([preset])
+                replyHandler(["confirmation": true])
+                return
+            } else if tempTargetID == "cancel" {
+                let entry = TempTarget(
+                    name: TempTarget.cancel,
+                    createdAt: Date(),
+                    targetTop: 0,
+                    targetBottom: 0,
+                    duration: 0,
+                    enteredBy: TempTarget.manual,
+                    reason: TempTarget.cancel
+                )
+                tempTargetsStorage.storeTempTargets([entry])
+                replyHandler(["confirmation": true])
+                return
+            }
+        }
+
         replyHandler(["confirmation": false])
     }
 

+ 8 - 0
FreeAPSWatch WatchKit Extension/DataFlow.swift

@@ -13,4 +13,12 @@ struct WatchState: Codable {
     var bolusRecommended: Decimal?
     var iob: Decimal?
     var cob: Decimal?
+    var tempTargets: [TempTargetWatchPreset] = []
+}
+
+struct TempTargetWatchPreset: Codable, Identifiable {
+    let name: String
+    let id: String
+    let description: String
+    let until: Date?
 }

+ 16 - 16
FreeAPSWatch WatchKit Extension/Views/MainView.swift

@@ -68,37 +68,37 @@ struct MainView: View {
                 CarbsView()
                     .environmentObject(state)
             } label: {
-                HStack {
-                    Image("carbs", bundle: nil)
-                        .renderingMode(.template)
-                        .resizable()
-                        .frame(width: 24, height: 24)
-                        .foregroundColor(.loopGreen)
-                }
+                Image("carbs", bundle: nil)
+                    .renderingMode(.template)
+                    .resizable()
+                    .frame(width: 24, height: 24)
+                    .foregroundColor(.loopGreen)
             }
 
             NavigationLink(isActive: $state.isTempTargetViewActive) {
-                EmptyView()
+                TempTargetsView()
+                    .environmentObject(state)
             } label: {
-                HStack {
+                VStack {
                     Image("target", bundle: nil)
                         .renderingMode(.template)
                         .resizable()
                         .frame(width: 24, height: 24)
                         .foregroundColor(.loopYellow)
+                    if let until = state.tempTargets.compactMap(\.until).first, until > Date() {
+                        Text(until, style: .timer).font(.system(size: 8))
+                    }
                 }
             }
 
             NavigationLink(isActive: $state.isBolusViewActive) {
                 EmptyView()
             } label: {
-                HStack {
-                    Image("bolus", bundle: nil)
-                        .renderingMode(.template)
-                        .resizable()
-                        .frame(width: 24, height: 24)
-                        .foregroundColor(.insulin)
-                }
+                Image("bolus", bundle: nil)
+                    .renderingMode(.template)
+                    .resizable()
+                    .frame(width: 24, height: 24)
+                    .foregroundColor(.insulin)
             }
         }
     }

+ 54 - 0
FreeAPSWatch WatchKit Extension/Views/TempTargetsView.swift

@@ -0,0 +1,54 @@
+import SwiftUI
+
+struct TempTargetsView: View {
+    @EnvironmentObject var state: WatchStateModel
+
+    var body: some View {
+        List {
+            if state.tempTargets.isEmpty {
+                Text("Set temp targets presets on iPhone first").padding()
+            } else {
+                ForEach(state.tempTargets) { target in
+                    Button {
+                        state.enactTempTarget(id: target.id)
+                    } label: {
+                        VStack(alignment: .leading) {
+                            HStack {
+                                Text(target.name)
+                                if let until = target.until, until > Date() {
+                                    Spacer()
+                                    Text(until, style: .timer).foregroundColor(.loopGreen)
+                                }
+                            }
+                            Text(target.description).font(.caption2).foregroundColor(.secondary)
+                        }
+                    }
+                }
+            }
+
+            Button {
+                state.enactTempTarget(id: "cancel")
+            } label: {
+                Text("Cancel Temp Target")
+            }
+        }
+        .navigationTitle("Temp Targets")
+    }
+}
+
+struct TempTargetsView_Previews: PreviewProvider {
+    static var previews: some View {
+        let model = WatchStateModel()
+        model.tempTargets = [
+            TempTargetWatchPreset(
+                name: "Target 0",
+                id: UUID().uuidString,
+                description: "blablabla",
+                until: Date().addingTimeInterval(60 * 60)
+            ),
+            TempTargetWatchPreset(name: "target1", id: UUID().uuidString, description: "blablabla", until: nil),
+            TempTargetWatchPreset(name: "🤖 Target 2", id: UUID().uuidString, description: "blablabla", until: nil)
+        ]
+        return TempTargetsView().environmentObject(model)
+    }
+}

+ 25 - 10
FreeAPSWatch WatchKit Extension/Views/WatchStateModel.swift

@@ -18,6 +18,7 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var carbsRequired: Decimal?
     @Published var iob: Decimal?
     @Published var cob: Decimal?
+    @Published var tempTargets: [TempTargetWatchPreset] = []
 
     @Published var isCarbsViewActive = false
     @Published var isTempTargetViewActive = false
@@ -39,18 +40,19 @@ class WatchStateModel: NSObject, ObservableObject {
         confirmationSuccess = nil
         isConfirmationViewActive = true
         isCarbsViewActive = false
-        session.sendMessage(["carbs": carbs], replyHandler: { replay in
-            if let ok = replay["confirmation"] as? Bool {
-                DispatchQueue.main.async {
-                    self.confirmation(ok)
-                }
-            } else {
-                DispatchQueue.main.async {
-                    self.confirmation(false)
-                }
+        session.sendMessage(["carbs": carbs], replyHandler: completionHandler) { error in
+            print(error.localizedDescription)
+            DispatchQueue.main.async {
+                self.confirmation(false)
             }
+        }
+    }
 
-        }) { error in
+    func enactTempTarget(id: String) {
+        confirmationSuccess = nil
+        isConfirmationViewActive = true
+        isTempTargetViewActive = false
+        session.sendMessage(["tempTarget": id], replyHandler: completionHandler) { error in
             print(error.localizedDescription)
             DispatchQueue.main.async {
                 self.confirmation(false)
@@ -58,6 +60,18 @@ class WatchStateModel: NSObject, ObservableObject {
         }
     }
 
+    private func completionHandler(_ reply: [String: Any]) {
+        if let ok = reply["confirmation"] as? Bool {
+            DispatchQueue.main.async {
+                self.confirmation(ok)
+            }
+        } else {
+            DispatchQueue.main.async {
+                self.confirmation(false)
+            }
+        }
+    }
+
     private func confirmation(_ ok: Bool) {
         WKInterfaceDevice.current().play(ok ? .success : .failure)
         withAnimation {
@@ -83,6 +97,7 @@ class WatchStateModel: NSObject, ObservableObject {
         carbsRequired = state.carbsRequired
         iob = state.iob
         cob = state.cob
+        tempTargets = state.tempTargets
     }
 }