Просмотр исходного кода

Merge branch 'dev' into maxIOB-maxCOB

Mike Plante 1 год назад
Родитель
Сommit
69526df612

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

@@ -2196,6 +2196,9 @@
         }
       }
     },
+    " SMB" : {
+      "comment" : "Super Micro Bolus indicator in delete alert"
+    },
     " SMBs are disabled either by schedule or during the entire duration." : {
       "comment" : "Alert string. Keep spaces.",
       "extractionState" : "manual",
@@ -27266,6 +27269,9 @@
         }
       }
     },
+    "All FPUs and the carbs of the meal will be deleted." : {
+      "comment" : "Alert message for meal deletion"
+    },
     "All FPUs of the meal will be deleted." : {
       "extractionState" : "manual",
       "localizations" : {
@@ -62833,6 +62839,9 @@
         }
       }
     },
+    "Delete Carbs Equivalents?" : {
+      "comment" : "Alert title for deleting carb equivalents"
+    },
     "Delete Carbs?" : {
       "comment" : "Delete carbs from data table and Nightscout",
       "extractionState" : "manual",
@@ -63888,6 +63897,9 @@
         }
       }
     },
+    "Delete the Temp Target Preset \"%@\"?" : {
+      "comment" : "Delete confirmation title for temporary target presets"
+    },
     "Delivery limits" : {
       "extractionState" : "manual",
       "localizations" : {

+ 6 - 0
Trio/Sources/Models/Determination.swift

@@ -19,6 +19,11 @@ struct Determination: JSON, Equatable {
     let reservoir: Decimal?
     var isf: Decimal?
     var timestamp: Date?
+
+    /// `tdd` (Total Daily Dose) is included so it can be part of the
+    /// enacted and suggested devicestatus data that gets uploaded to Nightscout.
+    var tdd: Decimal?
+
     var current_target: Decimal?
     let insulinForManualBolus: Decimal?
     let manualBolusErrorString: Decimal?
@@ -59,6 +64,7 @@ extension Determination {
         case timestamp
         case isf = "ISF"
         case current_target
+        case tdd = "TDD"
         case insulinForManualBolus
         case manualBolusErrorString
         case minDelta

+ 5 - 1
Trio/Sources/Modules/Adjustments/View/TempTargets/AdjustmentsRootView+TempTargets.swift

@@ -96,7 +96,11 @@ extension Adjustments.RootView {
     }
 
     private var deleteConfirmationTitle: String {
-        "Delete the Temp Target Preset \"\(selectedTempTarget?.name ?? "")\"?"
+        let presetName = selectedTempTarget?.name ?? ""
+        return String(
+            localized: "Delete the Temp Target Preset \"\(presetName)\"?",
+            comment: "Delete confirmation title for temporary target presets"
+        )
     }
 
     private func deleteConfirmationButtons() -> some View {

+ 17 - 6
Trio/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -367,7 +367,7 @@ extension DataTable {
                                 action: {
                                     alertGlucoseToDelete = glucose
 
-                                    alertTitle = "Delete Glucose?"
+                                    alertTitle = String(localized: "Delete Glucose?", comment: "Alert title for deleting glucose")
                                     alertMessage = Formatter.dateFormatter
                                         .string(from: glucose.date ?? Date()) + ", " +
                                         (Formatter.decimalFormatterWithTwoFractionDigits.string(for: glucose.glucose) ?? "0")
@@ -526,7 +526,7 @@ extension DataTable {
                         role: .none,
                         action: {
                             alertTreatmentToDelete = item
-                            alertTitle = "Delete Insulin?"
+                            alertTitle = String(localized: "Delete Insulin?", comment: "Alert title for deleting insulin")
                             alertMessage = Formatter.dateFormatter
                                 .string(from: item.timestamp ?? Date()) + ", " +
                                 (Formatter.decimalFormatterWithTwoFractionDigits.string(from: item.bolus?.amount ?? 0) ?? "0") +
@@ -534,7 +534,11 @@ extension DataTable {
 
                             if let bolus = item.bolus {
                                 // Add text snippet, so that alert message is more descriptive for SMBs
-                                alertMessage += bolus.isSMB ? " SMB" : ""
+                                alertMessage += bolus.isSMB ? String(
+                                    localized: " SMB",
+                                    comment: "Super Micro Bolus indicator in delete alert"
+                                )
+                                    : ""
                             }
 
                             isRemoveHistoryItemAlertPresented = true
@@ -603,7 +607,7 @@ extension DataTable {
 
                         // meal is carb-only
                         if meal.fpuID == nil {
-                            alertTitle = "Delete Carbs?"
+                            alertTitle = String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
                             alertMessage = Formatter.dateFormatter
                                 .string(from: meal.date ?? Date()) + ", " +
                                 (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
@@ -611,8 +615,15 @@ extension DataTable {
                         }
                         // meal is complex-meal or fpu-only
                         else {
-                            alertTitle = meal.isFPU ? "Delete Carbs Equivalents?" : "Delete Carbs?"
-                            alertMessage = "All FPUs and the carbs of the meal will be deleted."
+                            alertTitle = meal.isFPU ? String(
+                                localized: "Delete Carbs Equivalents?",
+                                comment: "Alert title for deleting carb equivalents"
+                            )
+                                : String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
+                            alertMessage = String(
+                                localized: "All FPUs and the carbs of the meal will be deleted.",
+                                comment: "Alert message for meal deletion"
+                            )
                         }
 
                         isRemoveHistoryItemAlertPresented = true

+ 6 - 6
Trio/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -123,6 +123,9 @@ extension NightscoutConfig {
                                         }
                                     }
                                 ).buttonStyle(BorderlessButtonStyle())
+                                    .alert(isPresented: $isImportAlertPresented) {
+                                        importAlert ?? Alert(title: Text("Unknown Error"))
+                                    }
                             }.padding(.top)
                         }.padding(.vertical)
                     }.listRowBackground(Color.chart)
@@ -177,6 +180,9 @@ extension NightscoutConfig {
                                             }
                                         }
                                     ).buttonStyle(BorderlessButtonStyle())
+                                        .alert(isPresented: $isBackfillAlertPresented) {
+                                            backfillAlert ?? Alert(title: Text("Unknown Error"))
+                                        }
                                 }.padding(.top)
                             }.padding(.vertical)
                         }
@@ -206,12 +212,6 @@ extension NightscoutConfig {
             }
             .navigationBarTitle("Nightscout")
             .navigationBarTitleDisplayMode(.automatic)
-            .alert(isPresented: $isImportAlertPresented) {
-                importAlert ?? Alert(title: Text("Unknown Error"))
-            }
-            .alert(isPresented: $isBackfillAlertPresented) {
-                backfillAlert ?? Alert(title: Text("Unknown Error"))
-            }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
         }

+ 70 - 10
Trio/Sources/Services/Network/Nightscout/NightscoutManager.swift

@@ -504,6 +504,19 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             return
         }
 
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TDDStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.predicateFor30MinAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        )
+
+        let tdd: Decimal? = await backgroundContext.perform {
+            (results as? [TDDStored])?.first?.total as? Decimal
+        }
+
         // Suggested / Enacted
         async let enactedDeterminationID = determinationStorage
             .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDeterminationsNotYetUploadedToNightscout)
