Przeglądaj źródła

Merge branch 'dev' into maxIOB-maxCOB

Mike Plante 1 rok temu
rodzic
commit
12366f039d
34 zmienionych plików z 415 dodań i 204 usunięć
  1. 1 1
      G7SensorKit
  2. 1 1
      LibreTransmitter
  3. 1 1
      OmniKit
  4. 1 1
      RileyLinkKit
  5. 4 0
      Trio.xcodeproj/project.pbxproj
  6. 2 2
      Trio/Sources/APS/APSManager.swift
  7. 2 2
      Trio/Sources/APS/FetchGlucoseManager.swift
  8. 1 0
      Trio/Sources/Application/AppDelegate.swift
  9. 9 4
      Trio/Sources/Application/TrioApp.swift
  10. 30 2
      Trio/Sources/Helpers/BuildDetails.swift
  11. 22 6
      Trio/Sources/Helpers/CustomProgressView.swift
  12. 99 2
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  13. 1 0
      Trio/Sources/Models/NightscoutStatus.swift
  14. 2 2
      Trio/Sources/Models/TrioSettings.swift
  15. 60 47
      Trio/Sources/Modules/CGMSettings/CGMSettingsStateModel.swift
  16. 5 1
      Trio/Sources/Modules/CGMSettings/View/CGMRootView.swift
  17. 4 6
      Trio/Sources/Modules/CGMSettings/View/CustomCGMOptionsView.swift
  18. 1 1
      Trio/Sources/Modules/DataTable/View/DataTableRootView.swift
  19. 77 0
      Trio/Sources/Modules/Home/HomeStateModel+CGM.swift
  20. 26 64
      Trio/Sources/Modules/Home/HomeStateModel.swift
  21. 1 1
      Trio/Sources/Modules/Settings/SettingsStateModel.swift
  22. 1 1
      Trio/Sources/Modules/Settings/View/SettingsRootView.swift
  23. 8 4
      Trio/Sources/Modules/Stat/StatStateModel.swift
  24. 2 2
      Trio/Sources/Modules/Stat/View/StatRootView.swift
  25. 1 1
      Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift
  26. 6 1
      Trio/Sources/Services/Network/Nightscout/NightscoutManager.swift
  27. 1 1
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+APNS.swift
  28. 2 2
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+Meal.swift
  29. 18 16
      Trio/Sources/Shortcuts/Bolus/BolusIntent.swift
  30. 3 6
      Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift
  31. 2 2
      Trio/Sources/Shortcuts/LiveActivity/RestartLiveActivityIntent.swift
  32. 16 17
      Trio/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift
  33. 3 3
      Trio/Sources/Shortcuts/Override/CancelOverrideIntent.swift
  34. 2 4
      Trio/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift

+ 1 - 1
G7SensorKit

@@ -1 +1 @@
-Subproject commit 205054e7537723c2aec58d807634b4853f687244
+Subproject commit bdfcfe83fbb9fab515a2456a4be9991420ed44bb

+ 1 - 1
LibreTransmitter

@@ -1 +1 @@
-Subproject commit a230b91a3d30c7b0d4ffbd240234b34cbaf354b1
+Subproject commit 044cf70bd79813d47048291b740a599e1ab4ab40

+ 1 - 1
OmniKit

@@ -1 +1 @@
-Subproject commit 39915b05fe46b5d73eca52e156dd7efd11193ee8
+Subproject commit 92948a7684ec382714becc53c643a1617597bb37

+ 1 - 1
RileyLinkKit

@@ -1 +1 @@
-Subproject commit a0e419da314d0ad42b41ccb04af73cd1fbf41257
+Subproject commit eddfd4f00bbf0d78035dc31e6f7715e72252c566

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -204,6 +204,7 @@
 		38FEF413273B317A00574A46 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF412273B317A00574A46 /* HKUnit.swift */; };
 		38FEF413273B317A00574A46 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF412273B317A00574A46 /* HKUnit.swift */; };
 		3B2F77862D7E52ED005ED9FA /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77852D7E52ED005ED9FA /* TDD.swift */; };
 		3B2F77862D7E52ED005ED9FA /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77852D7E52ED005ED9FA /* TDD.swift */; };
 		3B2F77882D7E5387005ED9FA /* CurrentTDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */; };
 		3B2F77882D7E5387005ED9FA /* CurrentTDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */; };
+		3B4196E02D8C4BC00091DFF7 /* HomeStateModel+CGM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B4196DF2D8C4BBB0091DFF7 /* HomeStateModel+CGM.swift */; };
 		3BAD36B22D7CDC1A00CC298D /* MainLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */; };
 		3BAD36B22D7CDC1A00CC298D /* MainLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */; };
 		3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */; };
 		3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
@@ -925,6 +926,7 @@
 		38FEF412273B317A00574A46 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = "<group>"; };
 		38FEF412273B317A00574A46 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = "<group>"; };
 		3B2F77852D7E52ED005ED9FA /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
 		3B2F77852D7E52ED005ED9FA /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
 		3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentTDDSetup.swift; sourceTree = "<group>"; };
 		3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentTDDSetup.swift; sourceTree = "<group>"; };
+		3B4196DF2D8C4BBB0091DFF7 /* HomeStateModel+CGM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeStateModel+CGM.swift"; sourceTree = "<group>"; };
 		3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoadingView.swift; sourceTree = "<group>"; };
 		3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoadingView.swift; sourceTree = "<group>"; };
 		3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataInitializationCoordinator.swift; sourceTree = "<group>"; };
 		3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataInitializationCoordinator.swift; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
@@ -1755,6 +1757,7 @@
 				3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */,
 				3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */,
 				3811DE2925C9D49500A708ED /* HomeProvider.swift */,
 				3811DE2925C9D49500A708ED /* HomeProvider.swift */,
 				3811DE2825C9D49500A708ED /* HomeStateModel.swift */,
 				3811DE2825C9D49500A708ED /* HomeStateModel.swift */,
+				3B4196DF2D8C4BBB0091DFF7 /* HomeStateModel+CGM.swift */,
 				58645B972CA2D16A008AFCE7 /* HomeStateModel+Setup */,
 				58645B972CA2D16A008AFCE7 /* HomeStateModel+Setup */,
 				3811DE2C25C9D49500A708ED /* View */,
 				3811DE2C25C9D49500A708ED /* View */,
 			);
 			);
@@ -3880,6 +3883,7 @@
 				CE7950242997D81700FA576E /* CGMSettingsView.swift in Sources */,
 				CE7950242997D81700FA576E /* CGMSettingsView.swift in Sources */,
 				58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */,
 				58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */,
 				BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */,
 				BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */,
+				3B4196E02D8C4BC00091DFF7 /* HomeStateModel+CGM.swift in Sources */,
 				38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */,
 				38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */,
 				DD32CF9C2CC82499003686D6 /* TrioRemoteControl+TempTarget.swift in Sources */,
 				DD32CF9C2CC82499003686D6 /* TrioRemoteControl+TempTarget.swift in Sources */,
 				BD249D882D42FC0000412DEB /* BolusStatsView.swift in Sources */,
 				BD249D882D42FC0000412DEB /* BolusStatsView.swift in Sources */,

+ 2 - 2
Trio/Sources/APS/APSManager.swift

@@ -945,11 +945,11 @@ final class BaseAPSManager: APSManager, Injectable {
             }
             }
             let af = pref.adjustmentFactor
             let af = pref.adjustmentFactor
             let insulin_type = pref.curve
             let insulin_type = pref.curve
-            let buildDate = BuildDetails.default.buildDate()
+            let buildDate = BuildDetails.shared.buildDate()
             let version = Bundle.main.releaseVersionNumber
             let version = Bundle.main.releaseVersionNumber
             let build = Bundle.main.buildVersionNumber
             let build = Bundle.main.buildVersionNumber
 
 
-            var branch = BuildDetails.default.branchAndSha
+            var branch = BuildDetails.shared.branchAndSha
 
 
             let copyrightNotice_ = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
             let copyrightNotice_ = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
             let pump_ = pumpManager?.localizedTitle ?? ""
             let pump_ = pumpManager?.localizedTitle ?? ""

+ 2 - 2
Trio/Sources/APS/FetchGlucoseManager.swift

