Price display: station sign drops the £ (134.9) — matches roadside signs; pounds & pence uses forecourt superscript style £1.29⁹/L (small superscript third digit, small /L); attributed Text on list/widgets/Live Activity, Unicode superscript in Siri cards

This commit is contained in:
FuelBoard Contributor
2026-08-14 18:14:10 +01:00
parent fd7fa5f9c9
commit 80113456ae
7 changed files with 76 additions and 33 deletions
+59 -11
View File
@@ -9,6 +9,7 @@
import Foundation
import Security
import SwiftUI
// MARK: - Fuel types
@@ -145,19 +146,20 @@ enum DistanceUnit: String, Codable, CaseIterable, Identifiable {
/// Display style for fuel prices. Internally prices are always stored and
/// computed in pence-per-litre (GOV.UK's unit, e.g. 129.9); the style only
/// affects RENDERING: the station-sign convention shows the pence figure
/// ("£129.9", what a forecourt sign shows), pounds & pence shows the
/// converted value ("£1.299"). Calculations never see this.
/// affects RENDERING: the station-sign convention shows the bare pence
/// figure ("129.9", no £ exactly what a UK forecourt sign shows), pounds &
/// pence shows the converted value in the forecourt's superscript style
/// ("£1.29/L"). Calculations never see this.
enum PriceDisplayStyle: String, Codable, CaseIterable, Identifiable {
case stationSign // £129.9 matches the sign
case poundsPence // £1.299
case stationSign // 129.9 bare pence figure, like the roadside sign
case poundsPence // £1.29/L small superscript third digit, small /L
var id: String { rawValue }
var displayName: String {
switch self {
case .stationSign: return "Station sign (£129.9)"
case .poundsPence: return "Pounds & pence (£1.299)"
case .stationSign: return "Station sign (129.9)"
case .poundsPence: return "Pounds & pence (£1.29)"
}
}
}
@@ -454,7 +456,7 @@ struct FuelStore {
saveString(unit.rawValue, service: distanceUnitKey)
}
// MARK: Price display station-sign (£129.9) vs pounds & pence (£1.299).
// MARK: Price display station sign (129.9) vs pounds & pence (£1.29/L).
// Prices are always stored/computed in pence-per-litre; this style only
// changes how they are RENDERED, so it can never affect calculations.
// Default station sign = the forecourt convention.
@@ -472,12 +474,58 @@ struct FuelStore {
saveString(style.rawValue, service: priceDisplayStyleKey)
}
/// Superscript digit glyphs for the pounds & pence format's small raised
/// third digit (the forecourt style: "£1.29").
private static let superscriptDigits: [Character] = ["", "¹", "²", "³", "", "", "", "", "", ""]
/// Render a pence-per-litre price per the saved style:
/// stationSign -> "£129.9" (sign convention), poundsPence -> "£1.299".
/// stationSign -> "129.9" (bare pence figure, no £ the roadside sign),
/// poundsPence -> "£1.29/L" (pounds with a superscript third digit and a
/// small /L unit, matching UK garage displays).
static func priceText(_ pence: Double, style: PriceDisplayStyle? = nil) -> String {
switch style ?? loadPriceDisplayStyle() {
case .stationSign: return String(format: "£%.1f", pence)
case .poundsPence: return String(format: "£%.3f", pence / 100)
case .stationSign:
return String(format: "%.1f", pence)
case .poundsPence:
let tenths = Int((pence * 10).rounded())
let whole = tenths / 1000
let major = (tenths % 1000) / 10
let minor = tenths % 10
return String(format: "£%d.%02d", whole, major)
+ String(superscriptDigits[minor])
+ "/L"
}
}
/// Rich, forecourt-styled price text for SwiftUI surfaces. Same formats as
/// `priceText`, but the pounds & pence mode renders the third digit small
/// and superscripted and /L small and muted proper text styling instead
/// of Unicode glyphs. Pass the surface's size/weight/color; the returned
/// Text carries its own fonts, so do NOT apply `.font(...)` on top.
static func priceTextAttributed(_ pence: Double, style: PriceDisplayStyle? = nil,
size: CGFloat = 17, weight: Font.Weight = .semibold,
color: Color = .primary) -> Text {
let s = style ?? loadPriceDisplayStyle()
let base = Font.system(size: size, weight: weight).monospaced()
switch s {
case .stationSign:
return Text(priceText(pence, style: s)).font(base).foregroundColor(color)
case .poundsPence:
let tenths = Int((pence * 10).rounded())
let whole = tenths / 1000
let major = (tenths % 1000) / 10
let minor = tenths % 10
let main = Text(String(format: "£%d.%02d", whole, major))
.font(base)
.foregroundColor(color)
let sup = Text(String(superscriptDigits[minor]))
.font(.system(size: size * 0.6, weight: weight).monospaced())
.baselineOffset(size * 0.35)
.foregroundColor(color)
let perL = Text("/L")
.font(.system(size: size * 0.5, weight: .regular).monospaced())
.foregroundColor(color.opacity(0.55))
return main + sup + perL
}
}