3 Commits
Author SHA1 Message Date
FuelBoard Contributor 5d26509034 live activity: always render full card (drop compact fallback)
Long station names inflated richBody's IDEAL width, so ViewThatFits (which
measures ideal, untruncated text) decided 'rich doesn't fit' and fell back to
the compact strip — that's why 5-mi / long-named activities rendered small
while 10-15-mi / short names stayed full. With .supplementalActivityFamilies
already removed, the body is only ever Lock-Screen width, so drop ViewThatFits
and compactBody entirely and always render the full three-column card; the
station caption line-limits + scale-downs + tail-truncates in place.
2026-08-19 16:39:49 +01:00
FuelBoard Contributor 221b96097d live activity: drop .small family so iPhone/iPad show full-width card
The active activity was rendering the narrow .small form on the iPhone Lock
Screen (squeezed, small text) because .supplementalActivityFamilies([.small])
made iOS eligible to present it that way. Remove it so iPhone/iPad always get
the full three-column card; CarPlay's small form still comes from the Dynamic
Island compact closures. Keep compactBody as a narrow-width safety fallback.
2026-08-19 16:09:02 +01:00
FuelBoard Contributor b012232012 live activity: robust full-vs-compact split; scale long station names
The adaptive layout picks the full three-column view when there's Lock
Screen width and a compact price strip in the small CarPlay/Watch slot.
Tighten the discriminant (rich ≥290pt, compact ≤220pt) so iPhone/iPad
always get the full view and only genuinely small slots get the compact
strip. Long station names used to clip at inconsistent points across
the rich/compact/Dynamic Island views; both bodies now scale + tail-
truncate so the caption stays consistent regardless of station name.
2026-08-19 13:58:54 +01:00
@@ -4,27 +4,25 @@
// Shows the cheapest station for the pinned fuel within the app's chosen // Shows the cheapest station for the pinned fuel within the app's chosen
// radius. Tapping anywhere opens Apple Maps directions to that station. // radius. Tapping anywhere opens Apple Maps directions to that station.
// //
// ADAPTIVE LAYOUT: ActivityConfiguration shares ONE content view across the // ADAPTIVE LAYOUT: the Lock Screen body provides two layouts and lets
// Lock Screen, banner, and the CarPlay small slot there is no per-platform // ViewThatFits pick by available width, but the narrow `.small` family is NOT
// closure. So this view provides two layouts and lets ViewThatFits pick by // declared (see note on the config), so iPhone/iPad always render the full
// available width: // three-column card (`richBody`) with proper text sizes. `compactBody` is kept
// richBody the full three-column design (glyph · fuel+station · price), // as a safety fallback should any surface ever hand this view a narrow width.
// wins wherever there's Lock Screen width (it carries an //
// explicit minWidth so it can never be squeezed into the car). // IMPORTANT: `.supplementalActivityFamilies([.small])` is deliberately absent
// compactBody a minimal price-strip (glyph+fuel+price, station caption // it made iOS render the squeezed `.small` card on the iPhone Lock Screen.
// below) that wins in the CarPlay/Apple Watch Smart Stack // CarPlay's small form comes from the Dynamic Island compact closures.
// small slot.
import ActivityKit import ActivityKit
import SwiftUI import SwiftUI
import WidgetKit import WidgetKit
/// The Live Activity itself registered in the widget bundle alongside the /// The Live Activity itself registered in the widget bundle alongside the
/// regular price widget. No CarPlay entitlement involved: this renders on the /// regular price widget. No CarPlay entitlement involved: renders on the
/// Lock Screen, Dynamic Island, and the car display (CarPlay Ultra, iOS 26+). /// Lock Screen (full-width card on iPhone/iPad) and the Dynamic Island
/// `.supplementalActivityFamilies([.small])` makes it eligible for the car's /// (incl. the island's compact form used in the car, display-only FuelBoard
/// small Live Activity slot display-only there (FuelBoard is not a /// is not a CarPlay-enabled app, so car-side taps can't launch anything).
/// CarPlay-enabled app, so car-side taps can't launch anything).
struct FuelBoardLiveActivity: Widget { struct FuelBoardLiveActivity: Widget {
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
ActivityConfiguration(for: FuelBoardLiveActivityAttributes.self) { context in ActivityConfiguration(for: FuelBoardLiveActivityAttributes.self) { context in
@@ -51,7 +49,12 @@ struct FuelBoardLiveActivity: Widget {
.font(.caption2.bold().monospacedDigit()) .font(.caption2.bold().monospacedDigit())
} }
} }
.supplementalActivityFamilies([.small]) // NOTE: deliberately NO `.supplementalActivityFamilies([.small])`.
// That modifier makes iOS eligible to render this activity in the
// narrow `.small` form on the iPhone/iPad Lock Screen, which is what
// produced the squeezed, small-text card. Dropping it keeps the
// full-width Lock Screen card on iPhone/iPad; CarPlay still shows a
// small form via the Dynamic Island compact closures below.
} }
} }
@@ -61,15 +64,17 @@ private struct FuelBoardLiveActivityView: View {
var body: some View { var body: some View {
Link(destination: context.state.mapsURL) { Link(destination: context.state.mapsURL) {
ViewThatFits(in: .horizontal) { // Always the full three-column card. The station caption is
// rich first wins on full-width Lock Screen / banner // line-limited + scale-down + tail-truncated, so a LONG station
// name truncates in place instead of inflating this view's ideal
// width and tricking ViewThatFits into falling back to the compact
// strip (that is exactly what made 5-mi / long-named activities
// render small while 10-15-mi / short names stayed full).
//
// No ViewThatFits / compactBody: with `.supplementalActivityFamilies`
// removed, this body is only ever handed Lock-Screen width, so the
// compact fallback was both dead weight and the cause of the bug.
richBody richBody
// CarPlay / Watch small slot is far narrower than this,
// so ViewThatFits reliably falls through to compactBody.
.frame(minWidth: 280)
// compact fallback the car's small supplemental slot
compactBody
}
} }
} }
@@ -90,6 +95,8 @@ private struct FuelBoardLiveActivityView: View {
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(1) .lineLimit(1)
.minimumScaleFactor(0.75)
.truncationMode(.tail)
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -106,30 +113,7 @@ private struct FuelBoardLiveActivityView: View {
.padding() .padding()
} }
/// Minimal strip for the small CarPlay / Watch Smart Stack slot: // NOTE: `compactBody` was removed always render `richBody` (see body).
/// glyph + fuel left, bold price right, truncated station below.
private var compactBody: some View {
VStack(alignment: .leading, spacing: 3) {
HStack(spacing: 5) {
Image(systemName: "fuelpump.fill")
.font(.caption2)
.foregroundStyle(.green)
Text(context.state.fuel.displayName)
.font(.caption2.weight(.semibold))
.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)
}
.padding(8)
}
} }
/// Dynamic Island expanded regions + compact trailing price only. /// Dynamic Island expanded regions + compact trailing price only.