@@ -122,12 +122,12 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
     @MainActor func deleteGlucoseSource() async {
     @MainActor func deleteGlucoseSource() async {
         cgmManager = nil
         cgmManager = nil
         glucoseSource = nil
         glucoseSource = nil
+        settingsManager.settings.cgm = cgmDefaultModel.type
+        settingsManager.settings.cgmPluginIdentifier = cgmDefaultModel.id
         updateGlucoseSource(
         updateGlucoseSource(
             cgmGlucoseSourceType: cgmDefaultModel.type,
             cgmGlucoseSourceType: cgmDefaultModel.type,
             cgmGlucosePluginId: cgmDefaultModel.id
             cgmGlucosePluginId: cgmDefaultModel.id
         )
         )
-        settingsManager.settings.cgm = cgmDefaultModel.type
-        settingsManager.settings.cgmPluginIdentifier = cgmDefaultModel.id
     }
     }
 
 
     func saveConfigManager() {
     func saveConfigManager() {

+ 1 - 0
Trio/Sources/Application/AppDelegate.swift

@@ -31,6 +31,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject, UNUserNoti
                         .default,
                         .default,
                         "\(DebuggingIdentifiers.failed) failed to handle remote notification with error: \(error.localizedDescription)"
                         "\(DebuggingIdentifiers.failed) failed to handle remote notification with error: \(error.localizedDescription)"
                     )
                     )
+                    completionHandler(.failed)
                 }
                 }
             }
             }
         } catch {
         } catch {

+ 9 - 4
Trio/Sources/Application/TrioApp.swift

@@ -80,13 +80,13 @@ extension Notification.Name {
     }
     }
 
 
     init() {
     init() {
-        let submodulesInfo = BuildDetails.default.submodules.map { key, value in
+        let submodulesInfo = BuildDetails.shared.submodules.map { key, value in
             "\(key): \(value.branch) \(value.commitSHA)"
             "\(key): \(value.branch) \(value.commitSHA)"
         }.joined(separator: ", ")
         }.joined(separator: ", ")
 
 
         debug(
         debug(
             .default,
             .default,
-            "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(String(describing: BuildDetails.default.buildDate()))] [buildExpires: \(String(describing: BuildDetails.default.calculateExpirationDate()))] [submodules: \(submodulesInfo)]"
+            "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(String(describing: BuildDetails.shared.buildDate()))] [buildExpires: \(String(describing: BuildDetails.shared.calculateExpirationDate()))] [submodules: \(submodulesInfo)]"
         )
         )
 
 
         // Fix bug in iOS 18 related to the translucent tab bar
         // Fix bug in iOS 18 related to the translucent tab bar
@@ -104,7 +104,7 @@ extension Notification.Name {
             do {
             do {
                 try await coreDataStack.initializeStack()
                 try await coreDataStack.initializeStack()
 
 
-                await MainActor.run {
+                await Task { @MainActor in
                     // Only load services after successful Core Data initialization
                     // Only load services after successful Core Data initialization
                     loadServices()
                     loadServices()
 
 
@@ -114,7 +114,12 @@ extension Notification.Name {
                     self.initState.complete = true
                     self.initState.complete = true
                     Foundation.NotificationCenter.default.post(name: .initializationCompleted, object: nil)
                     Foundation.NotificationCenter.default.post(name: .initializationCompleted, object: nil)
                     UIApplication.shared.registerForRemoteNotifications()
                     UIApplication.shared.registerForRemoteNotifications()
-                }
+                    do {
+                        try await BuildDetails.shared.handleExpireDateChange()
+                    } catch {
+                        debug(.default, "Failed to handle expire date change: \(error)")
+                    }
+                }.value
             } catch {
             } catch {
                 debug(
                 debug(
                     .coreData,
                     .coreData,

+ 30 - 2
Trio/Sources/Helpers/BuildDetails.swift

@@ -1,9 +1,12 @@
 import Foundation
 import Foundation
+import Swinject
 
 
-class BuildDetails {
-    static var `default` = BuildDetails()
+class BuildDetails: Injectable {
+    static var shared = BuildDetails()
+    @Injected() internal var nightscoutManager: NightscoutManager!
 
 
     let dict: [String: Any]
     let dict: [String: Any]
+    let previousExpireDateKey = "previousExpireDate"
 
 
     init() {
     init() {
         guard let url = Bundle.main.url(forResource: "BuildDetails", withExtension: "plist"),
         guard let url = Bundle.main.url(forResource: "BuildDetails", withExtension: "plist"),
@@ -89,4 +92,29 @@ class BuildDetails {
             return String(localized: "App Expires")
             return String(localized: "App Expires")
         }
         }
     }
     }
+
+    // Upload new profile if expire date has changed
+    func handleExpireDateChange() async throws
+    {
+        if nightscoutManager == nil {
+            await injectServices(TrioApp.resolver)
+        }
+
+        let previousExpireDate = UserDefaults.standard.object(forKey: previousExpireDateKey) as? Date
+        let expireDate = calculateExpirationDate()
+
+        if previousExpireDate != expireDate {
+            debug(.nightscout, "New build expire date detected, uploading profile")
+            try await nightscoutManager.uploadProfiles()
+        }
+    }
+
+    // Store the uploaded expire date
+    func recordUploadedExpireDate(expireDate: Date?) {
+        if let expireDate = expireDate {
+            UserDefaults.standard.set(expireDate, forKey: previousExpireDateKey)
+        } else {
+            UserDefaults.standard.removeObject(forKey: previousExpireDateKey)
+        }
+    }
 }
 }

+ 22 - 6
Trio/Sources/Helpers/CustomProgressView.swift

@@ -43,10 +43,26 @@ struct CustomProgressView: View {
     }
     }
 }
 }
 
 
-enum ProgressText: String {
-    case updatingIOB = "Updating IOB ..."
-    case updatingCOB = "Updating COB ..."
-    case updatingHistory = "Updating History ..."
-    case updatingTreatments = "Updating Treatments ..."
-    case updatingIOBandCOB = "Updating IOB and COB ..."
+enum ProgressText: CaseIterable {
+    case updatingIOB
+    case updatingCOB
+    case updatingHistory
+    case updatingTreatments
+    case updatingIOBandCOB
+
+    var displayName: String {
+        switch self {
+        case .updatingIOB:
+            return String(localized: "Updating IOB ...", comment: "Status message for updating IOB")
+        case .updatingCOB:
+
+            return String(localized: "Updating COB ...", comment: "Status message for updating COB")
+        case .updatingHistory:
+            return String(localized: "Updating History ...", comment: "Status message for updating history")
+        case .updatingTreatments:
+            return String(localized: "Updating Treatments ...", comment: "Status message for updating treatments")
+        case .updatingIOBandCOB:
+            return String(localized: "Updating IOB and COB ...", comment: "Status message for updating both IOB and COB")
+        }
+    }
 }
 }

+ 99 - 2
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -17356,6 +17356,9 @@
         }
         }
       }
       }
     },
     },
+    "A bolus command of %@ U of insulin was sent." : {
+
+    },
     "A display of On/On indicates both Dynamic ISF and CR are enabled. On/Off indicates only Dynamic ISF is enabled. Dynamic CR cannot be enabled when Dynamic ISF is disabled." : {
     "A display of On/On indicates both Dynamic ISF and CR are enabled. On/Off indicates only Dynamic ISF is enabled. Dynamic CR cannot be enabled when Dynamic ISF is disabled." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -18772,6 +18775,9 @@
         }
         }
       }
       }
     },
     },
+    "Activate an override" : {
+
+    },
     "Activate Dynamic Carb Ratio (CR)" : {
     "Activate Dynamic Carb Ratio (CR)" : {
       "comment" : "Enable Dyn CR",
       "comment" : "Enable Dyn CR",
       "localizations" : {
       "localizations" : {
@@ -28996,6 +29002,9 @@
         }
         }
       }
       }
     },
     },
+    "Allow to send a bolus to the app" : {
+
+    },
     "Allow Trio to read from and write to Apple Health." : {
     "Allow Trio to read from and write to Apple Health." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -33599,6 +33608,9 @@
         }
         }
       }
       }
     },
     },
+    "Applying ${bolusQuantity} U" : {
+
+    },
     "Applying ${carbQuantity} at ${dateAdded}" : {
     "Applying ${carbQuantity} at ${dateAdded}" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -33799,6 +33811,9 @@
         }
         }
       }
       }
     },
     },
+    "Applying ${preset} override" : {
+
+    },
     "Are you sure to add %@ g of carbs ?" : {
     "Are you sure to add %@ g of carbs ?" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -33899,6 +33914,9 @@
         }
         }
       }
       }
     },
     },
+    "Are you sure you want to bolus %@ U of insulin?" : {
+
+    },
     "Are you sure you want to delete %@?" : {
     "Are you sure you want to delete %@?" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -39772,6 +39790,12 @@
         }
         }
       }
       }
     },
     },
