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

fix Live Activity not updating, refactoring; fix bobble not showing the according trend

polscm32 1 год назад
Родитель
Сommit
dfdd98b12e

+ 34 - 16
FreeAPS.xcodeproj/project.pbxproj

@@ -316,8 +316,8 @@
 		6B1A8D2E2B156EEF00E76752 /* LiveActivityBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */; };
 		6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A708CDB546692C2230B385 /* SnoozeDataFlow.swift */; };
 		6B9625766B697D1C98E455A2 /* PumpSettingsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72778B68C3004F71F6E79BDC /* PumpSettingsEditorStateModel.swift */; };
-		6BCF84DD2B16843A003AD46E /* LiveActitiyShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActitiyShared.swift */; };
-		6BCF84DE2B16843A003AD46E /* LiveActitiyShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActitiyShared.swift */; };
+		6BCF84DD2B16843A003AD46E /* LiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActivityAttributes.swift */; };
+		6BCF84DE2B16843A003AD46E /* LiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActivityAttributes.swift */; };
 		6EADD581738D64431902AC0A /* LibreConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EBA7C03C26FCC67E16D798 /* LibreConfigProvider.swift */; };
 		6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; };
 		711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; };
@@ -345,8 +345,6 @@
 		BA00D96F7B2FF169A06FB530 /* CGMStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C018D1680307A31C9ED7120 /* CGMStateModel.swift */; };
 		BA90041DC8991147E5C8C3AA /* CalibrationsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500371C09F54F89A97D65FDB /* CalibrationsRootView.swift */; };
 		BD1661312B82ADAB00256551 /* CustomProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1661302B82ADAB00256551 /* CustomProgressView.swift */; };
-		BD188BEC2B1B805B00B183BF /* WidgetBobble.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD188BEB2B1B805A00B183BF /* WidgetBobble.swift */; };
-		BD188BED2B1B805B00B183BF /* WidgetBobble.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD188BEB2B1B805A00B183BF /* WidgetBobble.swift */; };
 		BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */; };
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
@@ -362,6 +360,11 @@
 		BDB3C1052C0341E600CEEAA1 /* TempBasalStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C0FF2C0341E500CEEAA1 /* TempBasalStored+CoreDataProperties.swift */; };
 		BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */; };
 		BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */; };
+		BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F822C10C5B600D51995 /* DataManager.swift */; };
+		BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F842C10C62E00D51995 /* GlucoseData.swift */; };
+		BDF34F902C10CF8C00D51995 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F8F2C10CF8C00D51995 /* CoreDataStack.swift */; };
+		BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */; };
+		BDF34F952C10D27300D51995 /* DeterminationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F942C10D27300D51995 /* DeterminationData.swift */; };
 		BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */; };
 		BDFD165A2AE40438007F0DDA /* BolusRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFD16592AE40438007F0DDA /* BolusRootView.swift */; };
 		BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; };
@@ -469,7 +472,6 @@
 		FE41E4D629463EE20047FD55 /* NightscoutPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */; };
 		FE66D16B291F74F8005D6F77 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */; };
 		FEFA5C0F299F810B00765C17 /* Core_Data.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FEFA5C0D299F810B00765C17 /* Core_Data.xcdatamodeld */; };
-		FEFA5C11299F814A00765C17 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFA5C10299F814A00765C17 /* CoreDataStack.swift */; };
 		FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */; };
 /* End PBXBuildFile section */
 
@@ -921,7 +923,7 @@
 		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>"; };