@@ -543,6 +556,10 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 suggestion.minPredBG = suggestion.minPredBG?.asMmolL
                 suggestion.threshold = suggestion.threshold?.asMmolL
             }
+
+            suggestion.reason = injectTDD(into: suggestion.reason, tdd: tdd)
+            suggestion.tdd = tdd
+
             // Check whether the last suggestion that was uploaded is the same that is fetched again when we are attempting to upload the enacted determination
             // Apparently we are too fast; so the flag update is not fast enough to have the predicate filter last suggestion out
             // If this check is truthy, set suggestion to nil so it's not uploaded again
@@ -553,17 +570,20 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             }
         }
 
-        if let fetchedEnacted = fetchedEnactedDetermination, settingsManager.settings.units == .mmolL {
-            var modifiedFetchedEnactedDetermination = fetchedEnactedDetermination
-            modifiedFetchedEnactedDetermination?
-                .reason = parseReasonGlucoseValuesToMmolL(fetchedEnacted.reason)
-            // TODO: verify that these parsings are needed for 3rd party apps, e.g., LoopFollow
-            modifiedFetchedEnactedDetermination?.current_target = fetchedEnacted.current_target?.asMmolL
-            modifiedFetchedEnactedDetermination?.minGuardBG = fetchedEnacted.minGuardBG?.asMmolL
-            modifiedFetchedEnactedDetermination?.minPredBG = fetchedEnacted.minPredBG?.asMmolL
-            modifiedFetchedEnactedDetermination?.threshold = fetchedEnacted.threshold?.asMmolL
+        if var enacted = fetchedEnactedDetermination {
+            if settingsManager.settings.units == .mmolL {
+                enacted.reason = parseReasonGlucoseValuesToMmolL(enacted.reason)
+                // TODO: verify that these parsings are needed for 3rd party apps, e.g., LoopFollow
+                enacted.current_target = enacted.current_target?.asMmolL
+                enacted.minGuardBG = enacted.minGuardBG?.asMmolL
+                enacted.minPredBG = enacted.minPredBG?.asMmolL
+                enacted.threshold = enacted.threshold?.asMmolL
+            }
 
-            fetchedEnactedDetermination = modifiedFetchedEnactedDetermination
+            enacted.reason = injectTDD(into: enacted.reason, tdd: tdd)
+            enacted.tdd = tdd
+
+            fetchedEnactedDetermination = enacted
         }
 
         // Gather all relevant data for OpenAPS Status
@@ -1453,3 +1473,43 @@ extension BaseNightscoutManager {
         return updatedReason
     }
 }