+    "Bolus amount (units of insulin)?" : {
+
+    },
+    "Bolus amount in U" : {
+
+    },
     "Bolus Calculator" : {
     "Bolus Calculator" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -41221,6 +41245,9 @@
         }
         }
       }
       }
     },
     },
+    "Bolusing via Shortcuts is disabled in Trio settings." : {
+
+    },
     "Bottom target" : {
     "Bottom target" : {
       "comment" : "Bottom target temp",
       "comment" : "Bottom target temp",
       "extractionState" : "manual",
       "extractionState" : "manual",
@@ -43080,6 +43107,9 @@
         }
         }
       }
       }
     },
     },
+    "Cancel an active override" : {
+
+    },
     "Cancel Bolus" : {
     "Cancel Bolus" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -43180,6 +43210,9 @@
         }
         }
       }
       }
     },
     },
+    "Cancel override" : {
+
+    },
     "Cancel Override" : {
     "Cancel Override" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -51679,6 +51712,9 @@
         }
         }
       }
       }
     },
     },
+    "Confirm to apply override '%@'" : {
+
+    },
     "Confirm to apply Temporary Target '%@'" : {
     "Confirm to apply Temporary Target '%@'" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -88993,7 +89029,7 @@
       }
       }
     },
     },
     "Glucose" : {
     "Glucose" : {
-      "comment" : "Glucose\nHistory Mode",
+      "comment" : "Glucose\nHistory Mode\nTitle for glucose-related statistics",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -96937,6 +96973,9 @@
         }
         }
       }
       }
     },
     },
+    "If toggled, you will need to confirm before applying." : {
+
+    },
     "If using Dynamic ISF (with Sigmoid), overriding your ISF will adjust the ISF used at your glucose target which extends to the ISF used at other glucose. Overriding your glucose target will change glucose level your ISF will be set to your profile ISF. Both of these can be combined in a single Override." : {
     "If using Dynamic ISF (with Sigmoid), overriding your ISF will adjust the ISF used at your glucose target which extends to the ISF used at other glucose. Overriding your glucose target will change glucose level your ISF will be set to your profile ISF. Both of these can be combined in a single Override." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -97754,6 +97793,9 @@
     "Ignore Warning and Enact Bolus" : {
     "Ignore Warning and Enact Bolus" : {
 
 
     },
     },
+    "Immediately applying ${bolusQuantity} U" : {
+
+    },
     "Immediately applying ${carbQuantity} at ${dateAdded}" : {
     "Immediately applying ${carbQuantity} at ${dateAdded}" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -97954,6 +97996,9 @@
         }
         }
       }
       }
     },
     },
+    "Immediately applying ${preset} override" : {
+
+    },
     "Import Error" : {
     "Import Error" : {
       "comment" : "Import Error HeadlineImport Error Headline",
       "comment" : "Import Error HeadlineImport Error Headline",
       "extractionState" : "manual",
       "extractionState" : "manual",
@@ -100623,6 +100668,7 @@
       }
       }
     },
     },
     "Insulin" : {
     "Insulin" : {
+      "comment" : "Title for insulin-related statistics",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -109440,6 +109486,9 @@
         }
         }
       }
       }
     },
     },
+    "Looping" : {
+      "comment" : "Title for looping and system statistics"
+    },
     "Looping Chart Type" : {
     "Looping Chart Type" : {
 
 
     },
     },
@@ -112445,6 +112494,9 @@
         }
         }
       }
       }
     },
     },
+    "Max bolus" : {
+
+    },
     "Max Bolus" : {
     "Max Bolus" : {
       "comment" : "Max setting",
       "comment" : "Max setting",
       "localizations" : {
       "localizations" : {
@@ -116547,7 +116599,7 @@
 
 
     },
     },
     "Meals" : {
     "Meals" : {
-      "comment" : "History Mode",
+      "comment" : "History Mode\nTitle for meal-related statistics",
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
           "stringUnit" : {
           "stringUnit" : {
@@ -124597,6 +124649,9 @@
         }
         }
       }
       }
     },
     },
+    "Not allowed" : {
+
+    },
     "Not paired yet" : {
     "Not paired yet" : {
       "extractionState" : "manual",
       "extractionState" : "manual",
       "localizations" : {
       "localizations" : {
@@ -130723,6 +130778,18 @@
         }
         }
       }
       }
     },
     },
+    "Override '%@' applied" : {
+
+    },
+    "Override '%@' failed" : {
+
+    },
+    "Override canceled" : {
+
+    },
+    "Override choice" : {
+
+    },
     "Override eA1c Unit" : {
     "Override eA1c Unit" : {
 
 
     },
     },
@@ -142332,6 +142399,9 @@
     "Restart Live Activity" : {
     "Restart Live Activity" : {
 
 
     },
     },
+    "Restarts Trio's Live Activity" : {
+
+    },
     "Result" : {
     "Result" : {
       "comment" : "For the  Bolus View pop-up",
       "comment" : "For the  Bolus View pop-up",
       "extractionState" : "stale",
       "extractionState" : "stale",
@@ -147059,6 +147129,9 @@
         }
         }
       }
       }
     },
     },
+    "Select override" : {
+
+    },
     "Select Pump Model" : {
     "Select Pump Model" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -165292,6 +165365,12 @@
         }
         }
       }
       }
     },
     },
+    "Temporary Target '%@' applied" : {
+
+    },
+    "Temporary Target '%@' failed" : {
+
+    },
     "Temporary Target canceled" : {
     "Temporary Target canceled" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -165902,6 +165981,9 @@
     "The bolus calculator uses various inputs to determine the recommended insulin dosage. Find the detailed calculations below." : {
     "The bolus calculator uses various inputs to determine the recommended insulin dosage. Find the detailed calculations below." : {
 
 
     },
     },
+    "The bolus cannot be larger than the pump setting max bolus (%@)." : {
+
+    },
     "The current version has a critical issue and should be updated as soon as possible." : {
     "The current version has a critical issue and should be updated as soon as possible." : {
       "comment" : "Message for critical update alert",
       "comment" : "Message for critical update alert",
       "localizations" : {
       "localizations" : {
@@ -186560,6 +186642,18 @@
         }
         }
       }
       }
     },
     },
+    "Updating COB ..." : {
+      "comment" : "Status message for updating COB"
+    },
+    "Updating History ..." : {
+      "comment" : "Status message for updating history"
+    },
+    "Updating IOB ..." : {
+      "comment" : "Status message for updating IOB"
+    },
+    "Updating IOB and COB ..." : {
+      "comment" : "Status message for updating both IOB and COB"
+    },
     "Updating IOB..." : {
     "Updating IOB..." : {
       "comment" : "Progress text when updating IOB",
       "comment" : "Progress text when updating IOB",
       "localizations" : {
       "localizations" : {
@@ -186661,6 +186755,9 @@
         }
         }
       }
       }
     },
     },