-		6BCF84DC2B16843A003AD46E /* LiveActitiyShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActitiyShared.swift; sourceTree = "<group>"; };
+		6BCF84DC2B16843A003AD46E /* LiveActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityAttributes.swift; sourceTree = "<group>"; };
 		6F8BA8533F56BC55748CA877 /* PreferencesEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorProvider.swift; sourceTree = "<group>"; };
 		72778B68C3004F71F6E79BDC /* PumpSettingsEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorStateModel.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
@@ -951,7 +953,6 @@
 		BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorDataFlow.swift; sourceTree = "<group>"; };
 		BC210C0F3CB6D3C86E5DED4E /* LibreConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigRootView.swift; sourceTree = "<group>"; };
 		BD1661302B82ADAB00256551 /* CustomProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomProgressView.swift; sourceTree = "<group>"; };
-		BD188BEB2B1B805A00B183BF /* WidgetBobble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBobble.swift; sourceTree = "<group>"; };
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
@@ -966,6 +967,11 @@
 		BDB3C0FF2C0341E500CEEAA1 /* TempBasalStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempBasalStored+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
 		BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNotification.swift; sourceTree = "<group>"; };
+		BDF34F822C10C5B600D51995 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = "<group>"; };
+		BDF34F842C10C62E00D51995 /* GlucoseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseData.swift; sourceTree = "<group>"; };
+		BDF34F8F2C10CF8C00D51995 /* CoreDataStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = "<group>"; };
+		BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivityAttributes+Helper.swift"; sourceTree = "<group>"; };
+		BDF34F942C10D27300D51995 /* DeterminationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationData.swift; sourceTree = "<group>"; };
 		BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenView.swift; sourceTree = "<group>"; };
 		BDFD16592AE40438007F0DDA /* BolusRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusRootView.swift; sourceTree = "<group>"; };
 		BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorRootView.swift; sourceTree = "<group>"; };
@@ -1057,7 +1063,6 @@
 		FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutPreferences.swift; sourceTree = "<group>"; };
 		FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
 		FEFA5C0E299F810B00765C17 /* Core_Data.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Core_Data.xcdatamodel; sourceTree = "<group>"; };
-		FEFA5C10299F814A00765C17 /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = "<group>"; };
 		FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -2178,7 +2183,7 @@
 			isa = PBXGroup;
 			children = (
 				FEFA5C0D299F810B00765C17 /* Core_Data.xcdatamodeld */,
-				FEFA5C10299F814A00765C17 /* CoreDataStack.swift */,
+				BDF34F8F2C10CF8C00D51995 /* CoreDataStack.swift */,
 				5825D1052BD4056700F36E9B /* Classes+Properties */,
 				5825D1622BD405AE00F36E9B /* Helper */,
 			);
@@ -2220,7 +2225,6 @@
 			children = (
 				6B1A8D1D2B14D91600E76752 /* LiveActivityBundle.swift */,
 				6B1A8D1F2B14D91600E76752 /* LiveActivity.swift */,
-				BD188BEB2B1B805A00B183BF /* WidgetBobble.swift */,
 				6B1A8D232B14D91700E76752 /* Assets.xcassets */,
 				6B1A8D252B14D91700E76752 /* Info.plist */,
 			);
@@ -2231,7 +2235,9 @@
 			isa = PBXGroup;
 			children = (
 				6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */,
-				6BCF84DC2B16843A003AD46E /* LiveActitiyShared.swift */,
+				6BCF84DC2B16843A003AD46E /* LiveActivityAttributes.swift */,
+				BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */,
+				BDF34F882C10C65E00D51995 /* Data */,
 			);
 			path = LiveActivity;
 			sourceTree = "<group>";
