- PreviewWallpaper image set: the actual iPhone wallpaper extracted from the iOS 26.3 sim runtime (ActionButtonSelector Display.ca, 1320x2870 native res, JPEG ~180 KB) — replaces the gradient backdrop in the widget mock - -seedFavourite <substring> launch hook: pins the first matching station for Unleaded via the normal keychain+app-group save path, so the Favourites tab can be captured populated (Ring Road Service Station, 129.9, 59.1 mi, TOP badge) - Re-captured 2-favourites (light), 6-favourites-dark, 9-widgets-dark, 10-widgets-light with real data - 95/95 tests green; Release IPA rebuilt + re-served
86 lines
3.6 KiB
Swift
86 lines
3.6 KiB
Swift
import SwiftUI
|
||
import WidgetKit
|
||
|
||
/// Full-screen widget preview (launch arg `-widgets`): the real app icon
|
||
/// plus the REAL widget faces (Shared/FuelPriceWidgetViews.swift — the same
|
||
/// code the widget extension renders) at small + medium sizes. Used for App
|
||
/// Store screenshot captures; not reachable from the UI.
|
||
struct WidgetMockScreen: View {
|
||
var body: some View {
|
||
ZStack {
|
||
// Real iPhone wallpaper (extracted from the iOS 26.3 sim runtime —
|
||
// ActionButtonSelector Display.ca asset, 1320×2870 native res).
|
||
Image("PreviewWallpaper")
|
||
.resizable()
|
||
.scaledToFill()
|
||
.ignoresSafeArea()
|
||
|
||
VStack(alignment: .leading, spacing: 26) {
|
||
// App icon + name, home-screen style
|
||
HStack(spacing: 16) {
|
||
Image("AppIconPreview")
|
||
.resizable()
|
||
.frame(width: 76, height: 76)
|
||
.clipShape(RoundedRectangle(cornerRadius: 17, style: .continuous))
|
||
VStack(alignment: .leading, spacing: 3) {
|
||
Text("FuelBoard")
|
||
.font(.title2.bold())
|
||
.foregroundStyle(.white)
|
||
.shadow(radius: 2, y: 1)
|
||
Text("Cheapest fuel near you")
|
||
.font(.subheadline)
|
||
.foregroundStyle(.white.opacity(0.9))
|
||
.shadow(radius: 2, y: 1)
|
||
}
|
||
}
|
||
.padding(.top, 8)
|
||
|
||
widgetCard(.systemSmall, label: "Small widget")
|
||
widgetCard(.systemMedium, label: "Medium widget")
|
||
|
||
Spacer(minLength: 0)
|
||
}
|
||
.padding(30)
|
||
}
|
||
}
|
||
|
||
private func widgetCard(_ family: WidgetFamily, label: String) -> some View {
|
||
// Real widget proportions: system small content is 158×158pt,
|
||
// medium 338×158pt (card adds the 14pt padding each side).
|
||
let contentW: CGFloat = family == .systemSmall ? 158 : 338
|
||
let cardW: CGFloat = contentW + 28
|
||
let cardH: CGFloat = contentW + 28
|
||
return VStack(alignment: .leading, spacing: 10) {
|
||
Text(label.uppercased())
|
||
.font(.caption.weight(.semibold))
|
||
.tracking(1)
|
||
.foregroundStyle(.secondary)
|
||
FuelPriceWidgetContent(entry: mockEntry(family: family), family: family, isWidget: false)
|
||
.padding(14)
|
||
.frame(width: cardW, height: cardH, alignment: .leading)
|
||
.background(Color(.systemBackground), in: RoundedRectangle(cornerRadius: 24, style: .continuous))
|
||
}
|
||
}
|
||
|
||
/// Real stations (loaded dump), cheapest-first for the chosen fuel — the
|
||
/// same selection the widget's own provider makes.
|
||
private func mockEntry(family: WidgetFamily) -> FuelPriceWidgetEntryData {
|
||
let fuel = FuelType.e10
|
||
let all = FuelStore.loadStations()
|
||
let selling = all.filter { $0.prices[fuel] != nil }
|
||
.sorted { ($0.prices[fuel] ?? .infinity) < ($1.prices[fuel] ?? .infinity) }
|
||
let location = FuelStore.loadLocation().map { Coordinate(lat: $0.lat, lng: $0.lng) }
|
||
let count = family == .systemSmall ? 1 : 3
|
||
return FuelPriceWidgetEntryData(
|
||
date: Date(),
|
||
stations: Array(selling.prefix(count)),
|
||
fuel: fuel,
|
||
sort: .cheapest,
|
||
isFavourites: false,
|
||
location: location,
|
||
locationSource: location != nil ? "live" : "none",
|
||
unit: FuelStore.loadDistanceUnit()
|
||
)
|
||
}
|
||
}
|