+    "Updating Treatments ..." : {
+      "comment" : "Status message for updating treatments"
+    },
     "Updating..." : {
     "Updating..." : {
       "comment" : "Updating Watch app",
       "comment" : "Updating Watch app",
       "extractionState" : "manual",
       "extractionState" : "manual",

+ 1 - 0
Trio/Sources/Models/NightscoutStatus.swift

@@ -58,6 +58,7 @@ struct NightscoutProfileStore: JSON {
     let isAPNSProduction: Bool
     let isAPNSProduction: Bool
     let overridePresets: [NightscoutPresetOverride]?
     let overridePresets: [NightscoutPresetOverride]?
     let teamID: String
     let teamID: String
+    let expirationDate: Date?
 }
 }
 
 
 struct NightscoutPresetOverride: JSON {
 struct NightscoutPresetOverride: JSON {

+ 2 - 2
Trio/Sources/Models/TrioSettings.swift

@@ -8,9 +8,9 @@ enum BolusShortcutLimit: String, JSON, CaseIterable, Identifiable {
     var displayName: String {
     var displayName: String {
         switch self {
         switch self {
         case .notAllowed:
         case .notAllowed:
-            return String(localized: "Not allowed", table: "ShortcutsDetail")
+            return String(localized: "Not allowed")
         case .limitBolusMax:
         case .limitBolusMax:
-            return String(localized: "Max bolus", table: "ShortcutsDetail")
+            return String(localized: "Max bolus")
         }
         }
     }
     }
 }
 }

+ 60 - 47
Trio/Sources/Modules/CGMSettings/CGMSettingsStateModel.swift

@@ -4,6 +4,9 @@ import G7SensorKit
 import LoopKitUI
 import LoopKitUI
 import SwiftUI
 import SwiftUI
 
 
+/// For a full description of the events that can happen for the CGM lifecycle, see comment at the top
+/// of HomeStateModel+CGM since these are the same events
+
 struct CGMModel: Identifiable, Hashable {
 struct CGMModel: Identifiable, Hashable {
     var id: String
     var id: String
     var type: CGMType
     var type: CGMType
@@ -23,18 +26,6 @@ let cgmDefaultModel = CGMModel(
     subtitle: CGMType.none.subtitle
     subtitle: CGMType.none.subtitle
 )
 )
 
 
-struct OtherCGMSourceCompletionNotifying: CompletionNotifying {
-    var completionDelegate: (any LoopKitUI.CompletionDelegate)?
-}
-
-class CGMSetupCompletionNotifying: CompletionNotifying {
-    var completionDelegate: (any LoopKitUI.CompletionDelegate)?
-}
-
-class CGMDeletionCompletionNotifying: CompletionNotifying {
-    var completionDelegate: (any LoopKitUI.CompletionDelegate)?
-}
-
 extension CGMSettings {
 extension CGMSettings {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
         // Singleton implementation
         // Singleton implementation
@@ -49,7 +40,7 @@ extension CGMSettings {
 
 
         @Injected() var fetchGlucoseManager: FetchGlucoseManager!
         @Injected() var fetchGlucoseManager: FetchGlucoseManager!
         @Injected() var pluginCGMManager: PluginManager!
         @Injected() var pluginCGMManager: PluginManager!
-        @Injected() private var broadcaster: Broadcaster!
+        @Injected() var broadcaster: Broadcaster!
         @Injected() var nightscoutManager: NightscoutManager!
         @Injected() var nightscoutManager: NightscoutManager!
 
 
         @Published var units: GlucoseUnits = .mgdL
         @Published var units: GlucoseUnits = .mgdL
@@ -60,8 +51,11 @@ extension CGMSettings {
         @Published var listOfCGM: [CGMModel] = []
         @Published var listOfCGM: [CGMModel] = []
         @Published var url: URL?
         @Published var url: URL?
 
 
+        var shouldRunDeleteOnSettingsChange = true
+
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
+            broadcaster.register(SettingsObserver.self, observer: self)
 
 
             // collect the list of CGM available with plugins and CGMType defined manually
             // collect the list of CGM available with plugins and CGMType defined manually
             listOfCGM = (
             listOfCGM = (
@@ -122,28 +116,36 @@ extension CGMSettings {
             subscribeSetting(\.smoothGlucose, on: $smoothGlucose, initial: { smoothGlucose = $0 })
             subscribeSetting(\.smoothGlucose, on: $smoothGlucose, initial: { smoothGlucose = $0 })
         }
         }
 
 
+        // this function will get called for all CGM types (plugin and non plugin)
         func addCGM(cgm: CGMModel) {
         func addCGM(cgm: CGMModel) {
             cgmCurrent = cgm
             cgmCurrent = cgm
-            switch cgmCurrent.type {
+            switch cgm.type {
             case .plugin:
             case .plugin:
                 shouldDisplayCGMSetupSheet.toggle()
                 shouldDisplayCGMSetupSheet.toggle()
             default:
             default:
-                fetchGlucoseManager.cgmGlucoseSourceType = cgmCurrent.type
-                completionNotifyingDidComplete(OtherCGMSourceCompletionNotifying())
+                // non plugin CGM types should be considered onboarded right away
+                shouldDisplayCGMSetupSheet = true
+                settingsManager.settings.cgm = cgmCurrent.type
+                settingsManager.settings.cgmPluginIdentifier = ""
+                fetchGlucoseManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id)
+                broadcaster.notify(GlucoseObserver.self, on: .main) {
+                    $0.glucoseDidUpdate([])
+                }
             }
             }
         }
         }
 
 
+        // Note: This function does _not_ get called for plugin CGMs
+        // instead, they will get cgmManagerWantsDeletion events which
+        // are handled by PluginSource
         func deleteCGM() {
         func deleteCGM() {
-            fetchGlucoseManager.performOnCGMManagerQueue {
-                // Call plugin functionality on the manager queue (or at least attempt to)
-                Task {
-                    await self.fetchGlucoseManager?.deleteGlucoseSource()
-                }
+            Task {
+                await self.fetchGlucoseManager?.deleteGlucoseSource()
 
 
-                // UI updates go back to Main
-                DispatchQueue.main.async {
+                await MainActor.run {
                     self.shouldDisplayCGMSetupSheet = false
                     self.shouldDisplayCGMSetupSheet = false
-                    self.completionNotifyingDidComplete(CGMDeletionCompletionNotifying())
+                    broadcaster.notify(GlucoseObserver.self, on: .main) {
+                        $0.glucoseDidUpdate([])
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -152,40 +154,36 @@ extension CGMSettings {
 
 
 extension CGMSettings.StateModel: CompletionDelegate {
 extension CGMSettings.StateModel: CompletionDelegate {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
-        // if CGM was deleted
-        if fetchGlucoseManager.cgmGlucoseSourceType == .none {
-            cgmCurrent = cgmDefaultModel
-            settingsManager.settings.cgm = cgmDefaultModel.type
-            settingsManager.settings.cgmPluginIdentifier = cgmDefaultModel.id
-            Task {
-                await fetchGlucoseManager.deleteGlucoseSource()
-            }
-            shouldDisplayCGMSetupSheet = false
-        } else {
-            settingsManager.settings.cgm = cgmCurrent.type
-            settingsManager.settings.cgmPluginIdentifier = cgmCurrent.id
-            fetchGlucoseManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id)
-            shouldDisplayCGMSetupSheet = cgmCurrent.type == .simulator || cgmCurrent.type == .nightscout || cgmCurrent
-                .type == .xdrip || cgmCurrent.type == .enlite
-        }
-
-        // update glucose source if required
-        DispatchQueue.main.async {
-            self.broadcaster.notify(GlucoseObserver.self, on: .main) {
-                $0.glucoseDidUpdate([])
+        Task {
+            // this sleep is because this event and cgmManagerWantsDeletion
+            // are called in parallel.
+            try await Task.sleep(for: .seconds(0.2))
+            await MainActor.run {
+                if fetchGlucoseManager.cgmGlucoseSourceType == .none {
+                    cgmCurrent = cgmDefaultModel
+                }
             }
             }
         }
         }
+        shouldDisplayCGMSetupSheet = false
     }
     }
 }
 }
 
 
 extension CGMSettings.StateModel: CGMManagerOnboardingDelegate {
 extension CGMSettings.StateModel: CGMManagerOnboardingDelegate {
     func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
     func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
-        // update the glucose source
+        // cgmCurrent should have been set in addCGM
+        debug(.service, "didCreateCGMManager called \(cgmCurrent)")
+        settingsManager.settings.cgm = cgmCurrent.type
+        settingsManager.settings.cgmPluginIdentifier = cgmCurrent.id
         fetchGlucoseManager.updateGlucoseSource(
         fetchGlucoseManager.updateGlucoseSource(
             cgmGlucoseSourceType: cgmCurrent.type,
             cgmGlucoseSourceType: cgmCurrent.type,
             cgmGlucosePluginId: cgmCurrent.id,
             cgmGlucosePluginId: cgmCurrent.id,
             newManager: manager
             newManager: manager
         )
         )
+        DispatchQueue.main.async {
+            self.broadcaster.notify(GlucoseObserver.self, on: .main) {
+                $0.glucoseDidUpdate([])
+            }
+        }
     }
     }
 
 
     func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
     func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
@@ -193,8 +191,23 @@ extension CGMSettings.StateModel: CGMManagerOnboardingDelegate {
     }
     }
 }
 }
 
 
-extension CGMSettings.StateModel {
+extension CGMSettings.StateModel: SettingsObserver {
     func settingsDidChange(_: TrioSettings) {
     func settingsDidChange(_: TrioSettings) {
         units = settingsManager.settings.units
         units = settingsManager.settings.units
+        // Deletes are handled differently for plugins vs non plugins
+        // but both will call deleteGlucoseSource on the fetchGlucoseManager
+        // so we listen for changes to the cgm setting and update our internal
+        // state accordingly
+        if settingsManager.settings.cgm == .none, shouldRunDeleteOnSettingsChange {
+            shouldRunDeleteOnSettingsChange = false
+            cgmCurrent = cgmDefaultModel
+            DispatchQueue.main.async {
+                self.broadcaster.notify(GlucoseObserver.self, on: .main) {
+                    $0.glucoseDidUpdate([])
+                }
+            }
+        } else {
+            shouldRunDeleteOnSettingsChange = true
+        }
     }
     }
 }
 }

+ 5 - 1
Trio/Sources/Modules/CGMSettings/View/CGMRootView.swift

@@ -161,7 +161,11 @@ extension CGMSettings {
                                 completionDelegate: state,
                                 completionDelegate: state,
                                 setupDelegate: state,
                                 setupDelegate: state,
                                 pluginCGMManager: self.state.pluginCGMManager
                                 pluginCGMManager: self.state.pluginCGMManager
-                            )
+                            ).onDisappear {
+                                if state.fetchGlucoseManager.cgmGlucoseSourceType == .none {
+                                    state.cgmCurrent = cgmDefaultModel
+                                }
+                            }
                         }
                         }
                     }
                     }
                 }
                 }

+ 4 - 6
Trio/Sources/Modules/CGMSettings/View/CustomCGMOptionsView.swift

@@ -140,12 +140,10 @@ extension CGMSettings {
                                 .padding(.vertical)
                                 .padding(.vertical)
                         }
                         }
 
 
-                        if state.url == nil {
-                            NavigationLink(
-                                destination: NightscoutConfig.RootView(resolver: resolver, displayClose: false),
-                                label: { Text("Configure Nightscout").foregroundStyle(Color.accentColor) }
-                            )
-                        }
+                        NavigationLink(
+                            destination: NightscoutConfig.RootView(resolver: resolver, displayClose: false),
+                            label: { Text("Configure Nightscout").foregroundStyle(Color.accentColor) }
+                        )
                     }
                     }
                 ).listRowBackground(Color.chart)
                 ).listRowBackground(Color.chart)
 
 

