Browse Source

Watch bolus confirmation screen

Jon B.M 4 years ago
parent
commit
d8028ee4c0

+ 6 - 1
FreeAPS.xcodeproj/project.pbxproj

@@ -205,6 +205,8 @@
 		38E8757927579D9200975559 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5525C9D4D500A708ED /* Publisher.swift */; };
 		38E8757B2757B1C300975559 /* TempTargetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E8757A2757B1C300975559 /* TempTargetsView.swift */; };
 		38E8757D2757C45D00975559 /* BolusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E8757C2757C45D00975559 /* BolusView.swift */; };
+		38E8757E2758C86A00975559 /* ConvenienceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38192E0C261BAF980094D973 /* ConvenienceExtensions.swift */; };
+		38E8758027595DC600975559 /* BolusConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E8757F27595DC500975559 /* BolusConfirmationView.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 */; };
@@ -591,6 +593,7 @@
 		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>"; };
 		38E8757C2757C45D00975559 /* BolusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusView.swift; sourceTree = "<group>"; };
+		38E8757F27595DC500975559 /* BolusConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusConfirmationView.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>"; };
@@ -1350,6 +1353,7 @@
 			children = (
 				38E8755527564B5000975559 /* FreeAPSWatch WatchKit Extension.entitlements */,
 				38E8755027555D0500975559 /* DataFlow.swift */,
+				38E8754B2755548F00975559 /* WatchStateModel.swift */,
 				38E875482755505800975559 /* Views */,
 				38E8752927554D5700975559 /* FreeAPSApp.swift */,
 				38E8752D27554D5700975559 /* NotificationController.swift */,
