Optional Fuel Savings feature (off by default): vehicle MPG in UK imperial gallons, fill amount in pounds or litres, persisted through the existing keychain/app-group store. Ranks the WHOLE station pool by estimated net saving (fuel-price saving at each pump minus the extra return-trip driving cost) using MapKit road distances with a straight-line fallback marked (est.). The closest station with a valid price for the selected fuel is the comparison baseline and is labelled "Closest station"; other rows read "Save £X.XX after travel" / "£X.XX worse off after travel" / "Break even". Labels live in the shared model so they are unit-tested. The TOP badge still marks the cheapest pump price and reads "CHEAPEST" when the list is ranked by net saving. Road routing now prioritises the Worth-the-Drive candidates plus the baseline and merges cache entries instead of replacing them, so far-out stations get real routes rather than permanent straight-line estimates. Numeric fields gain a Done button above the keypad (decimalPad has no Return key); the fill-up control is hidden unless the feature is configured; Favourites shares one session fill amount with the station list. 131 tests.
42 lines
1.7 KiB
Swift
42 lines
1.7 KiB
Swift
import SwiftUI
|
|
import UIKit
|
|
|
|
extension View {
|
|
/// Puts a Done button above the keyboard.
|
|
///
|
|
/// The numeric fields (Settings → Average fuel economy and Typical
|
|
/// fill-up, and the Worth the Drive fill-up box in the station list) use
|
|
/// `.decimalPad`, which has NO Return key. Without this there is no way to
|
|
/// dismiss the keyboard once such a field is tapped, and a
|
|
/// `TextField(value:format:)` only commits the typed value when editing
|
|
/// ends — so the number never stuck either.
|
|
///
|
|
/// Apply ONCE per screen, to the list or form containing the fields: the
|
|
/// keyboard toolbar belongs to the focused field, so a single attachment
|
|
/// covers every field inside it. Attaching it to each field would show
|
|
/// several Done buttons at once.
|
|
///
|
|
/// - Parameter action: clears the screen's focus state. Prefer this where a
|
|
/// `@FocusState` exists; the default resigns the current first responder,
|
|
/// which works for fields inside subviews that have no focus binding.
|
|
func keyboardDoneButton(action: (() -> Void)? = nil) -> some View {
|
|
toolbar {
|
|
ToolbarItemGroup(placement: .keyboard) {
|
|
Spacer()
|
|
Button("Done") {
|
|
if let action {
|
|
action()
|
|
} else {
|
|
UIApplication.shared.sendAction(
|
|
#selector(UIResponder.resignFirstResponder),
|
|
to: nil,
|
|
from: nil,
|
|
for: nil
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|