+ 1 - 1
Trio/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -101,7 +101,7 @@ extension DataTable {
                 // Show custom progress view
                 // Show custom progress view
                 /// don't show it if glucose is stale as it will block the UI
                 /// don't show it if glucose is stale as it will block the UI
                 if state.waitForSuggestion && state.isGlucoseDataFresh(glucoseStored.first?.date) {
                 if state.waitForSuggestion && state.isGlucoseDataFresh(glucoseStored.first?.date) {
-                    CustomProgressView(text: progressText.rawValue)
+                    CustomProgressView(text: progressText.displayName)
                 }
                 }
             })
             })
                 .background(appState.trioBackgroundColor(for: colorScheme))
                 .background(appState.trioBackgroundColor(for: colorScheme))

+ 77 - 0
Trio/Sources/Modules/Home/HomeStateModel+CGM.swift

@@ -0,0 +1,77 @@
+import LoopKitUI
+
+/// Notes on the CGM lifecycle:
+/// There are two classes of CGM devices: plugins and non-plugins. Plugins are implemented using
+/// LoopKit APIs and include most hardware CGMs like Dexcom G6, G7, Libre, and so on. Non-plugins
+/// drivers are implemented directly in Trio, and include the CGM Simulator and Nightscout CGM. For
+/// these different CGMs, there are a few different events, handled in different places, that happen to
+/// signify a change in the CGM lifecycle.
+///
+/// Both:
+/// - addCGM function invocation: Called by the UI in response to a user clicking the "add CGM" button
+///
+/// Non-plugins only:
+/// - deleteCGM function invocation: Called by the CGM View in response to a user clicking the "delete CGM" button
+///
+/// Plugins only:
+/// - completionNotifyingDidComplete: Called by the CGM driver to signify that Trio should close its UIViewController
+/// - cgmManagerOnboarding didCreateCGMManager: Called by the CGM driver after adding a new CGM
+/// - cgmManagerWantsDeletion: Called by the CGM driver when the user asks to delete a CGM
+/// There are no ordering constraints between completionNotifyingDidComplete and the other two
+/// Plugin events (it's up to the implementation of each individual driver). For example, the G7 driver invokes
+/// cgmManagerWantsDeletion on the delegate's queue while calling completionNotifyingDidComplete in parallel
+/// on the main queue.
+///
+/// In additinon to having different events for different types of CGMs, the handling of these events is spread out
+/// across various state managers, like HomeStateModel, CGMSettingsStateModel, and PluginSource.
+///
+/// There is CGM state in the HomeStateModel and CGMSettingsStateModel, FetchGlucoseManager, and
+/// SettingsManger
+///
+/// The flow for adding a CGM:
+/// - Non-plugin: addCGM (considered onboarded at this point)
+/// - Plugin: addCGM -> cgmManagerOnboarding (after success)
+///
+/// For deleting a CGM:
+/// - Non-plugin: deleteCGM (in HomeStateModel and CGMSettingsStateModel)
+/// - Plugin: cgmManagerWantsDeletion (in PluginSource)
+/// Then, both non-plugin and plugin:  set settings.cgm (in FetchGlucoseManager) ->
+///     settingsDidChange (in HomeStateModel and CGMSettingsStateModel)
+
+extension Home.StateModel: CompletionDelegate {
+    func completionNotifyingDidComplete(_ notifying: CompletionNotifying) {
+        debug(.service, "Completion fired by: \(type(of: notifying))")
+        Task {
+            // this sleep is because this event and cgmManagerWantsDeletion
+            // are called in parallel.
+            try await Task.sleep(for: .seconds(0.2))
+            await MainActor.run {
+                if fetchGlucoseManager.cgmGlucoseSourceType == .none {
+                    cgmCurrent = cgmDefaultModel
+                }
+            }
+        }
+        shouldDisplayCGMSetupSheet = false
+    }
+}
+
+extension Home.StateModel: CGMManagerOnboardingDelegate {
+    func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
+        settingsManager.settings.cgm = cgmCurrent.type
+        settingsManager.settings.cgmPluginIdentifier = cgmCurrent.id
+        fetchGlucoseManager.updateGlucoseSource(
+            cgmGlucoseSourceType: cgmCurrent.type,
+            cgmGlucosePluginId: cgmCurrent.id,
+            newManager: manager
+        )
+        DispatchQueue.main.async {
+            self.broadcaster.notify(GlucoseObserver.self, on: .main) {
+                $0.glucoseDidUpdate([])
+            }
+        }
+    }
+
+    func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
+        // nothing to do
+    }
+}

+ 26 - 64
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -103,6 +103,7 @@ extension Home {
         var cgmAvailable: Bool = false
         var cgmAvailable: Bool = false
         var listOfCGM: [CGMModel] = []
         var listOfCGM: [CGMModel] = []
         var cgmCurrent = cgmDefaultModel
         var cgmCurrent = cgmDefaultModel
+        var shouldRunDeleteOnSettingsChange = true
 
 
         var showCarbsRequiredBadge: Bool = true
         var showCarbsRequiredBadge: Bool = true
         private(set) var setupPumpType: PumpConfig.PumpType = .minimed
         private(set) var setupPumpType: PumpConfig.PumpType = .minimed
@@ -455,8 +456,13 @@ extension Home {
             case .plugin:
             case .plugin:
                 shouldDisplayCGMSetupSheet = true
                 shouldDisplayCGMSetupSheet = true
             default:
             default:
-                fetchGlucoseManager.cgmGlucoseSourceType = cgmCurrent.type
-                completionNotifyingDidComplete(CGMSetupCompletionNotifying())
+                shouldDisplayCGMSetupSheet = true
+                settingsManager.settings.cgm = cgmCurrent.type
+                settingsManager.settings.cgmPluginIdentifier = ""
+                fetchGlucoseManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id)
+                broadcaster.notify(GlucoseObserver.self, on: .main) {
+                    $0.glucoseDidUpdate([])
+                }
             }
             }
         }
         }
 
 
@@ -465,12 +471,14 @@ extension Home {
                 // Call plugin functionality on the manager queue (or at least attempt to)
                 // Call plugin functionality on the manager queue (or at least attempt to)
                 Task {
                 Task {
                     await self.fetchGlucoseManager?.deleteGlucoseSource()
                     await self.fetchGlucoseManager?.deleteGlucoseSource()
-                }
 
 
-                // UI updates go back to Main
-                DispatchQueue.main.async {
-                    self.shouldDisplayCGMSetupSheet = false
-                    self.completionNotifyingDidComplete(CGMDeletionCompletionNotifying())
+                    // UI updates go back to Main
+                    await MainActor.run {
+                        self.shouldDisplayCGMSetupSheet = false
+                        self.broadcaster.notify(GlucoseObserver.self, on: .main) {
+                            $0.glucoseDidUpdate([])
+                        }
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -643,6 +651,17 @@ extension Home.StateModel:
         Task {
         Task {
             await setupCGMSettings()
             await setupCGMSettings()
         }
         }
+        if settingsManager.settings.cgm == .none, shouldRunDeleteOnSettingsChange {
+            shouldRunDeleteOnSettingsChange = false
+            cgmCurrent = cgmDefaultModel
+            DispatchQueue.main.async {
+                self.broadcaster.notify(GlucoseObserver.self, on: .main) {
+                    $0.glucoseDidUpdate([])
+                }
+            }
+        } else {
+            shouldRunDeleteOnSettingsChange = true
+        }
     }
     }
 
 
     func preferencesDidChange(_: Preferences) {
     func preferencesDidChange(_: Preferences) {
@@ -685,48 +704,6 @@ extension Home.StateModel:
     }
     }
 }
 }
 
 
