Unify capsule, station label and price rating styling
One adaptive tone palette and one shape rule now drive every capsule on Stations, Favourites, the detail sheet and the Key. - CapsuleTone: the light/dark palette pairs in one place, resolved through a UIColor dynamic provider so a List row that re-renders without rebuilding its body still follows an appearance switch. Every pair clears 4.5:1 foreground-on-background in both appearances (4.96-6.72:1). - AdaptiveCapsule: capsule at one drawn line, rounded rect at two or more, radius 8 -> 10, measuring content before padding so insets cannot inflate the line count. - Listing verdict capsules move from white-on-muted-solid to the tinted palette, so Saves, Break even, Costs and Closest station read the same in the rows and in the detail sheet. - Best Value is purple, not green: green stays reserved for a financial saving, which is what the Saves capsule beside it means. - Price dots standardise to 12pt in the app (widget keeps its own 6pt dot; PriceRatingDot now takes a diameter so the difference is deliberate). - Station labels are sentence case with their recommended symbols (medal, tag, location.north.fill, trophy) and StationLabelKind replaces the Key's string-literal dispatch, so renaming a label can no longer drop a row. - Key says "Costs", matching the capsule's own headline (was "Costs more").
This commit is contained in:
+71
-31
@@ -364,41 +364,81 @@ struct FuelSavingsRankedStation: Equatable {
|
||||
let pricePencePerLitre: Double
|
||||
}
|
||||
|
||||
/// The user-facing copy shown by the Stations tab's Key sheet. Keeping the
|
||||
/// descriptions in one Foundation-only model lets tests pin the exact meaning of
|
||||
/// each visual indicator without coupling the wording to SwiftUI layout.
|
||||
enum KeySheetContent {
|
||||
struct Item: Equatable {
|
||||
let label: String
|
||||
let description: String
|
||||
/// The user-facing copy shown by the Stations tab's Key sheet. Keeping the
|
||||
/// descriptions in one Foundation-only model lets tests pin the exact meaning of
|
||||
/// each visual indicator without coupling the wording to SwiftUI layout.
|
||||
///
|
||||
/// Every label is SENTENCE CASE ("Best value", not "BEST VALUE"): the labels
|
||||
/// are read as phrases, and a capsule set in all caps reads as shouting beside
|
||||
/// the sentence-case copy in the rows around it. The tests pin these exact
|
||||
/// strings, and `StationLabelKind` maps each one to its indicator, so the view
|
||||
/// never matches on the literal text.
|
||||
enum KeySheetContent {
|
||||
struct Item: Equatable {
|
||||
let label: String
|
||||
let description: String
|
||||
}
|
||||
|
||||
static let priceRatings = [
|
||||
Item(label: "Good price", description: "Within 1.5p/L of the cheapest."),
|
||||
Item(label: "Okay", description: "More than 1.5p/L, up to 4p/L above the cheapest."),
|
||||
Item(label: "Pricey", description: "More than 4p/L above the cheapest.")
|
||||
]
|
||||
|
||||
/// The same three verdicts the listing capsule draws, so the legend and the
|
||||
/// rows cannot disagree on a word. "Costs" is the capsule's own headline.
|
||||
static let savings = [
|
||||
Item(label: "Saves", description: "You’ll save money compared with your closest station, including travel costs."),
|
||||
Item(label: "Break even", description: "The saving or extra cost is negligible."),
|
||||
Item(label: "Costs", description: "Filling up here costs more than at your closest station, including travel.")
|
||||
]
|
||||
|
||||
static let stationLabels = [
|
||||
Item(label: "Best value", description: "Meets your minimum saving after travel costs."),
|
||||
Item(label: "Cheapest", description: "The lowest fuel price per litre within your selected range."),
|
||||
Item(label: "Closest station", description: "The nearest station by journey distance."),
|
||||
Item(label: "Top", description: "The cheapest station in your Favourites for the selected fuel.")
|
||||
]
|
||||
|
||||
static let favourite = Item(
|
||||
label: "Favourites",
|
||||
description: "Star a station to save it to your Favourites."
|
||||
)
|
||||
|
||||
static let helpTitle = "How does Best Value work?"
|
||||
}
|
||||
|
||||
static let priceRatings = [
|
||||
Item(label: "Good price", description: "Within 1.5p/L of the cheapest."),
|
||||
Item(label: "Okay", description: "More than 1.5p/L, up to 4p/L above the cheapest."),
|
||||
Item(label: "Pricey", description: "More than 4p/L above the cheapest.")
|
||||
]
|
||||
/// A station label's meaning, independent of its copy.
|
||||
///
|
||||
/// The Key sheet used to dispatch its indicator on the label's literal text
|
||||
/// (`case "BEST VALUE":`), so renaming a label silently dropped that row to the
|
||||
/// fallback indicator. The enum makes the mapping explicit and lets the
|
||||
/// compiler catch a missing case.
|
||||
enum StationLabelKind: String, CaseIterable {
|
||||
case bestValue
|
||||
case cheapest
|
||||
case closestStation
|
||||
case top
|
||||
|
||||
static let savings = [
|
||||
Item(label: "Saves", description: "You’ll save money compared with your closest station, including travel costs."),
|
||||
Item(label: "Break even", description: "The saving or extra cost is negligible."),
|
||||
Item(label: "Costs more", description: "Filling up here costs more than at your closest station, including travel.")
|
||||
]
|
||||
/// Matches the corresponding `KeySheetContent.stationLabels` entry.
|
||||
var label: String {
|
||||
switch self {
|
||||
case .bestValue: return String(localized: "Best value")
|
||||
case .cheapest: return String(localized: "Cheapest")
|
||||
case .closestStation: return FuelSavingsResult.baselineHeadline
|
||||
case .top: return String(localized: "Top")
|
||||
}
|
||||
}
|
||||
|
||||
static let stationLabels = [
|
||||
Item(label: "BEST VALUE", description: "Meets your minimum saving after travel costs."),
|
||||
Item(label: "CHEAPEST", description: "The lowest fuel price per litre within your selected range."),
|
||||
Item(label: "CLOSEST", description: "The nearest station by journey distance."),
|
||||
Item(label: "TOP", description: "The cheapest station in your Favourites for the selected fuel.")
|
||||
]
|
||||
|
||||
static let favourite = Item(
|
||||
label: "Favourites",
|
||||
description: "Star a station to save it to your Favourites."
|
||||
)
|
||||
|
||||
static let helpTitle = "How does Best Value work?"
|
||||
}
|
||||
var symbol: String {
|
||||
switch self {
|
||||
case .bestValue: return "medal"
|
||||
case .cheapest: return "tag"
|
||||
case .closestStation: return "location.north.fill"
|
||||
case .top: return "trophy"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which recommendation badge a station listing row shows, if any.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user