Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidepool Merge #2237

Open
wants to merge 244 commits into
base: dev
Choose a base branch
from
Open
Changes from 7 commits
Commits
Show all changes
244 commits
Select commit Hold shift + click to select a range
26e1560
[COASTAL-1291] plugin identifier is no longer class property (#599)
nhamming Sep 25, 2023
cd15934
Fix merge
ps2 Sep 26, 2023
c9501c0
Merge remote-tracking branch 'origin/dev' into ps2/LOOP-4735/cgm-even…
ps2 Sep 26, 2023
6c90138
Merge pull request #600 from tidepool-org/ps2/LOOP-4735/cgm-event-store
ps2 Sep 27, 2023
fe1b0f9
adding testflight configuration (#601)
nhamming Oct 11, 2023
2735876
LOOP-4665: Dosing Recommendations from Stateless LoopAlgorithm (#602)
ps2 Oct 22, 2023
8605166
fixing the restore of a stateful plugin (#603)
nhamming Oct 27, 2023
99b29fd
[LOOP-4721] Copy size change
Camji55 Oct 31, 2023
619f2ad
[LOOP-4721] Copy size change
Camji55 Oct 31, 2023
6e6c6bb
[LOOP-4751] Dark Mode Fix
Camji55 Nov 3, 2023
e2e9ba1
[LOOP-4751] Dark Mode Fix
Camji55 Nov 6, 2023
9ccb7dd
[PAL-172] only display pump manager provided HUD view when there is n…
nhamming Dec 1, 2023
62b673b
[PAL-236] when bolus amount exceeds max, display warning (#608)
nhamming Dec 18, 2023
58e6a2a
LOOP-4752 Integrate stateless algorithm into Loop (#606)
ps2 Dec 19, 2023
8f99328
Temporarily Disable Favorite Foods
Camji55 Jan 12, 2024
5dea8ff
Merge pull request #609 from tidepool-org/disable-favorite-foods
Camji55 Jan 16, 2024
1926549
[LOOP-4716] iOS 17 Widget Fixes
Camji55 Jan 16, 2024
97d5e10
[LOOP-4788] Fix Unit Tests for iOS 17
Camji55 Jan 16, 2024
bcd2505
[LOOP-4716] iOS 17 Widget Fixes
Camji55 Jan 16, 2024
e2f4b50
[LOOP-4788] Fix Unit Tests for iOS 17
Camji55 Jan 16, 2024
de382e6
revert change for experimental features (#612)
nhamming Jan 22, 2024
2d5a3bc
[LOOP-4762] Fix Alert Management Icon Alignment
Camji55 Jan 22, 2024
af427d0
Merge branch 'dev' into cameron/LOOP-4762-alert-management-icon-align…
Camji55 Jan 22, 2024
27ea94b
[LOOP-4762] Fix Alert Management Icon Alignment
Camji55 Jan 23, 2024
d4b6420
[PAL-360] refactoring CancelTempBasalFailedError when .maximumBasalRa…
nhamming Jan 26, 2024
e413036
always load extensions (#615)
nhamming Jan 29, 2024
04583c3
[LOOP-4793] Beginning XCUI Tests
Camji55 Feb 8, 2024
b8f7b23
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Feb 8, 2024
b08b57f
[LOOP-4793] Beginning XCUI Tests
Camji55 Feb 8, 2024
362b68e
[LOOP-4793] Beginning XCUI Tests
Camji55 Feb 14, 2024
0d7ce81
[LOOP-4793] Beginning XCUI Tests
Camji55 Feb 14, 2024
c9dbc95
Fail loop if pump data is too old (#618)
ps2 Feb 16, 2024
db05327
[LOOP-4793] Beginning XCUI Tests
Camji55 Feb 20, 2024
4ac1f3f
[LOOP-4793] Beginning XCUI Tests
Camji55 Feb 20, 2024
ab646a8
[LOOP-4793] Beginning XCUI Tests
Camji55 Feb 22, 2024
7058b87
[LOOP-4548] Fix 0 Value Placeholder in Simple Bolus Calculator
Camji55 Feb 28, 2024
373c6c8
[LOOP-4548] Fix 0 Value Placeholder in Simple Bolus Calculator
Camji55 Feb 29, 2024
6796540
[LOOP-4548] Fix 0 Value Placeholder in Simple Bolus Calculator Tests
Camji55 Feb 29, 2024
41c8031
[LOOP-4548] Fix 0 Value Placeholder in Simple Bolus Calculator Tests
Camji55 Feb 29, 2024
0bd52ba
LOOP-4781 Loop to use LoopAlgorithm swift package (#617)
ps2 Mar 5, 2024
f35a3a6
[LOOP-4807] need to round the bolus before added to the context (#621)
nhamming Mar 14, 2024
ea73ac5
[LOOP-4782] 10s Canceled Bolus Status Banner
Camji55 Mar 19, 2024
21c78cd
[PAL-478] needed to trigger viewDidAppear to present the modal after …
nhamming Mar 20, 2024
d97b329
[PAL-471] allow manual glucose entry when recommendManualGlucoseEntry…
nhamming Mar 20, 2024
6c9a0cd
[LOOP-4782] 10s Canceled Bolus Status Banner
Camji55 Mar 21, 2024
d5140bf
[LOOP-4824] updating ZipFoundation (#626)
nhamming Mar 21, 2024
72fca58
[PAL-458] Adding the check for rapidly rising glucose (#622)
nhamming Mar 21, 2024
8c92e37
[PAL-471] reverting recent updates and adding missing loop algorithm …
nhamming Mar 26, 2024
d4dcb2f
[PAL-466-468] start the carb ratio at the most distant entry time (#628)
nhamming Mar 28, 2024
aa87bd8
[PAL-458] should be >= 3 (#629)
nhamming Mar 28, 2024
cf8fe2b
[LOOP-4782] 10s Canceled Bolus Status Banner
Camji55 Apr 2, 2024
9fffbf7
[LOOP-4782] 10s Canceled Bolus Status Banner
Camji55 Apr 2, 2024
5957dac
[PAL-470] hide action area when enter bolus is tapped (#630)
nhamming Apr 4, 2024
10e5477
[LOOP-4782] 10s Canceled Bolus Status Banner
Camji55 Apr 5, 2024
ae28436
[PAL-502] Recommendation updated (#631)
nhamming Apr 10, 2024
0103da2
[LOOP-4841-4843] updated bolus completed check (#632)
nhamming Apr 12, 2024
cbbc6c3
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Apr 15, 2024
b6e9a01
Keep DoseStore basal profile current (#635)
ps2 Apr 25, 2024
b7d8021
[PAL-511] do not reset tidepool service default environment (#636)
nhamming Apr 29, 2024
9238756
[COASTAL-1378] allow alerts during onboarding (#637)
nhamming Apr 30, 2024
f597c8a
[LOOP-4852] clamp to min...max (#633)
nhamming May 2, 2024
634799c
Update to new method signature (#638)
ps2 May 9, 2024
8675590
[LOOP-4863] Notification Permission Alert Updates
Camji55 May 9, 2024
f48135f
ConfirmationToggle and LoopStatusCircleView
ArwainK May 10, 2024
0fa9e69
Merge branch 'dev' into LOOP-4870
ArwainK May 10, 2024
79c82fc
Update LoopStatusCircleView rendering
ArwainK May 10, 2024
4f3b150
Merge pull request #639 from tidepool-org/cameron/LOOP-4863-notificat…
Camji55 May 21, 2024
1a64cdc
Merge branch 'dev' into LOOP-4870
ArwainK May 21, 2024
a9a6494
[LOOP-4870] Misc cleanup
Camji55 May 22, 2024
df920e6
[LOOP-4870] Misc cleanup
Camji55 May 22, 2024
3044b00
[LOOP-4870] Misc cleanup
Camji55 May 22, 2024
8e28c7f
[PAL-615] Scenario Loading Fixes
Camji55 May 24, 2024
ef4acc6
[PAL-615] Scenario Loading Fixes
Camji55 May 24, 2024
768946a
[PAL-615] Scenario Loading Fixes
Camji55 May 24, 2024
1ac9bfc
[PAL-615] Scenario Loading Fixes
Camji55 May 24, 2024
06d98e4
Merge branch 'dev' into LOOP-4870
ArwainK May 25, 2024
b34efc8
Update SettingsView section header to use localizedAppNameAndVersion
ArwainK May 25, 2024
a85263d
Merge pull request #640 from tidepool-org/LOOP-4870
ArwainK May 25, 2024
dc77394
reload CGM manager is async
nhamming May 29, 2024
a64473a
cleanup
Camji55 May 29, 2024
9ded17b
reload CGM manager is async
Camji55 May 30, 2024
2933402
Move LoopStatusCircleView and ConfirmationToggle to LoopKit
ArwainK Jun 3, 2024
4131fad
[LOOP-4883] Simple Calculator UI Updates
Camji55 Jun 4, 2024
b816eaa
[LOOP-4883] Simple Calculator UI Updates
Camji55 Jun 4, 2024
1433ae1
[LOOP-4869] Move LoopStatusCircleView and ConfirmationToggle to LoopKit
Camji55 Jun 4, 2024
7333fa3
[LOOP-4870] LoopCircleView Updates
Camji55 Jun 4, 2024
198c7f5
[LOOP-4870] LoopCircleView Updates
Camji55 Jun 4, 2024
a7a0518
[LOOP-4870] LoopCircleView Updates
Camji55 Jun 4, 2024
21a6d1c
[LOOP-4870] LoopCircleView Updates
Camji55 Jun 4, 2024
3ff80ab
[LOOP-4870] LoopCircleView Updates
Camji55 Jun 4, 2024
7bbffac
[LOOP-4870] Fix tests
Camji55 Jun 4, 2024
6d48072
[LOOP-4870] Fix tests
Camji55 Jun 5, 2024
22e09c9
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jun 6, 2024
23fc33e
[LOOP-4882] Mute App Sounds UI Updates
Camji55 Jun 6, 2024
895e382
[LOOP-4882] Mute App Sounds UI Updates
Camji55 Jun 6, 2024
727e09f
[LOOP-4870] Fix tests
Camji55 Jun 6, 2024
126b7c5
[LOOP-4801] adding pump failure and check during looping (#649)
nhamming Jun 7, 2024
a59478a
[LOOP-4890] revert change to acceptable color (#652)
nhamming Jun 10, 2024
081fd92
LOOP-1169 Upload device logs (#644)
ps2 Jun 10, 2024
89fb057
[LOOP-4890] adding loop status color to the environment (#653)
nhamming Jun 10, 2024
537471f
[PAL-638] report resume immediately after suspend (#650)
nhamming Jun 11, 2024
904e1ae
[LOOP-4847] align COB value (#651)
nhamming Jun 11, 2024
1fdf096
Merge branch 'dev' into cameron/LOOP-4882
Camji55 Jun 11, 2024
f5f6ac5
[LOOP-4882] Mute App Sounds UI Updates
Camji55 Jun 11, 2024
26ffff0
[LOOP-4882] Mute App Sounds UI Updates
Camji55 Jun 11, 2024
133818f
[LOOP-4882] Mute App Sounds UI Updates
Camji55 Jun 12, 2024
ae150f9
[LOOP-4882] Mute App Sounds Enhancements
Camji55 Jun 12, 2024
957d5c6
[LOOP-4882] Mute App Sounds Enhancements Design Review Feedback
Camji55 Jun 12, 2024
9aa88e7
DoseStore add reservoir updated to async (#656)
ps2 Jun 12, 2024
ed0bc2e
[PAL-666] also defer retractions, since cooresponding alerts will not…
nhamming Jun 13, 2024
c45d75b
[LOOP-4882] Mute App Sounds UI Updates
Camji55 Jun 13, 2024
40b0eb0
[LOOP-4882] Mute App Sounds Enhancements Design Review Feedback
Camji55 Jun 13, 2024
f901de5
LOOP-4849 Fix watch display bugs around handling GlucoseCondition (#659)
ps2 Jun 14, 2024
543ea17
[LOOP-4882] Mute App Sounds Button Label Update
Camji55 Jun 14, 2024
e3b3eed
[LOOP-4882] Mute App Sounds Button Label Update
Camji55 Jun 14, 2024
44298fc
[LOOP-4882] Mute App Sounds iOS Permissions Button Changes
Camji55 Jun 14, 2024
0c9cf49
[LOOP-4882] Mute App Sounds iOS Permissions Button Changes
Camji55 Jun 14, 2024
236e87f
[LOOP-4882] Mute App Sounds iOS Permissions Button Changes
Camji55 Jun 14, 2024
6b31bf4
[LOOP-4863] updated handling of notification permissions (#661)
nhamming Jun 14, 2024
b40a368
Update host identifier for plugins, which is used for dataset name in…
ps2 Jun 14, 2024
9ead16d
Support building Loop with Xcode 16
Camji55 Jun 17, 2024
4f061b1
[PAL-679] divider is full width of list (#665)
nhamming Jun 17, 2024
638dd3c
[LOOP-4884] notify that loop finished (#666)
nhamming Jun 17, 2024
3b54cd2
Support building Loop with Xcode 16
Camji55 Jun 18, 2024
51982ac
[LOOP-4863] corrected copy (#669)
nhamming Jun 18, 2024
f05851f
[LOOP-4097] only upload data after onboarding is complete (#668)
nhamming Jun 19, 2024
991f7a9
[LOOP-4908] Bolus Status Banner UI Updates
Camji55 Jun 20, 2024
fd8d86d
[LOOP-4908] Bolus Status Banner UI Updates
Camji55 Jun 20, 2024
aa5304a
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jun 20, 2024
4938d26
[LOOP-4908] Bolus Status Banner UI Updates
Camji55 Jun 21, 2024
9f2cc11
[LOOP-4910] Mute All App Sounds Copy Update
Camji55 Jun 24, 2024
21c6e58
[LOOP-4910] Mute All App Sounds Copy Update
Camji55 Jun 24, 2024
1ae3a5f
[LOOP-4853] Date in event history (#634)
nhamming Jun 25, 2024
5bf071f
[LOOP-4910] Mute All App Sounds Copy Update
Camji55 Jun 25, 2024
ba3942e
Remove suspend effect from glucose prediction details page (#673)
ps2 Jun 25, 2024
fec3635
[LOOP-4683] align IOB (#660)
nhamming Jun 27, 2024
19b6d0a
[PAL-612] protect selecdting carb entry when automative dosing off (#…
nhamming Jun 27, 2024
df5215f
[LOOP-4877] block UI updates for certaint bolus transitions (#674)
nhamming Jun 27, 2024
2e188b3
set max fractional digits to 2
nhamming Jun 28, 2024
09845cf
removed commented out code
nhamming Jun 28, 2024
2f5b94c
[LOOP-4390] Mute All App Sounds Picker UX Enhancement
Camji55 Jul 1, 2024
7d36e70
[LOOP-4390] Mute All App Sounds Picker UX Enhancement
Camji55 Jul 1, 2024
5946705
[LOOP-4390] Mute All App Sounds Picker UX Enhancement
Camji55 Jul 1, 2024
249cc6c
[PAL-653] Investigation Device Warning
Camji55 Jul 2, 2024
4501dae
[PAL-653] Investigation Device Warning
Camji55 Jul 2, 2024
a6284f1
[LOOP-4683] set max fractional digits to 2
Camji55 Jul 2, 2024
c91a09d
Merge branch 'dev' into cameron/PAL-653-investigation-device-warning
Camji55 Jul 2, 2024
7952770
[PAL-653] Investigation Device Warning
Camji55 Jul 3, 2024
eb70fac
[LOOP-4884] when loop opens and loop status icon is animated, stop an…
nhamming Jul 4, 2024
d11d435
allow insulin model selection configuration (#654)
nhamming Jul 8, 2024
af3a02d
[PAL-653] Investigation Device Warning
Camji55 Jul 8, 2024
f501048
Merge branch 'dev' into cameron/PAL-653-investigation-device-warning
Camji55 Jul 8, 2024
52252f2
[PAL-653] Investigation Device Warning
Camji55 Jul 8, 2024
def824f
[LOOP-4942] Use proper guidanceColors for DismissableHostingController
Camji55 Jul 8, 2024
ece83cc
[LOOP-4942] Use proper guidanceColors for DismissableHostingController
Camji55 Jul 9, 2024
d7c6532
[LOOP-4942] Use proper guidanceColors for DismissableHostingController
Camji55 Jul 9, 2024
a82befb
[LOOP-4905] Separating new data from uploads to remove blocking queue…
nhamming Jul 10, 2024
578ad3d
Only show pump events with doses (#682)
ps2 Jul 10, 2024
af0c417
[LOOP-4683] Add unit to IOB
Camji55 Jul 11, 2024
451935a
[LOOP-4683] Add unit to IOB
Camji55 Jul 11, 2024
e2898de
[LOOP-4877] need to keep track of bolusState during transitions (#683)
nhamming Jul 11, 2024
ae58355
Fix issue with target override application (#685)
ps2 Jul 16, 2024
8e6158f
[LOOP-4954, LOOP-4957] UI Enhancement for favorite foods in Carb Entr…
SwiftlyNoah Jul 24, 2024
030035b
Fix cyclical loop in tests (#688)
SwiftlyNoah Jul 24, 2024
909370c
[LOOP-4884] Use LoopCircleView for LoopStateView / SwiftUI Interop
Camji55 Jul 24, 2024
c04b368
[LOOP-4884] Use LoopCircleView for LoopStateView / SwiftUI Interop
Camji55 Jul 25, 2024
0ebc9ff
Merge branch 'dev' into cameron/LOOP-4884-pulsing-loop-status
Camji55 Jul 25, 2024
aaf56cc
Merge branch 'dev' into cameron/LOOP-4884-pulsing-loop-status
Camji55 Jul 25, 2024
a601a2f
[LOOP-4884] Use LoopCircleView for LoopStateView / SwiftUI Interop
Camji55 Jul 25, 2024
ebe4d78
[LOOP-4884] Use LoopCircleView for LoopStateView / SwiftUI Interop
Camji55 Jul 25, 2024
d7149e1
[PAL-694] issue report includes 100 most recent alerts (#690)
nhamming Jul 30, 2024
7ef44d1
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jul 30, 2024
ff5f5fa
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jul 30, 2024
22ebe68
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jul 30, 2024
5a7a7ff
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jul 30, 2024
24156a2
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jul 30, 2024
26c798f
LoopChartView + refactored PredictedGlucoseChartView
SwiftlyNoah Jul 31, 2024
923a5eb
[LOOP-4956] SwiftUI views for rest of loop charts
SwiftlyNoah Jul 31, 2024
a23baa5
[LOOP-4956] SwiftUI view for GlucoseCarbChart
SwiftlyNoah Jul 31, 2024
3db82da
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Jul 31, 2024
75006ce
[LOOP-4978] Favorite Food Insights card in CarbEntryView
SwiftlyNoah Aug 1, 2024
8d7652a
[LOOP-4953] Favorite Foods Insights Page
SwiftlyNoah Aug 2, 2024
f007ee7
Fix for landscape CarbEntryView/FavoriteFoodInsightsView
SwiftlyNoah Aug 5, 2024
2c914f8
Renaming/organizing favorite foods
SwiftlyNoah Aug 5, 2024
bfa714b
[LOOP-4942] Fix path to BolusEntryView with missing guidanceColors
Camji55 Aug 5, 2024
4fa7686
[LOOP-4942] Fix path to BolusEntryView with missing guidanceColors
Camji55 Aug 5, 2024
cee1369
Allow editing of favorite foods on detail view
SwiftlyNoah Aug 5, 2024
f6c10cf
Add edit/insights screens to favorite food detail view
SwiftlyNoah Aug 6, 2024
e02190c
Improve foodType/name UX for favorite foods
SwiftlyNoah Aug 6, 2024
871b48c
[LOOP-4982] Fix manual dose screen
SwiftlyNoah Aug 7, 2024
d6354da
Merge branch 'dev' into noah/LOOP-4953/favorite-food-insights
SwiftlyNoah Aug 7, 2024
bbfa148
Edit carb entry screen: favorite food insights + allow removal of fav…
SwiftlyNoah Aug 7, 2024
1b67115
Add analytics for favorite foods
SwiftlyNoah Aug 7, 2024
9ca5088
Code cleanup
SwiftlyNoah Aug 7, 2024
d737086
Merge pull request #691 from tidepool-org/noah/LOOP-4953/favorite-foo…
SwiftlyNoah Aug 8, 2024
dd8b59b
[LOOP-4994] Widget asset fix
SwiftlyNoah Aug 9, 2024
33337f2
Widget spacing ui fix
SwiftlyNoah Aug 9, 2024
c22b37f
Code cleanup
SwiftlyNoah Aug 9, 2024
a30cd12
[LOOP-4994] Fix rest of assets
SwiftlyNoah Aug 9, 2024
c80c8bb
Merge pull request #693 from tidepool-org/noah/LOOP-4994/widget-asset…
SwiftlyNoah Aug 9, 2024
2cf145e
Fix double arrow trend image not appearing on widget (#694)
SwiftlyNoah Aug 9, 2024
eea2354
[LOOP-4987] Fix color cycle of Loop Status
Camji55 Aug 9, 2024
4e3a0d6
[LOOP-4987] Fix color cycle of Loop Status
Camji55 Aug 12, 2024
4775711
LOOP-4960 Upload settings on restart (#697)
ps2 Aug 23, 2024
f55dac2
LOOP-4769 Premeal Storage (#698)
ps2 Aug 27, 2024
4c7978b
[LOOP-4945] remove stale glucose timer from CGM status HUD view (#699)
nhamming Sep 6, 2024
149925f
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Sep 9, 2024
2192d6b
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Sep 9, 2024
3761d96
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Sep 9, 2024
ae4dffa
Merge branch 'dev' into cameron/LOOP-4793
Camji55 Sep 9, 2024
9b89125
Cleanup
Camji55 Sep 10, 2024
d4cb32f
Cleanup
Camji55 Sep 10, 2024
48c44ac
Cleanup
Camji55 Sep 11, 2024
e0d901e
remove tidepool references
Camji55 Sep 11, 2024
d640b55
[LOOP-4793] Beginning XCUI Tests
Camji55 Sep 11, 2024
b6f2594
Use LoopAlgorithm basal overlay for computing total delivery (#700)
ps2 Sep 11, 2024
5dab230
LOOP-4098 Overlay basal from history timeline instead of schedule (#701)
ps2 Sep 13, 2024
5c35de6
[LOOP-4975] Update Open Loop Freshness Logic and Labeling
Camji55 Sep 13, 2024
22094e7
Add missing returns (#702)
ps2 Sep 14, 2024
198e120
[LOOP-4975] Naming Update
Camji55 Sep 16, 2024
aaf9a5c
Merge branch 'dev' into cameron/LOOP-4975-open-loop-icon-messaging-up…
Camji55 Sep 16, 2024
9486373
[LOOP-4975] Update Open Loop Freshness Logic and Labeling
Camji55 Sep 16, 2024
9e1a8c9
[PAL-704] removing debug from testflight (#704)
nhamming Sep 18, 2024
f9c01eb
[LOOP-4975] Update Open Loop Freshness Syncing Between StatusHUD and …
Camji55 Sep 19, 2024
f417eaf
[LOOP-4975] Update Open Loop Tests (#707)
Camji55 Sep 19, 2024
96c8481
Track automation history (#708)
ps2 Sep 20, 2024
a611867
Fix initialization order (#709)
ps2 Sep 20, 2024
5c129d7
[PAL-798] assign deliveryDelegate (#711)
nhamming Oct 2, 2024
63c11b4
Support remote data services with automation history (#710)
ps2 Oct 2, 2024
f3480b0
Merge commit '63c11b470c8fbe4c79208a98cbcbab5f47b0960e'
ps2 Oct 5, 2024
72078f0
LOOP-5088 Update Loop for LoopKit api changes for avoiding thread blo…
ps2 Oct 11, 2024
fd1130e
[LOOP-5107] async cgm manager wants deletion (#714)
Camji55 Oct 15, 2024
7804046
Enable mid-absorption ISF, and update forecast on settings change (#715)
ps2 Oct 15, 2024
cc8f328
LOOP-4665 Fix bugs relating to determining span of time to use for IS…
ps2 Oct 16, 2024
fb2ba32
[LOOP-5119] handle history events across 2 sections (#717)
nhamming Oct 23, 2024
3fe998b
LOOP-5122 onboarding updates (#719)
ps2 Oct 25, 2024
95901af
merge
ps2 Oct 27, 2024
b833eb4
Merge tidepool/dev
ps2 Oct 27, 2024
8805452
[PAL-818] block mock service when simulators are not allowed (#721)
nhamming Oct 29, 2024
3654d9d
[PAL-818] pass allowDebugFeatures to service (#722)
nhamming Oct 30, 2024
14686ef
Merge tidepool/dev
ps2 Oct 31, 2024
438096a
Use current data for manual injection prediction
ps2 Oct 31, 2024
95d4a23
update progressCell for bolus display
marionbarker Nov 17, 2024
5fbdd9b
Merge pull request #2263 from loopandlearn/fix_tidepool-merge/ui_bolu…
marionbarker Mar 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Common/Extensions/NSBundle.swift
Original file line number Diff line number Diff line change
@@ -60,5 +60,27 @@ extension Bundle {
}
return .days(localCacheDurationDays)
}

var hostIdentifier: String {
var identifier = bundleIdentifier ?? "com.loopkit.Loop"
let components = identifier.components(separatedBy: ".")
// DIY Loop has bundle identifiers like com.UY653SP37Q.loopkit.Loop
if components[2] == "loopkit" && components[3] == "Loop" {
identifier = "com.loopkit.Loop"
}
return identifier
}

var hostVersion: String {
var semanticVersion = shortVersionString

while semanticVersion.split(separator: ".").count < 3 {
semanticVersion += ".0"
}

semanticVersion += "+\(Bundle.main.version)"

return semanticVersion
}
}

39 changes: 19 additions & 20 deletions Loop Widget Extension/Timeline/StatusWidgetTimelineProvider.swift
Original file line number Diff line number Diff line change
@@ -30,10 +30,16 @@ class StatusWidgetTimelineProvider: TimelineProvider {
store: cacheStore,
expireAfter: localCacheDuration)

lazy var glucoseStore = GlucoseStore(
cacheStore: cacheStore,
provenanceIdentifier: HKSource.default().bundleIdentifier
)
var glucoseStore: GlucoseStore!

init() {
Task {
glucoseStore = await GlucoseStore(
cacheStore: cacheStore,
provenanceIdentifier: HKSource.default().bundleIdentifier
)
}
}

func placeholder(in context: Context) -> StatusWidgetTimelimeEntry {
log.default("%{public}@: context=%{public}@", #function, String(describing: context))
@@ -90,29 +96,22 @@ class StatusWidgetTimelineProvider: TimelineProvider {
}

func update(completion: @escaping (StatusWidgetTimelimeEntry) -> Void) {
let group = DispatchGroup()

var glucose: [StoredGlucoseSample] = []

let startDate = Date(timeIntervalSinceNow: -LoopAlgorithm.inputDataRecencyInterval)

group.enter()
glucoseStore.getGlucoseSamples(start: startDate) { (result) in
switch result {
case .failure:
Task {

var glucose: [StoredGlucoseSample] = []

do {
glucose = try await glucoseStore.getGlucoseSamples(start: startDate)
self.log.default("Fetched glucose: last = %{public}@, %{public}@", String(describing: glucose.last?.startDate), String(describing: glucose.last?.quantity))
} catch {
self.log.error("Failed to fetch glucose after %{public}@", String(describing: startDate))
glucose = []
case .success(let samples):
self.log.default("Fetched glucose: last = %{public}@, %{public}@", String(describing: samples.last?.startDate), String(describing: samples.last?.quantity))
glucose = samples
}
group.leave()
}
group.wait()

let finalGlucose = glucose
let finalGlucose = glucose

Task { @MainActor in
guard let defaults = self.defaults,
let context = defaults.statusExtensionContext,
let contextUpdatedAt = context.createdAt,
4 changes: 2 additions & 2 deletions Loop/Extensions/GlucoseStore+SimulatedCoreData.swift
Original file line number Diff line number Diff line change
@@ -82,8 +82,8 @@ extension GlucoseStore {
return addError
}

func purgeHistoricalGlucoseObjects(completion: @escaping (Error?) -> Void) {
purgeCachedGlucoseObjects(before: historicalEndDate, completion: completion)
func purgeHistoricalGlucoseObjects() async throws {
try await purgeCachedGlucoseObjects(before: historicalEndDate)
}
}

44 changes: 18 additions & 26 deletions Loop/Managers/CGMStalenessMonitor.swift
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ import LoopCore
import LoopAlgorithm

protocol CGMStalenessMonitorDelegate: AnyObject {
func getLatestCGMGlucose(since: Date, completion: @escaping (_ result: Swift.Result<StoredGlucoseSample?, Error>) -> Void)
func getLatestCGMGlucose(since: Date) async throws -> StoredGlucoseSample?
}

class CGMStalenessMonitor {
@@ -21,13 +21,7 @@ class CGMStalenessMonitor {

private var cgmStalenessTimer: Timer?

weak var delegate: CGMStalenessMonitorDelegate? = nil {
didSet {
if delegate != nil {
checkCGMStaleness()
}
}
}
weak var delegate: CGMStalenessMonitorDelegate?

@Published var cgmDataIsStale: Bool = true {
didSet {
@@ -57,29 +51,27 @@ class CGMStalenessMonitor {
cgmStalenessTimer?.invalidate()
cgmStalenessTimer = Timer.scheduledTimer(withTimeInterval: expiration.timeIntervalSinceNow, repeats: false) { [weak self] _ in
self?.log.debug("cgmStalenessTimer fired")
self?.checkCGMStaleness()
Task {
await self?.checkCGMStaleness()
}
}
cgmStalenessTimer?.tolerance = CGMStalenessMonitor.cgmStalenessTimerTolerance
}

private func checkCGMStaleness() {
delegate?.getLatestCGMGlucose(since: Date(timeIntervalSinceNow: -LoopAlgorithm.inputDataRecencyInterval)) { (result) in
DispatchQueue.main.async {
self.log.debug("Fetched latest CGM Glucose for checkCGMStaleness: %{public}@", String(describing: result))
switch result {
case .success(let sample):
if let sample = sample {
self.cgmDataIsStale = false
self.updateCGMStalenessTimer(expiration: sample.startDate.addingTimeInterval(LoopAlgorithm.inputDataRecencyInterval + CGMStalenessMonitor.cgmStalenessTimerTolerance))
} else {
self.cgmDataIsStale = true
}
case .failure(let error):
self.log.error("Unable to get latest CGM clucose: %{public}@ ", String(describing: error))
// Some kind of system error; check again in 5 minutes
self.updateCGMStalenessTimer(expiration: Date(timeIntervalSinceNow: .minutes(5)))
}
func checkCGMStaleness() async {
do {
let sample = try await delegate?.getLatestCGMGlucose(since: Date(timeIntervalSinceNow: -LoopAlgorithm.inputDataRecencyInterval))
self.log.debug("Fetched latest CGM Glucose for checkCGMStaleness: %{public}@", String(describing: sample))
if let sample = sample {
self.cgmDataIsStale = false
self.updateCGMStalenessTimer(expiration: sample.startDate.addingTimeInterval(LoopAlgorithm.inputDataRecencyInterval + CGMStalenessMonitor.cgmStalenessTimerTolerance))
} else {
self.cgmDataIsStale = true
}
} catch {
self.log.error("Unable to get latest CGM clucose: %{public}@ ", String(describing: error))
// Some kind of system error; check again in 5 minutes
self.updateCGMStalenessTimer(expiration: Date(timeIntervalSinceNow: .minutes(5)))
}
}
}
18 changes: 2 additions & 16 deletions Loop/Managers/CriticalEventLogExportManager.swift
Original file line number Diff line number Diff line change
@@ -199,16 +199,6 @@ public class CriticalEventLogExportManager {
calendar.timeZone = TimeZone(identifier: "UTC")!
return calendar
}()

// MARK: - Background Tasks

func registerBackgroundTasks() {
if Self.registerCriticalEventLogHistoricalExportBackgroundTask({ self.handleCriticalEventLogHistoricalExportBackgroundTask($0) }) {
log.debug("Critical event log export background task registered")
} else {
log.error("Critical event log export background task not registered")
}
}
}

// MARK: - CriticalEventLogBaseExporter
@@ -567,11 +557,7 @@ fileprivate extension FileManager {
// MARK: - Critical Event Log Export

extension CriticalEventLogExportManager {
private static var criticalEventLogHistoricalExportBackgroundTaskIdentifier: String { "com.loopkit.background-task.critical-event-log.historical-export" }

public static func registerCriticalEventLogHistoricalExportBackgroundTask(_ handler: @escaping (BGProcessingTask) -> Void) -> Bool {
return BGTaskScheduler.shared.register(forTaskWithIdentifier: criticalEventLogHistoricalExportBackgroundTaskIdentifier, using: nil) { handler($0 as! BGProcessingTask) }
}
static var historicalExportBackgroundTaskIdentifier: String { "com.loopkit.background-task.critical-event-log.historical-export" }

public func handleCriticalEventLogHistoricalExportBackgroundTask(_ task: BGProcessingTask) {
dispatchPrecondition(condition: .notOnQueue(.main))
@@ -602,7 +588,7 @@ extension CriticalEventLogExportManager {
public func scheduleCriticalEventLogHistoricalExportBackgroundTask(isRetry: Bool = false) {
do {
let earliestBeginDate = isRetry ? retryExportHistoricalDate() : nextExportHistoricalDate()
let request = BGProcessingTaskRequest(identifier: Self.criticalEventLogHistoricalExportBackgroundTaskIdentifier)
let request = BGProcessingTaskRequest(identifier: Self.historicalExportBackgroundTaskIdentifier)
request.earliestBeginDate = earliestBeginDate
request.requiresExternalPower = true

68 changes: 28 additions & 40 deletions Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
@@ -288,7 +288,11 @@ final class DeviceDataManager {
glucoseStore.delegate = self
cgmEventStore.delegate = self
doseStore.insulinDeliveryStore.delegate = self


Task {
await cgmStalenessMonitor.checkCGMStaleness()
}

setupPump()
setupCGM()

@@ -855,14 +859,17 @@ extension DeviceDataManager: PersistedAlertStore {
// MARK: - CGMManagerDelegate
extension DeviceDataManager: CGMManagerDelegate {
nonisolated
func cgmManagerWantsDeletion(_ manager: CGMManager) {
DispatchQueue.main.async {
self.log.default("CGM manager with identifier '%{public}@' wants deletion", manager.pluginIdentifier)
if let cgmManagerUI = self.cgmManager as? CGMManagerUI {
self.displayGlucoseUnitBroadcaster?.removeDisplayGlucoseUnitObserver(cgmManagerUI)
func cgmManagerWantsDeletion(_ manager: CGMManager) async {
await withCheckedContinuation { continuation in
DispatchQueue.main.async {
self.log.default("CGM manager with identifier '%{public}@' wants deletion", manager.pluginIdentifier)
if let cgmManagerUI = self.cgmManager as? CGMManagerUI {
self.displayGlucoseUnitBroadcaster?.removeDisplayGlucoseUnitObserver(cgmManagerUI)
}
self.cgmManager = nil
self.settingsManager.storeSettings()
continuation.resume()
}
self.cgmManager = nil
self.settingsManager.storeSettings()
}
}

@@ -1175,57 +1182,38 @@ extension DeviceDataManager: CgmEventStoreDelegate {

// MARK: - TestingPumpManager
extension DeviceDataManager {
func deleteTestingPumpData(completion: ((Error?) -> Void)? = nil) {
func deleteTestingPumpData() async throws {
guard let testingPumpManager = pumpManager as? TestingPumpManager else {
completion?(nil)
return
}

let devicePredicate = HKQuery.predicateForObjects(from: [testingPumpManager.testingDevice])
let insulinDeliveryStore = doseStore.insulinDeliveryStore

Task {
do {
try await doseStore.resetPumpData()
} catch {
completion?(error)
return
}
try await doseStore.resetPumpData()

let insulinSharingDenied = self.healthStore.authorizationStatus(for: HealthKitSampleStore.insulinQuantityType) == .sharingDenied
guard !insulinSharingDenied else {
// only clear cache since access to health kit is denied
insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
completion?(error)
}
return
}

insulinDeliveryStore.purgeAllDoseEntries(healthKitPredicate: devicePredicate) { error in
completion?(error)
}
let insulinSharingDenied = self.healthStore.authorizationStatus(for: HealthKitSampleStore.insulinQuantityType) == .sharingDenied
guard !insulinSharingDenied else {
// only clear cache since access to health kit is denied
await insulinDeliveryStore.purgeCachedInsulinDeliveryObjects()
return
}

try await insulinDeliveryStore.purgeDoseEntriesForDevice(testingPumpManager.testingDevice)
}

func deleteTestingCGMData(completion: ((Error?) -> Void)? = nil) {
func deleteTestingCGMData() async throws {
guard let testingCGMManager = cgmManager as? TestingCGMManager else {
completion?(nil)
return
}

let glucoseSharingDenied = self.healthStore.authorizationStatus(for: HealthKitSampleStore.glucoseType) == .sharingDenied
guard !glucoseSharingDenied else {
// only clear cache since access to health kit is denied
glucoseStore.purgeCachedGlucoseObjects() { error in
completion?(error)
}
try await glucoseStore.purgeCachedGlucoseObjects()
return
}

let predicate = HKQuery.predicateForObjects(from: [testingCGMManager.testingDevice])
glucoseStore.purgeAllGlucoseSamples(healthKitPredicate: predicate) { error in
completion?(error)
}
try await glucoseStore.purgeAllGlucose(for: testingCGMManager.testingDevice)
}
}

47 changes: 33 additions & 14 deletions Loop/Managers/LoopAppManager.swift
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@

import UIKit
import Intents
import BackgroundTasks
import Combine
import LoopKit
import LoopKitUI
@@ -133,9 +134,27 @@ class LoopAppManager: NSObject {
self.state = state.next
}

func registerBackgroundTasks() {
let taskIdentifier = CriticalEventLogExportManager.historicalExportBackgroundTaskIdentifier
let registered = BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { task in
guard let criticalEventLogExportManager = self.criticalEventLogExportManager else {
self.log.error("Critical event log export launch handler called before initialization complete!")
return
}
criticalEventLogExportManager.handleCriticalEventLogHistoricalExportBackgroundTask(task as! BGProcessingTask)
}
if registered {
log.debug("Critical event log export background task registered")
} else {
log.error("Critical event log export background task not registered")
}
}

func launch() {
precondition(isLaunchPending)

registerBackgroundTasks()

Task {
await resumeLaunch()
}
@@ -248,7 +267,7 @@ class LoopAppManager: NSObject {
observationStart: Date().addingTimeInterval(-CarbMath.maximumAbsorptionTimeInterval)
)

self.doseStore = DoseStore(
self.doseStore = await DoseStore(
healthKitSampleStore: insulinHealthStore,
cacheStore: cacheStore,
cacheLength: localCacheDuration,
@@ -263,7 +282,7 @@ class LoopAppManager: NSObject {
observationStart: Date().addingTimeInterval(-.hours(24))
)

self.glucoseStore = GlucoseStore(
self.glucoseStore = await GlucoseStore(
healthKitSampleStore: glucoseHealthStore,
cacheStore: cacheStore,
cacheLength: localCacheDuration,
@@ -390,9 +409,6 @@ class LoopAppManager: NSObject {
directory: FileManager.default.exportsDirectoryURL,
historicalDuration: localCacheDuration)

criticalEventLogExportManager.registerBackgroundTasks()


statusExtensionManager = ExtensionDataManager(
deviceDataManager: deviceDataManager,
loopDataManager: loopDataManager,
@@ -892,8 +908,16 @@ extension LoopAppManager: ResetLoopManagerDelegate {
}

func resetTestingData(completion: @escaping () -> Void) {
deviceDataManager.deleteTestingCGMData { [weak deviceDataManager] _ in
deviceDataManager?.deleteTestingPumpData { _ in
Task { [weak self] in
await withTaskGroup(of: Void.self) { group in
group.addTask {
try? await self?.deviceDataManager.deleteTestingCGMData()
}
group.addTask {
try? await self?.deviceDataManager?.deleteTestingPumpData()
}

await group.waitForAll()
completion()
}
}
@@ -1045,6 +1069,7 @@ extension LoopAppManager: SimulatedData {
Task { @MainActor in
do {
try await self.doseStore.purgeHistoricalPumpEvents()
try await self.glucoseStore.purgeHistoricalGlucoseObjects()
} catch {
completion(error)
return
@@ -1059,13 +1084,7 @@ extension LoopAppManager: SimulatedData {
completion(error)
return
}
self.glucoseStore.purgeHistoricalGlucoseObjects() { error in
guard error == nil else {
completion(error)
return
}
self.settingsManager.purgeHistoricalSettingsObjects(completion: completion)
}
self.settingsManager.purgeHistoricalSettingsObjects(completion: completion)
}
}
}
41 changes: 35 additions & 6 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
@@ -225,6 +225,19 @@ final class LoopDataManager: ObservableObject {
await self.updateDisplayState()
self.notify(forChange: .insulin)
}
},
NotificationCenter.default.addObserver(
forName: .LoopDataUpdated,
object: nil,
queue: nil
) { (note) in
let context = note.userInfo?[LoopDataManager.LoopUpdateContextKey] as! LoopUpdateContext.RawValue
if case .preferences = LoopUpdateContext(rawValue: context) {
Task { @MainActor in
self.logger.default("Received notification of settings changing")
await self.updateDisplayState()
}
}
}
]

@@ -330,7 +343,7 @@ final class LoopDataManager: ObservableObject {
throw LoopError.configurationError(.basalRateSchedule)
}

let forecastEndTime = baseTime.addingTimeInterval(InsulinMath.defaultInsulinActivityDuration).dateCeiledToTimeInterval(.minutes(GlucoseMath.defaultDelta))
let forecastEndTime = baseTime.addingTimeInterval(InsulinMath.defaultInsulinActivityDuration).dateCeiledToTimeInterval(GlucoseMath.defaultDelta)

let carbsStart = baseTime.addingTimeInterval(CarbMath.dateAdjustmentPast + .minutes(-1)) // additional minute to handle difference in seconds between carb entry and carb ratio

@@ -353,9 +366,24 @@ final class LoopDataManager: ObservableObject {

let glucose = try await glucoseStore.getGlucoseSamples(start: carbsStart, end: baseTime)

let sensitivityStart = min(carbsStart, dosesStart)
let dosesWithModel = doses.map { $0.simpleDose(with: insulinModel(for: $0.insulinType)) }

let recommendationInsulinModel = insulinModel(for: deliveryDelegate?.pumpInsulinType ?? .novolog)

let recommendationEffectInterval = DateInterval(
start: baseTime,
duration: recommendationInsulinModel.effectDuration
)
let neededSensitivityTimeline = LoopAlgorithm.timelineIntervalForSensitivity(
doses: dosesWithModel,
glucoseHistoryStart: glucose.first?.startDate ?? baseTime,
recommendationEffectInterval: recommendationEffectInterval
)

let sensitivity = try await settingsProvider.getInsulinSensitivityHistory(startDate: sensitivityStart, endDate: forecastEndTime)
let sensitivity = try await settingsProvider.getInsulinSensitivityHistory(
startDate: neededSensitivityTimeline.start,
endDate: neededSensitivityTimeline.end
)

let target = try await settingsProvider.getTargetRangeHistory(startDate: baseTime, endDate: forecastEndTime)

@@ -369,7 +397,7 @@ final class LoopDataManager: ObservableObject {
throw LoopError.configurationError(.maximumBasalRatePerHour)
}

var overrides = temporaryPresetsManager.overrideHistory.getOverrideHistory(startDate: sensitivityStart, endDate: forecastEndTime)
var overrides = temporaryPresetsManager.overrideHistory.getOverrideHistory(startDate: neededSensitivityTimeline.start, endDate: forecastEndTime)

// Bug (https://tidepool.atlassian.net/browse/LOOP-4759) pre-meal is not recorded in override history
// So currently we handle automatic forecast by manually adding it in, and when meal bolusing, we do not do this.
@@ -420,7 +448,7 @@ final class LoopDataManager: ObservableObject {

return StoredDataAlgorithmInput(
glucoseHistory: glucose,
doses: doses.map { $0.simpleDose(with: insulinModel(for: $0.insulinType)) },
doses: dosesWithModel,
carbEntries: carbEntries,
predictionStart: baseTime,
basal: basalWithOverrides,
@@ -433,7 +461,7 @@ final class LoopDataManager: ObservableObject {
useIntegralRetrospectiveCorrection: UserDefaults.standard.integralRetrospectiveCorrectionEnabled,
includePositiveVelocityAndRC: true,
carbAbsorptionModel: carbAbsorptionModel,
recommendationInsulinModel: insulinModel(for: deliveryDelegate?.pumpInsulinType ?? .novolog),
recommendationInsulinModel: recommendationInsulinModel,
recommendationType: .manualBolus,
automaticBolusApplicationFactor: effectiveBolusApplicationFactor)
}
@@ -1012,6 +1040,7 @@ extension StoredDataAlgorithmInput {
carbRatio: carbRatio,
algorithmEffectsOptions: effectsOptions,
useIntegralRetrospectiveCorrection: self.useIntegralRetrospectiveCorrection,
useMidAbsorptionISF: true,
carbAbsorptionModel: self.carbAbsorptionModel.model
)
return prediction.glucose
30 changes: 13 additions & 17 deletions Loop/Managers/OnboardingManager.swift
Original file line number Diff line number Diff line change
@@ -429,22 +429,6 @@ extension OnboardingManager: ServiceProvider {
var activeServices: [Service] { servicesManager.activeServices }

var availableServices: [ServiceDescriptor] { servicesManager.availableServices }

func onboardService(withIdentifier identifier: String) -> Swift.Result<OnboardingResult<ServiceViewController, Service>, Error> {
guard let service = activeServices.first(where: { $0.pluginIdentifier == identifier }) else {
return servicesManager.setupService(withIdentifier: identifier)
}

if service.isOnboarded {
return .success(.createdAndOnboarded(service))
}

guard let serviceUI = service as? ServiceUI else {
return .failure(OnboardingError.invalidState)
}

return .success(.userInteractionRequired(serviceUI.settingsViewController(colorPalette: .default)))
}
}

// MARK: - TherapySettingsProvider
@@ -455,10 +439,22 @@ extension OnboardingManager: TherapySettingsProvider {
}
}

// MARK: - PluginHost

extension OnboardingManager: PluginHost {
nonisolated var hostIdentifier: String {
return Bundle.main.hostIdentifier
}

nonisolated var hostVersion: String {
return Bundle.main.hostVersion
}
}

// MARK: - OnboardingProvider

extension OnboardingManager: OnboardingProvider {
var allowDebugFeatures: Bool { FeatureFlags.allowDebugFeatures } // NOTE: DEBUG FEATURES - DEBUG AND TEST ONLY
nonisolated var allowDebugFeatures: Bool { FeatureFlags.allowDebugFeatures } // NOTE: DEBUG FEATURES - DEBUG AND TEST ONLY
}

// MARK: - SupportProvider
61 changes: 28 additions & 33 deletions Loop/Managers/RemoteDataServicesManager.swift
Original file line number Diff line number Diff line change
@@ -431,24 +431,22 @@ extension RemoteDataServicesManager {
let previousQueryAnchor = UserDefaults.appGroup?.getQueryAnchor(for: remoteDataService, withRemoteDataType: .glucose) ?? GlucoseStore.QueryAnchor()
var continueUpload = false

self.glucoseStore.executeGlucoseQuery(fromQueryAnchor: previousQueryAnchor, limit: remoteDataService.glucoseDataLimit ?? Int.max) { result in
switch result {
case .failure(let error):
Task {
do {
let (queryAnchor, data) = try await self.glucoseStore.executeGlucoseQuery(fromQueryAnchor: previousQueryAnchor, limit: remoteDataService.glucoseDataLimit ?? Int.max)
do {
try await remoteDataService.uploadGlucoseData(data)
UserDefaults.appGroup?.setQueryAnchor(for: remoteDataService, withRemoteDataType: .glucose, queryAnchor)
continueUpload = queryAnchor != previousQueryAnchor
await self.uploadSucceeded(key)
} catch {
self.log.error("Error synchronizing glucose data: %{public}@", String(describing: error))
await self.uploadFailed(key)
}
semaphore.signal()
} catch {
self.log.error("Error querying glucose data: %{public}@", String(describing: error))
semaphore.signal()
case .success(let queryAnchor, let data):
Task {
do {
try await remoteDataService.uploadGlucoseData(data)
UserDefaults.appGroup?.setQueryAnchor(for: remoteDataService, withRemoteDataType: .glucose, queryAnchor)
continueUpload = queryAnchor != previousQueryAnchor
self.uploadSucceeded(key)
} catch {
self.log.error("Error synchronizing glucose data: %{public}@", String(describing: error))
self.uploadFailed(key)
}
semaphore.signal()
}
}
}

@@ -472,25 +470,22 @@ extension RemoteDataServicesManager {
let semaphore = DispatchSemaphore(value: 0)
let previousQueryAnchor = UserDefaults.appGroup?.getQueryAnchor(for: remoteDataService, withRemoteDataType: .pumpEvent) ?? DoseStore.QueryAnchor()
var continueUpload = false

self.doseStore.executePumpEventQuery(fromQueryAnchor: previousQueryAnchor, limit: remoteDataService.pumpEventDataLimit ?? Int.max) { result in
switch result {
case .failure(let error):
Task {
do {
let (queryAnchor, data) = try await self.doseStore.executePumpEventQuery(fromQueryAnchor: previousQueryAnchor, limit: remoteDataService.pumpEventDataLimit ?? Int.max)
do {
try await remoteDataService.uploadPumpEventData(data)
UserDefaults.appGroup?.setQueryAnchor(for: remoteDataService, withRemoteDataType: .pumpEvent, queryAnchor)
continueUpload = queryAnchor != previousQueryAnchor
self.uploadSucceeded(key)
} catch {
self.log.error("Error synchronizing pump event data: %{public}@", String(describing: error))
self.uploadFailed(key)
}
semaphore.signal()
} catch {
self.log.error("Error querying pump event data: %{public}@", String(describing: error))
semaphore.signal()
case .success(let queryAnchor, let data):
Task {
do {
try await remoteDataService.uploadPumpEventData(data)
UserDefaults.appGroup?.setQueryAnchor(for: remoteDataService, withRemoteDataType: .pumpEvent, queryAnchor)
continueUpload = queryAnchor != previousQueryAnchor
self.uploadSucceeded(key)
} catch {
self.log.error("Error synchronizing pump event data: %{public}@", String(describing: error))
self.uploadFailed(key)
}
semaphore.signal()
}
}
}

31 changes: 10 additions & 21 deletions Loop/Managers/ServicesManager.swift
Original file line number Diff line number Diff line change
@@ -259,31 +259,20 @@ extension ServicesManager: StatefulPluggableDelegate {
}
}

// MARK: - ServiceDelegate

extension ServicesManager: ServiceDelegate {
var hostIdentifier: String {
var identifier = Bundle.main.bundleIdentifier ?? "com.loopkit.Loop"
let components = identifier.components(separatedBy: ".")
// DIY Loop has bundle identifiers like com.UY653SP37Q.loopkit.Loop
if components[2] == "loopkit" && components[3] == "Loop" {
identifier = "com.loopkit.Looo"
}
return identifier
// MARK: - PluginHost
extension ServicesManager: PluginHost {
nonisolated var hostIdentifier: String {
return Bundle.main.hostIdentifier
}

var hostVersion: String {
var semanticVersion = Bundle.main.shortVersionString

while semanticVersion.split(separator: ".").count < 3 {
semanticVersion += ".0"
}
nonisolated var hostVersion: String {
return Bundle.main.hostVersion
}
}

semanticVersion += "+\(Bundle.main.version)"
// MARK: - ServiceDelegate

return semanticVersion
}

extension ServicesManager: ServiceDelegate {
func enactRemoteOverride(name: String, durationTime: TimeInterval?, remoteAddress: String) async throws {

var duration: TemporaryScheduleOverride.Duration? = nil
154 changes: 74 additions & 80 deletions Loop/Managers/TestingScenariosManager.swift
Original file line number Diff line number Diff line change
@@ -230,67 +230,68 @@ extension TestingScenariosManager {
completion(error)
}

Task {
guard FeatureFlags.scenariosEnabled else {
fatalError("\(#function) should be invoked only when scenarios are enabled")
}

let instance = scenario.instantiate()

var testingCGMManager: TestingCGMManager?
var testingPumpManager: TestingPumpManager?

if instance.hasCGMData {
if let cgmManager = deviceManager.cgmManager as? TestingCGMManager {
if instance.shouldReloadManager?.cgm == true {
testingCGMManager = await reloadCGMManager(withIdentifier: cgmManager.pluginIdentifier)
} else {
testingCGMManager = cgmManager
}
} else {
bail(with: ScenarioLoadingError.noTestingCGMManagerEnabled)
return
guard FeatureFlags.scenariosEnabled else {
fatalError("\(#function) should be invoked only when scenarios are enabled")
}

Task { [weak self] in
do {
try await self?.wipeExistingData()
let instance = scenario.instantiate()

let _: Void = try await withCheckedThrowingContinuation { continuation in
self?.carbStore.addNewCarbEntries(entries: instance.carbEntries, completion: { error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
})
}
}

if instance.hasPumpData {
if let pumpManager = deviceManager.pumpManager as? TestingPumpManager {
if instance.shouldReloadManager?.pump == true {
testingPumpManager = reloadPumpManager(withIdentifier: pumpManager.pluginIdentifier)

var testingCGMManager: TestingCGMManager?
var testingPumpManager: TestingPumpManager?

if instance.hasCGMData {
if let cgmManager = self?.deviceManager.cgmManager as? TestingCGMManager {
if instance.shouldReloadManager?.cgm == true {
testingCGMManager = await self?.reloadCGMManager(withIdentifier: cgmManager.pluginIdentifier)
} else {
testingCGMManager = cgmManager
}
} else {
testingPumpManager = pumpManager
bail(with: ScenarioLoadingError.noTestingCGMManagerEnabled)
return
}
} else {
bail(with: ScenarioLoadingError.noTestingPumpManagerEnabled)
return
}
}

wipeExistingData { error in
guard error == nil else {
bail(with: error!)
return
}

self.carbStore.addNewCarbEntries(entries: instance.carbEntries) { error in
if let error {
bail(with: error)

if instance.hasPumpData {
if let pumpManager = self?.deviceManager.pumpManager as? TestingPumpManager {
if instance.shouldReloadManager?.pump == true {
testingPumpManager = self?.reloadPumpManager(withIdentifier: pumpManager.pluginIdentifier)
} else {
testingPumpManager = pumpManager
}
} else {
testingPumpManager?.reservoirFillFraction = 1.0
testingPumpManager?.injectPumpEvents(instance.pumpEvents)
testingCGMManager?.injectGlucoseSamples(instance.pastGlucoseSamples, futureSamples: instance.futureGlucoseSamples)
self.activeScenario = scenario
completion(nil)
bail(with: ScenarioLoadingError.noTestingPumpManagerEnabled)
return
}
}
}

instance.deviceActions.forEach { [testingCGMManager, testingPumpManager] action in
if testingCGMManager?.pluginIdentifier == action.managerIdentifier {

testingPumpManager?.reservoirFillFraction = 1.0
testingPumpManager?.injectPumpEvents(instance.pumpEvents)
testingCGMManager?.injectGlucoseSamples(instance.pastGlucoseSamples, futureSamples: instance.futureGlucoseSamples)

self?.activeScenario = scenario

instance.deviceActions.forEach { [testingCGMManager, testingPumpManager] action in
testingCGMManager?.trigger(action: action)
} else if testingPumpManager?.pluginIdentifier == action.managerIdentifier {
testingPumpManager?.trigger(action: action)
}

completion(nil)
} catch {
bail(with: error)
}
}
}
@@ -343,32 +344,21 @@ extension TestingScenariosManager {
}
}

private func wipeExistingData(completion: @escaping (Error?) -> Void) {
private func wipeExistingData() async throws {
guard FeatureFlags.scenariosEnabled else {
fatalError("\(#function) should be invoked only when scenarios are enabled")
}

deviceManager.deleteTestingPumpData { error in
guard error == nil else {
completion(error!)
return
}

self.deviceManager.deleteTestingCGMData { error in
guard error == nil else {
completion(error!)
return
}

self.carbStore.deleteAllCarbEntries() { error in
guard error == nil else {
completion(error!)
return
}

self.deviceManager.alertManager.alertStore.purge(before: Date(), completion: completion)
}
}
try await deviceManager.deleteTestingPumpData()

try await deviceManager.deleteTestingCGMData()

try await carbStore.deleteAllCarbEntries()

await withCheckedContinuation { [weak alertStore = deviceManager.alertManager.alertStore] continuation in
alertStore?.purge(before: Date(), completion: { _ in
continuation.resume()
})
}
}
}
@@ -377,13 +367,17 @@ extension TestingScenariosManager {
private extension CarbStore {

/// Errors if getting carb entries errors, or if deleting any individual entry errors.
func deleteAllCarbEntries(completion: @escaping (Error?) -> Void) {
getCarbEntries() { result in
switch result {
case .success(let entries):
self.deleteCarbEntries(entries[...], completion: completion)
case .failure(let error):
completion(error)
func deleteAllCarbEntries() async throws {
try await withCheckedThrowingContinuation { continuation in
getCarbEntries() { result in
switch result {
case .success(let entries):
self.deleteCarbEntries(entries[...], completion: { _ in
continuation.resume()
})
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
14 changes: 8 additions & 6 deletions Loop/Managers/WatchDataManager.swift
Original file line number Diff line number Diff line change
@@ -205,12 +205,14 @@ final class WatchDataManager: NSObject {
return
}

log.default("*** sendWatchContextIfNeeded")

guard case .activated = session.activationState else {
session.activate()
return
}

Task { @MainActor in
Task {
let context = await createWatchContext()
self.sendWatchContext(context)
}
@@ -464,13 +466,13 @@ extension WatchDataManager: WCSessionDelegate {
}
case GlucoseBackfillRequestUserInfo.name?:
if let userInfo = GlucoseBackfillRequestUserInfo(rawValue: message) {
glucoseStore.getSyncGlucoseSamples(start: userInfo.startDate.addingTimeInterval(1)) { (result) in
switch result {
case .failure(let error):
Task {
do {
let samples = try await glucoseStore.getSyncGlucoseSamples(start: userInfo.startDate.addingTimeInterval(1))
replyHandler(WatchHistoricalGlucose(samples: samples).rawValue)
} catch {
self.log.error("Failure getting sync glucose objects: %{public}@", String(describing: error))
replyHandler([:])
case .success(let samples):
replyHandler(WatchHistoricalGlucose(samples: samples).rawValue)
}
}
} else {
2 changes: 2 additions & 0 deletions Loop/Models/StoredDataAlgorithmInput.swift
Original file line number Diff line number Diff line change
@@ -51,4 +51,6 @@ struct StoredDataAlgorithmInput: AlgorithmInput {
var recommendationType: DoseRecommendationType

var automaticBolusApplicationFactor: Double?

let useMidAbsorptionISF: Bool = true
}
67 changes: 35 additions & 32 deletions Loop/View Controllers/InsulinDeliveryTableViewController.swift
Original file line number Diff line number Diff line change
@@ -201,7 +201,7 @@ public final class InsulinDeliveryTableViewController: UITableViewController {
case manualEntryDoses([DoseEntry])
}

private enum HistorySection: Int {
fileprivate enum HistorySection: Int {
case today
case yesterday
}
@@ -401,7 +401,7 @@ public final class InsulinDeliveryTableViewController: UITableViewController {
return 0
case .display:
switch self.values {
case .history(let values): return values.valuesBeforeToday.isEmpty ? 1 : 2
case .history(let pumpEvents): return pumpEvents.pumpEventsBeforeToday.isEmpty ? 1 : 2
default: return 1
}
}
@@ -411,10 +411,10 @@ public final class InsulinDeliveryTableViewController: UITableViewController {
switch values {
case .reservoir(let values):
return values.count
case .history(let values):
case .history(let pumpEvents):
switch HistorySection(rawValue: section) {
case .today: return values.valuesFromToday.count
case .yesterday: return values.valuesBeforeToday.count
case .today: return pumpEvents.pumpEventsFromToday.count
case .yesterday: return pumpEvents.pumpEventsBeforeToday.count
case .none: return 0
}
case .manualEntryDoses(let values):
@@ -426,13 +426,13 @@ public final class InsulinDeliveryTableViewController: UITableViewController {
switch state {
case .display:
switch self.values {
case .history(let values):
case .history(let pumpEvents):
switch HistorySection(rawValue: section) {
case .today:
guard let firstValue = values.valuesFromToday.first else { return nil }
guard let firstValue = pumpEvents.pumpEventsFromToday.first else { return nil }
return dateFormatter.string(from: firstValue.date).uppercased()
case .yesterday:
guard let firstValue = values.valuesBeforeToday.first else { return nil }
guard let firstValue = pumpEvents.pumpEventsBeforeToday.first else { return nil }
return dateFormatter.string(from: firstValue.date).uppercased()
case .none: return nil
}
@@ -457,24 +457,18 @@ public final class InsulinDeliveryTableViewController: UITableViewController {
cell.detailTextLabel?.text = time
cell.accessoryType = .none
cell.selectionStyle = .none
case .history(let values):
let filterValues: [PersistedPumpEvent]
if HistorySection(rawValue: indexPath.section) == .today {
filterValues = values.valuesFromToday
} else {
filterValues = values.valuesBeforeToday
}
let entry = filterValues[indexPath.row]
let time = timeFormatter.string(from: entry.date)
case .history(let pumpEvents):
let pumpEvent = pumpEvents.pumpEventForIndexPath(indexPath)
let time = timeFormatter.string(from: pumpEvent.date)

if let attributedText = entry.localizedAttributedDescription {
if let attributedText = pumpEvent.localizedAttributedDescription {
cell.textLabel?.attributedText = attributedText
} else {
cell.textLabel?.text = NSLocalizedString("Unknown", comment: "The default description to use when an entry has no dose description")
}

cell.detailTextLabel?.text = time
cell.accessoryType = entry.isUploaded ? .checkmark : .none
cell.accessoryType = pumpEvent.isUploaded ? .checkmark : .none
cell.selectionStyle = .default
case .manualEntryDoses(let values):
let entry = values[indexPath.row]
@@ -517,14 +511,13 @@ public final class InsulinDeliveryTableViewController: UITableViewController {
}
}
}
case .history(let historyValues):
var historyValues = historyValues
let value = historyValues.remove(at: indexPath.row)
self.values = .history(historyValues)
case .history(let pumpEvents):
let pumpEvent = pumpEvents.pumpEventForIndexPath(indexPath)
self.values = .history(pumpEvents.filter { $0.dose != pumpEvent.dose })

tableView.deleteRows(at: [indexPath], with: .automatic)

doseStore?.deletePumpEvent(value) { (error) -> Void in
doseStore?.deletePumpEvent(pumpEvent) { (error) -> Void in
if let error = error {
DispatchQueue.main.async {
self.present(UIAlertController(with: error), animated: true)
@@ -555,23 +548,23 @@ public final class InsulinDeliveryTableViewController: UITableViewController {
}

public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if case .display = state, case .history(let history) = values {
let entry = history[indexPath.row]
if case .display = state, case .history(let pumpEvents) = values {
let pumpEvent = pumpEvents.pumpEventForIndexPath(indexPath)

let vc = CommandResponseViewController(command: { (completionHandler) -> String in
var description = [String]()

description.append(self.timeFormatter.string(from: entry.date))
description.append(self.timeFormatter.string(from: pumpEvent.date))

if let title = entry.title {
if let title = pumpEvent.title {
description.append(title)
}

if let dose = entry.dose {
if let dose = pumpEvent.dose {
description.append(String(describing: dose))
}

if let raw = entry.raw {
if let raw = pumpEvent.raw {
description.append(raw.hexadecimalString)
}

@@ -688,13 +681,23 @@ extension PersistedPumpEvent {
extension InsulinDeliveryTableViewController: IdentifiableClass { }

fileprivate extension Array where Element == PersistedPumpEvent {
var valuesFromToday: [PersistedPumpEvent] {
var pumpEventsFromToday: [PersistedPumpEvent] {
let startOfDay = Calendar.current.startOfDay(for: Date())
return self.filter({ $0.date >= startOfDay})
}

var valuesBeforeToday: [PersistedPumpEvent] {
var pumpEventsBeforeToday: [PersistedPumpEvent] {
let startOfDay = Calendar.current.startOfDay(for: Date())
return self.filter({ $0.date < startOfDay})
}

func pumpEventForIndexPath(_ indexPath: IndexPath) -> PersistedPumpEvent {
let filterPumpEvents: [PersistedPumpEvent]
if InsulinDeliveryTableViewController.HistorySection(rawValue: indexPath.section) == .today {
filterPumpEvents = self.pumpEventsFromToday
} else {
filterPumpEvents = self.pumpEventsBeforeToday
}
return filterPumpEvents[indexPath.row]
}
}
8 changes: 4 additions & 4 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
@@ -1599,13 +1599,13 @@ final class StatusTableViewController: LoopChartsTableViewController {
private func presentSettings() {
let deletePumpDataFunc: () -> PumpManagerViewModel.DeleteTestingDataFunc? = { [weak self] in
(self?.deviceManager.pumpManager is TestingPumpManager) ? {
[weak self] in self?.deviceManager.deleteTestingPumpData()
} : nil
Task { [weak self] in try? await self?.deviceManager.deleteTestingPumpData()
}} : nil
}
let deleteCGMDataFunc: () -> CGMManagerViewModel.DeleteTestingDataFunc? = { [weak self] in
(self?.deviceManager.cgmManager is TestingCGMManager) ? {
[weak self] in self?.deviceManager.deleteTestingCGMData()
} : nil
Task { [weak self] in try? await self?.deviceManager.deleteTestingCGMData()
}} : nil
}
let pumpViewModel = PumpManagerViewModel(
image: { [weak self] in (self?.deviceManager.pumpManager as? PumpManagerUI)?.smallImage },
36 changes: 22 additions & 14 deletions LoopTests/Managers/CGMStalenessMonitorTests.swift
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ class CGMStalenessMonitorTests: XCTestCase {
XCTAssert(monitor.cgmDataIsStale)
}

func testStalenessWithRecentCMGSample() {
func testStalenessWithRecentCMGSample() async throws {
let monitor = CGMStalenessMonitor()
fetchExpectation = expectation(description: "Fetch latest cgm glucose")
latestCGMGlucose = storedGlucoseSample
@@ -46,13 +46,16 @@ class CGMStalenessMonitorTests: XCTestCase {
}

monitor.delegate = self
waitForExpectations(timeout: 2)


await monitor.checkCGMStaleness()

await fulfillment(of: [fetchExpectation!, exp], timeout: 2)

XCTAssertNotNil(cancelable)
XCTAssertEqual(receivedValues, [true, false])
}

func testStalenessWithNoRecentCGMData() {
func testStalenessWithNoRecentCGMData() async throws {
let monitor = CGMStalenessMonitor()
fetchExpectation = expectation(description: "Fetch latest cgm glucose")
latestCGMGlucose = nil
@@ -68,13 +71,16 @@ class CGMStalenessMonitorTests: XCTestCase {
}

monitor.delegate = self
waitForExpectations(timeout: 2)


await monitor.checkCGMStaleness()

await fulfillment(of: [fetchExpectation!, exp], timeout: 2)

XCTAssertNotNil(cancelable)
XCTAssertEqual(receivedValues, [true, true])
}

func testStalenessNewReadingsArriving() {
func testStalenessNewReadingsArriving() async throws {
let monitor = CGMStalenessMonitor()
fetchExpectation = expectation(description: "Fetch latest cgm glucose")
latestCGMGlucose = nil
@@ -90,19 +96,21 @@ class CGMStalenessMonitorTests: XCTestCase {
}

monitor.delegate = self


await monitor.checkCGMStaleness()

monitor.cgmGlucoseSamplesAvailable([newGlucoseSample])
waitForExpectations(timeout: 2)

await fulfillment(of: [fetchExpectation!, exp], timeout: 2)

XCTAssertNotNil(cancelable)
XCTAssertEqual(receivedValues, [true, false])
XCTAssertEqual(receivedValues, [true, true, false])
}
}

extension CGMStalenessMonitorTests: CGMStalenessMonitorDelegate {
func getLatestCGMGlucose(since: Date, completion: @escaping (Result<StoredGlucoseSample?, Error>) -> Void) {
completion(.success(latestCGMGlucose))
public func getLatestCGMGlucose(since: Date) async throws -> StoredGlucoseSample? {
fetchExpectation?.fulfill()
return latestCGMGlucose
}
}
7 changes: 3 additions & 4 deletions LoopTests/Managers/DeviceDataManagerTests.swift
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ final class DeviceDataManagerTests: XCTestCase {
}
}

override func setUpWithError() throws {
override func setUp() async throws {
let mockUserNotificationCenter = MockUserNotificationCenter()
let mockBluetoothProvider = MockBluetoothProvider()
let alertPresenter = MockPresenter()
@@ -56,7 +56,7 @@ final class DeviceDataManagerTests: XCTestCase {
cacheLength: .days(1)
)

let doseStore = DoseStore(
let doseStore = await DoseStore(
cacheStore: persistenceController
)

@@ -72,8 +72,7 @@ final class DeviceDataManagerTests: XCTestCase {
}
let deviceLog = PersistentDeviceLog(storageFile: deviceLogDirectory.appendingPathComponent("Storage.sqlite"))


let glucoseStore = GlucoseStore(cacheStore: persistenceController)
let glucoseStore = await GlucoseStore(cacheStore: persistenceController)

let cgmEventStore = CgmEventStore(cacheStore: persistenceController)

3 changes: 1 addition & 2 deletions LoopTests/Managers/MealDetectionManagerTests.swift
Original file line number Diff line number Diff line change
@@ -234,8 +234,7 @@ class MealDetectionManagerTests: XCTestCase {
includePositiveVelocityAndRC: true,
carbAbsorptionModel: .piecewiseLinear,
recommendationInsulinModel: ExponentialInsulinModelPreset.rapidActingAdult.model,
recommendationType: .automaticBolus
)
recommendationType: .automaticBolus)

// These tests don't actually run the loop algorithm directly; they were written to take ICE from fixtures, compute carb effects, and subtract them.
let counteractionEffects = counteractionEffects(for: testType)
33 changes: 19 additions & 14 deletions WatchApp Extension/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ import LoopAlgorithm
class LoopDataManager {
let carbStore: CarbStore

let glucoseStore: GlucoseStore
var glucoseStore: GlucoseStore!

@PersistedProperty(key: "Settings")
private var rawWatchInfo: LoopSettingsUserInfo.RawValue?
@@ -69,16 +69,19 @@ class LoopDataManager {
cacheLength: .hours(24), // Require 24 hours to store recent carbs "since midnight" for CarbEntryListController
syncVersion: 0
)
glucoseStore = GlucoseStore(
cacheStore: cacheStore,
cacheLength: .hours(4)
)

self.watchInfo = LoopSettingsUserInfo(
loopSettings: LoopSettings(),
scheduleOverride: nil,
preMealOverride: nil
)

Task {
glucoseStore = await GlucoseStore(
cacheStore: cacheStore,
cacheLength: .hours(4)
)
}

if let rawWatchInfo = rawWatchInfo, let watchInfo = LoopSettingsUserInfo(rawValue: rawWatchInfo) {
self.watchInfo = watchInfo
@@ -96,7 +99,9 @@ extension LoopDataManager {

if activeContext == nil || context.shouldReplace(activeContext!) {
if let newGlucoseSample = context.newGlucoseSample {
self.glucoseStore.addGlucoseSamples([newGlucoseSample]) { (_) in }
Task {
try? await self.glucoseStore.addGlucoseSamples([newGlucoseSample])
}
}
activeContext = context
}
@@ -153,8 +158,10 @@ extension LoopDataManager {
WCSession.default.sendGlucoseBackfillRequestMessage(userInfo) { (result) in
switch result {
case .success(let context):
self.glucoseStore.setSyncGlucoseSamples(context.samples) { (error) in
if let error = error {
Task {
do {
try await self.glucoseStore.setSyncGlucoseSamples(context.samples)
} catch {
self.log.error("Failure setting sync glucose samples: %{public}@", String(describing: error))
}
}
@@ -198,14 +205,12 @@ extension LoopDataManager {
return
}

glucoseStore.getGlucoseSamples(start: .earliestGlucoseCutoff) { result in
Task {
var historicalGlucose: [StoredGlucoseSample]?
switch result {
case .failure(let error):
do {
historicalGlucose = try await glucoseStore.getGlucoseSamples(start: .earliestGlucoseCutoff)
} catch {
self.log.error("Failure getting glucose samples: %{public}@", String(describing: error))
historicalGlucose = nil
case .success(let samples):
historicalGlucose = samples
}
let chartData = GlucoseChartData(
unit: activeContext.displayGlucoseUnit,