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.
This commit is contained in:
FuelBoard Contributor
2026-10-14 13:57:19 +01:00
parent 6e534ed5b7
commit 7dbb3d1a15
+35 -43
View File
@@ -118,58 +118,27 @@ struct StationDetailView: View {
.accessibilityHint(String(localized: "Opens Apple Maps with driving directions",
comment: "Accessibility hint for the tappable map preview in the More info sheet"))
}
// Floating "Copied" confirmation: a capsule hovering over the
// bottom of the map, directly above the Address row and
// horizontally centred. It is an overlay, so nothing moves
// while it is up, and `allowsHitTesting(false)` keeps the
// map's tap target live underneath. The map's clipShape is
// applied BEFORE this, so the capsule is not clipped to the
// map's rounded rect.
.overlay(alignment: .bottom) {
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 over map tiles: a
// 14% green wash alone (the notice-capsule recipe)
// vanishes against the map.
.background {
Capsule()
.fill(.regularMaterial)
.overlay(Capsule().fill(Color.green.opacity(0.22)))
}
.padding(.bottom, 12)
.allowsHitTesting(false)
.accessibilityHidden(true)
.transition(.opacity)
}
}
// No hairline between the map and the Address row: the map
// already reads as its own block, so the line only added
// clutter. The Address/Distance and Distance/actions lines
// stay.
.listRowSeparator(.hidden, edges: .bottom)
// The map, the address, the distance, any notices and the two
// station actions all share ONE card. The list's own row
// hairlines separate them (inset, the same line that already
// separates Address from Distance), so no hand-rolled Divider is
// needed — adding one would double the line.
// The station name is NOT repeated here: the sheet header
// (navigationTitle, inline) already shows it, and the address
// line below has any leading repeat of the name stripped, so
// a "Name" row only said it twice.
// The map, the address, the distance, any notices and the two
// station actions all share ONE card (the list's own row
// hairlines separate them), so no hand-rolled Divider here —
// adding one would double the line.
//
// The station name is NOT repeated: the sheet header already
// shows it.
//
// The whole row is a copy control: it puts JUST the postcode
// on the clipboard so it can be pasted into another
// navigation app. The copy glyph sits ON the address line,
// and the "Copied" confirmation is a floating capsule over
// the map's bottom edge (see the map overlay above), so the
// confirmation can never reflow this row.
// and the "Copied" confirmation is a capsule that floats OVER
// 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 {
copyPostcode()
} label: {
@@ -195,6 +164,29 @@ struct StationDetailView: View {
? ""
: String(localized: "Copies the postcode",
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 {
let distance = FuelStore.stationDistanceDisplay(
station: station, userLat: location.lat, userLng: location.lng