Add Worth the Drive fuel savings with road-routed estimates
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.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user