Преглед изворни кода

Merge pull request #426 from nightscout/improve-loop-status

Improve Loop Status UX and OR/TT Handling
marv-out пре 1 година
родитељ
комит
5ef7cef37d

+ 12 - 15
Trio/Sources/APS/APSManager.swift

@@ -33,23 +33,20 @@ enum APSError: LocalizedError {
     case invalidPumpState(message: String)
     case glucoseError(message: String)
     case apsError(message: String)
-    case deviceSyncError(message: String)
     case manualBasalTemp(message: String)
 
     var errorDescription: String? {
         switch self {
         case let .pumpError(error):
-            return "Pump error: \(error.localizedDescription)"
+            return String(localized: "Pump Error (\(error.localizedDescription)).")
         case let .invalidPumpState(message):
-            return "Error: Invalid Pump State: \(message)"
+            return String(localized: "Invalid Pump State (\(message)).")
         case let .glucoseError(message):
-            return "Error: Invalid glucose: \(message)"
+            return String(localized: "Invalid Glucose (\(message)).")
         case let .apsError(message):
-            return "APS error: \(message)"
-        case let .deviceSyncError(message):
-            return "Sync error: \(message)"
+            return String(localized: "Invalid Algorithm Response (\(message)).")
         case let .manualBasalTemp(message):
-            return "Manual Basal Temp : \(message)"
+            return String(localized: "Manual Temporary Basal Rate (\(message)). Looping suspended.")
         }
     }
 }
@@ -351,21 +348,21 @@ final class BaseAPSManager: APSManager, Injectable {
 
     private func verifyStatus() -> Error? {
         guard let pump = pumpManager else {
-            return APSError.invalidPumpState(message: "Pump not set")
+            return APSError.invalidPumpState(message: String(localized: "Pump not set"))
         }
         let status = pump.status.pumpStatus
 
         guard !status.bolusing else {
-            return APSError.invalidPumpState(message: "Pump is bolusing")
+            return APSError.invalidPumpState(message: String(localized: "Pump is bolusing"))
         }
 
         guard !status.suspended else {
-            return APSError.invalidPumpState(message: "Pump suspended")
+            return APSError.invalidPumpState(message: String(localized: "Pump suspended"))
         }
 
         let reservoir = storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self) ?? 100
         guard reservoir >= 0 else {
-            return APSError.invalidPumpState(message: "Reservoir is empty")
+            return APSError.invalidPumpState(message: String(localized: "Reservoir is empty"))
         }
 
         return nil
@@ -417,20 +414,20 @@ final class BaseAPSManager: APSManager, Injectable {
 
             guard glucose.count > 2 else {
                 debug(.apsManager, "Not enough glucose data")
-                self.processError(APSError.glucoseError(message: "Not enough glucose data"))
+                self.processError(APSError.glucoseError(message: String(localized: "Not enough glucose data")))
                 return false
             }
 
             let dateOfLastGlucose = glucose.first?.date
             guard dateOfLastGlucose ?? Date() >= Date().addingTimeInterval(-12.minutes.timeInterval) else {
                 debug(.apsManager, "Glucose data is stale")
-                self.processError(APSError.glucoseError(message: "Glucose data is stale"))
+                self.processError(APSError.glucoseError(message: String(localized: "Glucose data is stale")))
                 return false
             }
 
             guard !GlucoseStored.glucoseIsFlat(glucose) else {
                 debug(.apsManager, "Glucose data is too flat")
-                self.processError(APSError.glucoseError(message: "Glucose data is too flat"))
+                self.processError(APSError.glucoseError(message: String(localized: "Glucose data is too flat")))
                 return false
             }
 

+ 1 - 1
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -373,7 +373,7 @@ final class OpenAPS {
 
             return determination
         } else {
-            throw APSError.apsError(message: "Determination is nil")
+            throw APSError.apsError(message: "No determination data.")
         }
     }
 

+ 46 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -77509,6 +77509,9 @@
         }
       }
     },
+    "Enacted at %@" : {
+
+    },
     "Enacting bolus..." : {
       "comment" : "Successful message sent to watch when enacting bolus",
       "localizations" : {
@@ -78834,6 +78837,7 @@
       }
     },
     "Error During Algorithm Run at %@" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -89337,6 +89341,12 @@
     "Glucose Count" : {
 
     },
+    "Glucose data is stale" : {
+
+    },
+    "Glucose data is too flat" : {
+
+    },
     "Glucose Data used for statistics" : {
       "comment" : "Debug option view Glucose Data used for statistics",
       "extractionState" : "manual",
@@ -102222,6 +102232,9 @@
         }
       }
     },
+    "Invalid Algorithm Response (%@)." : {
+
+    },
     "Invalid CGM reading (HIGH)." : {
       "localizations" : {
         "bg" : {
@@ -102322,6 +102335,9 @@
         }
       }
     },
+    "Invalid Glucose (%@)." : {
+
+    },
     "Invalid Glucose sample detected, try again later" : {
       "comment" : "Invalid Glucose sample detected, try again later",
       "extractionState" : "manual",
@@ -102538,6 +102554,9 @@
         }
       }
     },
+    "Invalid Pump State (%@)." : {
+
+    },
     "Invalid sensor" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -109182,6 +109201,9 @@
         }
       }
     },
+    "Loop at %@ failed." : {
+
+    },
     "Loop Cycles" : {
       "comment" : "Debug option view",
       "extractionState" : "manual",
@@ -112199,6 +112221,9 @@
         }
       }
     },
