From 7aaaffa1c5d6b7c5e48df27480176fe6d00b64b5 Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Sat, 17 Oct 2026 18:31:45 +0100 Subject: [PATCH] Revert station capsule layout changes --- FuelBoard/ContentView.swift | 48 +++++++++++++++++++++----------- FuelBoard/FuelSavingsViews.swift | 17 ++++++----- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/FuelBoard/ContentView.swift b/FuelBoard/ContentView.swift index 899f535..7d6aa89 100644 --- a/FuelBoard/ContentView.swift +++ b/FuelBoard/ContentView.swift @@ -1842,13 +1842,26 @@ struct StationRow: View { /// 22pt keeps the price the row's loudest element after the station name. @ScaledMetric(relativeTo: .title2) private var priceSize: CGFloat = 22 + /// The identity column's share of the ROW, for every row and both price + /// styles. The mockup's divider sits at ~53% of its row; this is that ratio, + /// held per row rather than derived from the row's text — the point is that + /// the hairline, the price axis and the star cannot move as the list scrolls. + /// The price column takes the remainder, and its savings capsule scales down + /// instead of widening the column and dragging the hairline with it. + /// + /// One value for both price styles: the star used to sit at the end of the + /// price line and hold ~28pt of the price column, so the wide form `£1.58⁷/L` + /// — ~20pt wider than the bare `158.7` at the same point size — did not fit on + /// a 393pt screen. With the star now under the station logo the price column + /// has that width back, so the identity column no longer has to give width up + /// in pounds & pence, and names keep the same room in both styles. + private static let leftSectionShare: CGFloat = 0.52 /// The comparison label sits beside the capsule only where the row is wide /// enough for both (regular width). On an iPhone the mockup's canvas is /// wider than the whole screen, so the label drops under the capsule rather /// than starving the station name. @Environment(\.horizontalSizeClass) private var sizeClass - @Environment(\.dynamicTypeSize) private var dynamicTypeSize private var ragColor: Color { guard let price = station.prices[fuel], let baselinePrice else { return .gray } @@ -1918,9 +1931,13 @@ struct StationRow: View { // the detail sheet instead of being repeated on every row. HStack(alignment: .center, spacing: 12) { // LEFT SECTION — station identity: brand logo, then name, address and - // distance. This section yields horizontal space to the financial - // column first: a truncated station identity is recoverable from the - // detail sheet, while a truncated saving is materially misleading. + // distance. Given a FIXED SHARE of the row (~52%, the mockup's ratio) + // rather than sizing to its content: sized to content, this column + // grew and shrank with each row's badge and savings copy, which moved + // the hairline 23pt between adjacent rows (x767 on a BEST VALUE row, + // x699 on a "costs more" row) — so the divider and the price axis + // both jumped sideways as the list scrolled. One share for every row + // puts them on a single vertical line down the whole list. HStack(alignment: .top, spacing: 12) { // The logo, with the favourite star directly beneath it. Moving // the star out of the price line is what gives the price column @@ -1955,15 +1972,16 @@ struct StationRow: View { // Name, full address, distance VStack(alignment: .leading, spacing: 2) { - // Keep the established compact row. Long names yield space - // to the complete price/verdict column rather than wrapping - // and forcing a financial amount to truncate. + // The name WRAPS instead of truncating: the row is tall + // enough now, and a name cut to "Z A Service Stat…" cannot + // be told apart from the station next door. Baseline + // alignment keeps the notice symbols on the name's FIRST + // line instead of floating beside a two-line name. HStack(alignment: .firstTextBaseline, spacing: 6) { Text(station.name) .font(.headline) .multilineTextAlignment(.leading) - .lineLimit(1) - .truncationMode(.tail) + .fixedSize(horizontal: false, vertical: true) ForEach(StationNotice.notices(for: station)) { notice in Image(systemName: notice.symbolName) .font(.caption2.weight(.semibold)) @@ -2001,10 +2019,11 @@ struct StationRow: View { } } .frame(maxWidth: .infinity, alignment: .leading) - .layoutPriority(0) } .frame(maxWidth: .infinity, alignment: .leading) - .layoutPriority(0) + .containerRelativeFrame(.horizontal, alignment: .leading) { length, _ in + length * Self.leftSectionShare + } // 1pt hairline between the two sections. Inset top and bottom so it // spans the row's content without touching the row edges, and @@ -2056,12 +2075,7 @@ struct StationRow: View { savingsCapsule(result) } } - // The verdict owns its natural one-line width at normal Dynamic Type - // sizes. The identity column compresses first, preventing "Approx. - // Save £…" while keeping the same two-column visual design. - .fixedSize(horizontal: !dynamicTypeSize.isAccessibilitySize, - vertical: false) - .layoutPriority(1) + .frame(maxWidth: .infinity, alignment: .leading) // The spec's ~20-24pt of air between the divider and the price // column: the row's own 12pt spacing plus this 10pt. .padding(.leading, 10) diff --git a/FuelBoard/FuelSavingsViews.swift b/FuelBoard/FuelSavingsViews.swift index aac2bc2..4d9d7d5 100644 --- a/FuelBoard/FuelSavingsViews.swift +++ b/FuelBoard/FuelSavingsViews.swift @@ -105,26 +105,25 @@ struct FuelSavingsCapsule: View { let result: FuelSavingsResult var compact: Bool = true - @Environment(\.dynamicTypeSize) private var dynamicTypeSize - var body: some View { HStack(spacing: compact ? 3 : 5) { Image(systemName: Self.symbol(for: result.status)) .accessibilityHidden(true) Text(result.capsuleHeadline()) - // Financial verdicts stay complete and single-line at ordinary - // text sizes. Accessibility sizes may use a second line rather - // than clipping, shrinking or losing the amount to an ellipsis. - .lineLimit(dynamicTypeSize.isAccessibilitySize ? 2 : 1) - .fixedSize(horizontal: !dynamicTypeSize.isAccessibilitySize, - vertical: true) + .lineLimit(1) + // Measured on a 393pt screen: the capsule is handed ~101pt, of + // which the icon, its spacing and the 8pt padding either side + // take 32pt, leaving ~69pt for the text. "Closest station" needs + // ~90pt at full size, so a 0.8 floor — 72pt — could not reach it + // and the row showed "Closest stati…" instead. 0.7 leaves ~63pt, + // which clears it at ~10pt type: scaled, never truncated. + .minimumScaleFactor(0.7) } .font(compact ? .caption.weight(.semibold) : .headline) .foregroundStyle(Self.color(for: result.status)) .padding(.horizontal, compact ? 8 : 12) .padding(.vertical, compact ? 4 : 7) .background(Capsule().fill(Self.color(for: result.status).opacity(0.15))) - .accessibilityElement(children: .combine) } /// Verdict colour: green save, red worse off, grey break even, blue