Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
63326d8
feat: Live Activity auto-renewal to work around 8-hour system limit
MtlPhil Mar 13, 2026
a020c8f
test: reduce LA renewal threshold to 20 min for testing
MtlPhil Mar 13, 2026
bae228d
feat: improve LA renewal robustness and stale indicator
MtlPhil Mar 13, 2026
2785502
feat: renewal warning overlay + restore 7.5h threshold
MtlPhil Mar 13, 2026
0250633
fix: overlay not appearing + foreground restart not working
MtlPhil Mar 13, 2026
4e48c45
test: set renewalThreshold to 20 min for testing
MtlPhil Mar 13, 2026
136dba0
fix: renewal overlay not clearing after LA is refreshed
MtlPhil Mar 13, 2026
921a966
fix: overlay permanently active when warning window equals threshold
MtlPhil Mar 13, 2026
8989103
fix: include showRenewalOverlay in APNs payload and clear laRenewBy s…
MtlPhil Mar 13, 2026
1ab3930
fix: await LA end before restarting on foreground retry to avoid reus…
MtlPhil Mar 13, 2026
cdd4f85
chore: restore production renewal timing (7.5h threshold, 20min warning)
MtlPhil Mar 13, 2026
e0a729a
feat: laEnabled toggle, forceRestart(), and RestartLiveActivityIntent
MtlPhil Mar 14, 2026
7588c93
Added RestartLiveActivityIntent to project
MtlPhil Mar 14, 2026
0c21909
fix: resolve two build errors in LiveActivityManager and RestartLiveA…
MtlPhil Mar 14, 2026
9f5ddf2
fix: guard continueInForeground() behind iOS 26 availability check
MtlPhil Mar 14, 2026
c2e4c34
fix: use startFromCurrentState in handleDidBecomeActive instead of fo…
MtlPhil Mar 14, 2026
2869d24
feat: LA foreground tab navigation, button feedback, and toggle sync
MtlPhil Mar 14, 2026
3259dcb
fix: flush LA update on willResignActive to ensure lock screen shows …
MtlPhil Mar 14, 2026
54e3ed9
feat: redesign Dynamic Island compact and expanded views
MtlPhil Mar 14, 2026
6752fb2
fix: match Proj text style to delta; add trailing padding to IOB/COB
MtlPhil Mar 14, 2026
a3a37a0
feat: separate Live Activity and APN settings into distinct menus
MtlPhil Mar 14, 2026
6f43a2c
Added Live Activity menu
MtlPhil Mar 14, 2026
48ddc77
chore: add LiveActivitySettingsView to Xcode project
MtlPhil Mar 14, 2026
fc0bafd
merge: integrate upstream live-activity (Mac Catalyst guards + renewa…
MtlPhil Mar 14, 2026
5939ed9
fix: LA tap navigation, manual dismissal prevention, and toggle start
MtlPhil Mar 14, 2026
ef3f2f5
fix: end Live Activity on app force-quit
MtlPhil Mar 14, 2026
11aeadd
fix: use dismissedByUser flag instead of disabling laEnabled on manua…
MtlPhil Mar 14, 2026
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
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
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
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
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
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
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
4 changes: 2 additions & 2 deletions LoopFollow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2413,7 +2413,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = "$(MARKETING_VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = "com.$(unique_id).LoopFollow$(app_suffix).LoopFollowLAExtension";
PRODUCT_MODULE_NAME = LoopFollowLAExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -2465,7 +2465,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = "$(MARKETING_VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = "com.$(unique_id).LoopFollow$(app_suffix).LoopFollowLAExtension";
PRODUCT_MODULE_NAME = LoopFollowLAExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
4 changes: 1 addition & 3 deletions LoopFollow/LiveActivitySettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ struct LiveActivitySettingsView: View {
}
slots[index] = option
LAAppGroupSettings.setSlots(slots)
Task {
await LiveActivityManager.shared.refreshFromCurrentState(reason: "slot config changed")
}
LiveActivityManager.shared.refreshFromCurrentState(reason: "slot config changed")
}
}
3 changes: 3 additions & 0 deletions LoopFollowLAExtension/LoopFollowLABundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ struct LoopFollowLABundle: WidgetBundle {
if #available(iOS 16.1, *) {
LoopFollowLiveActivityWidget()
}
if #available(iOS 18.0, *) {
LoopFollowLiveActivityWidgetWithCarPlay()
}
}
}
102 changes: 62 additions & 40 deletions LoopFollowLAExtension/LoopFollowLiveActivity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,71 @@ import ActivityKit
import SwiftUI
import WidgetKit

/// Builds the shared Dynamic Island content used by both widget variants.
@available(iOS 16.1, *)
private func makeDynamicIsland(context: ActivityViewContext<GlucoseLiveActivityAttributes>) -> DynamicIsland {
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Link(destination: URL(string: "loopfollow://la-tap")!) {
DynamicIslandLeadingView(snapshot: context.state.snapshot)
.overlay(RenewalOverlayView(show: context.state.snapshot.showRenewalOverlay))
}
.id(context.state.seq)
}
DynamicIslandExpandedRegion(.trailing) {
Link(destination: URL(string: "loopfollow://la-tap")!) {
DynamicIslandTrailingView(snapshot: context.state.snapshot)
.overlay(RenewalOverlayView(show: context.state.snapshot.showRenewalOverlay))
}
.id(context.state.seq)
}
DynamicIslandExpandedRegion(.bottom) {
Link(destination: URL(string: "loopfollow://la-tap")!) {
DynamicIslandBottomView(snapshot: context.state.snapshot)
.overlay(RenewalOverlayView(show: context.state.snapshot.showRenewalOverlay, showText: true))
}
.id(context.state.seq)
}
} compactLeading: {
DynamicIslandCompactLeadingView(snapshot: context.state.snapshot)
.id(context.state.seq)
} compactTrailing: {
DynamicIslandCompactTrailingView(snapshot: context.state.snapshot)
.id(context.state.seq)
} minimal: {
DynamicIslandMinimalView(snapshot: context.state.snapshot)
.id(context.state.seq)
}
.keylineTint(LAColors.keyline(for: context.state.snapshot).opacity(0.75))
}

