diff --git a/LoopFollow.xcodeproj/project.pbxproj b/LoopFollow.xcodeproj/project.pbxproj index 795346767..34c2b838c 100644 --- a/LoopFollow.xcodeproj/project.pbxproj +++ b/LoopFollow.xcodeproj/project.pbxproj @@ -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)"; @@ -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)"; diff --git a/LoopFollow/LiveActivitySettingsView.swift b/LoopFollow/LiveActivitySettingsView.swift index 99dbc13e6..efe4ec321 100644 --- a/LoopFollow/LiveActivitySettingsView.swift +++ b/LoopFollow/LiveActivitySettingsView.swift @@ -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") } } diff --git a/LoopFollowLAExtension/LoopFollowLABundle.swift b/LoopFollowLAExtension/LoopFollowLABundle.swift index e3a043783..a9f7daf6c 100644 --- a/LoopFollowLAExtension/LoopFollowLABundle.swift +++ b/LoopFollowLAExtension/LoopFollowLABundle.swift @@ -14,5 +14,8 @@ struct LoopFollowLABundle: WidgetBundle { if #available(iOS 16.1, *) { LoopFollowLiveActivityWidget() } + if #available(iOS 18.0, *) { + LoopFollowLiveActivityWidgetWithCarPlay() + } } } diff --git a/LoopFollowLAExtension/LoopFollowLiveActivity.swift b/LoopFollowLAExtension/LoopFollowLiveActivity.swift index 354ea13df..a70008414 100644 --- a/LoopFollowLAExtension/LoopFollowLiveActivity.swift +++ b/LoopFollowLAExtension/LoopFollowLiveActivity.swift @@ -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) -> 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]) } @@ -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 @@ -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 @@ -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) @@ -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)) diff --git a/Podfile b/Podfile index 5a8c0f868..f8c2df3b2 100644 --- a/Podfile +++ b/Podfile @@ -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'