-extension Home.StateModel: CompletionDelegate {
-    func completionNotifyingDidComplete(_ notifying: CompletionNotifying) {
-        debug(.service, "Completion fired by: \(type(of: notifying))")
-        shouldDisplayCGMSetupSheet = false
-
-        if notifying is CGMSetupCompletionNotifying || notifying is CGMDeletionCompletionNotifying ||
-            notifying is CGMManagerSettingsNavigationViewController || notifying is any SetupTableViewControllerDelegate ||
-            notifying is any CGMManagerOnboarding
-        {
-            if fetchGlucoseManager.cgmGlucoseSourceType == .none {
-                debug(.service, "CGMDeletionCompletionNotifying: CGM Deletion Completed")
-
-                cgmCurrent = cgmDefaultModel
-                settingsManager.settings.cgm = cgmDefaultModel.type
-                settingsManager.settings.cgmPluginIdentifier = cgmDefaultModel.id
-                Task {
-                    await fetchGlucoseManager.deleteGlucoseSource()
-                }
-            } else {
-                debug(.service, "CGMSetupCompletionNotifying: CGM Setup Completed")
-
-                settingsManager.settings.cgm = cgmCurrent.type
-                settingsManager.settings.cgmPluginIdentifier = cgmCurrent.id
-                fetchGlucoseManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id)
-
-                shouldDisplayCGMSetupSheet = cgmCurrent.type == .simulator || cgmCurrent.type == .nightscout || cgmCurrent
-                    .type == .xdrip || cgmCurrent.type == .enlite
-            }
-
-            // update glucose source if required
-            DispatchQueue.main.async {
-                self.broadcaster.notify(GlucoseObserver.self, on: .main) {
-                    $0.glucoseDidUpdate([])
-                }
-            }
-        } else {
-            // pump related handling
-            shouldDisplayPumpSetupSheet = false // hides sheet
-        }
-    }
-}
-
 extension Home.StateModel: PumpManagerOnboardingDelegate {
 extension Home.StateModel: PumpManagerOnboardingDelegate {
     func pumpManagerOnboarding(didCreatePumpManager pumpManager: PumpManagerUI) {
     func pumpManagerOnboarding(didCreatePumpManager pumpManager: PumpManagerUI) {
         provider.apsManager.pumpManager = pumpManager
         provider.apsManager.pumpManager = pumpManager
@@ -743,18 +720,3 @@ extension Home.StateModel: PumpManagerOnboardingDelegate {
         // nothing to do
         // nothing to do
     }
     }
 }
 }
-
-extension Home.StateModel: CGMManagerOnboardingDelegate {
-    func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
-        // update the glucose source
-        fetchGlucoseManager.updateGlucoseSource(
-            cgmGlucoseSourceType: cgmCurrent.type,
-            cgmGlucosePluginId: cgmCurrent.id,
-            newManager: manager
-        )
-    }
-
-    func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
-        // nothing to do
-    }
-}

+ 1 - 1
Trio/Sources/Modules/Settings/SettingsStateModel.swift

