Give the closest capsule the rounded rect whenever it wraps
The capsule's shape was keyed to the verdict's declared line count, so "Closest station" — one declared line — always drew as a Capsule while its text wrapped to two lines in the row's narrow column, leaving the semicircular ends crowding the words they wrapped. Saves never showed this because its amount line makes the declared count two. The shape now follows the DRAWN height: a GeometryReader behind the text block reports what it actually rendered, divided by the caption's line height taken through ScaledMetric so the test holds at every Dynamic Type size. One drawn line keeps the capsule; two or more get the same 8pt rounded rect the other verdicts use. The first layout pass, before there is a height, falls back to the declared count.
This commit is contained in:
@@ -160,6 +160,12 @@ struct ListingFuelSavingsCapsule: View {
|
||||
/// Station rows carry the figure; the Key sheet's legend shows the wording only.
|
||||
var showsAmount: Bool = true
|
||||
|
||||
/// The height the text actually drew, so the shape can follow the drawn line
|
||||
/// count rather than the data's. See `drawnLineCount`.
|
||||
@State private var textHeight: CGFloat = 0
|
||||
/// One caption line's height, scaled with Dynamic Type, to divide that by.
|
||||
@ScaledMetric(relativeTo: .caption) private var captionLineHeight: CGFloat = 14
|
||||
|
||||
private var verdict: FuelSavingsResult.ListingVerdict {
|
||||
result.listingVerdict(showsAmount: showsAmount)
|
||||
}
|
||||
@@ -185,6 +191,13 @@ struct ListingFuelSavingsCapsule: View {
|
||||
.multilineTextAlignment(.leading)
|
||||
// Never compress or truncate: the capsule wraps and the row grows taller.
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.background(
|
||||
GeometryReader { proxy in
|
||||
Color.clear
|
||||
.preference(key: CapsuleTextHeightKey.self,
|
||||
value: proxy.size.height)
|
||||
}
|
||||
)
|
||||
}
|
||||
// White on the muted fill. These capsules carry a figure the eye is asked
|
||||
// to read at a glance, which is why they are the only filled-and-white
|
||||
@@ -200,6 +213,7 @@ struct ListingFuelSavingsCapsule: View {
|
||||
.background(shape.fill(fill))
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel(verdict.spokenText)
|
||||
.onPreferenceChange(CapsuleTextHeightKey.self) { textHeight = $0 }
|
||||
}
|
||||
|
||||
/// Saves and Costs only — the two verdicts that carry a figure. See the
|
||||
@@ -216,12 +230,22 @@ struct ListingFuelSavingsCapsule: View {
|
||||
ListingCapsuleFill.color(for: result.status)
|
||||
}
|
||||
|
||||
/// How many lines the text DREW, not how many the verdict declares.
|
||||
/// "Closest station" is one declared line that wraps to two in the row's
|
||||
/// narrow column, and the shape has to follow what is on screen or the pill's
|
||||
/// semicircular ends crowd the text they are wrapping. Falls back to the
|
||||
/// declared count on the first layout pass, before there is a height.
|
||||
private var drawnLineCount: Int {
|
||||
guard textHeight > 0, captionLineHeight > 0 else { return verdict.lines.count }
|
||||
return max(1, Int((textHeight / captionLineHeight).rounded()))
|
||||
}
|
||||
|
||||
/// One line keeps the capsule. Two or more switch to a rounded rect: a pill's
|
||||
/// semicircular ends crowd the text corners at that height, while an 8pt
|
||||
/// radius does not. Keyed to the LINE COUNT, so Break even flips shape only
|
||||
/// when "(approx.)" pushes it onto a second line.
|
||||
/// radius does not. Keyed to the DRAWN line count, so a wrapped "Closest
|
||||
/// station" gets the same rounded rect as a figure pushed onto its own line.
|
||||
private var shape: AnyShape {
|
||||
verdict.lines.count > 1
|
||||
drawnLineCount > 1
|
||||
? AnyShape(RoundedRectangle(cornerRadius: 8))
|
||||
: AnyShape(Capsule())
|
||||
}
|
||||
@@ -236,6 +260,14 @@ struct ListingFuelSavingsCapsule: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Carries the drawn height of a capsule's text block up to `drawnLineCount`.
|
||||
private struct CapsuleTextHeightKey: PreferenceKey {
|
||||
static let defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = max(value, nextValue())
|
||||
}
|
||||
}
|
||||
|
||||
/// The station listing's verdict-capsule fills: muted semantic colours carrying
|
||||
/// white text and icon.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user