/// Primary widget (iOS 16.1+) — Lock Screen + Dynamic Island for all iOS versions.
@available(iOS 16.1, *)
struct LoopFollowLiveActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: GlucoseLiveActivityAttributes.self) { context in
// LOCK SCREEN / BANNER UI — also used for CarPlay Dashboard and Watch Smart Stack
// (small family) via supplementalActivityFamilies([.small])
LockScreenLiveActivityView(state: context.state)
.id(context.state.seq)
.activitySystemActionForegroundColor(.white)
.activityBackgroundTint(LAColors.backgroundTint(for: context.state.snapshot))
.applyActivityContentMarginsFixIfAvailable()
.widgetURL(URL(string: "loopfollow://la-tap")!)
} dynamicIsland: { context in
makeDynamicIsland(context: context)
}
}
}

/// Supplemental widget (iOS 18.0+) — adds CarPlay Dashboard + Watch Smart Stack
/// via supplementalActivityFamilies([.small]).
@available(iOS 18.0, *)
struct LoopFollowLiveActivityWidgetWithCarPlay: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: GlucoseLiveActivityAttributes.self) { context in
LockScreenFamilyAdaptiveView(state: context.state)
.id(context.state.seq)
} dynamicIsland: { context in
// DYNAMIC ISLAND UI
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Link(destination: URL(string: "loopfollow://la-tap")!) {
DynamicIslandLeadingView(snapshot: context.state.snapshot)
.overlay(RenewalOverlayView(show: context.state.snapshot.showRenewalOverlay))
}
.id(context.state.seq)
}
DynamicIslandExpandedRegion(.trailing) {
Link(destination: URL(string: "loopfollow://la-tap")!) {
DynamicIslandTrailingView(snapshot: context.state.snapshot)
.overlay(RenewalOverlayView(show: context.state.snapshot.showRenewalOverlay))
}
.id(context.state.seq)
}
DynamicIslandExpandedRegion(.bottom) {
Link(destination: URL(string: "loopfollow://la-tap")!) {
DynamicIslandBottomView(snapshot: context.state.snapshot)
.overlay(RenewalOverlayView(show: context.state.snapshot.showRenewalOverlay, showText: true))
}
.id(context.state.seq)
}
} compactLeading: {
DynamicIslandCompactLeadingView(snapshot: context.state.snapshot)
.id(context.state.seq)
} compactTrailing: {
DynamicIslandCompactTrailingView(snapshot: context.state.snapshot)
.id(context.state.seq)
} minimal: {
DynamicIslandMinimalView(snapshot: context.state.snapshot)
.id(context.state.seq)
}
.keylineTint(LAColors.keyline(for: context.state.snapshot).opacity(0.75))
makeDynamicIsland(context: context)
}
.supplementalActivityFamilies([.small])
}
Expand All @@ -72,7 +94,7 @@ private extension View {
/// Reads the activityFamily environment value and routes to the appropriate layout.
/// - `.small` → CarPlay Dashboard & Watch Smart Stack: compact glucose-only view
/// - everything else → full lock screen layout with configurable grid
@available(iOS 16.1, *)
@available(iOS 18.0, *)
private struct LockScreenFamilyAdaptiveView: View {
let state: GlucoseLiveActivityAttributes.ContentState

Expand All @@ -95,7 +117,7 @@ private struct LockScreenFamilyAdaptiveView: View {

/// Compact view shown on CarPlay Dashboard (iOS 26+) and Apple Watch Smart Stack (watchOS 11+).
/// Hardcoded to glucose + trend arrow + delta + time since last reading.
@available(iOS 16.1, *)
@available(iOS 18.0, *)
private struct SmallFamilyView: View {
let snapshot: GlucoseSnapshot

Expand Down Expand Up @@ -213,7 +235,7 @@ private struct LockScreenLiveActivityView: View {
.overlay(
ZStack {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.gray.opacity(0.6))
.fill(Color.gray.opacity(0.9))
Text("Tap to update")
.font(.system(size: 20, weight: .semibold))
.foregroundStyle(.white)
Expand All @@ -231,7 +253,7 @@ private struct RenewalOverlayView: View {

var body: some View {
ZStack {
Color.gray.opacity(0.6)
Color.gray.opacity(0.9)
if showText {
Text("Tap to update")
.font(.system(size: 14, weight: .semibold))
Expand Down
9 changes: 9 additions & 0 deletions Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ target 'LoopFollow' do
end

post_install do |installer|
# Set minimum deployment target for all pods to match the app (suppresses deprecation warnings)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 16.6
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.6'
end
end
end

# Patch Charts Transformer to avoid "CGAffineTransformInvert: singular matrix"
# warnings when chart views have zero dimensions (before layout).
transformer = 'Pods/Charts/Source/Charts/Utils/Transformer.swift'
Expand Down