@@ -33,7 +33,7 @@ extension Settings {
 
 
             versionNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
             versionNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
 
 
-            branch = BuildDetails.default.branchAndSha
+            branch = BuildDetails.shared.branchAndSha
 
 
             copyrightNotice = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
             copyrightNotice = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
 
 

+ 1 - 1
Trio/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -72,7 +72,7 @@ extension Settings {
         var body: some View {
         var body: some View {
             List {
             List {
                 if searchText.isEmpty {
                 if searchText.isEmpty {
-                    let buildDetails = BuildDetails.default
+                    let buildDetails = BuildDetails.shared
 
 
                     Section(
                     Section(
                         header: Text("BRANCH: \(buildDetails.branchAndSha)").textCase(nil),
                         header: Text("BRANCH: \(buildDetails.branchAndSha)").textCase(nil),

+ 8 - 4
Trio/Sources/Modules/Stat/StatStateModel.swift

@@ -327,10 +327,14 @@ extension Stat.StateModel {
 
 
         var displayName: String {
         var displayName: String {
             switch self {
             switch self {
-            case .glucose: return "Glucose"
-            case .insulin: return "Insulin"
-            case .looping: return "Looping"
-            case .meals: return "Meals"
+            case .glucose:
+                return String(localized: "Glucose", comment: "Title for glucose-related statistics")
+            case .insulin:
+                return String(localized: "Insulin", comment: "Title for insulin-related statistics")
+            case .looping:
+                return String(localized: "Looping", comment: "Title for looping and system statistics")
+            case .meals:
+                return String(localized: "Meals", comment: "Title for meal-related statistics")
             }
             }
         }
         }
     }
     }

+ 2 - 2
Trio/Sources/Modules/Stat/View/StatRootView.swift

@@ -176,7 +176,7 @@ extension Stat {
 
 
             Picker("Duration", selection: $state.selectedIntervalForInsulinStats) {
             Picker("Duration", selection: $state.selectedIntervalForInsulinStats) {
                 ForEach(StateModel.StatsTimeInterval.allCases) { timeInterval in
                 ForEach(StateModel.StatsTimeInterval.allCases) { timeInterval in
-                    Text(timeInterval.rawValue).tag(timeInterval)
+                    Text(timeInterval.displayName).tag(timeInterval)
                 }
                 }
             }
             }
             .pickerStyle(.segmented)
             .pickerStyle(.segmented)
@@ -316,7 +316,7 @@ extension Stat {
 
 
             Picker("Duration", selection: $state.selectedIntervalForMealStats) {
             Picker("Duration", selection: $state.selectedIntervalForMealStats) {
                 ForEach(StateModel.StatsTimeInterval.allCases, id: \.self) { timeInterval in
                 ForEach(StateModel.StatsTimeInterval.allCases, id: \.self) { timeInterval in
-                    Text(timeInterval.rawValue)
+                    Text(timeInterval.displayName)
                 }
                 }
             }
             }
             .pickerStyle(.segmented)
             .pickerStyle(.segmented)

+ 1 - 1
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -347,7 +347,7 @@ extension Treatments {
                 .blur(radius: state.isAwaitingDeterminationResult ? 5 : 0)
                 .blur(radius: state.isAwaitingDeterminationResult ? 5 : 0)
 
 
                 if state.isAwaitingDeterminationResult {
                 if state.isAwaitingDeterminationResult {
-                    CustomProgressView(text: progressText.rawValue)
+                    CustomProgressView(text: progressText.displayName)
                 }
                 }
             }
             }
             .padding(.top)
             .padding(.top)

+ 6 - 1
Trio/Sources/Services/Network/Nightscout/NightscoutManager.swift

@@ -786,6 +786,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 let isAPNSProduction = UserDefaults.standard.bool(forKey: "isAPNSProduction")
                 let isAPNSProduction = UserDefaults.standard.bool(forKey: "isAPNSProduction")
                 let presetOverrides = try await overridesStorage.getPresetOverridesForNightscout()
                 let presetOverrides = try await overridesStorage.getPresetOverridesForNightscout()
                 let teamID = Bundle.main.object(forInfoDictionaryKey: "TeamID") as? String ?? ""
                 let teamID = Bundle.main.object(forInfoDictionaryKey: "TeamID") as? String ?? ""
+                let expireDate = BuildDetails.shared.calculateExpirationDate()
 
 
                 let profileStore = NightscoutProfileStore(
                 let profileStore = NightscoutProfileStore(
                     defaultProfile: defaultProfile,
                     defaultProfile: defaultProfile,
@@ -798,7 +799,8 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                     deviceToken: deviceToken,
                     deviceToken: deviceToken,
                     isAPNSProduction: isAPNSProduction,
                     isAPNSProduction: isAPNSProduction,
                     overridePresets: presetOverrides,
                     overridePresets: presetOverrides,
-                    teamID: teamID
+                    teamID: teamID,
+                    expirationDate: expireDate
                 )
                 )
 
 
                 guard let nightscout = nightscoutAPI, isNetworkReachable else {
                 guard let nightscout = nightscoutAPI, isNetworkReachable else {
@@ -810,6 +812,9 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 }
                 }
 
 
                 try await nightscout.uploadProfile(profileStore)
                 try await nightscout.uploadProfile(profileStore)
+
+                BuildDetails.shared.recordUploadedExpireDate(expireDate: expireDate)
+
                 debug(.nightscout, "Profile uploaded")
                 debug(.nightscout, "Profile uploaded")
             } catch {
             } catch {
                 debug(.nightscout, "NightscoutManager uploadProfile: \(error.localizedDescription)")
                 debug(.nightscout, "NightscoutManager uploadProfile: \(error.localizedDescription)")

+ 1 - 1
Trio/Sources/Services/RemoteControl/TrioRemoteControl+APNS.swift

@@ -28,6 +28,6 @@ extension TrioRemoteControl {
     }
     }
 
 
     private func isRunningInAPNSProductionEnvironment() -> Bool {
     private func isRunningInAPNSProductionEnvironment() -> Bool {
-        BuildDetails.default.isTestFlightBuild()
+        BuildDetails.shared.isTestFlightBuild()
     }
     }
 }
 }

+ 2 - 2
Trio/Sources/Services/RemoteControl/TrioRemoteControl+Meal.swift

@@ -45,8 +45,8 @@ extension TrioRemoteControl {
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: taskContext,
             onContext: taskContext,
-            predicate: NSPredicate(format: "createdAt > %@", pushMessageDate as NSDate),
-            key: "createdAt",
+            predicate: NSPredicate(format: "date > %@", pushMessageDate as NSDate),
+            key: "date",
             ascending: false
             ascending: false
         )
         )
 
 

+ 18 - 16
Trio/Sources/Shortcuts/Bolus/BolusIntent.swift

@@ -5,38 +5,39 @@ import Swinject
 
 
 @available(iOS 16.0,*) struct BolusIntent: AppIntent {
 @available(iOS 16.0,*) struct BolusIntent: AppIntent {
     // Title of the action in the Shortcuts app
     // Title of the action in the Shortcuts app
-    static var title = LocalizedStringResource("Enact Bolus", table: "ShortcutsDetail")
+    static var title = LocalizedStringResource("Enact Bolus")
 
 
     // Description of the action in the Shortcuts app
     // Description of the action in the Shortcuts app
-    static var description = IntentDescription(.init("Allow to send a bolus to the app", table: "ShortcutsDetail"))
+    static var description = IntentDescription(.init("Allow to send a bolus to the app"))
 
 
     @Parameter(
     @Parameter(
-        title: LocalizedStringResource("Amount", table: "ShortcutsDetail"),
-        description: LocalizedStringResource("Bolus amount in U", table: "ShortcutsDetail"),
+        title: LocalizedStringResource("Amount"),
+        description: LocalizedStringResource("Bolus amount in U"),
         controlStyle: .field,
         controlStyle: .field,
         /// The 200 upperBound does nothing here, the true max is set based on pump max
         /// The 200 upperBound does nothing here, the true max is set based on pump max
         /// An upperBound is specificed so that we can usethe lowerBound of 0, which ensures no negatives are allowed
         /// An upperBound is specificed so that we can usethe lowerBound of 0, which ensures no negatives are allowed
         /// A preferred approach would be to just block negatives and not specify an upperBound here, since it is implemented elsewhere
         /// A preferred approach would be to just block negatives and not specify an upperBound here, since it is implemented elsewhere
         inclusiveRange: (lowerBound: 0, upperBound: 200),
         inclusiveRange: (lowerBound: 0, upperBound: 200),
-        requestValueDialog: IntentDialog(LocalizedStringResource(
-            "Bolus amount (units of insulin)?",
-            table: "ShortcutsDetail"
-        ))
+        requestValueDialog: IntentDialog(
+            LocalizedStringResource(
+                "Bolus amount (units of insulin)?"
+            )
+        )
     ) var bolusQuantity: Double
     ) var bolusQuantity: Double
 
 
     @Parameter(
     @Parameter(
-        title: LocalizedStringResource("Confirm Before applying", table: "ShortcutsDetail"),
-        description: LocalizedStringResource("If toggled, you will need to confirm before applying.", table: "ShortcutsDetail"),
+        title: LocalizedStringResource("Confirm Before applying"),
+        description: LocalizedStringResource("If toggled, you will need to confirm before applying."),
         default: true
         default: true
     ) var confirmBeforeApplying: Bool
     ) var confirmBeforeApplying: Bool
 
 
     static var parameterSummary: some ParameterSummary {
     static var parameterSummary: some ParameterSummary {
         When(\.$confirmBeforeApplying, .equalTo, true, {
         When(\.$confirmBeforeApplying, .equalTo, true, {
-            Summary("Applying \(\.$bolusQuantity) U", table: "ShortcutsDetail") {
+            Summary("Applying \(\.$bolusQuantity) U") {
                 \.$confirmBeforeApplying
                 \.$confirmBeforeApplying
             }
             }
         }, otherwise: {
         }, otherwise: {
-            Summary("Immediately applying \(\.$bolusQuantity) U", table: "ShortcutsDetail") {
+            Summary("Immediately applying \(\.$bolusQuantity) U") {
                 \.$confirmBeforeApplying
                 \.$confirmBeforeApplying
             }
             }
         })
         })
@@ -50,10 +51,11 @@ import Swinject
             if confirmBeforeApplying {
             if confirmBeforeApplying {
                 try await requestConfirmation(
                 try await requestConfirmation(
                     result: .result(
                     result: .result(
-                        dialog: IntentDialog(LocalizedStringResource(
-                            "Are you sure you want to bolus \(bolusFormatted) U of insulin?",
-                            table: "ShortcutsDetail"
-                        ))
+                        dialog: IntentDialog(
+                            LocalizedStringResource(
+                                "Are you sure you want to bolus \(bolusFormatted) U of insulin?"
+                            )
+                        )
                     )
                     )
                 )
                 )
             }
             }

+ 3 - 6
Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift

@@ -9,24 +9,21 @@ import Foundation
         // Block boluses if they are disabled
         // Block boluses if they are disabled
         case .notAllowed:
         case .notAllowed:
             return LocalizedStringResource(
             return LocalizedStringResource(
-                "Bolusing via Shortcuts is disabled in Trio settings.",
-                table: "ShortcutsDetail"
+                "Bolusing via Shortcuts is disabled in Trio settings."
             )
             )
 
 
         // Block any bolus attempted if it is larger than the max bolus in settings
         // Block any bolus attempted if it is larger than the max bolus in settings
         case .limitBolusMax:
         case .limitBolusMax:
             if Decimal(bolusAmount) > settingsManager.pumpSettings.maxBolus {
             if Decimal(bolusAmount) > settingsManager.pumpSettings.maxBolus {
                 return LocalizedStringResource(
                 return LocalizedStringResource(
-                    "The bolus cannot be larger than the pump setting max bolus (\(settingsManager.pumpSettings.maxBolus.description)).",
-                    table: "ShortcutsDetail"
+                    "The bolus cannot be larger than the pump setting max bolus (\(settingsManager.pumpSettings.maxBolus.description))."
                 )
                 )
             } else {
             } else {
                 bolusQuantity = apsManager.roundBolus(amount: Decimal(bolusAmount))
                 bolusQuantity = apsManager.roundBolus(amount: Decimal(bolusAmount))
             }
             }
             await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false, callback: nil)
             await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false, callback: nil)
             return LocalizedStringResource(
             return LocalizedStringResource(
-                "A bolus command of \(bolusQuantity.formatted()) U of insulin was sent.",
-                table: "ShortcutsDetail"
+                "A bolus command of \(bolusQuantity.formatted()) U of insulin was sent."
             )
             )
         }
         }
     }
     }

+ 2 - 2
Trio/Sources/Shortcuts/LiveActivity/RestartLiveActivityIntent.swift

@@ -6,10 +6,10 @@ import Foundation
 /// dependencies injected via Swinject, and calls the restart functionality.
 /// dependencies injected via Swinject, and calls the restart functionality.
 @available(iOS 16.2, *) struct RestartLiveActivityIntent: LiveActivityIntent {
 @available(iOS 16.2, *) struct RestartLiveActivityIntent: LiveActivityIntent {
     /// Title of the action in the Shortcuts app.
     /// Title of the action in the Shortcuts app.
-    static var title = LocalizedStringResource("Restart Live Activity", table: "ShortcutsDetail")
+    static var title = LocalizedStringResource("Restart Live Activity")
 
 
     /// Description of the action in the Shortcuts app.
     /// Description of the action in the Shortcuts app.
-    static var description = IntentDescription(.init("Restarts Trio's Live Activity", table: "ShortcutsDetail"))
+    static var description = IntentDescription(.init("Restarts Trio's Live Activity"))
 
 
     /// Performs the intent by triggering the live activity restart.
     /// Performs the intent by triggering the live activity restart.
     ///
     ///

+ 16 - 17
Trio/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift

@@ -4,32 +4,32 @@ import Foundation
 /// An App Intent that allows users to activate an override preset through the Shortcuts app.
 /// An App Intent that allows users to activate an override preset through the Shortcuts app.
 struct ApplyOverridePresetIntent: AppIntent {
 struct ApplyOverridePresetIntent: AppIntent {
     /// The title displayed for this action in the Shortcuts app.
     /// The title displayed for this action in the Shortcuts app.
-    static var title = LocalizedStringResource("Activate an override", table: "ShortcutsDetail")
+    static var title = LocalizedStringResource("Activate an override")
 
 
     /// The description displayed for this action in the Shortcuts app.
     /// The description displayed for this action in the Shortcuts app.
-    static var description = IntentDescription(.init("Activate an override", table: "ShortcutsDetail"))
+    static var description = IntentDescription(.init("Activate an override"))
 
 
     /// The override preset to be applied.
     /// The override preset to be applied.
     @Parameter(
     @Parameter(
-        title: LocalizedStringResource("Override", table: "ShortcutsDetail"),
-        description: LocalizedStringResource("Override choice", table: "ShortcutsDetail")
+        title: LocalizedStringResource("Override"),
+        description: LocalizedStringResource("Override choice")
     ) var preset: OverridePreset?
     ) var preset: OverridePreset?
 
 
     /// A boolean parameter that determines whether confirmation is required before applying the override.
     /// A boolean parameter that determines whether confirmation is required before applying the override.
     @Parameter(
     @Parameter(
-        title: LocalizedStringResource("Confirm Before applying", table: "ShortcutsDetail"),
-        description: LocalizedStringResource("If toggled, you will need to confirm before applying", table: "ShortcutsDetail"),
+        title: LocalizedStringResource("Confirm Before applying"),
+        description: LocalizedStringResource("If toggled, you will need to confirm before applying"),
         default: true
         default: true
     ) var confirmBeforeApplying: Bool
     ) var confirmBeforeApplying: Bool
 
 
     /// Defines the summary format shown in the Shortcuts app when configuring this intent.
     /// Defines the summary format shown in the Shortcuts app when configuring this intent.
     static var parameterSummary: some ParameterSummary {
     static var parameterSummary: some ParameterSummary {
         When(\ApplyOverridePresetIntent.$confirmBeforeApplying, .equalTo, true, {
         When(\ApplyOverridePresetIntent.$confirmBeforeApplying, .equalTo, true, {
-            Summary("Applying \(\.$preset) override", table: "ShortcutsDetail") {
+            Summary("Applying \(\.$preset) override") {
                 \.$confirmBeforeApplying
                 \.$confirmBeforeApplying
             }
             }
         }, otherwise: {
         }, otherwise: {
-            Summary("Immediately applying \(\.$preset) override", table: "ShortcutsDetail") {
+            Summary("Immediately applying \(\.$preset) override") {
                 \.$confirmBeforeApplying
                 \.$confirmBeforeApplying
             }
             }
         })
         })
@@ -49,7 +49,7 @@ struct ApplyOverridePresetIntent: AppIntent {
                 // Request user selection if no preset is provided
                 // Request user selection if no preset is provided
                 presetToApply = try await $preset.requestDisambiguation(
                 presetToApply = try await $preset.requestDisambiguation(
                     among: await OverridePresetsIntentRequest().fetchAndProcessOverrides(),
                     among: await OverridePresetsIntentRequest().fetchAndProcessOverrides(),
-                    dialog: IntentDialog(LocalizedStringResource("Select override", table: "ShortcutsDetail"))
+                    dialog: IntentDialog(LocalizedStringResource("Select override"))
                 )
                 )
             }
             }
 
 
@@ -59,10 +59,11 @@ struct ApplyOverridePresetIntent: AppIntent {
             if confirmBeforeApplying {
             if confirmBeforeApplying {
                 try await requestConfirmation(
                 try await requestConfirmation(
                     result: .result(
                     result: .result(
-                        dialog: IntentDialog(LocalizedStringResource(
-                            "Confirm to apply override '\(displayName)'",
-                            table: "ShortcutsDetail"
-                        ))
+                        dialog: IntentDialog(
+                            LocalizedStringResource(
+                                "Confirm to apply override '\(displayName)'"
+                            )
+                        )
                     )
                     )
                 )
                 )
             }
             }
@@ -72,8 +73,7 @@ struct ApplyOverridePresetIntent: AppIntent {
                 return .result(
                 return .result(
                     dialog: IntentDialog(
                     dialog: IntentDialog(
                         LocalizedStringResource(
                         LocalizedStringResource(
-                            "Override '\(presetToApply.name)' applied",
-                            table: "ShortcutsDetail"
+                            "Override '\(presetToApply.name)' applied"
                         )
                         )
                     )
                     )
                 )
                 )
@@ -81,8 +81,7 @@ struct ApplyOverridePresetIntent: AppIntent {
                 return .result(
                 return .result(
                     dialog: IntentDialog(
                     dialog: IntentDialog(
                         LocalizedStringResource(
                         LocalizedStringResource(
-                            "Override '\(presetToApply.name)' failed",
-                            table: "ShortcutsDetail"
+                            "Override '\(presetToApply.name)' failed"
                         )
                         )
                     )
                     )
                 )
                 )

+ 3 - 3
Trio/Sources/Shortcuts/Override/CancelOverrideIntent.swift

@@ -4,10 +4,10 @@ import Foundation
 /// An App Intent that allows users to cancel an active override through the Shortcuts app.
 /// An App Intent that allows users to cancel an active override through the Shortcuts app.
 struct CancelOverrideIntent: AppIntent {
 struct CancelOverrideIntent: AppIntent {
     /// The title displayed for this action in the Shortcuts app.
     /// The title displayed for this action in the Shortcuts app.
-    static var title = LocalizedStringResource("Cancel override", table: "ShortcutsDetail")
+    static var title = LocalizedStringResource("Cancel override")
 
 
     /// The description displayed for this action in the Shortcuts app.
     /// The description displayed for this action in the Shortcuts app.
-    static var description = IntentDescription(.init("Cancel an active override", table: "ShortcutsDetail"))
+    static var description = IntentDescription(.init("Cancel an active override"))
 
 
     /// Performs the intent action to cancel an active override.
     /// Performs the intent action to cancel an active override.
     ///
     ///
@@ -16,7 +16,7 @@ struct CancelOverrideIntent: AppIntent {
     @MainActor func perform() async throws -> some ProvidesDialog {
     @MainActor func perform() async throws -> some ProvidesDialog {
         await OverridePresetsIntentRequest().cancelOverride()
         await OverridePresetsIntentRequest().cancelOverride()
         return .result(
         return .result(
-            dialog: IntentDialog(LocalizedStringResource("Override canceled", table: "ShortcutsDetail"))
+            dialog: IntentDialog(LocalizedStringResource("Override canceled"))
         )
         )
     }
     }
 }
 }

+ 2 - 4
Trio/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift

@@ -80,8 +80,7 @@ struct ApplyTempPresetIntent: AppIntent {
                 return .result(
                 return .result(
                     dialog: IntentDialog(
                     dialog: IntentDialog(
                         LocalizedStringResource(
                         LocalizedStringResource(
-                            "Temporary Target '\(presetToApply.name)' applied",
-                            table: "ShortcutsDetail"
+                            "Temporary Target '\(presetToApply.name)' applied"
                         )
                         )
                     )
                     )
                 )
                 )
@@ -89,8 +88,7 @@ struct ApplyTempPresetIntent: AppIntent {
                 return .result(
                 return .result(
                     dialog: IntentDialog(
                     dialog: IntentDialog(
                         LocalizedStringResource(
                         LocalizedStringResource(
-                            "Temporary Target '\(presetToApply.name)' failed",
-                            table: "ShortcutsDetail"
+                            "Temporary Target '\(presetToApply.name)' failed"
                         )
                         )
                     )
                     )
                 )
                 )