+    "Manual Temporary Basal Rate (%@). Looping suspended." : {
+
+    },
     "Manual:" : {
 
     },
@@ -124780,6 +124805,12 @@
     "Not allowed" : {
 
     },
+    "Not enough glucose data" : {
+
+    },
+    "Not looping." : {
+
+    },
     "Not paired yet" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -137643,6 +137674,9 @@
         }
       }
     },
+    "Pump Error (%@)." : {
+
+    },
     "Pump History" : {
       "comment" : "Debug option view Pump History",
       "extractionState" : "manual",
@@ -137851,6 +137885,9 @@
         }
       }
     },
+    "Pump is bolusing" : {
+
+    },
     "Pump Model" : {
       "localizations" : {
         "bg" : {
@@ -137951,6 +137988,9 @@
         }
       }
     },
+    "Pump not set" : {
+
+    },
     "Pump profile" : {
       "comment" : "Debug option view Pump profile",
       "extractionState" : "manual",
@@ -142321,6 +142361,9 @@
         }
       }
     },
+    "Reservoir is empty" : {
+
+    },
     "Reset to Defaults" : {
       "localizations" : {
         "bg" : {
@@ -182408,6 +182451,9 @@
         }
       }
     },
+    "Trio has not looped in %lld minutes." : {
+
+    },
     "Trio has only been actively used and looping for less than seven days. Cannot enable dynamic ISF." : {
 
     },

+ 3 - 0
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift

@@ -232,6 +232,9 @@ extension Adjustments.StateModel {
                 // execute sequentially instead of concurrently
                 await self.updateLatestOverrideConfigurationOfState(from: id)
                 await self.setCurrentOverride(from: id)
+
+                // perform determine basal sync to immediately apply override changes
+                try await apsManager.determineBasalSync()
             } catch {
                 debug(
                     .default,

+ 3 - 0
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift

@@ -16,6 +16,9 @@ extension Adjustments.StateModel {
                 async let updateState: () = updateLatestTempTargetConfigurationOfState(from: id)
                 async let setTempTarget: () = setCurrentTempTarget(from: id)
                 _ = await (updateState, setTempTarget)
+
+                // perform determine basal sync to immediately apply temp target changes
+                try await apsManager.determineBasalSync()
             } catch {
                 debug(
                     .default,

+ 33 - 5
Trio/Sources/Modules/Home/View/Header/LoopStatusView.swift

@@ -12,6 +12,7 @@ struct LoopStatusView: View {
     @State var helpSheetDetent = PresentationDetent.fraction(0.9)
 
     @State private var statusTitle: String = ""
+    @State private var lastDetermination: OrefDetermination?
 
     var body: some View {
         ScrollView {
@@ -44,7 +45,8 @@ struct LoopStatusView: View {
 
                 if let errorMessage = state.errorMessage, let date = state.errorDate {
                     Group {
-                        Text("Error During Algorithm Run at \(Formatter.dateFormatter.string(from: date))").font(.headline)
+                        Text("Loop at \(Formatter.dateFormatter.string(from: date)) failed.").font(.headline)
+                            .font(.headline)
                             .fixedSize(horizontal: false, vertical: true)
                         Text(errorMessage).font(.caption).fixedSize(horizontal: false, vertical: true)
                     }.foregroundColor(.loopRed)
@@ -124,6 +126,9 @@ struct LoopStatusView: View {
                 LoopStatusHelpView(state: state, helpSheetDetent: $helpSheetDetent, isHelpSheetPresented: $isHelpSheetPresented)
             }
         }
+        .onAppear {
+            lastDetermination = state.determinationsFromPersistence.first
+        }
         .presentationDetents([.height(sheetContentHeight)])
         .presentationDragIndicator(.visible)
         .onPreferenceChange(ContentSizeKey.self) { newSize in
@@ -163,11 +168,34 @@ struct LoopStatusView: View {
     }
 
     private func setStatusTitle() {
-        if let determination = state.determinationsFromPersistence.first {
-            statusTitle =
-                "Enacted at \(Formatter.dateFormatter.string(from: determination.deliverAt ?? Date()))"
+        if let determination = state.determinationsFromPersistence.first, let deliverAt = determination.deliverAt {
+            let minutesAgo = abs(deliverAt.timeIntervalSinceNow) / 60
+
+            if deliverAt < Date().addingTimeInterval(-5 * 60) {
+                let roundedMinutes = Int(minutesAgo.rounded())
+                statusTitle = String(
+                    localized: "Trio has not looped in \(roundedMinutes) minutes."
+                )
+            } else {
+                statusTitle = String(
+                    localized: "Enacted at \(Formatter.dateFormatter.string(from: deliverAt))"
+                )
+            }
+        } else if let determination = lastDetermination, let deliverAt = determination.deliverAt {
+            let minutesAgo = abs(deliverAt.timeIntervalSinceNow) / 60
+
+            if deliverAt < Date().addingTimeInterval(-5 * 60) {
+                let roundedMinutes = Int(minutesAgo.rounded())
+                statusTitle = String(
+                    localized: "Trio has not looped in \(roundedMinutes) minutes."
+                )
+            } else {
+                statusTitle = String(
+                    localized: "Enacted at \(Formatter.dateFormatter.string(from: deliverAt))"
+                )
+            }
         } else {
-            statusTitle = "Not enacted."
+            statusTitle = String(localized: "Not looping.")
         }
     }