123 lines
4.5 KiB
Swift
123 lines
4.5 KiB
Swift
import AppIntents
|
|
import Foundation
|
|
|
|
// Per-widget configuration intent (iOS 17+). Every widget instance carries
|
|
// its OWN fuel + sort choices, edited directly on the home screen (long-press
|
|
// → Edit Widget), independent of what the app has selected. This is what
|
|
// allows several FuelBoard widgets to show different fuels/orderings side by
|
|
// side.
|
|
|
|
enum WidgetFuel: String, AppEnum, CaseIterable, Codable {
|
|
case e10
|
|
case e5
|
|
case diesel
|
|
|
|
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Fuel"
|
|
|
|
static var caseDisplayRepresentations: [WidgetFuel: DisplayRepresentation] = [
|
|
.e10: "Unleaded",
|
|
.e5: "Premium",
|
|
.diesel: "Diesel",
|
|
]
|
|
}
|
|
|
|
// Per-widget sort order. A String-backed AppEnum (NOT an AppEntity): the
|
|
// parameterSummary conditionals (When(\.$sort, .equalTo, …)) fall back to the
|
|
// otherwise branch for AppEntity parameters (iOS 17+ bug FB13263902), which
|
|
// hid the Favourite picker. String AppEnums match reliably. Tradeoff: the
|
|
// option set is static — "Favourites" always appears, and picking it with no
|
|
// favourites shows the empty state.
|
|
enum WidgetSort: String, AppEnum, CaseIterable, Codable {
|
|
case cheapest
|
|
case closest
|
|
case favourites
|
|
|
|
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Sort by"
|
|
|
|
static var caseDisplayRepresentations: [WidgetSort: DisplayRepresentation] = [
|
|
.cheapest: "Cheapest",
|
|
.closest: "Closest",
|
|
.favourites: "Favourites",
|
|
]
|
|
}
|
|
|
|
// Per-widget distance filter. Modelled as an AppEntity (not an AppEnum) so
|
|
// the option labels can mirror the app's units setting at render time: an
|
|
// AppEnum's caseDisplayRepresentations are STATIC, so the Edit-Widget picker
|
|
// would always show "5 miles / 10 miles / 15 miles" regardless of the app's
|
|
// km mode. An entity query resolves labels per value, so they can read the
|
|
// shared unit preference ("8 km" in km mode).
|
|
struct WidgetDistance: AppEntity, Identifiable, Hashable, Codable {
|
|
/// The distance in miles (5/10/15) — the persisted value. Labels are
|
|
/// unit-aware, the stored value stays in miles.
|
|
let id: Int
|
|
|
|
var miles: Int { id }
|
|
|
|
var displayRepresentation: DisplayRepresentation {
|
|
let unit = FuelStore.loadDistanceUnit()
|
|
let shown = unit.displayMiles(id)
|
|
return DisplayRepresentation(stringLiteral: "\(shown) \(unit.label(for: Double(shown)))")
|
|
}
|
|
|
|
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Distance"
|
|
static var defaultQuery = WidgetDistanceQuery()
|
|
}
|
|
|
|
struct WidgetDistanceQuery: EntityQuery {
|
|
func entities(for identifiers: [Int]) async throws -> [WidgetDistance] {
|
|
identifiers.map { WidgetDistance(id: $0) }
|
|
}
|
|
|
|
func suggestedEntities() async throws -> [WidgetDistance] {
|
|
FuelStore.stationRadiusOptions.map { WidgetDistance(id: $0) }
|
|
}
|
|
}
|
|
|
|
// The knobs both the medium-list and small-single widget intents expose, so
|
|
// one timeline provider can drive either.
|
|
protocol WidgetConfigValues {
|
|
var fuel: WidgetFuel { get }
|
|
var sort: WidgetSort { get }
|
|
var distance: WidgetDistance { get }
|
|
}
|
|
|
|
struct FuelBoardWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConfigValues {
|
|
static var title: LocalizedStringResource = "Fuel & Sort"
|
|
static var description = IntentDescription("Which fuel, ordering and search radius this widget shows.")
|
|
|
|
@Parameter(title: "Fuel", default: .e10)
|
|
var fuel: WidgetFuel
|
|
|
|
@Parameter(title: "Sort by", default: .cheapest)
|
|
var sort: WidgetSort
|
|
|
|
@Parameter(title: "Distance", default: WidgetDistance(id: 5))
|
|
var distance: WidgetDistance
|
|
|
|
/// Edit-Widget UI: the Distance picker only makes sense for Cheapest
|
|
/// ordering — Closest is inherently "nearest within range" and Favourites
|
|
/// is not radius-bound. Show it for Cheapest only; hide for both others.
|
|
static var parameterSummary: some ParameterSummary {
|
|
When(\.$sort, .equalTo, WidgetSort.cheapest) {
|
|
Summary("Show \(\.$fuel) by \(\.$sort) within \(\.$distance)") {
|
|
\.$fuel
|
|
\.$sort
|
|
\.$distance
|
|
}
|
|
} otherwise: {
|
|
When(\.$sort, .equalTo, WidgetSort.favourites) {
|
|
Summary("Show \(\.$fuel) favourites") {
|
|
\.$fuel
|
|
\.$sort
|
|
}
|
|
} otherwise: {
|
|
Summary("Show \(\.$fuel) by \(\.$sort)") {
|
|
\.$fuel
|
|
\.$sort
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|