live activity: compact CarPlay strip shows fuel + price + station distance

Restore .supplementalActivityFamilies([.small]) + a ViewThatFits compact
fallback so CarPlay's small slot renders our shared compact strip (fuel type,
bold price, station · distance) instead of falling back to the Dynamic Island
compact closure, which showed the app name and omitted the distance the user
wants. Full iPhone/iPad card is preserved via the richMinWidth gate on
richBody plus its flexible, truncating middle column (long names can no longer
collapse it).
This commit is contained in:
FuelBoard Contributor
2026-08-19 18:31:36 +01:00
parent 5d26509034
commit 01bb3ba905
@@ -49,12 +49,15 @@ struct FuelBoardLiveActivity: Widget {
.font(.caption2.bold().monospacedDigit()) .font(.caption2.bold().monospacedDigit())
} }
} }
// NOTE: deliberately NO `.supplementalActivityFamilies([.small])`. .supplementalActivityFamilies([.small])
// That modifier makes iOS eligible to render this activity in the // Why `.small` is kept: it lets the SHARED body render a compact form
// narrow `.small` form on the iPhone/iPad Lock Screen, which is what // in genuinely small slots (CarPlay small / Apple Watch smart stack)
// produced the squeezed, small-text card. Dropping it keeps the // instead of falling back to the Dynamic Island compact closure
// full-width Lock Screen card on iPhone/iPad; CarPlay still shows a // which could NOT show the station distance the user wants on CarPlay.
// small form via the Dynamic Island compact closures below. // The full-width iPhone/iPad card is protected by the `richMinWidth`
// gate on `richBody` + its flexible, truncating middle column, so
// iPhone/iPad still get the full card; only truly small space picks
// the compact strip below.
} }
} }
@@ -62,19 +65,28 @@ struct FuelBoardLiveActivity: Widget {
private struct FuelBoardLiveActivityView: View { private struct FuelBoardLiveActivityView: View {
let context: ActivityViewContext<FuelBoardLiveActivityAttributes> let context: ActivityViewContext<FuelBoardLiveActivityAttributes>
/// Full card needs at least this much width; iPhone/iPad Lock Screen is
/// always wider, so they always get it. Only genuinely small slots
/// (CarPlay small / Watch smart stack) fall through to `compactBody`.
private let richMinWidth: CGFloat = 280
/// Cap the compact strip so a mid-width surface can't sneak it in.
private let compactMaxWidth: CGFloat = 230
var body: some View { var body: some View {
Link(destination: context.state.mapsURL) { Link(destination: context.state.mapsURL) {
// Always the full three-column card. The station caption is ViewThatFits(in: .horizontal) {
// line-limited + scale-down + tail-truncated, so a LONG station // rich FIRST wins on the full-width iPhone/iPad Lock Screen.
// name truncates in place instead of inflating this view's ideal // Its middle column is flexible + line-limited, so a LONG
// width and tricking ViewThatFits into falling back to the compact // station name truncates in place and never inflates the ideal
// strip (that is exactly what made 5-mi / long-named activities // width (which used to trick ViewThatFits into falling back to
// render small while 10-15-mi / short names stayed full). // compact on iPhone).
// richBody
// No ViewThatFits / compactBody: with `.supplementalActivityFamilies` .frame(minWidth: richMinWidth)
// removed, this body is only ever handed Lock-Screen width, so the // compact fallback the small CarPlay / Watch smart-stack
// compact fallback was both dead weight and the cause of the bug. // strip: fuel · price · station distance (no app name).
richBody compactBody
.frame(maxWidth: compactMaxWidth)
}
} }
} }
@@ -113,7 +125,31 @@ private struct FuelBoardLiveActivityView: View {
.padding() .padding()
} }
// NOTE: `compactBody` was removed always render `richBody` (see body). /// Minimal strip for small space (CarPlay small / Watch smart stack):
/// fuel type + bold price on one line, station · distance below.
/// Deliberately no app name and no "Tap for directions" CarPlay is
/// display-only, and the user's asks here are just fuel + price + distance.
private var compactBody: some View {
VStack(alignment: .leading, spacing: 3) {
HStack(spacing: 5) {
Text(context.state.fuel.displayName)
.font(.caption.bold())
.lineLimit(1)
Spacer(minLength: 4)
FuelStore.priceTextAttributed(context.state.pricePence,
style: context.state.priceDisplayStyle,
size: 15, weight: .bold)
.lineLimit(1)
}
Text("\\(context.state.stationName) · \\(context.state.distanceText)")
.font(.system(size: 9))
.foregroundStyle(.secondary)
.lineLimit(1)
.minimumScaleFactor(0.7)
.truncationMode(.tail)
}
.padding(8)
}
} }
/// Dynamic Island expanded regions + compact trailing price only. /// Dynamic Island expanded regions + compact trailing price only.