diff --git a/.gitmodules b/.gitmodules index 556b37d3336..3ce4fa519ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "LoopKit"] path = LoopKit - url = https://github.com/loopandlearn/LoopKit.git + url = https://github.com/bjorkert/LoopKit.git [submodule "CGMBLEKit"] path = CGMBLEKit url = https://github.com/loopandlearn/CGMBLEKit.git @@ -30,4 +30,4 @@ url = https://github.com/loopandlearn/MedtrumKit [submodule "OmnipodKit"] path = OmnipodKit - url = https://github.com/loopandlearn/OmnipodKit + url = https://github.com/bjorkert/OmnipodKit.git diff --git a/LoopKit b/LoopKit index 0229bb18d30..9d919ec67c6 160000 --- a/LoopKit +++ b/LoopKit @@ -1 +1 @@ -Subproject commit 0229bb18d30b095420aae1e8fa04c37794e0a378 +Subproject commit 9d919ec67c6d93fafa2feb32a6e6fa7a9f047aef diff --git a/Model/Classes+Properties/BolusStored+CoreDataProperties.swift b/Model/Classes+Properties/BolusStored+CoreDataProperties.swift index 61cd103e1fe..b9f8cb1e967 100644 --- a/Model/Classes+Properties/BolusStored+CoreDataProperties.swift +++ b/Model/Classes+Properties/BolusStored+CoreDataProperties.swift @@ -7,6 +7,7 @@ public extension BolusStored { } @NSManaged var amount: NSDecimalNumber? + @NSManaged var bolusOrigin: String? @NSManaged var isExternal: Bool @NSManaged var isSMB: Bool @NSManaged var pumpEvent: PumpEventStored? diff --git a/Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents b/Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents index 98b3f7e68fe..c99dd86faa5 100644 --- a/Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents +++ b/Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents @@ -2,6 +2,7 @@ + diff --git a/OmnipodKit b/OmnipodKit index 4d254498cf0..3cf3432d96f 160000 --- a/OmnipodKit +++ b/OmnipodKit @@ -1 +1 @@ -Subproject commit 4d254498cf016806b3e0ec33dd5fbfdf6e2135a6 +Subproject commit 3cf3432d96f9ff563169b6316b04e6e11e56a966 diff --git a/Trio.xcodeproj/project.pbxproj b/Trio.xcodeproj/project.pbxproj index d9c95e4205e..8bfc7cbd8d0 100644 --- a/Trio.xcodeproj/project.pbxproj +++ b/Trio.xcodeproj/project.pbxproj @@ -57,7 +57,7 @@ 1D845DF2E3324130E1D95E67 /* HistoryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* HistoryProvider.swift */; }; 23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* TreatmentsProvider.swift */; }; 3171D2818C7C72CD1584BB5E /* GlucoseNotificationSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */; }; - 320D030F724170A637F06D50 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 320D030F724170A637F06D50 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; 3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0725C9D32E00A708ED /* BaseView.swift */; }; 3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0825C9D32F00A708ED /* BaseProvider.swift */; }; 3811DE1025C9D37700A708ED /* Swinject in Frameworks */ = {isa = PBXBuildFile; productRef = 3811DE0F25C9D37700A708ED /* Swinject */; }; @@ -138,9 +138,6 @@ 38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */; }; 38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A9260425F012D8009E3739 /* CarbRatios.swift */; }; 38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */; }; - BD11C001000000000000C001 /* CGMSensorDisplayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C001 /* CGMSensorDisplayState.swift */; }; - BD11C001000000000000C003 /* SensorLifecycleArcView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C003 /* SensorLifecycleArcView.swift */; }; - BD11C001000000000000C004 /* SensorStatusTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C004 /* SensorStatusTagView.swift */; }; 38AEE73D25F0200C0013F05B /* TrioSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AEE73C25F0200C0013F05B /* TrioSettings.swift */; }; 38AEE75225F022080013F05B /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AEE75125F022080013F05B /* SettingsManager.swift */; }; 38AEE75725F0F18E0013F05B /* CarbsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AEE75625F0F18E0013F05B /* CarbsStorage.swift */; }; @@ -388,7 +385,7 @@ 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 */; }; - 6EADD581738D64431902AC0A /* (null) in Sources */ = {isa = PBXBuildFile; }; + 6EADD581738D64431902AC0A /* BuildFile in Sources */ = {isa = PBXBuildFile; }; 6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; }; 711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; }; 715120D22D3C2BB4005D9FB6 /* GlucoseNotificationsOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */; }; @@ -411,13 +408,16 @@ B015AFE52E500000000D7351 /* BolusSafetyValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015AFE42E500000000D7351 /* BolusSafetyValidatorTests.swift */; }; B6E925132EB3932A0076D719 /* OmnipodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6E925122EB3932A0076D719 /* OmnipodKit.framework */; }; B6E925142EB3932A0076D719 /* OmnipodKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B6E925122EB3932A0076D719 /* OmnipodKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - B7C465E9472624D8A2BE2A6A /* (null) in Sources */ = {isa = PBXBuildFile; }; + B7C465E9472624D8A2BE2A6A /* BuildFile in Sources */ = {isa = PBXBuildFile; }; B958F1B72BA0711600484851 /* MKRingProgressView in Frameworks */ = {isa = PBXBuildFile; productRef = B958F1B62BA0711600484851 /* MKRingProgressView */; }; BA00D96F7B2FF169A06FB530 /* CGMSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C018D1680307A31C9ED7120 /* CGMSettingsStateModel.swift */; }; BB5227A51D9D4377A1A70BA6 /* TempTargetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F3B0E159844FD8746DFC0 /* TempTargetData.swift */; }; BD04ECCE2D29952A008C5FEB /* BolusProgressOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD04ECCD2D299522008C5FEB /* BolusProgressOverlay.swift */; }; BD0B2EF32C5998E600B3298F /* MealPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0B2EF22C5998E600B3298F /* MealPresetView.swift */; }; BD10516D2DA986E1007C6D89 /* PulsingLogoAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD10516C2DA986DC007C6D89 /* PulsingLogoAnimation.swift */; }; + BD11C001000000000000C001 /* CGMSensorDisplayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C001 /* CGMSensorDisplayState.swift */; }; + BD11C001000000000000C003 /* SensorLifecycleArcView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C003 /* SensorLifecycleArcView.swift */; }; + BD11C001000000000000C004 /* SensorStatusTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C004 /* SensorStatusTagView.swift */; }; BD1661312B82ADAB00256551 /* CustomProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1661302B82ADAB00256551 /* CustomProgressView.swift */; }; BD175EBE0000100000000001 /* HistoryDataFlow+Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000001 /* HistoryDataFlow+Models.swift */; }; BD175EBE0000100000000002 /* HistoryDeletionTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000002 /* HistoryDeletionTarget.swift */; }; @@ -549,6 +549,7 @@ C2BA6B992F758E7600348E6A /* NotificationIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BA6B982F758E7600348E6A /* NotificationIdentifiers.swift */; }; C2BA6B9A2F7593C300348E6A /* NotificationIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BA6B982F758E7600348E6A /* NotificationIdentifiers.swift */; }; C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; }; + CA02000000000000000010C2 /* DeliveryLimitsSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */; }; CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; }; CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; }; CC76E9512BD4812E008BEB61 /* Forecast+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */; }; @@ -602,7 +603,6 @@ CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9A6542BBB418300EB5194 /* CalibrationsDataFlow.swift */; }; CEE9A65C2BBB41C800EB5194 /* CalibrationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9A65B2BBB41C800EB5194 /* CalibrationService.swift */; }; CEE9A65E2BBC9F6500EB5194 /* CalibrationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */; }; - CA02000000000000000010C2 /* DeliveryLimitsSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */; }; CEF1ED6B2D58FB5800FAF41E /* CGMOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */; }; D6D02515BBFBE64FEBE89856 /* HistoryRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881E04BA5E0A003DE8E0A9C6 /* HistoryRootView.swift */; }; D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; }; @@ -835,6 +835,7 @@ E592A3782CEEC038009A472C /* ContactImageDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3732CEEC038009A472C /* ContactImageDataFlow.swift */; }; E592A3792CEEC038009A472C /* ContactImageRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3712CEEC038009A472C /* ContactImageRootView.swift */; }; E592A37A2CEEC038009A472C /* ContactImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3742CEEC038009A472C /* ContactImageProvider.swift */; }; + E8E7C3A411731D86E57A0162 /* BolusOriginStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36025314CBB2E1C6A215908A /* BolusOriginStoreTests.swift */; }; E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */; }; F5CA3DB1F9DC8B05792BBFAA /* CGMSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B5C0607505A38F256BF99A /* CGMSettingsDataFlow.swift */; }; F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */; }; @@ -1007,6 +1008,7 @@ 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigRootView.swift; sourceTree = ""; }; 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigDataFlow.swift; sourceTree = ""; }; 3260468377DA9DB4DEE9AF6D /* GlucoseNotificationSettingsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsDataFlow.swift; sourceTree = ""; }; + 36025314CBB2E1C6A215908A /* BolusOriginStoreTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusOriginStoreTests.swift; sourceTree = ""; }; 36A708CDB546692C2230B385 /* SnoozeDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeDataFlow.swift; sourceTree = ""; }; 36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorStateModel.swift; sourceTree = ""; }; 3811DE0725C9D32E00A708ED /* BaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseView.swift; sourceTree = ""; }; @@ -1102,9 +1104,6 @@ 38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtensions.swift; sourceTree = ""; }; 38A9260425F012D8009E3739 /* CarbRatios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatios.swift; sourceTree = ""; }; 38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentGlucoseView.swift; sourceTree = ""; }; - BD11C000000000000000C001 /* CGMSensorDisplayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMSensorDisplayState.swift; sourceTree = ""; }; - BD11C000000000000000C003 /* SensorLifecycleArcView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorLifecycleArcView.swift; sourceTree = ""; }; - BD11C000000000000000C004 /* SensorStatusTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorStatusTagView.swift; sourceTree = ""; }; 38AEE73C25F0200C0013F05B /* TrioSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioSettings.swift; sourceTree = ""; }; 38AEE75125F022080013F05B /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = ""; }; 38AEE75625F0F18E0013F05B /* CarbsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsStorage.swift; sourceTree = ""; }; @@ -1363,6 +1362,9 @@ BD04ECCD2D299522008C5FEB /* BolusProgressOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressOverlay.swift; sourceTree = ""; }; BD0B2EF22C5998E600B3298F /* MealPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealPresetView.swift; sourceTree = ""; }; BD10516C2DA986DC007C6D89 /* PulsingLogoAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PulsingLogoAnimation.swift; sourceTree = ""; }; + BD11C000000000000000C001 /* CGMSensorDisplayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMSensorDisplayState.swift; sourceTree = ""; }; + BD11C000000000000000C003 /* SensorLifecycleArcView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorLifecycleArcView.swift; sourceTree = ""; }; + BD11C000000000000000C004 /* SensorStatusTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorStatusTagView.swift; sourceTree = ""; }; BD1661302B82ADAB00256551 /* CustomProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomProgressView.swift; sourceTree = ""; }; BD175EFE0000100000000001 /* HistoryDataFlow+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryDataFlow+Models.swift"; sourceTree = ""; }; BD175EFE0000100000000002 /* HistoryDeletionTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryDeletionTarget.swift; sourceTree = ""; }; @@ -1494,6 +1496,7 @@ C2BA6B982F758E7600348E6A /* NotificationIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationIdentifiers.swift; sourceTree = ""; }; C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = ""; }; C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsDataFlow.swift; sourceTree = ""; }; + CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryLimitsSyncTests.swift; sourceTree = ""; }; CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = ""; }; CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Forecast+helper.swift"; sourceTree = ""; }; CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCarbPresetIntent.swift; sourceTree = ""; }; @@ -1547,7 +1550,6 @@ CEE9A6542BBB418300EB5194 /* CalibrationsDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalibrationsDataFlow.swift; sourceTree = ""; }; CEE9A65B2BBB41C800EB5194 /* CalibrationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalibrationService.swift; sourceTree = ""; }; CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalibrationsTests.swift; sourceTree = ""; }; - CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryLimitsSyncTests.swift; sourceTree = ""; }; CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMOptions.swift; sourceTree = ""; }; CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalStateModel.swift; sourceTree = ""; }; D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorRootView.swift; sourceTree = ""; }; @@ -1794,8 +1796,28 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - BDFF7A9E2D25FA970016C40C /* Preview Content */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "Preview Content"; sourceTree = ""; }; - DDCEBF412CC1B42500DF4C36 /* Views */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Views; sourceTree = ""; }; + BDFF7A9E2D25FA970016C40C /* Preview Content */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = "Preview Content"; + sourceTree = ""; + }; + DDCEBF412CC1B42500DF4C36 /* Views */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = Views; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2298,6 +2320,7 @@ 3811DEA525C9D88300A708ED /* UnlockManager */, 38E87406274F9AA500975559 /* UserNotifications */, 38E8754D275556E100975559 /* WatchManager */, + E6B918D07A068165A122D81C /* BolusOrigin */, ); path = Services; sourceTree = ""; @@ -2499,16 +2522,6 @@ path = Header; sourceTree = ""; }; - BD11C002000000000000C000 /* SensorLifecycle */ = { - isa = PBXGroup; - children = ( - BD11C000000000000000C001 /* CGMSensorDisplayState.swift */, - BD11C000000000000000C003 /* SensorLifecycleArcView.swift */, - BD11C000000000000000C004 /* SensorStatusTagView.swift */, - ); - path = SensorLifecycle; - sourceTree = ""; - }; 3856933F270B57A00002C50D /* CGM */ = { isa = PBXGroup; children = ( @@ -2871,6 +2884,7 @@ B3919BBB515547118D684CA2 /* SettingsSearchTests.swift */, BD8FC0532D66186000B95AED /* TestError.swift */, BD8FC0702D661B0000B95AED /* TidepoolTherapySettingsTests.swift */, + 36025314CBB2E1C6A215908A /* BolusOriginStoreTests.swift */, ); path = TrioTests; sourceTree = ""; @@ -3252,6 +3266,16 @@ path = View; sourceTree = ""; }; + BD11C002000000000000C000 /* SensorLifecycle */ = { + isa = PBXGroup; + children = ( + BD11C000000000000000C001 /* CGMSensorDisplayState.swift */, + BD11C000000000000000C003 /* SensorLifecycleArcView.swift */, + BD11C000000000000000C004 /* SensorStatusTagView.swift */, + ); + path = SensorLifecycle; + sourceTree = ""; + }; BD175EC00000100000000001 /* HistoryStateModel+Deletion */ = { isa = PBXGroup; children = ( @@ -4219,6 +4243,14 @@ path = ContactImage; sourceTree = ""; }; + E6B918D07A068165A122D81C /* BolusOrigin */ = { + isa = PBXGroup; + children = ( + ); + name = BolusOrigin; + path = BolusOrigin; + sourceTree = ""; + }; EEC747824D6593B5CD87E195 /* View */ = { isa = PBXGroup; children = ( @@ -4376,8 +4408,6 @@ dependencies = ( ); name = "Trio Watch Complication Extension"; - packageProductDependencies = ( - ); productName = "Trio Watch WidgetExtension"; productReference = BD8207C32D2B42E50023339D /* Trio Watch Complication Extension.appex */; productType = "com.apple.product-type.app-extension"; @@ -4400,8 +4430,6 @@ BDFF7A9E2D25FA970016C40C /* Preview Content */, ); name = "Trio Watch App"; - packageProductDependencies = ( - ); productName = "TrioWatch Watch App"; productReference = BDFF797C2D25AA870016C40C /* Trio Watch App.app */; productType = "com.apple.product-type.application"; @@ -4420,8 +4448,6 @@ BDFF798D2D25AA890016C40C /* PBXTargetDependency */, ); name = "Trio Watch AppTests"; - packageProductDependencies = ( - ); productName = "TrioWatch Watch AppTests"; productReference = BDFF798B2D25AA890016C40C /* Trio Watch AppTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -5238,16 +5264,16 @@ BDF34F952C10D27300D51995 /* DeterminationData.swift in Sources */, BA00D96F7B2FF169A06FB530 /* CGMSettingsStateModel.swift in Sources */, BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */, - 6EADD581738D64431902AC0A /* (null) in Sources */, + 6EADD581738D64431902AC0A /* BuildFile in Sources */, CE94598729E9E4110047C9C6 /* WatchConfigRootView.swift in Sources */, DD5DC9FB2CF3E1B100AB8703 /* AdjustmentsStateModel+Helpers.swift in Sources */, BD249D972D42FCBF00412DEB /* AreaChartSetup.swift in Sources */, DDF847E42C5C288F0049BB3B /* LiveActivitySettingsRootView.swift in Sources */, DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */, - B7C465E9472624D8A2BE2A6A /* (null) in Sources */, + B7C465E9472624D8A2BE2A6A /* BuildFile in Sources */, 71D44AAB2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift in Sources */, DDF691032DA2CA1E008BF16C /* AppDiagnosticsProvider.swift in Sources */, - 320D030F724170A637F06D50 /* (null) in Sources */, + 320D030F724170A637F06D50 /* BuildFile in Sources */, CE94598729E9E4110047C9C6 /* WatchConfigRootView.swift in Sources */, DD9E6DA22D59A12700514CEC /* MealHistory.swift in Sources */, 19E1F7E829D082D0005C8D20 /* IconConfigDataFlow.swift in Sources */, @@ -5379,6 +5405,7 @@ 41740E936552456AAC0EDAC3 /* SettingsSearchTests.swift in Sources */, B015AFE52E500000000D7351 /* BolusSafetyValidatorTests.swift in Sources */, BD8FC0712D661B0000B95AED /* TidepoolTherapySettingsTests.swift in Sources */, + E8E7C3A411731D86E57A0162 /* BolusOriginStoreTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Trio/Sources/APS/APSManager.swift b/Trio/Sources/APS/APSManager.swift index 305458fae96..4b3e2dec7de 100644 --- a/Trio/Sources/APS/APSManager.swift +++ b/Trio/Sources/APS/APSManager.swift @@ -8,7 +8,7 @@ import Swinject protocol APSManager { func heartbeat(date: Date) - func enactBolus(amount: Double, isSMB: Bool, callback: ((Bool, String) -> Void)?) async + func enactBolus(amount: Double, isSMB: Bool, origin: BolusOrigin?, callback: ((Bool, String) -> Void)?) async var pumpManager: PumpManagerUI? { get set } var bluetoothManager: BluetoothStateManager? { get } var pumpDisplayState: CurrentValueSubject { get } @@ -36,6 +36,13 @@ protocol APSManager { var iobFileDidUpdate: PassthroughSubject { get } } +extension APSManager { + /// Convenience for callers that do not tag a bolus origin. + func enactBolus(amount: Double, isSMB: Bool, callback: ((Bool, String) -> Void)?) async { + await enactBolus(amount: amount, isSMB: isSMB, origin: nil, callback: callback) + } +} + enum APSError: LocalizedError { case pumpError(Error) case invalidPumpState(message: String) @@ -550,7 +557,7 @@ final class BaseAPSManager: APSManager, Injectable { return min(rounded, maxBolus) } - func enactBolus(amount: Double, isSMB: Bool, callback: ((Bool, String) -> Void)?) async { + func enactBolus(amount: Double, isSMB: Bool, origin: BolusOrigin?, callback: ((Bool, String) -> Void)?) async { if amount <= 0 { return } @@ -575,10 +582,14 @@ final class BaseAPSManager: APSManager, Injectable { let roundedAmount = pump.roundToSupportedBolusVolume(units: amount) + // Mint the correlation reference only once the command is actually going to the pump, so none of the + // early returns above can leave an orphaned origin mapping behind. + let bolusReference = origin.map { BolusOriginStore.shared.makeReference(for: $0) } + debug(.apsManager, "Enact bolus \(roundedAmount), manual \(!isSMB)") do { - try await pump.enactBolus(units: roundedAmount, automatic: isSMB) + try await pump.enactBolus(units: roundedAmount, automatic: isSMB, bolusReference: bolusReference) debug(.apsManager, "Bolus succeeded") bolusProgress.send(0) callback?(true, String(localized: "Bolus enacted successfully.", comment: "Success message for enacting a bolus")) @@ -594,6 +605,11 @@ final class BaseAPSManager: APSManager, Injectable { } } catch { warning(.apsManager, "Bolus failed with error: \(error)") + // Drop the origin mapping on a definite failure. On uncertain delivery we keep it, because the + // dose may still be reported (and reconciled) later and should still resolve its origin. + if let bolusReference = bolusReference, !error.isUncertainDelivery { + BolusOriginStore.shared.remove(reference: bolusReference) + } processError(APSError.pumpError(error)) if !isSMB { // Use MainActor to handle broadcaster notification @@ -755,6 +771,7 @@ final class BaseAPSManager: APSManager, Injectable { } private func performBolus(pump: PumpManager, smbToDeliver: NSDecimalNumber) async throws { + // SMB is not origin-tagged: it is already distinguishable in Nightscout via eventType "SMB". try await pump.enactBolus(units: Double(truncating: smbToDeliver), automatic: true) bolusProgress.send(0) } @@ -1245,11 +1262,11 @@ private extension PumpManager { } } - func enactBolus(units: Double, automatic: Bool) async throws { + func enactBolus(units: Double, automatic: Bool, bolusReference: UUID? = nil) async throws { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in let automaticValue = automatic ? BolusActivationType.automatic : BolusActivationType.manualRecommendationAccepted - self.enactBolus(units: units, activationType: automaticValue) { error in + self.enactBolus(units: units, activationType: automaticValue, bolusReference: bolusReference) { error in if let error = error { debug(.apsManager, "Bolus failed: \(units)") continuation.resume(throwing: error) @@ -1358,3 +1375,13 @@ extension PumpManagerStatus { return PumpStatus(status: type, bolusing: bolusing, suspended: suspended, timestamp: Date()) } } + +private extension Error { + /// True when a bolus command failed with uncertain delivery — the pump may still report the dose later, + /// so any correlation state (e.g. the bolus-origin mapping) must be kept rather than cleaned up. + var isUncertainDelivery: Bool { + guard let pumpError = self as? PumpManagerError else { return false } + if case .uncertainDelivery = pumpError { return true } + return false + } +} diff --git a/Trio/Sources/APS/Storage/PumpHistoryStorage.swift b/Trio/Sources/APS/Storage/PumpHistoryStorage.swift index 14c3a1b3f81..183f751fd17 100644 --- a/Trio/Sources/APS/Storage/PumpHistoryStorage.swift +++ b/Trio/Sources/APS/Storage/PumpHistoryStorage.swift @@ -48,6 +48,10 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable { func storePumpEvents(_ events: [NewPumpEvent]) async throws { try await context.perform { + // Origin mappings consumed while tagging boluses below; removed from the store only after the + // context saves, so a failed save leaves them resolvable when the pump re-delivers the events. + var consumedBolusReferences: [UUID] = [] + for event in events { let existingEvents: [PumpEventStored] = try CoreDataStack.shared.fetchEntities( ofType: PumpEventStored.self, @@ -103,6 +107,18 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable { newBolusEntry.isExternal = dose.manuallyEntered newBolusEntry.isSMB = dose.automatic ?? true + // Record the finer origin of a user-initiated bolus (remote/watch/manual/shortcut) resolved + // from the reference the pump echoed back. SMB is not tagged here — it is already conveyed by + // isSMB / the SMB event type. The machine value goes to `bolusOrigin`, the display to `note`. + if let reference = dose.bolusReference, + let resolved = BolusOriginStore.shared.origin(forReference: reference) + { + newBolusEntry.bolusOrigin = resolved.rawValue + newPumpEvent.note = resolved.displayName + consumedBolusReferences.append(reference) + debug(.coreData, "Tagged bolus origin: \(resolved.displayName)") + } + case .tempBasal: guard let dose = event.dose else { continue } @@ -229,6 +245,10 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable { guard self.context.hasChanges else { return } try self.context.save() + for reference in consumedBolusReferences { + BolusOriginStore.shared.remove(reference: reference) + } + self.updateSubject.send(()) debug(.coreData, "\(DebuggingIdentifiers.succeeded) stored pump events in Core Data") } catch let error as NSError { @@ -348,7 +368,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable { enteredBy: NightscoutTreatment.local, bolus: nil, insulin: event.bolus?.amount as Decimal?, - notes: nil, + notes: event.note, + bolusOrigin: event.bolus?.bolusOrigin, carbs: nil, fat: nil, protein: nil, diff --git a/Trio/Sources/Models/NightscoutTreatment.swift b/Trio/Sources/Models/NightscoutTreatment.swift index cbd76d3fbc5..45bd2e6f976 100644 --- a/Trio/Sources/Models/NightscoutTreatment.swift +++ b/Trio/Sources/Models/NightscoutTreatment.swift @@ -12,6 +12,8 @@ struct NightscoutTreatment: JSON, Hashable, Equatable { var bolus: PumpHistoryEvent? var insulin: Decimal? var notes: String? + /// Machine-readable bolus origin, stable across locales (`notes` carries the human-readable label). + var bolusOrigin: String? var carbs: Decimal? var fat: Decimal? var protein: Decimal? @@ -50,6 +52,7 @@ extension NightscoutTreatment { case bolus case insulin case notes + case bolusOrigin case carbs case fat case protein diff --git a/Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift b/Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift index 443bf5d4f43..00d2f9f6403 100644 --- a/Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift +++ b/Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift @@ -577,7 +577,7 @@ extension Treatments { await MainActor.run { self.isAwaitingDeterminationResult = true } - await apsManager.enactBolus(amount: maxAmount, isSMB: false, callback: nil) + await apsManager.enactBolus(amount: maxAmount, isSMB: false, origin: .manual, callback: nil) } } catch { debug(.bolusState, "Authentication error for pump bolus: \(error)") diff --git a/Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift b/Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift index d62920596a1..4c41d1ae7c5 100644 --- a/Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift +++ b/Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift @@ -1,5 +1,6 @@ import Foundation import HealthKit +import LoopKit extension TrioRemoteControl { func handleBolusCommand(_ payload: CommandPayload) async throws { @@ -41,7 +42,11 @@ extension TrioRemoteControl { } await apsManager - .enactBolus(amount: Double(truncating: bolusAmount as NSNumber), isSMB: false) { [weak self] success, message in + .enactBolus( + amount: Double(truncating: bolusAmount as NSNumber), + isSMB: false, + origin: .remote + ) { [weak self] success, message in guard let self = self else { return } Task { if success { diff --git a/Trio/Sources/Services/WatchManager/AppleWatchManager.swift b/Trio/Sources/Services/WatchManager/AppleWatchManager.swift index 387bfc4e9dd..ea4f9438837 100644 --- a/Trio/Sources/Services/WatchManager/AppleWatchManager.swift +++ b/Trio/Sources/Services/WatchManager/AppleWatchManager.swift @@ -1,6 +1,7 @@ import Combine import CoreData import Foundation +import LoopKit import Swinject import UIKit import WatchConnectivity @@ -728,14 +729,15 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana /// - Parameter amount: The requested bolus amount in units private func handleBolusRequest(_ amount: Decimal) { Task { - await apsManager.enactBolus(amount: Double(amount), isSMB: false) { success, message in - // Acknowledge success or error of bolus - self.sendAcknowledgment( - toWatch: success, - message: message, - ackCode: success == true ? .genericSuccess : .genericFailure - ) - } + await apsManager + .enactBolus(amount: Double(amount), isSMB: false, origin: .watch) { success, message in + // Acknowledge success or error of bolus + self.sendAcknowledgment( + toWatch: success, + message: message, + ackCode: success == true ? .genericSuccess : .genericFailure + ) + } debug(.watchManager, "📱 Enacted bolus via APS Manager: \(amount)U") } } @@ -848,14 +850,15 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana // Enact bolus via APS Manager let bolusDouble = NSDecimalNumber(decimal: bolusAmount).doubleValue - await apsManager.enactBolus(amount: bolusDouble, isSMB: false) { success, message in - // Acknowledge success or error of bolus - self.sendAcknowledgment( - toWatch: success, - message: message, - ackCode: success == true ? .genericSuccess : .genericFailure - ) - } + await apsManager + .enactBolus(amount: bolusDouble, isSMB: false, origin: .watch) { success, message in + // Acknowledge success or error of bolus + self.sendAcknowledgment( + toWatch: success, + message: message, + ackCode: success == true ? .genericSuccess : .genericFailure + ) + } debug(.watchManager, "📱 Enacted bolus from watch via APS Manager: \(bolusDouble) U") // Notify Watch: "Carbs and bolus logged successfully" sendAcknowledgment( diff --git a/Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift b/Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift index 7513cf15214..211033666c4 100644 --- a/Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift +++ b/Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift @@ -1,6 +1,7 @@ import Combine import CoreData import Foundation +import LoopKit final class BolusIntentRequest: BaseIntentsRequest { func bolus(_ bolusAmount: Double) async throws -> String { @@ -23,7 +24,12 @@ final class BolusIntentRequest: BaseIntentsRequest { } let bolusQuantity = apsManager.roundBolus(amount: requestedAmount) - await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false, callback: nil) + await apsManager.enactBolus( + amount: Double(bolusQuantity), + isSMB: false, + origin: .shortcut, + callback: nil + ) return String( localized: "A bolus command of \(bolusQuantity.formatted()) U of insulin was sent." diff --git a/TrioTests/BolusOriginStoreTests.swift b/TrioTests/BolusOriginStoreTests.swift new file mode 100644 index 00000000000..dc2679c53ca --- /dev/null +++ b/TrioTests/BolusOriginStoreTests.swift @@ -0,0 +1,95 @@ +import Foundation +import LoopKit +import Testing + +@testable import Trio + +@Suite("Bolus Origin Store Tests") struct BolusOriginStoreTests { + /// Fresh file URL per test so tests never touch the app's real store and cannot interfere with each other. + private func makeTemporaryFileURL() -> URL { + FileManager.default.temporaryDirectory + .appendingPathComponent("bolus_origins_\(UUID().uuidString).json") + } + + @Test("displayName maps each origin") func displayNames() { + #expect(BolusOrigin.remote.displayName == "Remote") + #expect(BolusOrigin.watch.displayName == "Watch") + #expect(BolusOrigin.manual.displayName == "Manual") + #expect(BolusOrigin.shortcut.displayName == "Shortcut") + } + + @Test("reference round-trips and is removable") func roundTrip() { + let store = BolusOriginStore(fileURL: makeTemporaryFileURL()) + + let reference = store.makeReference(for: .remote) + #expect(store.origin(forReference: reference) == .remote) + + store.remove(reference: reference) + #expect(store.origin(forReference: reference) == nil) + } + + @Test("unknown reference resolves to nil") func unknownReference() { + let store = BolusOriginStore(fileURL: makeTemporaryFileURL()) + #expect(store.origin(forReference: UUID()) == nil) + } + + @Test("promotion re-keys a reference to a sync identifier") func promotion() { + let store = BolusOriginStore(fileURL: makeTemporaryFileURL()) + + let reference = store.makeReference(for: .watch) + store.promoteReference(reference, toSyncIdentifier: "sync-id-1") + + #expect(store.origin(forReference: reference) == nil) + #expect(store.origin(forSyncIdentifier: "sync-id-1") == .watch) + + // Promoting an already promoted (or unknown) reference must not disturb the promoted entry. + store.promoteReference(reference, toSyncIdentifier: "sync-id-2") + #expect(store.origin(forSyncIdentifier: "sync-id-1") == .watch) + #expect(store.origin(forSyncIdentifier: "sync-id-2") == nil) + + store.remove(syncIdentifier: "sync-id-1") + #expect(store.origin(forSyncIdentifier: "sync-id-1") == nil) + } + + @Test("mappings survive a reload from disk") func persistence() { + let fileURL = makeTemporaryFileURL() + + let reference = BolusOriginStore(fileURL: fileURL).makeReference(for: .shortcut) + + let reloaded = BolusOriginStore(fileURL: fileURL) + #expect(reloaded.origin(forReference: reference) == .shortcut) + } + + @Test("expired entries are dropped on load") func expiry() throws { + let fileURL = makeTemporaryFileURL() + let freshReference = UUID() + let staleReference = UUID() + + // Write the store's JSON shape directly: one entry well past the 6h TTL, one fresh. + let staleDate = Date().addingTimeInterval(-7 * 60 * 60).timeIntervalSinceReferenceDate + let freshDate = Date().timeIntervalSinceReferenceDate + let json = """ + { + "\(staleReference.uuidString)": { "origin": "manual", "createdAt": \(staleDate) }, + "\(freshReference.uuidString)": { "origin": "remote", "createdAt": \(freshDate) } + } + """ + try json.data(using: .utf8)!.write(to: fileURL) + + let store = BolusOriginStore(fileURL: fileURL) + #expect(store.origin(forReference: staleReference) == nil) + #expect(store.origin(forReference: freshReference) == .remote) + } + + @Test("corrupt store file is discarded, not fatal") func corruptFile() throws { + let fileURL = makeTemporaryFileURL() + try Data("not json".utf8).write(to: fileURL) + + let store = BolusOriginStore(fileURL: fileURL) + #expect(store.origin(forReference: UUID()) == nil) + + // The store must still be usable (and re-persistable) afterwards. + let reference = store.makeReference(for: .manual) + #expect(BolusOriginStore(fileURL: fileURL).origin(forReference: reference) == .manual) + } +}