Files
fuelboard/FuelBoard/WidgetMockScreen.swift

102 lines
4.7 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 {
@Environment(\.colorScheme) private var colorScheme
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. Uses the SAME artwork
// the app ships (AppIcon.appiconset), with the dark variant
// when the device is in dark mode — matching iOS behaviour.
HStack(spacing: 16) {
Image(colorScheme == .dark ? "AppIconPreviewDark" : "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) for the widget's DEFAULT configuration —
/// Unleaded / Cheapest / 5 miles — with the SAME radius test the widget's
/// provider applies. Without it this preview happily listed the UK-wide
/// cheapest (Banbridge, 184 mi away) under "Cheapest fuel near you", which
/// is not what the widget ever shows.
private func mockEntry(family: WidgetFamily) -> FuelPriceWidgetEntryData {
let fuel = FuelType.e10
let location = FuelStore.loadLocation().map { Coordinate(lat: $0.lat, lng: $0.lng) }
// 5 miles — the widget config's default Distance. WidgetDistance itself
// lives in the widget target, so the preview mirrors its default here.
let radiusKM = 5 * FuelSavingsCalculator.kilometresPerMile
let all = FuelStore.loadStations()
let selling = all.filter { station in
guard station.prices[fuel] != nil else { return false }
guard let location else { return true }
return FuelStore.isWithinDisplayRadius(station: station, radiusKM: radiusKM,
userLat: location.lat, userLng: location.lng)
}
.sorted { ($0.prices[fuel] ?? .infinity) < ($1.prices[fuel] ?? .infinity) }
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(),
bestValueResult: nil
)
}
}