Explorar o código

Merge branch 'dev' into maxIOB-maxCOB

Mike Plante hai 1 ano
pai
achega
380512e271

+ 24 - 4
Trio.xcodeproj/project.pbxproj

@@ -210,6 +210,8 @@
 		491D6FBE2D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FB92D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift */; };
 		491D6FBF2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */; };
 		491D6FC02D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */; };
+		49249B1C2D46E45E000F4866 /* CurrentTDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49249B1B2D46E45E000F4866 /* CurrentTDDSetup.swift */; };
+		49249B382D46E76A000F4866 /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49249B372D46E76A000F4866 /* TDD.swift */; };
 		49B9B57F2D5768D2009C6B59 /* AdjustmentStored+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */; };
 		5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */; };
 		53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */; };
@@ -262,7 +264,7 @@
 		6B1A8D202B14D91600E76752 /* LiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A8D1F2B14D91600E76752 /* LiveActivity.swift */; };
 		6B1A8D242B14D91700E76752 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B1A8D232B14D91700E76752 /* Assets.xcassets */; };
 		6B1A8D282B14D91700E76752 /* LiveActivityExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6B1A8D172B14D91600E76752 /* LiveActivityExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
-		6B1A8D2E2B156EEF00E76752 /* LiveActivityBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */; };
+		6B1A8D2E2B156EEF00E76752 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A8D2D2B156EEF00E76752 /* LiveActivityManager.swift */; };
 		6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A708CDB546692C2230B385 /* SnoozeDataFlow.swift */; };
 		6BCF84DD2B16843A003AD46E /* LiveActitiyAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */; };
 		6BCF84DE2B16843A003AD46E /* LiveActitiyAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */; };
@@ -486,6 +488,8 @@
 		DD498F2B2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2C2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2D2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
+		DD4C57A82D73ADEA001BFF2C /* RestartLiveActivityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */; };
+		DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */; };
 		DD4FFF332D458EE600B6CFF9 /* GarminWatchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4FFF322D458EE600B6CFF9 /* GarminWatchState.swift */; };
 		DD5DC9F12CF3D97C00AB8703 /* AdjustmentsStateModel+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F02CF3D96E00AB8703 /* AdjustmentsStateModel+Overrides.swift */; };
 		DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F22CF3D9D600AB8703 /* AdjustmentsStateModel+TempTargets.swift */; };
@@ -913,6 +917,8 @@
 		491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		491D6FBC2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
