Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
286 commits
Select commit Hold shift + click to select a range
c81911c
fix: dismiss modal (Settings sheet) before tab switch on LA tap
MtlPhil Mar 14, 2026
9ccc806
fix: LA tap navigation timing and LA reappear-after-dismiss
MtlPhil Mar 15, 2026
31a8e97
fix: handle loopfollow://la-tap URL in SceneDelegate, not AppDelegate
MtlPhil Mar 15, 2026
26b244e
Live Activity — UX Improvements and Reliability Fixes (#540)
MtlPhil Mar 15, 2026
ad647e5
feat: configurable LA grid slots + full InfoType snapshot coverage
MtlPhil Mar 15, 2026
0401c48
fix: label delta and footer on lock screen LA card
MtlPhil Mar 15, 2026
f42e502
docs: add PR description for configurable LA grid slots
MtlPhil Mar 15, 2026
b8c19cf
Update PR_configurable_slots.md
MtlPhil Mar 15, 2026
9b36dab
Merge dev into live-activity; replace CryptoSwift with CryptoKit
bjorkert Mar 15, 2026
b925d8a
Merge upstream/live-activity: resolve conflicts, keep extended InfoTy…
MtlPhil Mar 15, 2026
c7c9a59
Merge remote-tracking branch 'origin/live-activity' into live-activity
MtlPhil Mar 15, 2026
b571cad
chore: remove PR notes from tracking, keep docs/LiveActivity.md only
MtlPhil Mar 15, 2026
fec3f79
Configurable Live Activity Grid Slots + Full InfoType Snapshot Covera…
MtlPhil Mar 15, 2026
83ba7c5
Linting
bjorkert Mar 15, 2026
a20f3ec
Fix PRODUCT_BUNDLE_IDENTIFIER for Tests
bjorkert Mar 15, 2026
145744c
fix: include all extended InfoType fields in APNs push payload
MtlPhil Mar 15, 2026
191a1e4
Merge upstream/live-activity: apply linting fixes
MtlPhil Mar 15, 2026
dfe53b3
feat: add small family view for CarPlay Dashboard and Watch Smart Stack
MtlPhil Mar 16, 2026
2f28a1f
fix: include all extended InfoType fields in APNs push payload (#548)
MtlPhil Mar 16, 2026
a98f0a8
fix: guard CarPlay/Watch small family behind iOS 18 availability; inc…
MtlPhil Mar 16, 2026
65e679a
fix: move if #available into Widget.body to avoid WidgetBundleBuilder…
MtlPhil Mar 16, 2026
82e76a4
fix: use two separate single-branch if #available in bundle for CarPl…
MtlPhil Mar 16, 2026
17db9e9
merge: resolve conflicts with upstream/live-activity; keep renewal ov…
MtlPhil Mar 16, 2026
0183a9d
Live Activity: CarPlay Dashboard + Apple Watch Smart Stack support (#…
MtlPhil Mar 16, 2026
98de416
fix: restore two-widget bundle; guard supplementalActivityFamilies an…
MtlPhil Mar 16, 2026
e8dadda
fix: extension version inherits from parent; remove spurious await in…
MtlPhil Mar 16, 2026
9f9229a
Live Activity: fix iOS 18 availability guards, extension version, and…
MtlPhil Mar 16, 2026
83f4ad3
fix: prevent glucose + trend arrow clipping on wide mmol/L values
MtlPhil Mar 16, 2026
426fa3d
Live Activity: fix glucose + trend arrow clipping on wide mmol/L valu…
MtlPhil Mar 17, 2026
8b9fe86
Merge branch 'dev' into live-activity
bjorkert Mar 17, 2026
e20ec46
chore: remove redundant @available(iOS 16.1) guards
MtlPhil Mar 17, 2026
775b83d
Fix Live Activity glucose overflow with flexible layout and tighter g…
MtlPhil Mar 17, 2026
37c1a71
chore: remove redundant @available(iOS 16.1) guards
MtlPhil Mar 17, 2026
d99e778
Fix Live Activity glucose overflow with flexible layout and tighter g…
MtlPhil Mar 17, 2026
68d2a06
fix: restart LA on foreground when renewal overlay is showing
MtlPhil Mar 17, 2026
749264b
fix: recover from audio session failure and alert user via LA overlay
MtlPhil Mar 17, 2026
3769275
Update BackgroundTaskAudio.swift
MtlPhil Mar 18, 2026
27a6efc
Live Activity: foreground restart on overlay, audio session recovery,…
MtlPhil Mar 18, 2026
a26894d
Update BackgroundTaskAudio.swift
MtlPhil Mar 18, 2026
e8ee805
Update BackgroundTaskAudio.swift
MtlPhil Mar 18, 2026
cffc043
Update LiveActivityManager.swift
MtlPhil Mar 18, 2026
61a6035
Update LiveActivityManager.swift
MtlPhil Mar 18, 2026
d4f5c8c
Merge branch 'pr-555' into live-activity
bjorkert Mar 18, 2026
adbec89
Linting
bjorkert Mar 18, 2026
f677b2c
Removed CLAUDE.md
bjorkert Mar 18, 2026
9e16ba9
Merge branch 'dev' into live-activity
bjorkert Mar 19, 2026
01e2c1b
Removed duplicate code
bjorkert Mar 19, 2026
b3f2436
Live activity - final fixes (#557)
MtlPhil Mar 20, 2026
fa26039
Update to 5.0
bjorkert Mar 20, 2026
db5ddbf
Remove unnecessary @available(iOS 16.4) checks
bjorkert Mar 21, 2026
a62595c
BGAppRefreshTask audio recovery; LA expiry notification; code quality…
MtlPhil Mar 22, 2026
84e1736
Live Activity: CarPlay/Watch Smart Stack widget + BFU crash fix + BGA…
MtlPhil Mar 24, 2026
2576dca
Linting
bjorkert Mar 24, 2026
2a5c1ef
Fix JWT cache thread-safety to prevent TooManyProviderTokenUpdates
bjorkert Mar 24, 2026
39c4ae7
Apple Watch app: complications, data grid, background delivery
MtlPhil Apr 9, 2026
0730113
Merge branch 'dev' into apple-watch
bjorkert Apr 9, 2026
002df85
SwiftFormat
bjorkert Apr 9, 2026
c40be2f
Fix viewUpdateNSBG: re-declare lastBGTime local
bjorkert Apr 9, 2026
88816f7
Merge remote-tracking branch 'origin/dev' into apple-watch
bjorkert Apr 9, 2026
cb286c1
Watch PR review fixes (#598)
bjorkert Apr 10, 2026
c4cddf7
Merge branch 'dev' into apple-watch
bjorkert Apr 26, 2026
024e0ee
Merge remote-tracking branch 'origin/dev' into apple-watch
bjorkert Apr 26, 2026
b4f5ec4
Restore custom app icon
kristinkand Jul 22, 2026
3def534
Show active override and temp target with remaining time on the Live …
bjorkert Jul 23, 2026
ab74b97
Treat Trio's 30 day durations as indefinite and light up the remote c…
bjorkert Jul 23, 2026
dec054e
Draw temp targets at the BG level they target instead of in the overr…
bjorkert Jul 23, 2026
2cb74d8
Add an early warning option to the temp target and override end alarms
bjorkert Jul 23, 2026
2ea1151
Merge pull request #37 from loopandlearn/integration/dev-plus-prs
kristinkand Jul 23, 2026
fccd1d7
Use Trio's colors for the forecast curves
bjorkert Jul 24, 2026
b8ba4b5
Merge pull request #38 from loopandlearn/integration/dev-plus-prs
kristinkand Jul 24, 2026
ba439bb
Clear stored predBGs when clearing the OpenAPS forecast
bjorkert Jul 24, 2026
8af5ee5
Merge pull request #39 from loopandlearn/integration/dev-plus-prs
kristinkand Jul 25, 2026
f1304c6
Fix Advanced Settings graph toggles not hiding chart elements
bjorkert Jul 26, 2026
621cf40
Merge pull request #40 from loopandlearn/integration/dev-plus-prs
kristinkand Jul 28, 2026
e8671ef
Merge remote-tracking branch 'upstream/dev' into Integration
kristinkand Jul 28, 2026
2e358a0
Look ahead the full predictive low window
JustMaier Jul 28, 2026
7c05ffe
Fix main graph locking up after system-cancelled gestures
bjorkert Jul 29, 2026
94eb076
Create the Live Activity locally when the app is in the foreground
bjorkert Jul 29, 2026
f618e67
Create the Live Activity locally when the app is in the foreground
bjorkert Jul 29, 2026
20156da
Merge pull request #41 from loopandlearn/integration/dev-plus-prs
kristinkand Jul 29, 2026
9757558
Merge pull request #42 from loopandlearn/dev
kristinkand Jul 31, 2026
b584a72
Add Trio dynamic colors
kristinkand Jul 31, 2026
b711c74
Update LA dynamic colors
kristinkand Jul 31, 2026
93f9d96
Update DynamicGlucoseColor.swift
kristinkand Jul 31, 2026
c3b94d5
debug
kristinkand Jul 31, 2026
47be145
update LA dynamic colors
kristinkand Jul 31, 2026
78fdb73
LA graph
kristinkand Jul 31, 2026
590ddd0
Update LoopFollowLiveActivity.swift
kristinkand Jul 31, 2026
d3feadb
Update LoopFollowLiveActivity.swift
kristinkand Jul 31, 2026
c394c4c
Update LoopFollowLiveActivity.swift
kristinkand Jul 31, 2026
82ece29
Update LoopFollowLiveActivity.swift
kristinkand Jul 31, 2026
c92df56
Update LoopFollowLiveActivity.swift
kristinkand Jul 31, 2026
b4fb80a
Update LoopFollowLiveActivity.swift
kristinkand Jul 31, 2026
6c7e653
Update LoopFollowLiveActivity.swift
kristinkand Aug 1, 2026
798f2e8
Update LoopFollowLiveActivity.swift
kristinkand Aug 1, 2026
a600e2d
Update LoopFollowLiveActivity.swift
kristinkand Aug 1, 2026
078fe1b
Update LoopFollowLiveActivity.swift
kristinkand Aug 1, 2026
c78a660
Thinner graph line
kristinkand Aug 1, 2026
7bf2975
Trying to fix LA rendering
kristinkand Aug 1, 2026
3c83684
Merge loopandlearn/apple-watch: restore Watch app target
kristinkand Aug 1, 2026
18c0ec9
Merge loopandlearn/apple-watch: restore Watch app target
kristinkand Aug 1, 2026
4d6c5bf
Merge pull request #43 from kristinkand/feat/trio-dynamic-colors
kristinkand Aug 4, 2026
2a5f864
Share the horizon arithmetic and harden the look-ahead tests
bjorkert Aug 26, 2026
44d0b60
Show Trio Weekend Profile distinctly in the Treatments overview
claude Sep 2, 2026
a1c8d6f
Merge branch 'Integration' of https://github.com/kristinkand/LoopFoll…
kristinkand Sep 2, 2026
45bcdd9
Merge pull request #48 from loopandlearn/dev
kristinkand Sep 2, 2026
fe5586d
Merge pull request #49 from loopandlearn/fix/pr-732-followups
kristinkand Sep 6, 2026
afe96fe
Merge pull request #45 from loopandlearn/feature/la-local-creation
kristinkand Sep 10, 2026
b829486
Add remote start/stop for Trio's Weekend Profile
claude Sep 17, 2026
a80fbf2
Rename Weekend Profile's displayed name to Profile; show timestamp fi…
claude Sep 17, 2026
0f67fd5
Rename Weekend Profile to Profile in the Treatments overview
claude Sep 17, 2026
bdcf57d
Snap chart scrubbing to a five-minute grid locked to the readings
bjorkert Sep 17, 2026
534e414
Color the Profile band mint on the BG graph
kristinkand Sep 17, 2026
520eb65
Graph pill: show timestamp first for Bolus, Carbs, Suspend, Resume too
kristinkand Sep 17, 2026
37e1672
Merge remote-tracking branch 'origin/dev' into pr732-merge-dev
bjorkert Sep 18, 2026
4a74b78
Merge pull request #754 from loopandlearn/release/v7.1.0
marionbarker Sep 20, 2026
df08c7e
Merge remote-tracking branch 'upstream/dev' into Integration
kristinkand Sep 21, 2026
8335a94
Add target schedule line data builder
kristinkand Sep 23, 2026
b90e232
Add targetGraphStruct for NS target data
kristinkand Sep 23, 2026
56b8aa7
Add targetProfileSegment struct for Codable compliance
kristinkand Sep 23, 2026
d5949ac
Add targetScheduleData variable to MainViewController
kristinkand Sep 23, 2026
2a3fea0
Add updateTargetScheduledGraph method
kristinkand Sep 23, 2026
b0d2663
Fix indentation for graphBasal check in Profile.swift
kristinkand Sep 23, 2026
68318cf
Add graphTargetLine storage value and reload method
kristinkand Sep 23, 2026
e4287ef
Add graphTargetLine property to AdvancedSettingsViewModel
kristinkand Sep 23, 2026
e6393ea
Add toggle for Graph Target Line in settings
kristinkand Sep 23, 2026
b6d114e
Add ScheduledTargetPoint struct and targetScheduled array
kristinkand Sep 23, 2026
347d789
Add scheduled target marks to BGChartView
kristinkand Sep 23, 2026
1050d01
Merge pull request #51 from loopandlearn/fix/predictive-lookahead-hor…
kristinkand Sep 23, 2026
e7a2796
Merge pull request #52 from loopandlearn/fix/predictive-lookahead-hor…
kristinkand Sep 23, 2026
954827f
Merge remote-tracking branch 'origin/test/target-line' into Integration
kristinkand Sep 26, 2026
e428e27
Add ability to double-click carbs to enter Treatments screen
codebymini Sep 26, 2026
02dd910
Keep the carb detail sheet across remounts and share carb treatment c…
bjorkert Sep 26, 2026
62a3919
Merge branch 'pr-757' into test/remote-deletion
kristinkand Sep 26, 2026
4383f56
Fix stray Terminal command text accidentally left in file headers
kristinkand Sep 26, 2026
2d9a42c
Show CGM sensor states from Nightscout notes
bjorkert Sep 27, 2026
e7f98a1
Merge pull request #53 from loopandlearn/main
kristinkand Sep 29, 2026
b12bcb4
Merge pull request #54 from kristinkand/test/remote-deletion
kristinkand Sep 30, 2026
df3b9ed
Add home screen BG widget
kristinkand Sep 30, 2026
02d7c4f
Add device status history graphs and scrubbed-time values
bjorkert Sep 30, 2026
40dd65b
Merge branch 'feature/devicestatus-history' of https://github.com/loo…
kristinkand Sep 30, 2026
eb97790
Add Time in Range band
kristinkand Sep 30, 2026
e1f5240
Merge remote-tracking branch 'origin/home-screen-widget' into test/ti…
kristinkand Sep 30, 2026
39bf788
Widget adjustments, TING 3.5-10, stats style setting
kristinkand Oct 1, 2026
6e2e516
Include graph history in Live Activity push updates
kristinkand Oct 1, 2026
9bfd36d
Show IOB in widget
kristinkand Oct 1, 2026
e5c3e93
Widget, TIR band, device status history, LA graph fix
kristinkand Oct 1, 2026
9db0517
Silent Tune fallback when Bluetooth heartbeat is missing
kristinkand Oct 1, 2026
58786c7
Redraw widget when the app opens or closes
kristinkand Oct 1, 2026
118387d
Remove the scrub pill and the main-graph legend
bjorkert Oct 1, 2026
d605cdf
Merge branch 'feature/devicestatus-history' of https://github.com/loo…
kristinkand Oct 1, 2026
db6db21
Merge branch 'feature/devicestatus-history' of https://github.com/loo…
kristinkand Oct 1, 2026
a03a200
Fix chart merge with removed scrub legend
kristinkand Oct 1, 2026
663ba6c
Update BGChartView.swift
kristinkand Oct 1, 2026
e2982b0
Fade the jump to now button back in after scrubbing
bjorkert Oct 2, 2026
997dbbf
Hide the jump to now button while panning and zooming
bjorkert Oct 2, 2026
370974b
Merge pull request #56 from loopandlearn/feature/devicestatus-history
kristinkand Oct 6, 2026
a35ee5b
Merge branch 'feature/cgm-sensor-state' of https://github.com/loopand…
kristinkand Oct 6, 2026
a52628e
Use the selected Range Mode for Live Activity colors
kristinkand Oct 6, 2026
a64c585
Use 3.5–7.8 mmol/L for TING
kristinkand Oct 6, 2026
b444a2b
Use 3.5–7.8 mmol/L for TING
kristinkand Oct 6, 2026
f74b2a9
Update Overrides.swift
kristinkand Oct 8, 2026
7a79f5d
Update Overrides.swift
kristinkand Oct 8, 2026
5dfa159
TIR band fixes from Jonas (PR 57)
kristinkand Oct 10, 2026
ecc7d9a
TIR band fixes from Jonas, keep dynamic colours
kristinkand Oct 10, 2026
3ef94e9
Merge remote-tracking branch 'origin/test/ble-fallback' into Integration
kristinkand Oct 10, 2026
80dc23b
Merge branch 'dev' of https://github.com/loopandlearn/LoopFollow into…
kristinkand Oct 10, 2026
b858e4e
Fix SwiftFormat lint issues
kristinkand Oct 10, 2026
d2c176f
Use the same period for statistics box and TIR band (last 24h or sinc…
kristinkand Oct 10, 2026
88c7847
Custom range fixes and shared range title from Jonas (PR 58)
kristinkand Oct 10, 2026
4c6562b
Merge pull request #59 from loopandlearn/dev
kristinkand Oct 14, 2026
bbd80ad
Add upstream fixes #769 #771 #734 #689 #775
kristinkand Oct 14, 2026
094b754
Merge loopandlearn/LoopFollow dev into Integration
kristinkand Oct 17, 2026
6f781e7
Remove the MtlPhil Apple Watch app, to make room for loopandlearn#785
kristinkand Oct 19, 2026
081b37c
Add an Apple Watch app fed by the phone, with remote commands for Loo…
bjorkert Oct 19, 2026
1c597a4
Put the watch Remote page before Info, allow watch commands by defaul…
bjorkert Oct 19, 2026
eb60c3e
Make the watch app glanceable: one large BG, actions in the bottom to…
bjorkert Oct 19, 2026
bbd7b30
Point browser builders to the docs when the watch identifiers are not…
bjorkert Oct 19, 2026
b8eff63
Keep watch Info values clear of the page indicator and widen the Meal…
bjorkert Oct 19, 2026
575f978
Check that the watch identifiers exist before match, since its read-o…
bjorkert Oct 19, 2026
98b58a0
Mirror the phone's info table on the watch: same rows, order and colo…
bjorkert Oct 19, 2026
630f8ba
Share the APNs, JWT, TRC encryption and Loop payload code between the…
bjorkert Oct 19, 2026
c2e1364
Send credentials to the watch when they change, and share command val…
bjorkert Oct 19, 2026
f5c3ec6
Let the watch fetch from Nightscout or Dexcom and send commands direc…
bjorkert Oct 19, 2026
b680f1e
Show the phone's statistics on the watch and mark data fetched by the…
bjorkert Oct 19, 2026
72043c1
Scrub the watch chart with the Digital Crown and pick the time span f…
bjorkert Oct 19, 2026
864ffef
Test watch command validation, signing key choice, devicestatus parsi…
bjorkert Oct 19, 2026
5741c9b
Schedule the watch background refresh once the app is running
bjorkert Oct 19, 2026
b0c7a3c
Deliver credentials to the watch by message as well as by transfer, a…
bjorkert Oct 19, 2026
4b9bea3
Convert Nightscout dates on the watch and give up waiting for the pho…
bjorkert Oct 19, 2026
1e5683d
Leave out watch chart hour labels too close to the edges to fit
bjorkert Oct 19, 2026
9f04c7f
Weekend Profile commands: use ReturnNotificationInfo.loopFollow()
kristinkand Oct 19, 2026
f283520
Show only the reading age on the watch glucose page, keep the toolbar…
bjorkert Oct 20, 2026
5a8112d
Let the crown scroll the watch meal screen until a field is tapped
bjorkert Oct 21, 2026
c97c1c1
Show Other Amount on the watch bolus list as plain text
bjorkert Oct 21, 2026
aa49903
Fit the watch statistics page on one screen on every watch size and w…
bjorkert Oct 21, 2026
fc71f11
Merge loopandlearn/LoopFollow dev (7.1.20)
kristinkand Oct 25, 2026
5c7c726
Merge Integration: loopandlearn/LoopFollow dev (7.1.20)
kristinkand Oct 25, 2026
d25a62f
Fix the build after the dev merge: one cgmSensorState, the override's…
kristinkand Oct 25, 2026
4c1536f
Fix the build after the dev merge: one cgmSensorState, the override's…
kristinkand Oct 25, 2026
860eaf5
Add LF Hub: follow a family's push server for badge, Live Activity, a…
bjorkert Oct 25, 2026
9409a55
Offer LF Hub as the Background Refresh mode right after connecting to…
bjorkert Oct 25, 2026
5669f96
Merge test/watch-785 into LF-hub
kristinkand Oct 25, 2026
161ef4a
Make LF Hub failures visible and recoverable: a removed phone leaves …
bjorkert Oct 26, 2026
1f36561
Merge remote-tracking branch 'origin/dev' into feature/lfhub
bjorkert Oct 27, 2026
f8ad6c4
Adopt a push-to-start Live Activity that a refresh already bound, kee…
bjorkert Oct 28, 2026
1345a6b
Clear the alarm title override when the hub stops its alarm
bjorkert Oct 28, 2026
26cc6f3
Let the watch fetch on its own while the hub drives the alarms
bjorkert Oct 28, 2026
b5698bb
Statistics: set up the calendar and basal profile once for all temp b…
kristinkand Oct 29, 2026
ce421fe
Remove the Silent Tune fallback for a missing Bluetooth heartbeat
kristinkand Oct 29, 2026
ffc99cc
Merge loopandlearn/LoopFollow dev (7.1.26) into Integration
kristinkand Oct 29, 2026
cd4dff3
Statistics: set up the calendar and basal profile once for all temp b…
kristinkand Oct 29, 2026
34e4fb3
Merge Integration (dev 7.1.26, no heartbeat fallback) into LF-hub
kristinkand Oct 29, 2026
6dad900
Merge loopandlearn/LoopFollow feature/lfhub (26cc6f3) into LF-hub
kristinkand Oct 29, 2026
3f12c4f
TestFlight note: the branch and commit the build came from
kristinkand Oct 29, 2026
6fe9768
TestFlight note: the branch and commit the build came from
kristinkand Oct 29, 2026
69fb333
Watch: a snooze page to snooze all alarms ahead, as the phone's Snooz…
kristinkand Oct 30, 2026
7151693
Watch: a snooze page to snooze all alarms ahead, as the phone's Snooz…
kristinkand Oct 30, 2026
06112d6
Watch snooze page: snooze until a chosen time
kristinkand Oct 30, 2026
5e82245
Watch snooze page: snooze until a chosen time
kristinkand Oct 30, 2026
f5046f2
Watch snooze page: set the end time with the Digital Crown
kristinkand Oct 30, 2026
bc4a0e8
Watch snooze page: set the end time with the Digital Crown
kristinkand Oct 30, 2026
0606e4a
Merge LF-hub into Integration
kristinkand Oct 31, 2026
bb38710
Trio remote: a bolus or meal also goes straight to Trio's watch app
Oct 31, 2026
683e8f9
Trio untethered: show when the watch had the pump, like an override
Oct 31, 2026
5db5217
Trio remote: overrides and temp targets also go straight to Trio's wa…
Oct 31, 2026
b92f97e
Trio remote: say so when Trio doesn't answer a bolus or meal
Oct 31, 2026
5489ca7
Show the LF Hub version next to the newest one for the hub's channel,…
bjorkert Nov 1, 2026
799386a
Show a server icon before the source name in LF Hub mode, blue while …
bjorkert Nov 1, 2026
802898e
Trio remote: the watch copy waits at Apple while the watch is offline
Nov 1, 2026
a7c944c
Merge remote-tracking branch 'origin/dev' into feature/lfhub
bjorkert Nov 1, 2026
dc70ee0
Apply the hub's badge while LoopFollow is open
kristinkand Nov 1, 2026
861c1a0
Merge branch 'Integration' into feature/trio-watch-remote
kristinkand Nov 1, 2026
0187fba
Apply the hub's badge push while LoopFollow is in the foreground
bjorkert Nov 1, 2026
2b44f8a
Add an app icon picker under General Settings with eight alternate ic…
bjorkert Nov 1, 2026
ea18bc0
Merge loopandlearn dev (7.1.31) into Integration
kristinkand Nov 1, 2026
39343b1
Merge branch 'Integration' into feature/trio-watch-remote
kristinkand Nov 1, 2026
5aed1fb
Handle snoozeAll in the watch's arrival check
bjorkert Nov 1, 2026
c9dbf21
Merge branch 'Integration' into feature/trio-watch-remote
kristinkand Nov 2, 2026
87e9d14
Merge remote-tracking branch 'origin/dev' into feature/lfhub
bjorkert Nov 2, 2026
0fd22da
Drop the removed Download Prediction setting from the hub client sett…
bjorkert Nov 2, 2026
5c0f999
Use the phone's custom app icon on the watch
kristinkand Nov 2, 2026
2814678
Show the picked app icon on the watch face
kristinkand Nov 2, 2026
3c1909d
Watch app icon: Midnight
kristinkand Nov 2, 2026
18a0b87
Tester build: the standard LoopFollow icon
kristinkand Nov 2, 2026
360141f
Merge the LF Hub branch (server icon, hub version reminder)
kristinkand Nov 2, 2026
56091c2
Merge trio-watch-remote into the tester branch
kristinkand Nov 2, 2026
570804b
Merge LoopFollow dev 8.0.0 and the LF Hub branch
kristinkand Nov 2, 2026
7f9cca4
Storage: drop the reload of the removed Download Prediction setting
kristinkand Nov 2, 2026
0174b8c
Merge trio-watch-remote into the tester branch
kristinkand Nov 2, 2026
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
98 changes: 98 additions & 0 deletions HubShared/BGAnnouncement.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// LoopFollow
// BGAnnouncement.swift

// Compiled into the app and into LoopFollowNotificationService: the app speaks it
// for Speak BG, the extension renders it for the hub's speech push.

import Foundation

/// The sentence Speak BG says for a new reading, in the chosen language.
enum BGAnnouncement {
struct Texts {
var stable: String
var increase: String
var decrease: String
var currentBGIs: String

static func forLanguage(_ language: String) -> Texts {
switch language {
case "fr":
return Texts(
stable: "et c'est stable",
increase: "et c'est monté de",
decrease: "et c'est descendu de",
currentBGIs: "La glycémie est"
)
case "it":
return Texts(
stable: "ed è stabile",
increase: "ed è salita di",
decrease: "ed è scesa di",
currentBGIs: "Glicemia attuale è"
)
case "sk":
return Texts(
stable: "a je stabilná",
increase: "a stúpla o",
decrease: "a klesla o",
currentBGIs: "Aktuálna glykémia je"
)
case "sv":
return Texts(
stable: "och det är stabilt",
increase: "och det har ökat med",
decrease: "och det har minskat med",
currentBGIs: "Blodsockret är"
)
case "en": fallthrough
default:
return Texts(
stable: "and it is stable",
increase: "and it is up",
decrease: "and it is down",
currentBGIs: "Glucose is"
)
}
}
}

static let voiceLanguageMap: [String: String] = [
"en": "en-US",
"fr": "fr-FR",
"it": "it-IT",
"sk": "sk-SK",
"sv": "sv-SE",
]

static func voiceLanguageCode(forAppLanguage appLanguage: String) -> String {
voiceLanguageMap[appLanguage, default: "en-US"]
}

/// A change of this many mg/dL or less is spoken as stable.
static let negligibleChange = 3

/// The announcement for `current` after `previous` (mg/dL), with values in the
/// display unit and a period as the decimal mark, which every voice reads as one.
static func text(current: Int, previous: Int, language: String, mmol: Bool,
locale: Locale = .current) -> String
{
let texts = Texts.forLanguage(language)
let difference = current - previous
let currentText = format(current, mmol: mmol, locale: locale)
if abs(difference) <= negligibleChange {
return "\(texts.currentBGIs) \(currentText) \(texts.stable)"
}
let direction = difference < 0 ? texts.decrease : texts.increase
return "\(texts.currentBGIs) \(currentText) \(direction) \(format(abs(difference), mmol: mmol, locale: locale))"
}

static func format(_ mgdl: Int, mmol: Bool, locale: Locale) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = locale
formatter.minimumFractionDigits = mmol ? 1 : 0
formatter.maximumFractionDigits = mmol ? 1 : 0
let value = mmol ? Double(mgdl) * GlucoseConversion.mgDlToMmolL : Double(mgdl)
return (formatter.string(from: NSNumber(value: value)) ?? String(value)).replacingOccurrences(of: ",", with: ".")
}
}
174 changes: 174 additions & 0 deletions HubShared/HubAlarmKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// LoopFollow
// HubAlarmKit.swift

// Compiled into the app and into LoopFollowNotificationService only: the extension
// rings hub alarms through it; the app stops them, handles their snooze intent and
// rings its own notices.

import Foundation

#if canImport(AlarmKit) && !targetEnvironment(macCatalyst)
import ActivityKit
import AlarmKit
import AppIntents
import struct SwiftUI.Color

/// Rings an alarm LF Hub delivered as an AlarmKit alarm, which breaks through
/// the silent switch and Focus. The notification service extension calls this
/// for every hub alarm push, so it rings even while LoopFollow is suspended
/// or not running. The AlarmKit alarm takes the LoopFollow alarm's id, so a
/// repeated push for an alarm that is still ringing does not ring twice.
@available(iOS 26, *)
enum HubAlarmKit {
/// A fixed alarm at or before now never fires, so it is set this far ahead.
private static let immediateFireDelay: TimeInterval = 2

static var isAuthorized: Bool {
AlarmKit.AlarmManager.shared.authorizationState == .authorized
}

/// The user has not been asked yet; once asked, only the Settings app changes it.
static var canAsk: Bool {
AlarmKit.AlarmManager.shared.authorizationState == .notDetermined
}

static func requestAuthorization() async -> Bool {
if AlarmKit.AlarmManager.shared.authorizationState == .notDetermined {
_ = try? await AlarmKit.AlarmManager.shared.requestAuthorization()
}
return isAuthorized
}

/// Whether the alarm is ringing or about to, when it starts if this call scheduled it,
/// and why. Not ringing when AlarmKit is not authorized or scheduling failed, so the
/// notification keeps its sound. Without `soundName` it rings with the default sound.
/// `symbol` is the SF Symbol on the alarm's button: the alarm type's icon. The button
/// reads Acknowledge for an alarm whose snooze is 0 (`acknowledge`), as in the app.
static func ring(alarmID: UUID, title: String, soundName: String?, symbol: String = "moon.zzz",
acknowledge: Bool = false) async -> (ringing: Bool, firesAt: Date?, detail: String)
{
// A denied state is final; notDetermined is what an extension reads even
// after the app was authorized, so scheduling is attempted then too.
let state = AlarmKit.AlarmManager.shared.authorizationState
guard state != .denied else { return (false, nil, "authorization denied") }
if ((try? AlarmKit.AlarmManager.shared.alarms) ?? []).contains(where: { $0.id == alarmID }) {
return (true, nil, "already scheduled")
}

let presentation = AlarmPresentation(
alert: AlarmPresentation.Alert(
title: LocalizedStringResource(stringLiteral: title),
stopButton: AlarmButton(
text: LocalizedStringResource(stringLiteral: buttonTitle(acknowledge: acknowledge)),
textColor: .white,
systemImageName: symbol
)
)
)
let firesAt = Date().addingTimeInterval(immediateFireDelay)
let configuration = AlarmKit.AlarmManager.AlarmConfiguration<HubAlarmMetadata>.alarm(
schedule: .fixed(firesAt),
attributes: AlarmAttributes(presentation: presentation, tintColor: .red),
stopIntent: SnoozeHubAlarmIntent(alarmID: alarmID),
sound: soundName.map { .named($0) } ?? .default
)
do {
let alarm = try await AlarmKit.AlarmManager.shared.schedule(id: alarmID, configuration: configuration)
return (true, firesAt, "scheduled, state \(alarm.state) (authorization read \(state))")
} catch {
return (false, nil, "schedule failed with authorization \(state): \(error)")
}
}

static func buttonTitle(acknowledge: Bool) -> String {
acknowledge ? "Acknowledge" : "Snooze"
}

/// Rings a notice from the app itself (not a LoopFollow alarm) as a system
/// alarm now, with an OK button and no snooze. Replaces a notice with the same id.
static func ringNotice(id: UUID, title: String, symbol: String) async -> Bool {
guard AlarmKit.AlarmManager.shared.authorizationState == .authorized else { return false }
stop(alarmID: id)
let presentation = AlarmPresentation(
alert: AlarmPresentation.Alert(
title: LocalizedStringResource(stringLiteral: title),
stopButton: AlarmButton(
text: LocalizedStringResource(stringLiteral: "OK"),
textColor: .white,
systemImageName: symbol
)
)
)
let configuration = AlarmKit.AlarmManager.AlarmConfiguration<HubAlarmMetadata>.alarm(
schedule: .fixed(Date().addingTimeInterval(immediateFireDelay)),
attributes: AlarmAttributes(presentation: presentation, tintColor: .orange),
sound: .default
)
return (try? await AlarmKit.AlarmManager.shared.schedule(id: id, configuration: configuration)) != nil
}

/// Whether the alarm is sounding now.
static func isAlerting(alarmID: UUID) -> Bool {
((try? AlarmKit.AlarmManager.shared.alarms) ?? []).contains { $0.id == alarmID && $0.state == .alerting }
}

static func stop(alarmID: UUID) {
try? AlarmKit.AlarmManager.shared.stop(id: alarmID)
try? AlarmKit.AlarmManager.shared.cancel(id: alarmID)
}
}

@available(iOS 26, *)
struct HubAlarmMetadata: AlarmMetadata {}

/// Runs in the app when the AlarmKit alarm's button is tapped, and snoozes
/// the LoopFollow alarm the way the notification's snooze action does.
@available(iOS 26, *)
struct SnoozeHubAlarmIntent: LiveActivityIntent {
static let title: LocalizedStringResource = "Snooze LoopFollow Alarm"
static let isDiscoverable = false

@Parameter(title: "Alarm ID") var alarmID: String

init() {}

init(alarmID: UUID) {
self.alarmID = alarmID.uuidString
}

func perform() async throws -> some IntentResult {
if let id = UUID(uuidString: alarmID) {
await HubAlarmSnoozeBridge.shared.snooze(id)
}
return .result()
}
}
#endif

/// Hands an AlarmKit snooze to the app's alarm handling. Taps that arrive
/// before the app registers its handler are queued until it does.
@MainActor
final class HubAlarmSnoozeBridge {
static let shared = HubAlarmSnoozeBridge()
private init() {}

private var handler: (@MainActor (UUID) async -> Void)?
private var pending: [UUID] = []

func setHandler(_ handler: @escaping @MainActor (UUID) async -> Void) {
self.handler = handler
let queued = pending
pending = []
Task { for id in queued {
await handler(id)
} }
}

func snooze(_ id: UUID) async {
if let handler {
await handler(id)
} else {
pending.append(id)
}
}
}
32 changes: 32 additions & 0 deletions HubShared/HubAppGroup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// LoopFollow
// HubAppGroup.swift

/// What the app shares with LoopFollowNotificationService through the App Group's
/// defaults. The device key is not shared: the extension's one hub call, the
/// delivery receipt, needs no key.
enum HubAppGroup {
/// The hub's address while the app is connected to a hub; absent otherwise.
static let hubURLKey = "lfhubURL"

/// The notification category of a hub alarm whose snooze is 0: its buttons read
/// Acknowledge, as the app's own action for such an alarm does.
static let acknowledgeCategory = "lfhub-ack"

/// How the extension delivers a push from the hub.
enum Delivery: Equatable {
/// As a system alarm, when the hub asks for one.
case systemAlarm
/// As the notification the hub sent, with its sound.
case notification
/// Quietly to the notification list, with no sound: this phone has left the hub,
/// so nothing it cannot silence rings, and the push is still kept.
case quiet
}

/// A push reaching a phone that is not connected to a hub (`hubURL` absent) is
/// delivered quietly.
static func delivery(hubURL: String?, systemAlarm: Bool) -> Delivery {
guard let hubURL, !hubURL.isEmpty else { return .quiet }
return systemAlarm ? .systemAlarm : .notification
}
}
Loading