+
+extension BaseNightscoutManager {
+    /// Injects TDD into the provided `reason` string if TDD is available.
+    ///
+    /// - Parameters:
+    ///   - reason: The raw reason string (e.g., "minPredBG=5.2, IOBpredBG=102").
+    ///   - tdd: The total daily dose of insulin.
+    /// - Returns: A modified reason string that includes "TDD: x U" appended
+    ///   after the last matched prediction term, or at the end if no match is found.
+    func injectTDD(into reason: String, tdd: Decimal?) -> String {
+        guard let tdd = tdd else { return reason }
+
+        let tddString = ", TDD: \(tdd) U"
+
+        // Regex that matches any of the keywords followed by an optional colon, whitespace, then a number.
+        let pattern = "(minPredBG|minGuardBG|IOBpredBG|COBpredBG|UAMpredBG):?\\s*(-?\\d+(?:\\.\\d+)?)"
+        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
+            return reason + tddString
+        }
+
+        // Split the reason at the first semicolon (if present)
+        let components = reason.split(separator: ";", maxSplits: 1, omittingEmptySubsequences: false)
+        let mainPart = String(components[0])
+        let tailPart = components.count > 1 ? ";" + components[1] : ""
+
+        // Search only in the main part for the keywords
+        let nsRange = NSRange(mainPart.startIndex ..< mainPart.endIndex, in: mainPart)
+        let matches = regex.matches(in: mainPart, options: [], range: nsRange)
+
+        // If found, insert TDD after the last occurrence in the main part.
+        if let lastMatch = matches.last, let matchRange = Range(lastMatch.range, in: mainPart) {
+            var modifiedMainPart = mainPart
+            modifiedMainPart.insert(contentsOf: tddString, at: matchRange.upperBound)
+            return modifiedMainPart + tailPart
+        }
+
+        // If no match is found, append TDD at the end of the original reason string.
+        return reason + tddString
+    }
+}