+		49249B1B2D46E45E000F4866 /* CurrentTDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentTDDSetup.swift; sourceTree = "<group>"; };
+		49249B372D46E76A000F4866 /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
 		49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentStored+Helper.swift"; sourceTree = "<group>"; };
 		4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorRootView.swift; sourceTree = "<group>"; };
 		505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorStateModel.swift; sourceTree = "<group>"; };
@@ -967,7 +973,7 @@
 		6B1A8D1F2B14D91600E76752 /* LiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivity.swift; sourceTree = "<group>"; };
 		6B1A8D232B14D91700E76752 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		6B1A8D252B14D91700E76752 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBridge.swift; sourceTree = "<group>"; };
+		6B1A8D2D2B156EEF00E76752 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = "<group>"; };
 		6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActitiyAttributes.swift; sourceTree = "<group>"; };
 		715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationsOption.swift; sourceTree = "<group>"; };
 		71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertPermissionsChecker.swift; sourceTree = "<group>"; };
@@ -1190,6 +1196,8 @@
 		DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Helpers.swift"; sourceTree = "<group>"; };
 		DD3A3CE62D29C93F00AE478E /* Helper+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Helper+Extensions.swift"; sourceTree = "<group>"; };
 		DD3A3CE82D29C97800AE478E /* Helper+ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Helper+ButtonStyles.swift"; sourceTree = "<group>"; };
+		DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntent.swift; sourceTree = "<group>"; };
+		DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntentRequest.swift; sourceTree = "<group>"; };
 		DD4FFF322D458EE600B6CFF9 /* GarminWatchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarminWatchState.swift; sourceTree = "<group>"; };
 		DD5DC9F02CF3D96E00AB8703 /* AdjustmentsStateModel+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsStateModel+Overrides.swift"; sourceTree = "<group>"; };
 		DD5DC9F22CF3D9D600AB8703 /* AdjustmentsStateModel+TempTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsStateModel+TempTargets.swift"; sourceTree = "<group>"; };
@@ -2425,7 +2433,7 @@
 		6B1A8D2C2B156EC100E76752 /* LiveActivity */ = {
 			isa = PBXGroup;
 			children = (
-				6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */,
+				6B1A8D2D2B156EEF00E76752 /* LiveActivityManager.swift */,
 				6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */,
 				BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */,
 				BDF34F882C10C65E00D51995 /* Data */,
@@ -2660,6 +2668,7 @@
 		CE7CA3422A064973004BE681 /* Shortcuts */ = {
 			isa = PBXGroup;
 			children = (
+				DD4C57A42D73ADDA001BFF2C /* LiveActivity */,
 				118DF7692C5ECBC60067FEB7 /* Override */,
 				110AEDE22C5193D100615CC9 /* Bolus */,
 				CE1856F32ADC4835007E39C7 /* Carbs */,
@@ -2928,6 +2937,15 @@
 			path = Helper;
 			sourceTree = "<group>";
 		};
+		DD4C57A42D73ADDA001BFF2C /* LiveActivity */ = {
+			isa = PBXGroup;
+			children = (
+				DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */,
+				DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */,
+			);
+			path = LiveActivity;
+			sourceTree = "<group>";
+		};
 		DD5DC9EF2CF3D95400AB8703 /* AdjustmentsStateModel+Extensions */ = {
 			isa = PBXGroup;
 			children = (
@@ -3596,6 +3614,7 @@
 				38DF1786276A73D400B3528F /* TagCloudView.swift in Sources */,
 				38B4F3CD25E5031100E76A18 /* Broadcaster.swift in Sources */,
 				383420D925FFEB3F002D46C1 /* Popup.swift in Sources */,
+				DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */,
 				DD1745402C55BFC100211FAC /* AlgorithmAdvancedSettingsRootView.swift in Sources */,
 				58645BA52CA2D347008AFCE7 /* ForecastSetup.swift in Sources */,
 				110AEDEE2C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift in Sources */,
@@ -3779,7 +3798,7 @@
 				DD940BAC2CA75889000830A5 /* DynamicGlucoseColor.swift in Sources */,
 				3883581C25EE79BB00E024B2 /* TextFieldWithToolBar.swift in Sources */,
 				58D08B302C8DEA7500AA37D3 /* ForecastView.swift in Sources */,
-				6B1A8D2E2B156EEF00E76752 /* LiveActivityBridge.swift in Sources */,
+				6B1A8D2E2B156EEF00E76752 /* LiveActivityManager.swift in Sources */,
 				581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */,
 				DD4FFF332D458EE600B6CFF9 /* GarminWatchState.swift in Sources */,
 				DD6B7CB22C7B6F0800B75029 /* Rounding.swift in Sources */,
@@ -3935,6 +3954,7 @@
 				389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */,
 				110AEDEC2C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift in Sources */,
 				CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */,
+				DD4C57A82D73ADEA001BFF2C /* RestartLiveActivityIntent.swift in Sources */,
 				19E1F7EA29D082ED005C8D20 /* IconConfigProvider.swift in Sources */,
 				DD09D4822C5986F6003FEA5D /* CalendarEventSettingsRootView.swift in Sources */,
 				CE48C86428CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift in Sources */,

+ 2 - 3
Trio/Resources/Info.plist

@@ -7,7 +7,6 @@
 	<key>BGTaskSchedulerPermittedIdentifiers</key>
 	<array>
 		<string>$(PRODUCT_BUNDLE_IDENTIFIER).background-task.critical-event-log</string>
-		<string>com.trio.cleanup</string>
 	</array>
 	<key>CBBundleDisplayName</key>
 	<string>$(APP_DISPLAY_NAME)</string>
@@ -76,10 +75,10 @@
 	<string>To create events with BG reading values, so that they can be viewed on Apple Watch and CarPlay</string>
 	<key>NSCalendarsUsageDescription</key>
 	<string>Calendar is used to create a new glucose events.</string>
-	<key>NSFaceIDUsageDescription</key>
-	<string>For authorized acces to bolus</string>
 	<key>NSContactsUsageDescription</key>
 	<string>Contact is used to create a Apple Watch complication</string>
+	<key>NSFaceIDUsageDescription</key>
+	<string>For authorized acces to bolus</string>
 	<key>NSHealthShareUsageDescription</key>
 	<string>Health App is used to store blood glucose, carbs and insulin</string>
 	<key>NSHealthUpdateUsageDescription</key>

+ 13 - 13
Trio/Sources/Application/TrioApp.swift

@@ -58,7 +58,7 @@ import Swinject
         _ = resolver.resolve(PluginManager.self)!
         _ = resolver.resolve(AlertPermissionsChecker.self)!
         if #available(iOS 16.2, *) {
-            _ = resolver.resolve(LiveActivityBridge.self)!
+            _ = resolver.resolve(LiveActivityManager.self)!
         }
     }
 
@@ -117,12 +117,11 @@ import Swinject
                 {
                     AppVersionChecker.shared.checkAndNotifyVersionStatus(in: rootVC)
                 }
+
+                // Check if we need to perform a database cleaning
+                performCleanupIfNecessary()
             }
         }
-        .backgroundTask(.appRefresh("com.trio.cleanup")) {
-            await scheduleDatabaseCleaning()
-            await cleanupOldData()
-        }
     }
 
     func configureTabBarAppearance() {
@@ -146,14 +145,12 @@ import Swinject
         }
     }
 
-    func scheduleDatabaseCleaning() {
-        let request = BGAppRefreshTaskRequest(identifier: "com.trio.cleanup")
-        request.earliestBeginDate = .now.addingTimeInterval(7 * 24 * 60 * 60) // 7 days
-        do {
-            try BGTaskScheduler.shared.submit(request)
-            debug(.coreData, "Task for cleaning database scheduled successfully")
-        } catch {
-            debug(.coreData, "Failed to schedule tasks for cleaning database: \(error.localizedDescription)")
+    private func performCleanupIfNecessary() {
+        if let lastCleanupDate = UserDefaults.standard.object(forKey: "lastCleanupDate") as? Date {
+            let sevenDaysAgo = Date().addingTimeInterval(-7 * 24 * 60 * 60)
+            if lastCleanupDate < sevenDaysAgo {
+                cleanupOldData()
+            }
         }
     }
 
@@ -164,6 +161,9 @@ import Swinject
 
             await cleanupTokens
             try await purgeData
+
+            // Update the last cleanup date
+            UserDefaults.standard.set(Date(), forKey: "lastCleanupDate")
         }
     }
 

