Files
fuelboard/FuelBoard/StationsView.swift
T

141 lines
5.9 KiB
Swift

import SwiftUI
import WidgetKit
/// Stations tab — the main list with fuel picker, sort, station count limit.
struct StationsView: View {
let stations: [FuelStation]
let totalCount: Int
let isLoading: Bool
@Binding var selectedFuel: FuelType
@Binding var sortMode: SortMode
@Binding var stationLimit: Int
let cheapestPrice: Double?
let location: Coordinate?
let favouriteIDs: Set<String>
var onToggleFavourite: (FuelStation) -> Void = { _ in }
var onRefresh: () async -> Void = {}
var body: some View {
NavigationStack {
List {
Section {
if let location {
Text("\(sortMode == .closest ? "Closest" : "Cheapest") \(selectedFuel.displayName) within \(stationLimit) miles — tap a station for directions.")
.font(.footnote)
.foregroundStyle(.secondary)
} else {
Text("\(sortMode == .closest ? "Closest" : "Cheapest") \(selectedFuel.displayName) — tap a station for directions.")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
Section("Fuel type") {
Picker("Fuel type", selection: $selectedFuel) {
ForEach(FuelType.allCases) { fuel in
Text(fuel.displayName).tag(fuel)
}
}
.pickerStyle(.segmented)
.onChange(of: selectedFuel) { _, newValue in
FuelStore.saveSelectedFuel(newValue)
WidgetCenter.shared.reloadAllTimelines()
}
}
Section("Stations") {
Picker("Sort by", selection: $sortMode) {
ForEach(SortMode.allCases) { mode in
Text(mode.displayName).tag(mode)
}
}
.pickerStyle(.segmented)
.onChange(of: sortMode) { _, newValue in
FuelStore.saveSortMode(newValue)
}
.padding(.vertical, 2)
if isLoading {
HStack(spacing: 10) {
ProgressView()
Text("Fetching prices…")
}
} else if stations.isEmpty {
Text("No \(selectedFuel.displayName) stations found.")
.foregroundStyle(.secondary)
} else {
ForEach(Array(stations.enumerated()), id: \.element.id) { index, station in
StationRow(
station: station,
fuel: selectedFuel,
location: location,
cheapestPrice: cheapestPrice,
isTopResult: index == 0,
isFavourite: favouriteIDs.contains(station.id),
onToggleFavourite: { onToggleFavourite(station) }
)
}
}
if !stations.isEmpty {
Divider()
Picker("Within", selection: $stationLimit) {
ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in
Text("\(miles) miles").tag(miles)
}
}
.pickerStyle(.segmented)
.onChange(of: stationLimit) { _, newValue in
FuelStore.saveStationLimit(newValue)
}
.padding(.vertical, 2)
Text("\(totalCount) stations within \(stationLimit) miles")
.font(.caption2)
.foregroundStyle(.secondary)
}
}
Section("Key") {
HStack(spacing: 8) {
Circle().fill(.green).frame(width: 12, height: 12)
Text("Best value — within 1.5p of the cheapest")
.font(.caption)
}
HStack(spacing: 8) {
Circle().fill(.orange).frame(width: 12, height: 12)
Text("Okay — within 4p of the cheapest")
.font(.caption)
}
HStack(spacing: 8) {
Circle().fill(.red).frame(width: 12, height: 12)
Text("Pricey — more than 4p over the cheapest")
.font(.caption)
}
HStack(spacing: 8) {
Text("TOP")
.font(.caption2.bold())
.padding(.horizontal, 5)
.padding(.vertical, 1)
.background(Capsule().fill(.blue.opacity(0.15)))
.foregroundStyle(.blue)
Text("Top result for the current sort")
.font(.caption)
}
HStack(spacing: 8) {
Image(systemName: "star.fill")
.font(.caption2)
.foregroundStyle(.yellow)
Text("Star a station to add it to Favourites")
.font(.caption)
}
}
}
.refreshable {
// Manual override for the twice-a-day cache policy.
await onRefresh()
}
.navigationTitle("FuelBoard")
}
}
}