Fix small widget skeleton: use the proven medium intent; delete failing small intent
ROOT CAUSE (proven by A/B test on-device): the small widget's custom intent (FuelBoardSmallWidgetConfigurationIntent with favouriteFuel + favourite AppEntity params) fails fresh-widget default-config resolution at the system level — the whole default config fails, no timeline is ever requested, makeEntry never runs, widget stays on the skeleton. A minimal small widget sharing the MEDIUM intent (fuel/sort/distance only) populated immediately in the same extension on the same device. Neither removing defaultResult() (4d35b08) nor the @IntentParameterDependency (8a9e775) helped — the extra entity params themselves break resolution. FIX: FuelPriceSmallWidget now uses FuelBoardWidgetConfigurationIntent exactly like the working test widget. FuelBoardSmallWidgetConfiguration Intent, FavouriteFuel(+Query), WidgetFavourite(+Query) deleted; the pinned-favourite picker is gone (approved tradeoff). Favourites sort still works on both faces via the list branch — the small face renders the cheapest favourite of the chosen fuel. Beacon tag distinguishes the small widget in diagnostics; test widget removed, gallery order restored (small, medium, live activity).
This commit is contained in:
@@ -52,7 +52,7 @@ struct SettingsView: View {
|
|||||||
@State private var installedWidgets: String = ""
|
@State private var installedWidgets: String = ""
|
||||||
|
|
||||||
private func refreshWidgetDiag() {
|
private func refreshWidgetDiag() {
|
||||||
smallWidgetDiag = FuelStore.loadWidgetDiag(intentType: "FuelBoardSmallWidgetConfigurationIntent")
|
smallWidgetDiag = FuelStore.loadWidgetDiag(intentType: "FuelBoardSmallWidget")
|
||||||
mediumWidgetDiag = FuelStore.loadWidgetDiag(intentType: "FuelBoardWidgetConfigurationIntent")
|
mediumWidgetDiag = FuelStore.loadWidgetDiag(intentType: "FuelBoardWidgetConfigurationIntent")
|
||||||
WidgetCenter.shared.getCurrentConfigurations { result in
|
WidgetCenter.shared.getCurrentConfigurations { result in
|
||||||
let text: String
|
let text: String
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct FuelBoardWidgetsBundle: WidgetBundle {
|
struct FuelBoardWidgetsBundle: WidgetBundle {
|
||||||
var body: some Widget {
|
var body: some Widget {
|
||||||
// Gallery order follows registration order. The A/B test widget is
|
// Gallery order follows registration order: small first, then medium.
|
||||||
// first so it's easy to find; remove it once the small-widget root
|
|
||||||
// cause is confirmed.
|
|
||||||
FuelBoardTestSmallWidget()
|
|
||||||
FuelPriceSmallWidget()
|
FuelPriceSmallWidget()
|
||||||
FuelPriceWidget()
|
FuelPriceWidget()
|
||||||
FuelBoardLiveActivity()
|
FuelBoardLiveActivity()
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import AppIntents
|
|||||||
// FuelPriceWidget (medium): top 3 stations with price + distance, each row
|
// FuelPriceWidget (medium): top 3 stations with price + distance, each row
|
||||||
// opens Maps. Per-widget fuel + sort (+ distance for Cheapest).
|
// opens Maps. Per-widget fuel + sort (+ distance for Cheapest).
|
||||||
// FuelPriceSmallWidget (small): single station, whole widget opens Maps.
|
// FuelPriceSmallWidget (small): single station, whole widget opens Maps.
|
||||||
// Same knobs, plus a Favourite picker when Sort = Favourites (a small face
|
// Same knobs as the medium — the small face shows the first of the
|
||||||
// shows ONE station, so you choose which favourite to pin; the medium face
|
// configured result (cheapest/closest station, or the cheapest favourite
|
||||||
// lists all favourites and has no picker).
|
// of the chosen fuel in Favourites mode).
|
||||||
// Taps deep-link to Apple Maps directions (maps://?daddr=). On the Home
|
// Taps deep-link to Apple Maps directions (maps://?daddr=). On the Home
|
||||||
// Screen the system either opens Maps directly or delivers the URL to
|
// Screen the system either opens Maps directly or delivers the URL to
|
||||||
// FuelBoard, whose onOpenURL forwards it (and also still handles legacy
|
// FuelBoard, whose onOpenURL forwards it (and also still handles legacy
|
||||||
@@ -60,46 +60,22 @@ struct FuelPriceWidget: Widget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small widget — exactly ONE station. Favourites sort gains a picker to choose
|
// Small widget — exactly ONE station (the first of the configured result).
|
||||||
// WHICH favourite to pin, because a small face can show only one (the medium
|
// Shares the medium widget's intent: the small intent with extra favourite
|
||||||
// face lists them all, so the picker exists only here). Separate kind keeps
|
// AppEntity params failed fresh-widget default-config resolution at the
|
||||||
// the extra parameter off the list widget's Edit-Widget sheet.
|
// system level (whole default config failed → no timeline → skeleton; proven
|
||||||
|
// by an A/B test where a small widget using this intent populated while the
|
||||||
|
// custom-intent small widget never even reached makeEntry). Favourites sort
|
||||||
|
// on the small face shows the cheapest favourite of the chosen fuel.
|
||||||
struct FuelPriceSmallWidget: Widget {
|
struct FuelPriceSmallWidget: Widget {
|
||||||
let kind = "FuelPriceWidgetSmall"
|
let kind = "FuelPriceWidgetSmall"
|
||||||
|
|
||||||
var body: some WidgetConfiguration {
|
|
||||||
AppIntentConfiguration(
|
|
||||||
kind: kind,
|
|
||||||
intent: FuelBoardSmallWidgetConfigurationIntent.self,
|
|
||||||
provider: FuelPriceTimelineProvider<FuelBoardSmallWidgetConfigurationIntent>()
|
|
||||||
) { entry in
|
|
||||||
FuelPriceWidgetView(entry: entry)
|
|
||||||
.containerBackground(for: .widget) {
|
|
||||||
Color(.systemBackground)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.configurationDisplayName("FuelBoard Favourite")
|
|
||||||
.description("One pinned favourite station with its price.")
|
|
||||||
.supportedFamilies([.systemSmall])
|
|
||||||
.disfavoredLocations([.carPlay], for: [.systemSmall])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A/B TEST widget (temporary): a from-scratch SMALL widget driven by the
|
|
||||||
// PROVEN medium intent — no favourite params, no dependency, no custom
|
|
||||||
// query. If this populates while FuelPriceSmallWidget skeletons, the small
|
|
||||||
// SIZE is fine and the small intent's extra machinery is the culprit. If it
|
|
||||||
// ALSO skeletons, the failure is at the small-family/system level. Removed
|
|
||||||
// once the root cause is confirmed.
|
|
||||||
struct FuelBoardTestSmallWidget: Widget {
|
|
||||||
let kind = "FuelBoardTestSmallWidget"
|
|
||||||
|
|
||||||
var body: some WidgetConfiguration {
|
var body: some WidgetConfiguration {
|
||||||
AppIntentConfiguration(
|
AppIntentConfiguration(
|
||||||
kind: kind,
|
kind: kind,
|
||||||
intent: FuelBoardWidgetConfigurationIntent.self,
|
intent: FuelBoardWidgetConfigurationIntent.self,
|
||||||
provider: FuelPriceTimelineProvider<FuelBoardWidgetConfigurationIntent>(
|
provider: FuelPriceTimelineProvider<FuelBoardWidgetConfigurationIntent>(
|
||||||
beaconTag: "FuelBoardTestSmallWidget"
|
beaconTag: "FuelBoardSmallWidget"
|
||||||
)
|
)
|
||||||
) { entry in
|
) { entry in
|
||||||
FuelPriceWidgetView(entry: entry)
|
FuelPriceWidgetView(entry: entry)
|
||||||
@@ -107,8 +83,8 @@ struct FuelBoardTestSmallWidget: Widget {
|
|||||||
Color(.systemBackground)
|
Color(.systemBackground)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.configurationDisplayName("FuelBoard Small Test")
|
.configurationDisplayName("FuelBoard Small")
|
||||||
.description("A/B test: minimal small widget using the proven medium intent.")
|
.description("Fuel prices near you. Configure fuel + sort per widget.")
|
||||||
.supportedFamilies([.systemSmall])
|
.supportedFamilies([.systemSmall])
|
||||||
.disfavoredLocations([.carPlay], for: [.systemSmall])
|
.disfavoredLocations([.carPlay], for: [.systemSmall])
|
||||||
}
|
}
|
||||||
@@ -239,54 +215,16 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
|||||||
let favourites = FuelStore.loadFavourites()
|
let favourites = FuelStore.loadFavourites()
|
||||||
.filter { $0.station.prices[$0.fuel] != nil }
|
.filter { $0.station.prices[$0.fuel] != nil }
|
||||||
|
|
||||||
// Small widget, pinned favourite: exactly ONE station, its own
|
// Pinned stations for THIS fuel, cheapest-first. Not
|
||||||
// fuel, no radius. Freshen its price when a focused fetch covers
|
// radius-bound — a favourite in Edinburgh shows on a widget in
|
||||||
// it. The chosen favourite wins; when it's missing or stale (fresh
|
|
||||||
// widget, favourite removed, fuel row changed) fall back to the
|
|
||||||
// favourites fuel's first favourite so the face is never blank.
|
|
||||||
if let chosen = configuration.favouriteChoice {
|
|
||||||
let favouriteFuelType = configuration.favouriteFuelChoice?.fuel ?? fuel
|
|
||||||
// Fresh widget / static default (empty pinned favourite):
|
|
||||||
// auto-populate to the FIRST favourite of the fuel. This is
|
|
||||||
// the sheet's old defaultResult() auto-populate, moved to
|
|
||||||
// entry time so default-config resolution stays keychain-free
|
|
||||||
// (storage-backed defaultResult() stranding fresh small
|
|
||||||
// widgets on the skeleton placeholder).
|
|
||||||
let pinned: FavouriteEntry?
|
|
||||||
if chosen.stationID.isEmpty {
|
|
||||||
pinned = favourites
|
|
||||||
.filter { $0.fuel == favouriteFuelType && $0.station.prices[favouriteFuelType] != nil }
|
|
||||||
.first
|
|
||||||
} else {
|
|
||||||
pinned = favourites.first { $0.id == chosen.id }
|
|
||||||
?? favourites
|
|
||||||
.filter { $0.fuel == favouriteFuelType && $0.station.prices[favouriteFuelType] != nil }
|
|
||||||
.min { lhs, rhs in
|
|
||||||
(lhs.station.prices[favouriteFuelType] ?? .infinity) <
|
|
||||||
(rhs.station.prices[favouriteFuelType] ?? .infinity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let pinned {
|
|
||||||
var station = pinned.station
|
|
||||||
if let location,
|
|
||||||
let fetched = await Self.fetchFocused(near: location, fuel: pinned.fuel, radiusKM: radiusKM),
|
|
||||||
let fresh = fetched.first(where: { $0.id == pinned.station.id }) {
|
|
||||||
station = fresh
|
|
||||||
}
|
|
||||||
return FuelPriceEntry(
|
|
||||||
date: Date(), stations: [station],
|
|
||||||
fuel: pinned.fuel, sort: .cheapest, isFavourites: true,
|
|
||||||
location: location, locationSource: source, unit: unit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// List mode (medium): pinned stations for THIS fuel, cheapest-first.
|
|
||||||
// Not radius-bound — a favourite in Edinburgh shows on a widget in
|
|
||||||
// London. Prices come from the keychain snapshot (the app
|
// London. Prices come from the keychain snapshot (the app
|
||||||
// refreshes favourite prices into keychain after every fetch), or
|
// refreshes favourite prices into keychain after every fetch), or
|
||||||
// fresher from a focused relay fetch when a favourite happens to
|
// fresher from a focused relay fetch when a favourite happens to
|
||||||
// be in range.
|
// be in range. The small face renders the first of this list —
|
||||||
|
// the cheapest favourite of the chosen fuel. (The old per-widget
|
||||||
|
// pinned-favourite picker was removed: its AppEntity params made
|
||||||
|
// fresh-widget default-config resolution fail at the system level
|
||||||
|
// — see FuelPriceSmallWidget.)
|
||||||
let fuelFavourites = favourites
|
let fuelFavourites = favourites
|
||||||
.filter { $0.fuel == fuel && $0.station.prices[fuel] != nil }
|
.filter { $0.fuel == fuel && $0.station.prices[fuel] != nil }
|
||||||
.map(\.station)
|
.map(\.station)
|
||||||
|
|||||||
@@ -74,151 +74,12 @@ struct WidgetDistanceQuery: EntityQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fuel for the favourites face (small widget, Favourites sort). Modelled as an
|
|
||||||
// AppEntity so only fuels that ACTUALLY have favourites appear in the picker —
|
|
||||||
// mirrors the app's Favourites tab capsules. (The Favourite picker itself is
|
|
||||||
// fuel-independent now — see WidgetFavouriteQuery.)
|
|
||||||
struct FavouriteFuel: AppEntity, Identifiable, Hashable, Codable {
|
|
||||||
let fuel: FuelType
|
|
||||||
|
|
||||||
var id: String { fuel.rawValue }
|
|
||||||
|
|
||||||
var displayRepresentation: DisplayRepresentation {
|
|
||||||
DisplayRepresentation(stringLiteral: fuel.displayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Fuel"
|
|
||||||
static var defaultQuery = FavouriteFuelQuery()
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FavouriteFuelQuery: EntityQuery {
|
|
||||||
// LENIENT resolution: every valid fuel resolves, even one without
|
|
||||||
// favourites. The timeline request resolves the FULL configuration
|
|
||||||
// through these queries, and a nil result fails the whole timeline — a
|
|
||||||
// default-config widget would hang on the skeleton placeholder.
|
|
||||||
//
|
|
||||||
// NO defaultResult(): fresh/default configs resolve from the static
|
|
||||||
// @Parameter(default:) value through entities(for:) with ZERO storage
|
|
||||||
// reads. The old keychain-backed defaultResult() put storage access in
|
|
||||||
// the gallery-preview/fresh-add resolution path, which failed on-device
|
|
||||||
// (small widget stranded on the skeleton; configured widgets were fine).
|
|
||||||
// Auto-populate for the SHEET moved to entry time (makeEntry picks the
|
|
||||||
// first favourite of the fuel); picker options stay in suggestedEntities().
|
|
||||||
func entities(for identifiers: [String]) async throws -> [FavouriteFuel] {
|
|
||||||
identifiers.compactMap { FuelType(rawValue: $0) }.map { FavouriteFuel(fuel: $0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
func suggestedEntities() async throws -> [FavouriteFuel] {
|
|
||||||
let fuels = Set(FuelStore.loadFavourites().map(\.fuel))
|
|
||||||
return FuelType.allCases.filter { fuels.contains($0) }.map { FavouriteFuel(fuel: $0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A pinned favourite station, selectable on SMALL widgets (which show a single
|
|
||||||
// station). Reuses FavouriteEntry's id scheme ("fuel|stationID") so the
|
|
||||||
// provider can resolve the choice straight back to a stored favourite. Only
|
|
||||||
// surfaced when Sort = Favourites.
|
|
||||||
//
|
|
||||||
// The display name is EMBEDDED in the entity (stationName): the config sheet
|
|
||||||
// can resolve entities in a process where keychain/app-group storage is
|
|
||||||
// unavailable, and a storage-backed lookup there made every picker row fall
|
|
||||||
// back to the "Favourite" placeholder. With the name carried on the value,
|
|
||||||
// rows render with no storage read at all.
|
|
||||||
struct WidgetFavourite: AppEntity, Identifiable, Hashable, Codable {
|
|
||||||
let fuel: FuelType
|
|
||||||
let stationID: String
|
|
||||||
/// Station display name, embedded so picker rows render storage-free.
|
|
||||||
let stationName: String
|
|
||||||
|
|
||||||
var id: String { "\(fuel.rawValue)|\(stationID)" }
|
|
||||||
|
|
||||||
var displayRepresentation: DisplayRepresentation {
|
|
||||||
if !stationName.isEmpty {
|
|
||||||
return DisplayRepresentation(stringLiteral: stationName)
|
|
||||||
}
|
|
||||||
// Id-only entity (e.g. a stored default): best-effort storage lookup.
|
|
||||||
let favourites = FuelStore.loadFavourites()
|
|
||||||
guard let entry = favourites.first(where: { $0.id == id }) else {
|
|
||||||
return DisplayRepresentation(stringLiteral: "Favourite")
|
|
||||||
}
|
|
||||||
return DisplayRepresentation(stringLiteral: entry.station.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Favourite"
|
|
||||||
static var defaultQuery = WidgetFavouriteQuery()
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WidgetFavouriteQuery: EntityQuery {
|
|
||||||
// NO @IntentParameterDependency: the dependent-picker machinery resolves
|
|
||||||
// the favourite parameter through the (hidden, in Cheapest mode)
|
|
||||||
// favouriteFuel parameter during fresh-widget default-config resolution,
|
|
||||||
// and that resolution fails on-device — the whole default config fails
|
|
||||||
// and the small widget strands on the skeleton placeholder. The picker
|
|
||||||
// now lists ALL favourites, each row labelled with its fuel; the
|
|
||||||
// fuel-scoped filter was the dependency's only purpose. Configured
|
|
||||||
// Favourites-mode widgets are unaffected (stored configs skip the
|
|
||||||
// default resolution path entirely).
|
|
||||||
|
|
||||||
// LENIENT resolution: every stored identifier resolves to a
|
|
||||||
// WidgetFavourite (best-effort name lookup). NEVER return nil here — the
|
|
||||||
// timeline request resolves the whole configuration through this query
|
|
||||||
// and a failed resolution strands a default-config widget on the skeleton
|
|
||||||
// placeholder.
|
|
||||||
//
|
|
||||||
// NO defaultResult(): fresh/default configs resolve from the static
|
|
||||||
// @Parameter(default:) value (empty id "e10|") with NO storage reads —
|
|
||||||
// the old keychain-backed defaultResult() ran in the gallery-preview /
|
|
||||||
// fresh-add resolution path and failed on-device. The sheet's
|
|
||||||
// auto-populate moved to entry time (makeEntry pins the first favourite
|
|
||||||
// of the fuel); the dependent picker keeps working via suggestedEntities().
|
|
||||||
func entities(for identifiers: [String]) async throws -> [WidgetFavourite] {
|
|
||||||
return identifiers.map { id in
|
|
||||||
let parts = id.split(separator: "|", maxSplits: 1)
|
|
||||||
let fuel = FuelType(rawValue: String(parts.first ?? "")) ?? .e10
|
|
||||||
let stationID = String(parts.last ?? "")
|
|
||||||
// Id-only value (fresh widget's static default): resolve with
|
|
||||||
// zero keychain access. displayRepresentation falls back to a
|
|
||||||
// storage lookup if the name is empty, so stored picks still
|
|
||||||
// render their name.
|
|
||||||
guard !stationID.isEmpty else {
|
|
||||||
return WidgetFavourite(fuel: fuel, stationID: stationID, stationName: "")
|
|
||||||
}
|
|
||||||
let favourites = FuelStore.loadFavourites()
|
|
||||||
let entry = favourites.first { $0.id == id }
|
|
||||||
return WidgetFavourite(
|
|
||||||
fuel: fuel,
|
|
||||||
stationID: stationID,
|
|
||||||
stationName: entry?.station.name ?? ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func suggestedEntities() async throws -> [WidgetFavourite] {
|
|
||||||
// All favourites, each labelled with its fuel (no dependency available
|
|
||||||
// to filter by the chosen fuel — see the note above).
|
|
||||||
FuelStore.loadFavourites().map { entry in
|
|
||||||
WidgetFavourite(
|
|
||||||
fuel: entry.fuel,
|
|
||||||
stationID: entry.station.id,
|
|
||||||
stationName: "\(entry.fuel.displayName) · \(entry.station.name)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The knobs both the medium-list and small-single widget intents expose, so
|
// The knobs both the medium-list and small-single widget intents expose, so
|
||||||
// one timeline provider can drive either.
|
// one timeline provider can drive either.
|
||||||
protocol WidgetConfigValues {
|
protocol WidgetConfigValues {
|
||||||
var fuel: WidgetFuel { get }
|
var fuel: WidgetFuel { get }
|
||||||
var sort: WidgetSort { get }
|
var sort: WidgetSort { get }
|
||||||
var distance: WidgetDistance { get }
|
var distance: WidgetDistance { get }
|
||||||
/// The pinned favourite (small widget, Favourites sort) — nil when the
|
|
||||||
/// widget lists favourites instead (medium).
|
|
||||||
var favouriteChoice: WidgetFavourite? { get }
|
|
||||||
/// The favourites-face fuel (small widget, Favourites sort) — its options
|
|
||||||
/// are the fuels that have favourites, and it dictates which favourites
|
|
||||||
/// the Favourite picker offers. Nil on the medium widget.
|
|
||||||
var favouriteFuelChoice: FavouriteFuel? { get }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FuelBoardWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConfigValues {
|
struct FuelBoardWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConfigValues {
|
||||||
@@ -234,11 +95,6 @@ struct FuelBoardWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConf
|
|||||||
@Parameter(title: "Distance", default: WidgetDistance(id: 5))
|
@Parameter(title: "Distance", default: WidgetDistance(id: 5))
|
||||||
var distance: WidgetDistance
|
var distance: WidgetDistance
|
||||||
|
|
||||||
// Medium/list widgets show ALL favourites for the chosen fuel — no
|
|
||||||
// pinned-favourite knob, no favourites-scoped fuel.
|
|
||||||
var favouriteChoice: WidgetFavourite? { nil }
|
|
||||||
var favouriteFuelChoice: FavouriteFuel? { nil }
|
|
||||||
|
|
||||||
/// Edit-Widget UI: the Distance picker only makes sense for Cheapest
|
/// Edit-Widget UI: the Distance picker only makes sense for Cheapest
|
||||||
/// ordering — Closest is inherently "nearest within range" and Favourites
|
/// ordering — Closest is inherently "nearest within range" and Favourites
|
||||||
/// is not radius-bound. Show it for Cheapest only; hide for both others.
|
/// is not radius-bound. Show it for Cheapest only; hide for both others.
|
||||||
@@ -264,61 +120,3 @@ struct FuelBoardWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMALL widget intent: same knobs as the list widget, PLUS a pinned-favourite
|
|
||||||
// picker for Favourites sort (a small widget shows exactly ONE station, so the
|
|
||||||
// user chooses which favourite). Favourites mode swaps the Fuel row for a
|
|
||||||
// favourites-scoped one — only fuels with favourites are offered. The
|
|
||||||
// pinned favourite wins when set; the fuel row picks the default favourite on
|
|
||||||
// a fresh widget. Distance stays hidden (a pinned station has no radius).
|
|
||||||
struct FuelBoardSmallWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConfigValues {
|
|
||||||
static var title: LocalizedStringResource = "Fuel & Sort"
|
|
||||||
static var description = IntentDescription("Which fuel, ordering, radius and pinned favourite this small widget shows.")
|
|
||||||
|
|
||||||
// Declared in display order for EVERY summary branch: iOS may fall back to
|
|
||||||
// declaration order when re-rendering the sheet after a parameter change,
|
|
||||||
// so the relative order here must match the wanted layout in all modes:
|
|
||||||
// Favourites → Fuel(favouriteFuel) · Sort by · Favourite
|
|
||||||
// Cheapest → Fuel · Sort by · Distance
|
|
||||||
// Closest → Fuel · Sort by
|
|
||||||
@Parameter(title: "Fuel", default: FavouriteFuel(fuel: .e10))
|
|
||||||
var favouriteFuel: FavouriteFuel
|
|
||||||
|
|
||||||
@Parameter(title: "Fuel", default: .e10)
|
|
||||||
var fuel: WidgetFuel
|
|
||||||
|
|
||||||
@Parameter(title: "Sort by", default: .cheapest)
|
|
||||||
var sort: WidgetSort
|
|
||||||
|
|
||||||
@Parameter(title: "Favourite", default: WidgetFavourite(fuel: .e10, stationID: "", stationName: ""))
|
|
||||||
var favourite: WidgetFavourite
|
|
||||||
|
|
||||||
@Parameter(title: "Distance", default: WidgetDistance(id: 5))
|
|
||||||
var distance: WidgetDistance
|
|
||||||
|
|
||||||
var favouriteChoice: WidgetFavourite? { favourite }
|
|
||||||
var favouriteFuelChoice: FavouriteFuel? { favouriteFuel }
|
|
||||||
|
|
||||||
static var parameterSummary: some ParameterSummary {
|
|
||||||
When(\.$sort, .equalTo, WidgetSort.favourites) {
|
|
||||||
Summary("Show \(\.$favouriteFuel) favourite \(\.$favourite)") {
|
|
||||||
\.$favouriteFuel
|
|
||||||
\.$sort
|
|
||||||
\.$favourite
|
|
||||||
}
|
|
||||||
} otherwise: {
|
|
||||||
When(\.$sort, .equalTo, WidgetSort.cheapest) {
|
|
||||||
Summary("Show \(\.$fuel) by \(\.$sort) within \(\.$distance)") {
|
|
||||||
\.$fuel
|
|
||||||
\.$sort
|
|
||||||
\.$distance
|
|
||||||
}
|
|
||||||
} otherwise: {
|
|
||||||
Summary("Show \(\.$fuel) by \(\.$sort)") {
|
|
||||||
\.$fuel
|
|
||||||
\.$sort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user