+ 2 - 2
Trio/Sources/Assemblies/ServiceAssembly.swift

@@ -24,8 +24,8 @@ final class ServiceAssembly: Assembly {
         container.register(ContactImageManager.self) { r in BaseContactImageManager(resolver: r) }
         container.register(AlertPermissionsChecker.self) { r in AlertPermissionsChecker(resolver: r) }
         if #available(iOS 16.2, *) {
-            container.register(LiveActivityBridge.self) { r in
-                LiveActivityBridge(resolver: r)
+            container.register(LiveActivityManager.self) { r in
+                LiveActivityManager(resolver: r)
             }
         }
     }

+ 106 - 3
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -26446,6 +26446,35 @@
         }
       }
     },
+    "Allow SMB for 6 hrs after a carb entry." : {
+
+    },
+    "Allow SMB when a manual Temporary Target is set greater than %@ %@." : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Allow SMB when a manual Temporary Target is set greater than %1$@ %2$@."
+          }
+        }
+      }
+    },
+    "Allow SMB when a manual Temporary Target is set under %@ %@." : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Allow SMB when a manual Temporary Target is set under %1$@ %2$@."
+          }
+        }
+      }
+    },
+    "Allow SMB when carbs are on board." : {
+
+    },
+    "Allow SMB when glucose is above the High BG Target value." : {
+
+    },
     "Allow SMB With High Temporary Target" : {
       "localizations" : {
         "ar" : {
@@ -26659,6 +26688,12 @@
         }
       }
     },
+    "Allow SMBs at all times except when a high Temp Target is set." : {
+
+    },
+    "Allow the creation of saved, preset meals." : {
+
+    },
     "Allow to add carbs in Trio." : {
       "localizations" : {
         "ar" : {
@@ -45864,9 +45899,6 @@
         }
       }
     },
-    "Confirm when initiating a bolus with a very low or forecasted very low glucose." : {
-
-    },
     "Connect" : {
       "comment" : "Connect to NS",
       "localizations" : {
@@ -50671,6 +50703,16 @@
         }
       }
     },
+    "Decrease sensitivity when glucose is below target if a manual Temp Target < %@ %@ is set." : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Decrease sensitivity when glucose is below target if a manual Temp Target < %1$@ %2$@ is set."
+          }
+        }
+      }
+    },
     "Decreasing this setting may result in fewer FPU entries with larger carb values." : {
       "localizations" : {
         "ar" : {
@@ -50989,6 +51031,9 @@
         }
       }
     },
+    "Default impact of carb absorption over a 5 minute interval." : {
+
+    },
     "Default is 20 minutes. How often to update and save the statistics.json and to upload last array, when enabled, to Nightscout." : {
       "comment" : "Description for update interval for statistics",
       "extractionState" : "manual",
@@ -59556,6 +59601,9 @@
         }
       }
     },
+    "Disables SMBs if last two glucose values differ by more than this percent." : {
+
+    },
     "Disabling this setting will still allow other commands, like Temp Targets, Add Carbs, and Start/End Overrides" : {
       "localizations" : {
         "ar" : {
@@ -67322,6 +67370,9 @@
         }
       }
     },
+    "Enable Unannounced Meals SMB." : {
+
+    },
     "Enable uploading of CGM readings to Nightscout." : {
 
     },
@@ -91008,6 +91059,16 @@
     "Include IOB & COB in the calendar event data." : {
 
     },
+    "Increase sensitivity when glucose is above target if a manual Temp Target > %@ %@ is set." : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Increase sensitivity when glucose is above target if a manual Temp Target > %1$@ %2$@ is set."
+          }
+        }
+      }
+    },
     "Increase the safety threshold used to suspend insulin delivery." : {
 
     },
@@ -97571,6 +97632,12 @@
     "Limits temporary basal rates to this percentage of your largest basal rate." : {
       "comment" : "Mini Hint for Max Daily Safety Multiplier"
     },
+    "Limits the size of a single Super Micro Bolus (SMB) dose." : {
+
+    },
+    "Limits the size of a single Unannounced Meal (UAM) SMB dose." : {
+
+    },
     "Lines" : {
       "localizations" : {
         "ar" : {
@@ -101378,6 +101445,12 @@
         }
       }
     },
+    "Lower limit of the Autosens Ratio." : {
+
+    },
+    "Lower target glucose when Autosens Ratio is <1." : {
+
+    },
     "m" : {
       "comment" : "abbreviation for minutes",
       "localizations" : {
@@ -105842,6 +105915,9 @@
     "Maximum Meal Absorption Time" : {
 
     },
+    "Maximum units of insulin allowed to be active." : {
+
+    },
     "Meal" : {
       "comment" : "Debug option view Meal",
       "extractionState" : "manual",
@@ -108139,6 +108215,9 @@
         }
       }
     },
