Show full fetch in list (count picker respected); radius scope only for cheapest/RAG reference; row = logo | name/address/distance | £price + delta | star
This commit is contained in:
+23
-17
@@ -21,10 +21,11 @@ struct ContentView: View {
|
|||||||
@State private var locationManager = LocationManager()
|
@State private var locationManager = LocationManager()
|
||||||
@StateObject private var monitor = ProximityMonitor()
|
@StateObject private var monitor = ProximityMonitor()
|
||||||
|
|
||||||
/// Stations within the alert trigger radius of the current location, used
|
/// Stations within the alert trigger radius of the current location — used
|
||||||
/// as the pool for "cheapest" (RAG reference, TOP, sorting). Falls back to
|
/// ONLY as the "cheapest" reference (RAG, TOP, deltas), so the cheapest
|
||||||
/// the full fetch when location is unknown or the radius pool is empty
|
/// label matches what alerts would flag. The list itself shows the full
|
||||||
/// (rural areas) so the list is never blank.
|
/// fetch so the count picker works. Falls back to the full fetch when
|
||||||
|
/// location is unknown or the radius pool is empty.
|
||||||
private var radiusScopedStations: [FuelStation] {
|
private var radiusScopedStations: [FuelStation] {
|
||||||
guard let location else { return stations }
|
guard let location else { return stations }
|
||||||
let within = stations.filter {
|
let within = stations.filter {
|
||||||
@@ -42,7 +43,9 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var sortedStations: [FuelStation] {
|
private var sortedStations: [FuelStation] {
|
||||||
let available = radiusScopedStations.filter { $0.prices[selectedFuel] != nil }
|
// Full fetch pool — NOT radius-scoped, so the results count picker
|
||||||
|
// (10/25/50/75/100) is respected.
|
||||||
|
let available = stations.filter { $0.prices[selectedFuel] != nil }
|
||||||
switch sortMode {
|
switch sortMode {
|
||||||
case .closest:
|
case .closest:
|
||||||
guard let location else { return available.sorted { $0.prices[selectedFuel]! < $1.prices[selectedFuel]! } }
|
guard let location else { return available.sorted { $0.prices[selectedFuel]! < $1.prices[selectedFuel]! } }
|
||||||
@@ -224,7 +227,7 @@ struct StationRow: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
// Round brand logo (or generic fuel pump fallback)
|
// LEFT — round brand logo (or generic fuel pump fallback)
|
||||||
Group {
|
Group {
|
||||||
if let asset = station.brandImageName {
|
if let asset = station.brandImageName {
|
||||||
Image(asset)
|
Image(asset)
|
||||||
@@ -243,10 +246,9 @@ struct StationRow: View {
|
|||||||
.overlay(Circle().stroke(Color.primary.opacity(0.08), lineWidth: 1))
|
.overlay(Circle().stroke(Color.primary.opacity(0.08), lineWidth: 1))
|
||||||
.shadow(color: .black.opacity(0.08), radius: 2, y: 1)
|
.shadow(color: .black.opacity(0.08), radius: 2, y: 1)
|
||||||
|
|
||||||
// Name at natural width (does NOT fill the row — truncates);
|
// MIDDLE — name, full address, distance
|
||||||
// price + details stay on the row below, right-aligned.
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
VStack(alignment: .leading, spacing: 3) {
|
HStack(spacing: 6) {
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
|
||||||
Text(station.name)
|
Text(station.name)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
@@ -259,28 +261,32 @@ struct StationRow: View {
|
|||||||
.background(Capsule().fill(.blue.opacity(0.15)))
|
.background(Capsule().fill(.blue.opacity(0.15)))
|
||||||
.foregroundStyle(.blue)
|
.foregroundStyle(.blue)
|
||||||
}
|
}
|
||||||
Spacer(minLength: 6)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 5) {
|
|
||||||
Text("\(station.address), \(station.postcode)")
|
Text("\(station.address), \(station.postcode)")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
Spacer(minLength: 8)
|
.truncationMode(.tail)
|
||||||
if let location {
|
if let location {
|
||||||
Text(String(format: "%.1f km", station.distanceKM(to: location.lat, lng2: location.lng)))
|
Text(String(format: "%.1f km", station.distanceKM(to: location.lat, lng2: location.lng)))
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
// RIGHT — price in £ (no p) + difference vs cheapest
|
||||||
|
VStack(alignment: .trailing, spacing: 2) {
|
||||||
if let price = station.prices[fuel] {
|
if let price = station.prices[fuel] {
|
||||||
|
HStack(spacing: 5) {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(ragColor)
|
.fill(ragColor)
|
||||||
.frame(width: 10, height: 10)
|
.frame(width: 8, height: 8)
|
||||||
Text(String(format: "%.1fp", price))
|
Text(String(format: "£%.3f", price / 100))
|
||||||
.font(.title3.bold())
|
.font(.title3.bold())
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
|
}
|
||||||
if let deltaText {
|
if let deltaText {
|
||||||
Text(deltaText)
|
Text(deltaText)
|
||||||
.font(.caption2.bold())
|
.font(.caption2.bold())
|
||||||
@@ -288,8 +294,8 @@ struct StationRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// FAR RIGHT — favourite star
|
||||||
Button {
|
Button {
|
||||||
onToggleFavourite()
|
onToggleFavourite()
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
Reference in New Issue
Block a user