@@ -1375,11 +1379,11 @@
 			isa = PBXGroup;
 			children = (
 				38E8752B27554D5700975559 /* MainView.swift */,
-				38E8754B2755548F00975559 /* WatchStateModel.swift */,
 				38E87549275550BB00975559 /* CarbsView.swift */,
 				38E8755A27568A6700975559 /* ConfirmationView.swift */,
 				38E8757A2757B1C300975559 /* TempTargetsView.swift */,
 				38E8757C2757C45D00975559 /* BolusView.swift */,
+				38E8757F27595DC500975559 /* BolusConfirmationView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -2235,6 +2239,7 @@
 				38E8753027554D5700975559 /* NotificationView.swift in Sources */,
 				38E8754727554DF100975559 /* Color+Extensions.swift in Sources */,
 				38E8755927567CA600975559 /* Decimal+Extensions.swift in Sources */,
+				38E8758027595DC600975559 /* BolusConfirmationView.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 109 - 0
FreeAPSWatch WatchKit Extension/Views/BolusConfirmationView.swift

@@ -0,0 +1,109 @@
+import Combine
+import SwiftUI
+
+struct BolusConfirmationView: View {
+    @EnvironmentObject var state: WatchStateModel
+
+    @State var crownProgress: CGFloat = 100.0
+    @State var progress: CGFloat = 0
+
+    private let elementSize: CGFloat = 30
+
+    @State var progressReturn: AnyCancellable?
+
+    @State var done = false
+
+    var body: some View {
+        VStack {
+            GeometryReader { geo in
+                ZStack {
+                    RoundedRectangle(cornerRadius: elementSize / 2, style: .circular)
+                        .fill(.secondary)
+                        .frame(width: elementSize, height: geo.size.height)
+                        .opacity(0.2)
+
+                    Image(systemName: "arrow.right")
+                        .resizable()
+                        .frame(width: elementSize / 2, height: elementSize / 2)
+                        .foregroundColor(.primary)
+                        .position(x: geo.size.width - elementSize / 4, y: elementSize / 2)
+                        .transition(.opacity)
+
+                    if done {
+                        Image(systemName: "checkmark.circle.fill")
+                            .resizable()
+                            .foregroundColor(.loopGreen)
+                            .frame(width: elementSize, height: elementSize)
+                            .position(
+                                x: geo.size.width / 2,
+                                y: elementSize / 2 + ((geo.size.height - elementSize) * progress / 100)
+                            )
+                    } else {
+                        Image(systemName: "arrow.down.circle.fill")
+                            .resizable()
+                            .foregroundColor(.insulin)
+                            .frame(width: elementSize, height: elementSize)
+                            .position(
+                                x: geo.size.width / 2,
+                                y: elementSize / 2 + ((geo.size.height - elementSize) * progress / 100)
+                            )
+                    }
+                }
+                .frame(maxWidth: .infinity, maxHeight: .infinity)
+            }
+            .padding()
+            Button {
+                WKInterfaceDevice.current().play(.click)
+                state.pendingBolus = nil
+                state.isConfirmationBolusViewActive = false
+            }
+            label: {
+                Text("Cancel")
+            }
+        }
+        .focusable(true)
+        .digitalCrownRotation(
+            $crownProgress,
+            from: 0.0,
+            through: 100.0,
+            by: 1,
+            sensitivity: .high,
+            isContinuous: false,
+            isHapticFeedbackEnabled: true
+        )
+        .onChange(of: crownProgress) { _ in
+            guard !done else { return }
+
+            progressReturn?.cancel()
+            progress = min(max(0, 100 - crownProgress), 100)
+            if progress >= 100 {
+                success()
+            } else {
+                progressReturn = Just(())
+                    .delay(for: 0.1, scheduler: RunLoop.main)
+                    .sink { _ in
+                        crownProgress = 100
+                        withAnimation {
+                            progress = 0
+                        }
+                    }
+            }
+        }
+    }
+
+    private func success() {
+        WKInterfaceDevice.current().play(.success)
+        withAnimation {
+            done = true
+        }
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+            state.enactBolus()
+        }
+    }
+}
+
+struct BolusConfirmationView_Previews: PreviewProvider {
+    static var previews: some View {
+        BolusConfirmationView().environmentObject(WatchStateModel())
+    }
+}

+ 52 - 31
FreeAPSWatch WatchKit Extension/Views/BolusView.swift

@@ -17,37 +17,58 @@ struct BolusView: View {
     }
 
     var body: some View {
-        VStack(spacing: 16) {
-            HStack {
-                Button {
-                    let newValue = steps - 1
-                    steps = max(newValue, 0)
-                } label: { Image(systemName: "minus") }
-                    .frame(width: 50)
-                Spacer()
-                Text(numberFormatter.string(from: (steps * Double(state.bolusIncrement ?? 0.1)) as NSNumber)! + NSLocalizedString(" U", comment: "Abbreviation for insulin unit"))
-                    .font(.headline)
-                    .focusable(true)
-                    .digitalCrownRotation(
-                    $steps,
-                    from: 0,
-                    through: Double((state.maxBolus ?? 5) / (state.bolusIncrement ?? 0.1)),
-                    by: 1,
-                    sensitivity: .medium,
-                    isContinuous: false,
-                    isHapticFeedbackEnabled: true
-                )
-                Spacer()
-                Button {
-                    let newValue = steps + 1
-                    steps = min(newValue, Double((state.maxBolus ?? 5) / (state.bolusIncrement ?? 0.1)))
-                } label: { Image(systemName: "plus") }
-                    .frame(width: 50)
-            }
+        GeometryReader { geo in
+            VStack(spacing: 16) {
+                HStack {
+                    Button {
+                        WKInterfaceDevice.current().play(.click)
+                        let newValue = steps - 1
+                        steps = max(newValue, 0)
+                    } label: { Image(systemName: "minus") }
+                        .frame(width: geo.size.width / 4)
+                    Spacer()
+                    Text(numberFormatter.string(from: (steps * Double(state.bolusIncrement ?? 0.1)) as NSNumber)! + " U")
+                        .font(.headline)
+                        .focusable(true)
+                        .digitalCrownRotation(
+                            $steps,
+                            from: 0,
+                            through: Double((state.maxBolus ?? 5) / (state.bolusIncrement ?? 0.1)),
+                            by: 1,
+                            sensitivity: .medium,
+                            isContinuous: false,
+                            isHapticFeedbackEnabled: true
+                        )
+                    Spacer()
+                    Button {
+                        WKInterfaceDevice.current().play(.click)
+                        let newValue = steps + 1
+                        steps = min(newValue, Double((state.maxBolus ?? 5) / (state.bolusIncrement ?? 0.1)))
+                    } label: { Image(systemName: "plus") }
+                        .frame(width: geo.size.width / 4)
+                }
 
-            HStack {
-                Button {
-                    state.isBolusViewActive = false
+                HStack {
+                    Button {
+                        state.isBolusViewActive = false
+                    }
+                    label: {
+                        Image(systemName: "xmark.circle.fill")
+                            .resizable()
+                            .foregroundColor(.loopRed)
+                            .frame(width: 30, height: 30)
+                    }
+                    Button {
+                        WKInterfaceDevice.current().play(.click)
+                        enactBolus()
+                    }
+                    label: {
+                        Image(systemName: "checkmark.circle.fill")
+                            .resizable()
+                            .foregroundColor(.loopGreen)
+                            .frame(width: 30, height: 30)
+                    }
+                    .disabled(steps <= 0)
                 }
                 label: {
                     Image(systemName: "xmark.circle.fill")
@@ -75,7 +96,7 @@ struct BolusView: View {
 
     private func enactBolus() {
         let amount = steps * Double(state.bolusIncrement ?? 0.1)
-        state.enactBolus(amount: amount)
+        state.addBolus(amount: amount)
     }
 }
 

+ 47 - 2
FreeAPSWatch WatchKit Extension/Views/CarbsView.swift

@@ -54,9 +54,54 @@ struct CarbsView: View {
                         .frame(width: 24, height: 24)
                         .foregroundColor(.loopGreen)
                     Text("Add Carbs ")
+
+=======
+                    Button {
+                        WKInterfaceDevice.current().play(.click)
+                        let newValue = amount - 5
+                        amount = max(newValue, 0)
+                    } label: {
+                        Image(systemName: "minus")
+                    }
+                    .frame(width: geo.size.width / 4)
+                    Spacer()
+                    Text(numberFormatter.string(from: amount as NSNumber)! + " g")
+                        .font(.title2)
+                        .focusable(true)
+                        .digitalCrownRotation(
+                            $amount,
+                            from: 0,
+                            through: Double(state.maxCOB ?? 120),
+                            by: 1,
+                            sensitivity: .medium,
+                            isContinuous: false,
+                            isHapticFeedbackEnabled: true
+                        )
+                    Spacer()
+                    Button {
+                        WKInterfaceDevice.current().play(.click)
+                        let newValue = amount + 5
+                        amount = min(newValue, Double(state.maxCOB ?? 120))
+                    } label: { Image(systemName: "plus") }
+                        .frame(width: geo.size.width / 4)
                 }
-            }
-            .disabled(amount <= 0)
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    state.addCarbs(Int(amount))
+                }
+                label: {
+                    HStack {
+                        Image("carbs", bundle: nil)
+                            .renderingMode(.template)
+                            .resizable()
+                            .frame(width: 24, height: 24)
+                            .foregroundColor(.loopGreen)
+                        Text("Add Carbs ")
+                    }
+>>>>>>> 1e5c754... 38mm watch support and fixed bolus rounding
+                }
+                .disabled(amount <= 0)
+            }.frame(maxHeight: .infinity)
         }
         .navigationTitle("Add Carbs ")
         .onAppear {

+ 0 - 9
FreeAPSWatch WatchKit Extension/Views/ConfirmationView.swift

@@ -25,15 +25,6 @@ struct ConfirmationView: View {
             .frame(width: 50, height: 50)
         }
         .frame(maxWidth: .infinity, maxHeight: .infinity)
-        .onTapGesture {
-            toggleState()
-        }
-    }
-
-    func toggleState() {
-        withAnimation(.easeIn.speed(1)) {
-            success = success == nil ? true : success == true ? false : nil
-        }
     }
 }
 

+ 6 - 0
FreeAPSWatch WatchKit Extension/Views/MainView.swift

@@ -34,6 +34,12 @@ struct MainView: View {
                 ConfirmationView(success: $state.confirmationSuccess)
                     .background(Rectangle().fill(.black))
             }
+
+            if state.isConfirmationBolusViewActive {
+                BolusConfirmationView()
+                    .environmentObject(state)
+                    .background(Rectangle().fill(.black))
+            }
         }
         .frame(maxHeight: .infinity)
         .padding()

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

@@ -10,6 +10,7 @@ struct TempTargetsView: View {
             } else {
                 ForEach(state.tempTargets) { target in
                     Button {
+                        WKInterfaceDevice.current().play(.click)
                         state.enactTempTarget(id: target.id)
                     } label: {
                         VStack(alignment: .leading) {
@@ -27,6 +28,7 @@ struct TempTargetsView: View {
             }
 
             Button {
+                WKInterfaceDevice.current().play(.click)
                 state.enactTempTarget(id: "cancel")
             } label: {
                 Text("Cancel Temp Target")

+ 12 - 2
FreeAPSWatch WatchKit Extension/Views/WatchStateModel.swift

@@ -25,9 +25,11 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var isTempTargetViewActive = false
     @Published var isBolusViewActive = false
     @Published var isConfirmationViewActive = false
+    @Published var isConfirmationBolusViewActive = false
     @Published var confirmationSuccess: Bool?
     @Published var lastUpdate: Date = .distantPast
     @Published var timerDate = Date()
+    @Published var pendingBolus: Double?
 
     private var lifetime = Set<AnyCancellable>()
     let timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect()
@@ -71,10 +73,18 @@ class WatchStateModel: NSObject, ObservableObject {
         }
     }
 
-    func enactBolus(amount: Double) {
+    func addBolus(amount: Double) {
+        isBolusViewActive = false
+        pendingBolus = amount
+        isConfirmationBolusViewActive = true
+    }
+
+    func enactBolus() {
+        isConfirmationBolusViewActive = false
+        guard let amount = pendingBolus else { return }
+
         confirmationSuccess = nil
         isConfirmationViewActive = true
-        isBolusViewActive = false
         session.sendMessage(["bolus": amount], replyHandler: completionHandler) { error in
             print(error.localizedDescription)
             DispatchQueue.main.async {