+    "Minimum minutes since the last SMB or manual bolus to allow an automated SMB." : {
+
+    },
     "Minimum Safety Threshold" : {
       "localizations" : {
         "ar" : {
@@ -122145,6 +122224,9 @@
     "Percentage of bolus suggested in bolus calculator." : {
 
     },
+    "Percentage of calculated insulin required that is given as SMB." : {
+
+    },
     "Percentage of carbs still available if no absorption is detected." : {
       "comment" : "Mini Hint for Remaining Carbs Percentage"
     },
@@ -125795,6 +125877,9 @@
         }
       }
     },
+    "Pump rewind initiates a reset in Autosens Ratio." : {
+
+    },
     "Pump Settings" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -126759,6 +126844,9 @@
         }
       }
     },
+    "Raise target glucose if when Autosens Ratio is >1." : {
+
+    },
     "Random variation added to each reading to simulate real-world sensor noise." : {
 
     },
@@ -129576,6 +129664,9 @@
         }
       }
     },
+    "Restart Live Activity" : {
+
+    },
     "Result" : {
       "comment" : "For the  Bolus View pop-up",
       "extractionState" : "stale",
@@ -132540,6 +132631,9 @@
         }
       }
     },
+    "Scales down your basal rate to 50% at this value." : {
+
+    },
     "Schedule" : {
       "localizations" : {
         "ar" : {
@@ -167986,6 +168080,9 @@
         }
       }
     },
+    "Trio Live Activity restarted successfully." : {
+
+    },
     "Trio Not Active" : {
       "comment" : "Trio Not Active",
       "localizations" : {
@@ -171897,6 +171994,9 @@
         }
       }
     },
+    "Upper limit of the Autosens Ratio." : {
+
+    },
     "URL" : {
       "localizations" : {
         "ar" : {
@@ -174101,6 +174201,9 @@
         }
       }
     },