@@ -2332,6 +2338,16 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		BDF34F882C10C65E00D51995 /* Data */ = {
+			isa = PBXGroup;
+			children = (
+				BDF34F822C10C5B600D51995 /* DataManager.swift */,
+				BDF34F842C10C62E00D51995 /* GlucoseData.swift */,
+				BDF34F942C10D27300D51995 /* DeterminationData.swift */,
+			);
+			path = Data;
+			sourceTree = "<group>";
+		};
 		C11D545CED3ECEB525EDEE23 /* LibreConfig */ = {
 			isa = PBXGroup;
 			children = (
@@ -2923,6 +2939,7 @@
 				CC76E94F2BD471BA008BEB61 /* ForecastValue+CoreDataProperties.swift in Sources */,
 				389A572026079BAA00BC102F /* Interpolation.swift in Sources */,
 				19A910382A24EF3200C8951B /* ChartsView.swift in Sources */,
+				BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
 				19D466A729AA2C22004D5F33 /* FPUConfigStateModel.swift in Sources */,
 				38E44528274E401C00EC9A94 /* Protected.swift in Sources */,
@@ -2985,7 +3002,6 @@
 				38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */,
 				3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */,
 				191F62682AD6B05A004D7911 /* NightscoutSettings.swift in Sources */,
-				FEFA5C11299F814A00765C17 /* CoreDataStack.swift in Sources */,
 				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
 				3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */,
 				38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */,
@@ -3081,7 +3097,6 @@
 				38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */,
 				CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */,
 				385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */,
-				BD188BEC2B1B805B00B183BF /* WidgetBobble.swift in Sources */,
 				F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */,
 				38887CCE25F5725200944304 /* IOBEntry.swift in Sources */,
 				38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */,
@@ -3111,6 +3126,7 @@
 				69B9A368029F7EB39F525422 /* CREditorStateModel.swift in Sources */,
 				38E44538274E411700EC9A94 /* Disk+[Data].swift in Sources */,
 				98641AF4F92123DA668AB931 /* CREditorRootView.swift in Sources */,
+				BDF34F902C10CF8C00D51995 /* CoreDataStack.swift in Sources */,
 				38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
 				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
@@ -3137,6 +3153,7 @@
 				38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */,
 				5BFA1C2208114643B77F8CEB /* AddTempTargetProvider.swift in Sources */,
 				BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */,
+				BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */,
 				E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */,
 				919DBD08F13BAFB180DF6F47 /* AddTempTargetStateModel.swift in Sources */,
 				8BC2F5A29AD1ED08AC0EE013 /* AddTempTargetRootView.swift in Sources */,
@@ -3152,6 +3169,7 @@
 				19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */,
 				FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */,
 				F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */,
+				BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */,
 				3862CC1F273FDC9200BF832C /* CalibrationsChart.swift in Sources */,
 				19E1F7EC29D082FE005C8D20 /* IconConfigStateModel.swift in Sources */,
 				711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */,
@@ -3175,11 +3193,12 @@
 				1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */,
 				19F95FFA29F1102A00314DDC /* StatRootView.swift in Sources */,
 				0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */,
-				6BCF84DD2B16843A003AD46E /* LiveActitiyShared.swift in Sources */,
+				6BCF84DD2B16843A003AD46E /* LiveActivityAttributes.swift in Sources */,
 				195D80B92AF697F700D25097 /* DynamicProvider.swift in Sources */,
 				D6D02515BBFBE64FEBE89856 /* DataTableRootView.swift in Sources */,
 				38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */,
 				F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */,
+				BDF34F952C10D27300D51995 /* DeterminationData.swift in Sources */,
 				BA00D96F7B2FF169A06FB530 /* CGMStateModel.swift in Sources */,
 				61962FCAF8A2D222553AC5A3 /* LibreConfigDataFlow.swift in Sources */,
 				BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */,
@@ -3241,10 +3260,9 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				6BCF84DE2B16843A003AD46E /* LiveActitiyShared.swift in Sources */,
+				6BCF84DE2B16843A003AD46E /* LiveActivityAttributes.swift in Sources */,
 				6B1A8D1E2B14D91600E76752 /* LiveActivityBundle.swift in Sources */,
 				6B1A8D202B14D91600E76752 /* LiveActivity.swift in Sources */,
-				BD188BED2B1B805B00B183BF /* WidgetBobble.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 2 - 0
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -81,6 +81,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                         glucoseEntry.glucose = Int16(entry.glucose ?? 0)
                         glucoseEntry.date = entry.dateString
                         glucoseEntry.direction = entry.direction?.symbol
+                        debugPrint("\(DebuggingIdentifiers.failed)")
+                        debugPrint("\(String(describing: glucoseEntry.direction))")
                         return false // Continue processing
                     }
                 )

+ 13 - 13
FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift

@@ -104,26 +104,26 @@ struct CurrentGlucoseView: View {
                 }.frame(alignment: .top)
             }
         }
-        .onChange(of: combinedGlucoseValues.first?.direction) { newDirection in
+        .onChange(of: combinedGlucoseValues.first?.directionEnum) { newDirection in
             withAnimation {
                 switch newDirection {
-                case "DoubleUp",
-                     "SingleUp",
-                     "TripleUp":
+                case .doubleUp,
+                     .singleUp,
+                     .tripleUp:
                     rotationDegrees = -90
-                case "FortyFiveUp":
+                case .fortyFiveUp:
                     rotationDegrees = -45
-                case "Flat":
+                case .flat:
                     rotationDegrees = 0
-                case "FortyFiveDown":
+                case .fortyFiveDown:
                     rotationDegrees = 45
-                case "DoubleDown",
-                     "SingleDown",
-                     "TripleDown":
+                case .doubleDown,
+                     .singleDown,
+                     .tripleDown:
                     rotationDegrees = 90
-                case "NONE",
-                     "NOT COMPUTABLE",
-                     "RATE OUT OF RANGE":
+                case nil,
+                     .notComputable,
+                     .rateOutOfRange:
                     rotationDegrees = 0
                 default:
                     rotationDegrees = 0

+ 33 - 0
FreeAPS/Sources/Services/LiveActivity/Data/DataManager.swift

@@ -0,0 +1,33 @@
+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 {
+    func fetchAndMapGlucose() async {
+        await context.perform {
+            self.glucoseFromPersistence = CoreDataStack.shared.fetchEntities(
+                ofType: GlucoseStored.self,
+                onContext: self.context,
+                predicate: NSPredicate.predicateForSixHoursAgo,
+                key: "date",
+                ascending: false,
+                fetchLimit: 72
+            ).map { GlucoseData(glucose: Int($0.glucose), date: $0.date ?? Date(), direction: $0.directionEnum) }
+        }
+    }
+
+    func fetchAndMapDetermination() async {
+        await context.perform {
+            self.determination = CoreDataStack.shared.fetchEntities(
+                ofType: OrefDetermination.self,
+                onContext: self.context,
+                predicate: NSPredicate.enactedDetermination,
+                key: "deliverAt",
+                ascending: false,
+                fetchLimit: 1,
+                propertiesToFetch: ["iob", "cob", "deliverAt"]
+            ).first.map { DeterminationData(cob: Int($0.cob), iob: $0.iob?.decimalValue ?? 0) }
+        }
+    }
+}

+ 6 - 0
FreeAPS/Sources/Services/LiveActivity/Data/DeterminationData.swift

@@ -0,0 +1,6 @@
+import Foundation
+
+struct DeterminationData {
+    let cob: Int
+    let iob: Decimal
+}

+ 7 - 0
FreeAPS/Sources/Services/LiveActivity/Data/GlucoseData.swift

@@ -0,0 +1,7 @@
+import Foundation
+
+struct GlucoseData {
+    let glucose: Int
+    let date: Date
+    let direction: BloodGlucose.Direction?
+}

+ 101 - 0
FreeAPS/Sources/Services/LiveActivity/LiveActivityAttributes+Helper.swift

@@ -0,0 +1,101 @@
+import Foundation
+
+extension LiveActivityAttributes.ContentState {
+    static func formatGlucose(_ value: Int, mmol: Bool, forceSign: Bool) -> String {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        if mmol {
+            formatter.minimumFractionDigits = 1
+            formatter.maximumFractionDigits = 1
+        }
+        if forceSign {
+            formatter.positivePrefix = formatter.plusSign
+        }
+        formatter.roundingMode = .halfUp
+
+        return formatter
+            .string(from: mmol ? value.asMmolL as NSNumber : NSNumber(value: value))!
+    }
+
+    static func calculateChange(chart: [GlucoseData]) -> String {
+        guard chart.count > 2 else { return "" }
+        let lastGlucose = chart.first?.glucose ?? 0
+        let secondLastGlucose = chart.dropFirst().first?.glucose ?? 0
+        let delta = lastGlucose - secondLastGlucose
+        let deltaAsDecimal = Decimal(delta)
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        formatter.positivePrefix = "  +"
+        formatter.negativePrefix = "  -"
+        return formatter.string(from: deltaAsDecimal as NSNumber) ?? "--"
+    }
+
+    init?(
+        new bg: GlucoseData,
+        prev _: GlucoseData?,
+        mmol: Bool,
+        chart: [GlucoseData],
+        settings: FreeAPSSettings,
+        determination: DeterminationData?
+    ) {
+        let glucose = bg.glucose
+        let formattedBG = Self.formatGlucose(Int(glucose), mmol: mmol, forceSign: false)
+        var rotationDegrees: Double = 0.0
+
+        switch bg.direction {
+        case .doubleUp,
+             .singleUp,
+             .tripleUp:
+            rotationDegrees = -90
+        case .fortyFiveUp:
+            rotationDegrees = -45
+        case .flat:
+            rotationDegrees = 0
+        case .fortyFiveDown:
+            rotationDegrees = 45
+        case .doubleDown,
+             .singleDown,
+             .tripleDown:
+            rotationDegrees = 90
+        case nil,
+             .notComputable,
+             .rateOutOfRange:
+            rotationDegrees = 0
+        default:
+            rotationDegrees = 0
+        }
+
+        let trendString = bg.direction?.symbol as? String
+        let change = Self.calculateChange(chart: chart)
+        let chartBG = chart.map(\.glucose)
+        let conversionFactor: Double = settings.units == .mmolL ? 18.0 : 1.0
+        let convertedChartBG = chartBG.map { Double($0) / conversionFactor }
+        let chartDate = chart.map(\.date)
+
+        /// glucose limits from UI settings, not from notifications settings
+        let highGlucose = settings.high / Decimal(conversionFactor)
+        let lowGlucose = settings.low / Decimal(conversionFactor)
+        let cob = determination?.cob ?? 0
+        let iob = determination?.iob ?? 0
+        let lockScreenView = settings.lockScreenView.displayName
+        let unit = settings.units == .mmolL ? " mmol/L" : " mg/dL"
+
+        self.init(
+            bg: formattedBG,
+            direction: trendString,
+            change: change,
+            date: bg.date,
+            chart: convertedChartBG,
+            chartDate: chartDate,
+            rotationDegrees: rotationDegrees,
+            highGlucose: Double(highGlucose),
+            lowGlucose: Double(lowGlucose),
+            cob: Decimal(cob),
+            iob: iob as Decimal,
+            lockScreenView: lockScreenView,
+            unit: unit
+        )
+    }
+}

FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift → FreeAPS/Sources/Services/LiveActivity/LiveActivityAttributes.swift


+ 45 - 193
FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -4,106 +4,6 @@ import Foundation
 import Swinject
 import UIKit
 
-extension LiveActivityAttributes.ContentState {
-    static func formatGlucose(_ value: Int, mmol: Bool, forceSign: Bool) -> String {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 0
-        if mmol {
-            formatter.minimumFractionDigits = 1
-            formatter.maximumFractionDigits = 1
-        }
-        if forceSign {
-            formatter.positivePrefix = formatter.plusSign
-        }
-        formatter.roundingMode = .halfUp
-
-        return formatter
-            .string(from: mmol ? value.asMmolL as NSNumber : NSNumber(value: value))!
-    }
-
-    static func calculateChange(chart: [GlucoseStored]) -> String {
-        guard chart.count > 2 else { return "" }
-        let lastGlucose = chart.first?.glucose ?? 0
-        let secondLastGlucose = chart.dropFirst().first?.glucose ?? 0
-        let delta = lastGlucose - secondLastGlucose
-        let deltaAsDecimal = Decimal(delta)
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 1
-        formatter.positivePrefix = "  +"
-        formatter.negativePrefix = "  -"
-        return formatter.string(from: deltaAsDecimal as NSNumber) ?? "--"
-    }
-
-    init?(
-        new bg: GlucoseStored,
-        prev _: GlucoseStored?,
-        mmol: Bool,
-        chart: [GlucoseStored],
-        settings: FreeAPSSettings,
-        determination: OrefDetermination?
-    ) {
-        let glucose = bg.glucose
-        let formattedBG = Self.formatGlucose(Int(glucose), mmol: mmol, forceSign: false)
-        var rotationDegrees: Double = 0.0
-
-        switch bg.direction {
-        case "DoubleUp",
-             "SingleUp",
-             "TripleUp":
-            rotationDegrees = -90
-        case "FortyFiveUp":
-            rotationDegrees = -45
-        case "Flat":
-            rotationDegrees = 0
-        case "FortyFiveDown":
-            rotationDegrees = 45
-        case "DoubleDown",
-             "SingleDown",
-             "TripleDown":
-            rotationDegrees = 90
-        case "NONE",
-             "NOT COMPUTABLE",
-             "RATE OUT OF RANGE":
-            rotationDegrees = 0
-        default:
-            rotationDegrees = 0
-        }
-
-        let trendString = bg.direction?.symbol as? String
-        let change = Self.calculateChange(chart: chart)
-        let chartBG = chart.map(\.glucose)
-        let conversionFactor: Double = settings.units == .mmolL ? 18.0 : 1.0
-        let convertedChartBG = chartBG.map { Double($0) / conversionFactor }
-        let chartDate = chart.map(\.date)
-
-        /// glucose limits from UI settings, not from notifications settings
-        let highGlucose = settings.high / Decimal(conversionFactor)
-        let lowGlucose = settings.low / Decimal(conversionFactor)
-        let cob = determination?.cob ?? 0
-        let iob = determination?.iob ?? 0
-        let lockScreenView = settings.lockScreenView.displayName
-        let unit = settings.units == .mmolL ? " mmol/L" : " mg/dL"
-
-        self.init(
-            bg: formattedBG,
-            direction: trendString,
-            change: change,
-            date: bg.date ?? Date(),
-            chart: convertedChartBG,
-            chartDate: chartDate,
-            rotationDegrees: rotationDegrees,
-            highGlucose: Double(highGlucose),
-            lowGlucose: Double(lowGlucose),
-            cob: Decimal(cob),
-            iob: iob as Decimal,
-            lockScreenView: lockScreenView,
-            unit: unit
-        )
-    }
-}
-
 @available(iOS 16.2, *) private struct ActiveActivity {
     let activity: Activity<LiveActivityAttributes>
     let startDate: Date
@@ -124,8 +24,7 @@ extension LiveActivityAttributes.ContentState {
     }
 }
 
-@available(iOS 16.2, *) final class LiveActivityBridge: NSObject, Injectable, ObservableObject,
-    NSFetchedResultsControllerDelegate
+@available(iOS 16.2, *) final class LiveActivityBridge: Injectable, ObservableObject
 {
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
@@ -138,62 +37,49 @@ extension LiveActivityAttributes.ContentState {
         settingsManager.settings
     }
 
-    private var determination: OrefDetermination?
+    var determination: DeterminationData?
     private var currentActivity: ActiveActivity?
-    private var latestGlucose: GlucoseStored?
-    private var fetchedResultsController: NSFetchedResultsController<GlucoseStored>?
+    private var latestGlucose: GlucoseData?
+    var glucoseFromPersistence: [GlucoseData]?
+
+    let context = CoreDataStack.shared.newTaskContext()
 
     init(resolver: Resolver) {
         systemEnabled = activityAuthorizationInfo.areActivitiesEnabled
-        super.init()
-
         injectServices(resolver)
         setupNotifications()
         monitorForLiveActivityAuthorizationChanges()
-        initializeFetchedResultsController()
+        setupGlucoseArray()
     }
 
     private func setupNotifications() {
-        Foundation.NotificationCenter.default.addObserver(
-            forName: UIApplication.didEnterBackgroundNotification,
-            object: nil,
-            queue: nil
-        ) { [weak self] _ in
-            self?.forceActivityUpdate()
-        }
+        let notificationCenter = Foundation.NotificationCenter.default
+        notificationCenter.addObserver(self, selector: #selector(handleBatchInsert), name: .didPerformBatchInsert, object: nil)
+        notificationCenter
+            .addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in
+                self?.forceActivityUpdate()
+            }
+        notificationCenter
+            .addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in
+                self?.forceActivityUpdate()
+            }
+    }
 
-        Foundation.NotificationCenter.default.addObserver(
-            forName: UIApplication.didBecomeActiveNotification,
-            object: nil,
-            queue: nil
-        ) { [weak self] _ in
-            self?.forceActivityUpdate()
-        }
+    @objc private func handleBatchInsert() {
+        setupGlucoseArray()
     }
 
-    private func initializeFetchedResultsController() {
-        let fetchRequest: NSFetchRequest<GlucoseStored> = GlucoseStored.fetchRequest()
-        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
-        fetchRequest.fetchLimit = 72
-        fetchRequest.predicate = NSPredicate.predicateForSixHoursAgo
+    private func setupGlucoseArray() {
+        Task {
+            // Fetch and map glucose to GlucoseData struct
+            await fetchAndMapGlucose()
 
-        fetchedResultsController = NSFetchedResultsController(
-            fetchRequest: fetchRequest,
-            managedObjectContext: CoreDataStack.shared.persistentContainer.viewContext,
-            sectionNameKeyPath: nil,
-            cacheName: nil
-        )
-        fetchedResultsController?.delegate = self
+            // Fetch and map Determination to DeterminationData struct
+            await fetchAndMapDetermination()
 
-        do {
-            try fetchedResultsController?.performFetch()
-            debugPrint(
-                "LA Bridge: \(#function) \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) fetched glucose"
-            )
-        } catch {
-            debugPrint(
-                "LA Bridge: \(#function) \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to fetch glucose"
-            ) }
+            // Push the update to the Live Activity
+            glucoseDidUpdate(glucoseFromPersistence ?? [])
+        }
     }
 
     private func monitorForLiveActivityAuthorizationChanges() {
@@ -208,21 +94,6 @@ extension LiveActivityAttributes.ContentState {
         }
     }
 
-    private func fetchDetermination() -> OrefDetermination? {
-        let context = CoreDataStack.shared.persistentContainer.viewContext
-        do {
-            let determinations = try context.fetch(OrefDetermination.fetch(NSPredicate.enactedDetermination))
-            debugPrint("LA Bridge: \(#function) \(DebuggingIdentifiers.succeeded) fetched determinations")
-            if let latestDetermination = determinations.first {
-                return latestDetermination
-            }
-            return nil
-        } catch {
-            debugPrint("LA Bridge: \(#function) \(DebuggingIdentifiers.failed) failed to fetch determinaions")
-            return nil
-        }
-    }
-
     /// creates and tries to present a new activity update from the current GlucoseStorage values if live activities are enabled in settings
     /// Ends existing live activities if live activities are not enabled in settings
     private func forceActivityUpdate() {
@@ -231,9 +102,7 @@ extension LiveActivityAttributes.ContentState {
         if settings.useLiveActivity {
             if currentActivity?.needsRecreation() ?? true
             {
-                if let glucoseRecords = fetchedResultsController?.fetchedObjects as? [GlucoseStored] {
-                    glucoseDidUpdate(glucoseRecords)
-                }
+                glucoseDidUpdate(glucoseFromPersistence ?? [])
             }
         } else {
             Task {
@@ -244,31 +113,26 @@ extension LiveActivityAttributes.ContentState {
 
     /// attempts to present this live activity state, creating a new activity if none exists yet
     @MainActor private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
-        // hide duplicate/unknown activities
-        for unknownActivity in Activity<LiveActivityAttributes>.activities
-            .filter({ self.currentActivity?.activity.id != $0.id })
-        {
-            await unknownActivity.end(nil, dismissalPolicy: .immediate)
-        }
+//        // End all activities that are not the current one
+//        for unknownActivity in Activity<LiveActivityAttributes>.activities.filter({ self.currentActivity?.activity.id != $0.id }) {
+//            await unknownActivity.end(nil, dismissalPolicy: .immediate)
+//        }
 
-        if let currentActivity {
+        if let currentActivity = currentActivity {
             if currentActivity.needsRecreation(), UIApplication.shared.applicationState == .active {
-                // activity is no longer visible or old. End it and try to push the update again
                 await endActivity()
                 await pushUpdate(state)
             } else {
                 let content = ActivityContent(
                     state: state,
-                    staleDate: min(state.date, Date.now).addingTimeInterval(TimeInterval(6 * 60))
+                    staleDate: min(state.date, Date.now).addingTimeInterval(360) // 6 minutes in seconds
                 )
                 await currentActivity.activity.update(content)
             }
         } else {
             do {
-                // always push a non-stale content as the first update
-                // pushing a stale content as the frst content results in the activity not being shown at all
-                // we want it shown though even if it is iniially stale, as we expect new BG readings to become available soon, which should then be displayed
-                let nonStale = ActivityContent(
+                // Create initial non-stale content
+                let nonStaleContent = ActivityContent(
                     state: LiveActivityAttributes.ContentState(
                         bg: "--",
                         direction: nil,
@@ -277,8 +141,8 @@ extension LiveActivityAttributes.ContentState {
                         chart: [],
                         chartDate: [],
                         rotationDegrees: 0,
-                        highGlucose: Double(180),
-                        lowGlucose: Double(70),
+                        highGlucose: 180,
+                        lowGlucose: 70,
                         cob: 0,
                         iob: 0,
                         lockScreenView: "Simple",
@@ -287,17 +151,18 @@ extension LiveActivityAttributes.ContentState {
                     staleDate: Date.now.addingTimeInterval(60)
                 )
 
+                // Request a new activity
                 let activity = try Activity.request(
                     attributes: LiveActivityAttributes(startDate: Date.now),
-                    content: nonStale,
+                    content: nonStaleContent,
                     pushType: nil
                 )
                 currentActivity = ActiveActivity(activity: activity, startDate: Date.now)
 
-                // then show the actual content
+                // Push the actual content
                 await pushUpdate(state)
             } catch {
-                print("activity creation error: \(error)")
+                print("Activity creation error: \(error)")
             }
         }
     }
@@ -318,18 +183,7 @@ extension LiveActivityAttributes.ContentState {
 
 @available(iOS 16.2, *)
 extension LiveActivityBridge {
-    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
-        guard let glucoseChanges = controller.fetchedObjects as? [GlucoseStored] else {
-            return
-        }
-
-        glucoseDidUpdate(glucoseChanges)
-    }
-}
-
-@available(iOS 16.2, *)
-extension LiveActivityBridge {
-    func glucoseDidUpdate(_ glucose: [GlucoseStored]) {
+    func glucoseDidUpdate(_ glucose: [GlucoseData]) {
         guard settings.useLiveActivity else {
             if currentActivity != nil {
                 Task {
@@ -351,8 +205,6 @@ extension LiveActivityBridge {
             return
         }
 
-        determination = fetchDetermination()
-
         if let determination = determination {
             let content = LiveActivityAttributes.ContentState(
                 new: bg,

+ 1 - 1
LiveActivity/LiveActivity.swift

@@ -298,7 +298,7 @@ struct LiveActivity: Widget {
                 .padding(.all, 14)
                 .imageScale(.small)
                 .foregroundColor(Color.white)
-                .activityBackgroundTint(Color.black)
+                .activityBackgroundTint(Color.black.opacity(0.8))
             }
         } dynamicIsland: { context in
             DynamicIsland {

+ 0 - 58
LiveActivity/WidgetBobble.swift

@@ -1,58 +0,0 @@
-import SwiftUI
-
-struct WidgetBobble: View {
-    @Environment(\.colorScheme) var colorScheme
-
-    let gradient: AngularGradient
-    let color: Color
-
-    var body: some View {
-        HStack(alignment: .center) {
-            ZStack {
-                Group {
-                    CircleShapeWidget(gradient: gradient)
-                    TriangleShapeWidget(color: color)
-                }
-                CircleShapeWidget(gradient: gradient)
-            }
-        }
-    }
-}
-
-struct CircleShapeWidget: View {
-    @Environment(\.colorScheme) var colorScheme
-
-    let gradient: AngularGradient
-
-    var body: some View {
-        Circle()
-            .stroke(gradient, lineWidth: 10)
-            .background(Circle().fill(.clear))
-            .frame(width: 130, height: 130)
-    }
-}
-
-struct TriangleShapeWidget: View {
-    let color: Color
-
-    var body: some View {
-        TriangleWidget()
-            .fill(color)
-            .frame(width: 35, height: 35)
-            .rotationEffect(.degrees(90))
-            .offset(x: 88)
-    }
-}
-
-struct TriangleWidget: Shape {
-    func path(in rect: CGRect) -> Path {
-        var path = Path()
-
-        path.move(to: CGPoint(x: rect.midX, y: rect.minY + 15))
-        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
-        path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY), control: CGPoint(x: rect.midX, y: rect.midY + 10))
-        path.closeSubpath()
-
-        return path
-    }
-}

+ 7 - 0
Model/Helper/GlucoseStored+helper.swift

@@ -102,3 +102,10 @@ extension GlucoseStored: Encodable {
         }
     }
 }
+
+// In order to show the correct direction in the bobble we convert the direction property of the NSManagedObject GlucoseStored back to the Direction type
+extension GlucoseStored {
+    var directionEnum: BloodGlucose.Direction? {
+        BloodGlucose.Direction(rawValue: direction ?? "")
+    }
+}