Compare commits

...
Author SHA1 Message Date
FuelBoard Contributor 7dbb3d1a15 More info sheet: float the "Copied" capsule over the address row, not the map
The confirmation belongs to the address row, so hovering it over the map read as
belonging to the map. It now overlays the address row itself, horizontally centred
on the address line, as .overlay(alignment: .center) on the row with
allowsHitTesting(false) still keeping the row tappable through the capsule.

Verified: with the capsule up, the OCR box sits at y 1189-1231 inside the address
band (map bottom is 1075), the tint blob is centred at x 602.5 against a screen
centre of 603, the row's ink runs outside the capsule footprint are byte-identical
to the idle frame, and a real tap on the capsule's own footprint still copies.
2026-10-14 13:57:19 +01:00
FuelBoard Contributor 6e534ed5b7 More info sheet: float the "Copied" capsule and put the copy glyph on the address line
The address row is back to a single line: the copy glyph now sits on the address
line itself (trailing, plain glyph — the confirmation says it elsewhere), and the
always-reserved second line is gone.

The "Copied" confirmation is now a green-tinted capsule that floats over the
bottom of the map, directly above the address and horizontally centred. It is an
overlay with allowsHitTesting(false), so nothing reflows while it is up (verified:
the address row's ink, column for column, does not move) and the map's tap target
stays live underneath (verified: tapping where the capsule floats still opens
Apple Maps). A material base keeps it legible against map tiles, where a 14% wash
alone — the notice-capsule recipe — disappears. Timing is unchanged: 2s, then
fades, with the success haptic on copy.
2026-10-14 13:45:05 +01:00
+40 -23
View File
@@ -124,42 +124,36 @@ struct StationDetailView: View {
// stay. // stay.
.listRowSeparator(.hidden, edges: .bottom) .listRowSeparator(.hidden, edges: .bottom)
// The map, the address, the distance, any notices and the two // The map, the address, the distance, any notices and the two
// station actions all share ONE card. The list's own row // station actions all share ONE card (the list's own row
// hairlines separate them (inset, the same line that already // hairlines separate them), so no hand-rolled Divider here —
// separates Address from Distance), so no hand-rolled Divider is // adding one would double the line.
// needed — adding one would double the line. //
// The station name is NOT repeated here: the sheet header // The station name is NOT repeated: the sheet header already
// (navigationTitle, inline) already shows it, and the address // shows it.
// line below has any leading repeat of the name stripped, so
// a "Name" row only said it twice.
// //
// The whole row is a copy control: it puts JUST the postcode // The whole row is a copy control: it puts JUST the postcode
// on the clipboard so it can be pasted into another // on the clipboard so it can be pasted into another
// navigation app. The copy glyph and its "Copied" // navigation app. The copy glyph sits ON the address line,
// confirmation share a SECOND line that is always there, so // and the "Copied" confirmation is a capsule that floats OVER
// the confirmation can never reflow the address above it. // THIS ROW — it confirms the address, so it must not hover
// over the map. It is an overlay, so nothing moves while it is
// up, and `allowsHitTesting(false)` keeps the row tappable
// through the capsule.
Button { Button {
copyPostcode() copyPostcode()
} label: { } label: {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: 8) { HStack(alignment: .firstTextBaseline, spacing: 8) {
Text("Address") Text("Address")
Spacer(minLength: 8) Spacer(minLength: 8)
Text(addressLine) Text(addressLine)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} // Stays the plain copy glyph — the capsule carries
HStack(spacing: 4) { // the confirmation, so swapping this to a checkmark
Spacer(minLength: 0) // would say it twice.
if showingCopied { Image(systemName: "doc.on.doc")
Text("Copied")
.foregroundStyle(.green)
.transition(.opacity)
}
Image(systemName: showingCopied ? "checkmark" : "doc.on.doc")
.foregroundStyle(showingCopied ? Color.green : Color.secondary)
}
.font(.footnote.weight(.semibold)) .font(.footnote.weight(.semibold))
.foregroundStyle(.secondary)
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
@@ -170,6 +164,29 @@ struct StationDetailView: View {
? "" ? ""
: String(localized: "Copies the postcode", : String(localized: "Copies the postcode",
comment: "Accessibility hint for the address row in the More info sheet")) comment: "Accessibility hint for the address row in the More info sheet"))
// Horizontally centred over the address line itself.
.overlay(alignment: .center) {
if showingCopied {
HStack(spacing: 4) {
Image(systemName: "checkmark")
Text("Copied")
}
.font(.footnote.weight(.semibold))
.foregroundStyle(.green)
.padding(.horizontal, 10)
.padding(.vertical, 6)
// A material base keeps it legible where the tint
// alone (the notice-capsule recipe) washes out.
.background {
Capsule()
.fill(.regularMaterial)
.overlay(Capsule().fill(Color.green.opacity(0.22)))
}
.allowsHitTesting(false)
.accessibilityHidden(true)
.transition(.opacity)
}
}
if let location { if let location {
let distance = FuelStore.stationDistanceDisplay( let distance = FuelStore.stationDistanceDisplay(
station: station, userLat: location.lat, userLng: location.lng station: station, userLat: location.lat, userLng: location.lng