+    "Warning when bolusing with a very low or forecasted very low glucose." : {
+
+    },
     "Warning: Before adjusting these settings, make sure you are fully aware of the impact those changes will have." : {
       "localizations" : {
         "ar" : {

+ 1 - 1
Trio/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -264,7 +264,7 @@ extension AlgorithmAdvancedSettings {
                     units: state.units,
                     type: .decimal("min5mCarbimpact"),
                     label: String(localized: "Min 5m Carb Impact", comment: "Min 5m Carb Impact"),
-                    miniHint: "Default impact of carb absorption over a 5 minute interval.",
+                    miniHint: String(localized: "Default impact of carb absorption over a 5 minute interval."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text(

+ 3 - 3
Trio/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -140,7 +140,7 @@ extension AutosensSettings {
                     units: state.units,
                     type: .decimal("autosensMax"),
                     label: String(localized: "Autosens Max", comment: "Autosens Max"),
-                    miniHint: "Upper limit of the Autosens Ratio.",
+                    miniHint: String(localized: "Upper limit of the Autosens Ratio."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 120%").bold()
@@ -171,7 +171,7 @@ extension AutosensSettings {
                     units: state.units,
                     type: .decimal("autosensMin"),
                     label: String(localized: "Autosens Min", comment: "Autosens Min"),
-                    miniHint: "Lower limit of the Autosens Ratio.",
+                    miniHint: String(localized: "Lower limit of the Autosens Ratio."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 70%").bold()
@@ -201,7 +201,7 @@ extension AutosensSettings {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Rewind Resets Autosens", comment: "Rewind Resets Autosens"),
-                    miniHint: "Pump rewind initiates a reset in Autosens Ratio.",
+                    miniHint: String(localized: "Pump rewind initiates a reset in Autosens Ratio."),
                     verboseHint: VStack(alignment: .leading, spacing: 5) {
                         Text("Default: ON").bold()
                         Text("Medtronic Users Only").bold()

+ 1 - 1
Trio/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -47,7 +47,7 @@ extension BolusCalculatorConfig {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Display Meal Presets"),
-                    miniHint: "Allow the creation of saved, preset meals.",
+                    miniHint: String(localized: "Allow the creation of saved, preset meals."),
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                         Text("Default: ON").bold()
                         Text("Enabling this feature allows you to create and save preset meals.")

+ 1 - 1
Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift

@@ -42,7 +42,7 @@ extension UnitsLimitsSettings {
                     units: state.units,
                     type: .decimal("maxIOB"),
                     label: String(localized: "Max IOB", comment: "Max IOB"),
-                    miniHint: "Maximum units of insulin allowed to be active.",
+                    miniHint: String(localized: "Maximum units of insulin allowed to be active."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 0 units").bold()

+ 1 - 1
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift

@@ -155,7 +155,7 @@ extension LiveActivitySettings {
                 }
             }
             .listSectionSpacing(sectionSpacing)
-            .onReceive(resolver.resolve(LiveActivityBridge.self)!.$systemEnabled, perform: {
+            .onReceive(resolver.resolve(LiveActivityManager.self)!.$systemEnabled, perform: {
                 self.systemLiveActivitySetting = $0
             })
             .sheet(isPresented: $shouldDisplayHint) {

+ 16 - 12
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -32,7 +32,7 @@ extension SMBSettings {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Enable SMB Always", comment: "Enable SMB Always"),
-                    miniHint: "Allow SMBs at all times except when a high Temp Target is set.",
+                    miniHint: String(localized: "Allow SMBs at all times except when a high Temp Target is set."),
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text(
@@ -60,7 +60,7 @@ extension SMBSettings {
                         units: state.units,
                         type: .boolean,
                         label: String(localized: "Enable SMB With COB", comment: "Enable SMB With COB"),
-                        miniHint: "Allow SMB when carbs are on board.",
+                        miniHint: String(localized: "Allow SMB when carbs are on board."),
                         verboseHint:
                         VStack(alignment: .leading, spacing: 10) {
                             Text("Default: OFF").bold()
@@ -87,7 +87,9 @@ extension SMBSettings {
                         units: state.units,
                         type: .boolean,
                         label: String(localized: "Enable SMB With Temptarget", comment: "Enable SMB With Temptarget"),
-                        miniHint: "Allow SMB when a manual Temporary Target is set under \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue).",
+                        miniHint: String(
+                            localized: "Allow SMB when a manual Temporary Target is set under \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue)."
+                        ),
                         verboseHint:
                         VStack(alignment: .leading, spacing: 10) {
                             Text("Default: OFF").bold()
@@ -114,7 +116,7 @@ extension SMBSettings {
                         units: state.units,
                         type: .boolean,
                         label: String(localized: "Enable SMB After Carbs", comment: "Enable SMB After Carbs"),
-                        miniHint: "Allow SMB for 6 hrs after a carb entry.",
+                        miniHint: String(localized: "Allow SMB for 6 hrs after a carb entry."),
                         verboseHint:
                         VStack(alignment: .leading, spacing: 10) {
                             Text("Default: OFF").bold()
@@ -142,7 +144,7 @@ extension SMBSettings {
                         type: .conditionalDecimal("enableSMB_high_bg_target"),
                         label: String(localized: "Enable SMB With High BG", comment: "Enable SMB With High BG"),
                         conditionalLabel: "High BG Target",
-                        miniHint: "Allow SMB when glucose is above the High BG Target value.",
+                        miniHint: String(localized: "Allow SMB when glucose is above the High BG Target value."),
                         verboseHint:
                         VStack(alignment: .leading, spacing: 10) {
                             Text("Default: OFF").bold()
@@ -178,7 +180,9 @@ extension SMBSettings {
                         "Allow SMB With High Temptarget",
                         comment: "Allow SMB With High Temptarget"
                     ),
-                    miniHint: "Allow SMB when a manual Temporary Target is set greater than \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue).",
+                    miniHint: String(
+                        localized: "Allow SMB when a manual Temporary Target is set greater than \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue)."
+                    ),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
@@ -208,7 +212,7 @@ extension SMBSettings {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Enable UAM", comment: "Enable UAM"),
-                    miniHint: "Enable Unannounced Meals SMB.",
+                    miniHint: String(localized: "Enable Unannounced Meals SMB."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
@@ -238,7 +242,7 @@ extension SMBSettings {
                     units: state.units,
                     type: .decimal("maxSMBBasalMinutes"),
                     label: String(localized: "Max SMB Basal Minutes", comment: "Max SMB Basal Minutes"),
-                    miniHint: "Limits the size of a single Super Micro Bolus (SMB) dose.",
+                    miniHint: String(localized: "Limits the size of a single Super Micro Bolus (SMB) dose."),
                     verboseHint: VStack(spacing: 10) {
                         VStack(alignment: .leading, spacing: 10) {
                             VStack(alignment: .leading, spacing: 1) {
@@ -284,7 +288,7 @@ extension SMBSettings {
                     units: state.units,
                     type: .decimal("maxUAMSMBBasalMinutes"),
                     label: String(localized: "Max UAM Basal Minutes", comment: "Max UAM Basal Minutes"),
-                    miniHint: "Limits the size of a single Unannounced Meal (UAM) SMB dose.",
+                    miniHint: String(localized: "Limits the size of a single Unannounced Meal (UAM) SMB dose."),
                     verboseHint: VStack(spacing: 10) {
                         VStack(alignment: .leading, spacing: 10) {
                             VStack(alignment: .leading, spacing: 1) {
@@ -329,7 +333,7 @@ extension SMBSettings {
                     units: state.units,
                     type: .decimal("maxDeltaBGthreshold"),
                     label: String(localized: "Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold"),
-                    miniHint: "Disables SMBs if last two glucose values differ by more than this percent.",
+                    miniHint: String(localized: "Disables SMBs if last two glucose values differ by more than this percent."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 20% increase").bold()
@@ -354,7 +358,7 @@ extension SMBSettings {
                     units: state.units,
                     type: .decimal("smbDeliveryRatio"),
                     label: String(localized: "SMB Delivery Ratio", comment: "SMB Delivery Ratio"),
-                    miniHint: "Percentage of calculated insulin required that is given as SMB.",
+                    miniHint: String(localized: "Percentage of calculated insulin required that is given as SMB."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 50%").bold()
@@ -382,7 +386,7 @@ extension SMBSettings {
                     units: state.units,
                     type: .decimal("smbInterval"),
                     label: String(localized: "SMB Interval", comment: "SMB Interval"),
-                    miniHint: "Minimum minutes since the last SMB or manual bolus to allow an automated SMB.",
+                    miniHint: String(localized: "Minimum minutes since the last SMB or manual bolus to allow an automated SMB."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 3 min").bold()

+ 9 - 5
Trio/Sources/Modules/TargetBehavoir/View/TargetBehavoirRootView.swift

@@ -40,7 +40,9 @@ extension TargetBehavoir {
                         "High Temp Target Raises Sensitivity",
                         comment: "High Temp Target Raises Sensitivity"
                     ),
-                    miniHint: "Increase sensitivity when glucose is above target if a manual Temp Target > \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue) is set.",
+                    miniHint: String(
+                        localized: "Increase sensitivity when glucose is above target if a manual Temp Target > \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue) is set."
+                    ),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
@@ -77,7 +79,9 @@ extension TargetBehavoir {
                         "Low Temp Target Lowers Sensitivity",
                         comment: "Low Temp Target Lowers Sensitivity"
                     ),
-                    miniHint: "Decrease sensitivity when glucose is below target if a manual Temp Target < \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue) is set.",
+                    miniHint: String(
+                        localized: "Decrease sensitivity when glucose is below target if a manual Temp Target < \(state.units == .mgdL ? "100" : 100.formattedAsMmolL) \(state.units.rawValue) is set."
+                    ),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
@@ -105,7 +109,7 @@ extension TargetBehavoir {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Sensitivity Raises Target", comment: "Sensitivity Raises Target"),
-                    miniHint: "Raise target glucose if when Autosens Ratio is >1.",
+                    miniHint: String(localized: "Raise target glucose if when Autosens Ratio is >1."),
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text(
@@ -128,7 +132,7 @@ extension TargetBehavoir {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Resistance Lowers Target", comment: "Resistance Lowers Target"),
-                    miniHint: "Lower target glucose when Autosens Ratio is <1.",
+                    miniHint: String(localized: "Lower target glucose when Autosens Ratio is <1."),
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text(
@@ -151,7 +155,7 @@ extension TargetBehavoir {
                     units: state.units,
                     type: .decimal("halfBasalExerciseTarget"),
                     label: String(localized: "Half Basal Exercise Target", comment: "Half Basal Exercise Target"),
-                    miniHint: "Scales down your basal rate to 50% at this value.",
+                    miniHint: String(localized: "Scales down your basal rate to 50% at this value."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text(

+ 1 - 1
Trio/Sources/Services/LiveActivity/Data/DataManager.swift

@@ -3,7 +3,7 @@ import Foundation
 // Fetch Data for Glucose and Determination from Core Data and map them to the Structs in order to pass them thread safe to the glucoseDidUpdate/ pushUpdate function
 
 @available(iOS 16.2, *)
-extension LiveActivityBridge {
+extension LiveActivityManager {
     func fetchAndMapGlucose() async throws -> [GlucoseData] {
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,

+ 111 - 3
Trio/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -9,6 +9,9 @@ import UIKit
     let activity: Activity<LiveActivityAttributes>
     let startDate: Date
 
+    /// Determines if the current activity needs to be recreated.
+    ///
+    /// - Returns: `true` if the activity is dismissed, ended, stale, or has been active for more than 60 minutes; otherwise, `false`.
     func needsRecreation() -> Bool {
         switch activity.activityState {
         case .dismissed,
@@ -24,34 +27,54 @@ import UIKit
     }
 }
 
+/// A service managing live activity updates and state management.
+///
+/// This class handles the creation, update, and termination of live activities based on various data sources
+/// (e.g. Core Data notifications, glucose updates, settings changes). It integrates with system notifications,
+/// dependency injection, and user defaults to ensure that the live activity reflects the current app state.
+///
+/// Additionally, it supports a restart functionality (via `restartActivityFromLiveActivityIntent()`)
+/// via iOS shortcuts, similar to other iOS apps like xDrip4iOS or Sweet Dreams.
 @available(iOS 16.2, *)
-final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
+final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver {
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var storage: FileStorage!
     @Injected() private var glucoseStorage: GlucoseStorage!
 
     private let activityAuthorizationInfo = ActivityAuthorizationInfo()
+    /// Indicates whether system live activities are enabled.
     @Published private(set) var systemEnabled: Bool
 
+    /// Returns the current Trio settings.
     private var settings: TrioSettings {
         settingsManager.settings
     }
 
+    /// Determination data used to update live activity state.
     var determination: DeterminationData?
+    /// The current active live activity.
     private var currentActivity: ActiveActivity?
+    /// The most recent glucose reading.
     private var latestGlucose: GlucoseData?
+    /// Array of glucose readings fetched from persistent storage.
     var glucoseFromPersistence: [GlucoseData]?
+    /// The current override data (if any).
     var override: OverrideData?
+    /// The widget items displayed within the live activity.
     var widgetItems: [LiveActivityAttributes.LiveActivityItem]?
 
+    /// A Core Data task context.
     let context = CoreDataStack.shared.newTaskContext()
 
-    // Queue for handling Core Data change notifications
+    /// A dispatch queue for handling Core Data change notifications.
     private let queue = DispatchQueue(label: "LiveActivityBridge.queue", qos: .userInitiated)
     private var coreDataPublisher: AnyPublisher<Set<NSManagedObjectID>, Never>?
     private var subscriptions = Set<AnyCancellable>()
 
+    /// Initializes a new instance of `LiveActivityBridge` and sets up observers, subscribers, and notifications.
+    ///
+    /// - Parameter resolver: The dependency injection resolver.
     init(resolver: Resolver) {
         coreDataPublisher =
             changedObjectsOnManagedObjectContextDidSavePublisher()
@@ -69,6 +92,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         broadcaster.register(SettingsObserver.self, observer: self)
     }
 
+    /// Sets up application notifications that trigger live activity updates when the app state changes.
     private func setupNotifications() {
         let notificationCenter = Foundation.NotificationCenter.default
         notificationCenter
@@ -91,12 +115,17 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         )
     }
 
+    /// Called when the app settings change.
+    ///
+    /// This method triggers an update to the live activity content state based on the new settings.
+    /// - Parameter _: The updated `TrioSettings`.
     func settingsDidChange(_: TrioSettings) {
         Task {
             await updateContentState(determination)
         }
     }
 
+    /// Registers handlers for Core Data changes related to overrides, glucose readings, and determinations.
     private func registerHandler() {
         coreDataPublisher?.filteredByEntityName("OverrideStored").sink { [weak self] _ in
             guard let self = self else { return }
@@ -116,6 +145,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
             }.store(in: &subscriptions)
     }
 
+    /// Registers subscribers for updates from the glucose storage.
     private func registerSubscribers() {
         glucoseStorage.updatePublisher
             .receive(on: queue)
@@ -126,6 +156,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
             .store(in: &subscriptions)
     }
 
+    /// Fetches and maps new determination data and updates the live activity content state.
     private func cobOrIobDidUpdate() {
         Task { @MainActor in
             do {
@@ -142,6 +173,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Fetches and maps override data and updates the live activity content state.
     private func overridesDidUpdate() {
         Task { @MainActor in
             do {
@@ -155,6 +187,9 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Handles changes to the live activity order.
+    ///
+    /// Loads widget items from user defaults and triggers an update to the live activity order.
     @objc private func handleLiveActivityOrderChange() {
         Task {
             self.widgetItems = UserDefaults.standard.loadLiveActivityOrderFromUserDefaults() ?? LiveActivityAttributes
@@ -163,6 +198,9 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Updates the live activity content state based on new determination or override data.
+    ///
+    /// - Parameter update: An object representing new `DeterminationData` or `OverrideData`.
     @MainActor private func updateContentState<T>(_ update: T) async {
         guard let latestGlucose = latestGlucose else {
             return
@@ -201,12 +239,16 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Triggers an update of the live activity order.
+    ///
+    /// This method refreshes the activity’s content state to reflect any changes in the widget order.
     @MainActor private func updateLiveActivityOrder() async {
         Task {
             await updateContentState(determination)
         }
     }
 
+    /// Sets up the array of glucose data from persistent storage and triggers an update to the live activity.
     private func setupGlucoseArray() {
         Task { @MainActor in
             do {
@@ -218,6 +260,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Monitors live activity authorization changes and updates the `systemEnabled` flag.
     private func monitorForLiveActivityAuthorizationChanges() {
         Task {
             for await activityState in activityAuthorizationInfo.activityEnablementUpdates {
@@ -230,6 +273,10 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Forces an update to the live activity.
+    ///
+    /// If live activities are enabled and the current activity requires recreation, this method triggers a new glucose update.
+    /// Otherwise, it ends the current live activity.
     @MainActor private func forceActivityUpdate() {
         if settings.useLiveActivity {
             if currentActivity?.needsRecreation() ?? true {
@@ -242,6 +289,12 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Pushes an update to the live activity with the specified content state.
+    ///
+    /// If an existing activity requires recreation or is outdated, this method ends it and starts a new one.
+    /// Otherwise, it updates the current live activity.
+    ///
+    /// - Parameter state: The new content state to push to the live activity.
     @MainActor private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
         for unknownActivity in Activity<LiveActivityAttributes>.activities
             .filter({ self.currentActivity?.activity.id != $0.id })
@@ -297,20 +350,75 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
         }
     }
 
+    /// Ends the current live activity and ensures that all unknown activities are terminated.
     private func endActivity() async {
+        debug(.default, "Ending all live activities...")
+
         if let currentActivity {
+            debug(.default, "Ending current activity: \(currentActivity.activity.id)")
             await currentActivity.activity.end(nil, dismissalPolicy: .immediate)
             self.currentActivity = nil
         }
 
+        for activity in Activity<LiveActivityAttributes>.activities {
+            debug(.default, "Ending lingering activity: \(activity.id)")
+            await activity.end(nil, dismissalPolicy: .immediate)
+        }
+
         for unknownActivity in Activity<LiveActivityAttributes>.activities {
+            debug(.default, "Ending unknown activity: \(unknownActivity.id)")
             await unknownActivity.end(nil, dismissalPolicy: .immediate)
         }
+
+        debug(.default, "All live activities ended.")
+    }
+
+    /// Restarts the live activity from a Live Activity Intent.
+    ///
+    /// This method mimics xdrip’s `restartActivityFromLiveActivityIntent()` behavior by verifying that a valid content state exists,
+    /// ending the current live activity, and starting a new one using the current state.
+    @MainActor func restartActivityFromLiveActivityIntent() async {
+        guard let latestGlucose = latestGlucose,
+              let determination = determination
+        else {
+            debug(.default, "Cannot restart live activity because required persistent state is not available. Fetching data...")
+            return
+        }
+
+        guard let contentState = LiveActivityAttributes.ContentState(
+            new: latestGlucose,
+            prev: latestGlucose,
+            units: settings.units,
+            chart: glucoseFromPersistence ?? [],
+            settings: settings,
+            determination: determination,
+            override: override,
+            widgetItems: widgetItems
+        ) else {
+            debug(.default, "Cannot restart live activity because content state cannot be created")
+            return
+        }
+
+        await endActivity()
+
+        while (currentActivity != nil && currentActivity!.activity.activityState != .ended) || Activity<LiveActivityAttributes>
+            .activities.contains(where: { $0.activityState != .ended })
+        {
+            debug(.default, "Waiting for Live Activity to end...")
+            try? await Task.sleep(nanoseconds: 200_000_000) // 0.2s sleep
+        }
+
+        await pushUpdate(contentState)
+        debug(.default, "Restarted Live Activity from LiveActivityIntent (via iOS Shortcut)")
     }
 }
 
 @available(iOS 16.2, *)
-extension LiveActivityBridge {
+extension LiveActivityManager {
+    /// Updates the live activity when new glucose data is available.
+    ///
+    /// This function adjusts the live activity content based on new glucose readings and triggers an update to the live activity.
+    /// - Parameter glucose: An array of `GlucoseData` objects.
     @MainActor func glucoseDidUpdate(_ glucose: [GlucoseData]) {
         guard settings.useLiveActivity else {
             if currentActivity != nil {

+ 9 - 0
Trio/Sources/Shortcuts/AppShortcuts.swift

@@ -66,5 +66,14 @@ struct AppShortcuts: AppShortcutsProvider {
             shortTitle: "Cancel Temp Target",
             systemImageName: "xmark.circle.fill"
         )
+        AppShortcut(
+            intent: RestartLiveActivityIntent(),
+            phrases: [
+                "Restart \(.applicationName) Live Activity",
+                "Restarts the Live Activity for \(.applicationName)"
+            ],
+            shortTitle: "Restart Live Activity",
+            systemImageName: "arrow.clockwise.circle.fill"
+        )
     }
 }

+ 1 - 0
Trio/Sources/Shortcuts/BaseIntentsRequest.swift

@@ -14,6 +14,7 @@ import Swinject
     @Injected() var glucoseStorage: GlucoseStorage!
     @Injected() var apsManager: APSManager!
     @Injected() var overrideStorage: OverrideStorage!
+    @Injected() var liveActivityManager: LiveActivityManager!
 
     let resolver: Resolver
 

+ 29 - 0
Trio/Sources/Shortcuts/LiveActivity/RestartLiveActivityIntent.swift

@@ -0,0 +1,29 @@
+import AppIntents
+import Foundation
+
+/// App Intent used to restart the live activity via Apple Shortcuts automation.
+/// When invoked, this intent instantiates a RestartLiveActivityIntentRequest, which has its
+/// dependencies injected via Swinject, and calls the restart functionality.
+@available(iOS 16.2, *) struct RestartLiveActivityIntent: LiveActivityIntent {
+    /// Title of the action in the Shortcuts app.
+    static var title = LocalizedStringResource("Restart Live Activity", table: "ShortcutsDetail")
+
+    /// Description of the action in the Shortcuts app.
+    static var description = IntentDescription(.init("Restarts Trio's Live Activity", table: "ShortcutsDetail"))
+
+    /// Performs the intent by triggering the live activity restart.
+    ///
+    /// This method creates an instance of RestartLiveActivityIntentRequest (which inherits from BaseIntentsRequest)
+    /// so that dependency injection provides the required services, then calls its restart functionality.
+    ///
+    /// - Returns: An intent result indicating success.
+    @MainActor func perform() async throws -> some ReturnsValue<String> {
+        let request = RestartLiveActivityIntentRequest()
+        do {
+            try await request.performRestart()
+        } catch {
+            debug(.default, "Error restarting Live Activity: \(error)")
+        }
+        return .result(value: String(localized: "Trio Live Activity restarted successfully."))
+    }
+}

+ 43 - 0
Trio/Sources/Shortcuts/LiveActivity/RestartLiveActivityIntentRequest.swift

@@ -0,0 +1,43 @@
+import AppIntents
+import Foundation
+import UIKit
+
+/// Request object that uses dependency injection to perform a live activity restart.
+/// This class inherits from BaseIntentsRequest so that its dependencies (including liveActivityManager)
+/// are automatically injected.
+@available(iOS 16.2, *) final class RestartLiveActivityIntentRequest: BaseIntentsRequest {
+    /// Triggers the live activity restart via the injected LiveActivityManager.
+    ///
+    /// - Throws: An error if the restart process fails.
+    /// - Returns: Void upon successful restart.
+    @MainActor func performRestart() async throws {
+        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+
+        // Start background task
+        backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Restart Live Activity") {
+            Task { @MainActor in
+                if backgroundTaskID != .invalid {
+                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
+                    backgroundTaskID = .invalid
+                    debug(.default, "Background task expired and ended.")
+                }
+            }
+        }
+
+        guard backgroundTaskID != .invalid else {
+            debug(.default, "Failed to start background task.")
+            return
+        }
+
+        debug(.default, "Background task started: \(backgroundTaskID)")
+
+        await liveActivityManager.restartActivityFromLiveActivityIntent()
+
+        // Ensure background task ends properly
+        if backgroundTaskID != .invalid {
+            UIApplication.shared.endBackgroundTask(backgroundTaskID)
+            debug(.default, "Background task ended successfully.")
+            backgroundTaskID = .invalid
+        }
+    }
+}