Widgets: merge small+medium into one kind (both sizes, one gallery entry)
This commit is contained in:
@@ -4,8 +4,9 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct FuelBoardWidgetsBundle: WidgetBundle {
|
struct FuelBoardWidgetsBundle: WidgetBundle {
|
||||||
var body: some Widget {
|
var body: some Widget {
|
||||||
// Gallery order follows registration order: small first, then medium.
|
// ONE widget kind (both sizes) first — the iOS long-press app-icon
|
||||||
FuelPriceSmallWidget()
|
// menu shows the FIRST-registered kind's sizes; this kind supports
|
||||||
|
// .systemSmall + .systemMedium so both tiles appear.
|
||||||
FuelPriceWidget()
|
FuelPriceWidget()
|
||||||
FuelBoardLiveActivity()
|
FuelBoardLiveActivity()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ private extension FuelType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FuelBoard widget — petrol stations near you.
|
// FuelBoard widget — petrol stations near you. ONE widget kind, TWO sizes:
|
||||||
// FuelPriceWidget (medium): top 3 stations with price + distance, each row
|
// .systemSmall: single station (the first of the configured result — the
|
||||||
// opens Maps. Per-widget fuel + sort (+ distance for Cheapest).
|
// cheapest/closest station, or the cheapest favourite of the chosen fuel
|
||||||
// FuelPriceSmallWidget (small): single station, whole widget opens Maps.
|
// in Favourites mode); the whole face opens Maps.
|
||||||
// Same knobs as the medium — the small face shows the first of the
|
// .systemMedium: top 3 stations with price + distance, each row opens Maps.
|
||||||
// configured result (cheapest/closest station, or the cheapest favourite
|
// Per-widget fuel + sort (+ distance for Cheapest) via one config intent.
|
||||||
// of the chosen fuel in Favourites mode).
|
// The view branches on widgetFamily, not kind — the small face renders the
|
||||||
|
// first station, the medium face the list.
|
||||||
// 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
|
||||||
@@ -49,6 +50,13 @@ private extension FuelType {
|
|||||||
// (no permission yet / timeout) it falls back to the app's cached location,
|
// (no permission yet / timeout) it falls back to the app's cached location,
|
||||||
// then to price-only sorting. So the widget is location-driven the moment the
|
// then to price-only sorting. So the widget is location-driven the moment the
|
||||||
// app has been opened once and permission granted.
|
// app has been opened once and permission granted.
|
||||||
|
//
|
||||||
|
// History: a separate "small" kind existed (FuelPriceWidgetSmall) and was
|
||||||
|
// merged here on 2026-08-14. The small kind had gained .systemMedium so the
|
||||||
|
// iOS long-press app-icon menu (which shows only the FIRST-registered kind's
|
||||||
|
// sizes) would offer both tiles — but that duplicated the medium gallery
|
||||||
|
// entry. Merging keeps both size tiles in the icon menu AND one gallery entry
|
||||||
|
// with both sizes.
|
||||||
|
|
||||||
struct FuelPriceWidget: Widget {
|
struct FuelPriceWidget: Widget {
|
||||||
let kind = "FuelPriceWidget"
|
let kind = "FuelPriceWidget"
|
||||||
@@ -70,49 +78,8 @@ struct FuelPriceWidget: Widget {
|
|||||||
}
|
}
|
||||||
.configurationDisplayName("FuelBoard Prices")
|
.configurationDisplayName("FuelBoard Prices")
|
||||||
.description("Fuel prices near you. Configure fuel + sort per widget.")
|
.description("Fuel prices near you. Configure fuel + sort per widget.")
|
||||||
.supportedFamilies([.systemMedium])
|
|
||||||
.disfavoredLocations([.carPlay], for: [.systemMedium])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Small widget — exactly ONE station (the first of the configured result)
|
|
||||||
// when rendered at systemSmall; the SAME kind also supports .systemMedium,
|
|
||||||
// where the shared view renders the station list (identical content to the
|
|
||||||
// medium widget — the view branches on family, not kind).
|
|
||||||
//
|
|
||||||
// WHY medium is here (2026-08-14): the iOS 18+ long-press app-icon menu
|
|
||||||
// ("turn app icon into a widget") shows the supported SIZES of the
|
|
||||||
// FIRST-registered widget kind only — it does NOT list widget kinds, and it
|
|
||||||
// is NOT keyed by configuration-intent type (a two-intent experiment,
|
|
||||||
// 6375af4, changed nothing on-device). So the bundle's first kind decides
|
|
||||||
// which size tiles the icon menu offers: small-only meant the menu showed
|
|
||||||
// only the small tile even though the app has a medium widget. Giving this
|
|
||||||
// first-registered kind BOTH .systemSmall and .systemMedium puts both size
|
|
||||||
// tiles in the icon menu (ChatGPT/Safari show multiple sizes the same way —
|
|
||||||
// their first kind supports several families). The gallery still lists both
|
|
||||||
// widget kinds in bundle order (small, then medium); this kind's entry just
|
|
||||||
// gains a medium variant. Favourites sort on the small face shows the
|
|
||||||
// cheapest favourite of the chosen fuel.
|
|
||||||
struct FuelPriceSmallWidget: Widget {
|
|
||||||
let kind = "FuelPriceWidgetSmall"
|
|
||||||
|
|
||||||
var body: some WidgetConfiguration {
|
|
||||||
AppIntentConfiguration(
|
|
||||||
kind: kind,
|
|
||||||
intent: FuelBoardSmallWidgetConfigurationIntent.self,
|
|
||||||
provider: FuelPriceTimelineProvider<FuelBoardSmallWidgetConfigurationIntent>(
|
|
||||||
beaconTag: "FuelBoardSmallWidget"
|
|
||||||
)
|
|
||||||
) { entry in
|
|
||||||
FuelPriceWidgetView(entry: entry)
|
|
||||||
.containerBackground(for: .widget) {
|
|
||||||
Color(.systemBackground)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.configurationDisplayName("FuelBoard Small")
|
|
||||||
.description("Fuel prices near you. Configure fuel + sort per widget.")
|
|
||||||
.supportedFamilies([.systemSmall, .systemMedium])
|
.supportedFamilies([.systemSmall, .systemMedium])
|
||||||
.disfavoredLocations([.carPlay], for: [.systemSmall])
|
.disfavoredLocations([.carPlay], for: [.systemSmall, .systemMedium])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +217,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
|||||||
// the cheapest favourite of the chosen fuel. (The old per-widget
|
// the cheapest favourite of the chosen fuel. (The old per-widget
|
||||||
// pinned-favourite picker was removed: its AppEntity params made
|
// pinned-favourite picker was removed: its AppEntity params made
|
||||||
// fresh-widget default-config resolution fail at the system level
|
// fresh-widget default-config resolution fail at the system level
|
||||||
// — see FuelPriceSmallWidget.)
|
// — the merged widget uses the minimal fuel/sort/distance intent.)
|
||||||
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)
|
||||||
|
|||||||
@@ -120,51 +120,3 @@ struct FuelBoardWidgetConfigurationIntent: WidgetConfigurationIntent, WidgetConf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The SMALL widget's OWN intent TYPE. iOS keys the long-press app-icon
|
|
||||||
// contextual menu (iOS 18+ "turn icon into a widget") by configuration-intent
|
|
||||||
// TYPE, so two widget kinds sharing one intent collapse there to a single
|
|
||||||
// entry — the first-registered kind. (The + gallery is kind-keyed, which is
|
|
||||||
// why it still lists both.) This intent carries the IDENTICAL minimal
|
|
||||||
// parameter set as the medium intent — fuel/sort/distance only, NO AppEntity
|
|
||||||
// params (the favouriteFuel/favourite AppEntity params were what broke
|
|
||||||
// fresh-widget default-config resolution, not intent separation; see
|
|
||||||
// Fresh-widget defaults). Sharing the generic provider keeps the timeline
|
|
||||||
// logic in one place.
|
|
||||||
struct FuelBoardSmallWidgetConfigurationIntent: 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: same layout as the medium intent — Distance only for
|
|
||||||
/// Cheapest ordering; hidden for Closest and Favourites.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user