Widget preview screen + shared widget rendering; dark-mode screenshots with real data
- Move widget entry view into Shared/FuelPriceWidgetViews.swift (FuelPriceWidgetContent, FuelPriceWidgetEntryData) so the app can render the REAL widget faces; widget extension now wraps it (FuelPriceEntry.data) — one source of truth for the face - WidgetMockScreen (-widgets launch arg): app icon + small + medium widget previews for App Store captures; AppIconPreview image set (1024 drip master) - AppStoreScreenshots: 5-8 dark-mode tab captures with real Halifax data, 9 widgets - 95/95 tests green; Release IPA rebuilt + re-served
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
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 {
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.09, green: 0.10, blue: 0.14),
|
||||
Color(red: 0.02, green: 0.03, blue: 0.06)
|
||||
],
|
||||
startPoint: .top, endPoint: .bottom
|
||||
)
|
||||
.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)
|
||||
Text("Cheapest fuel near you")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.white.opacity(0.65))
|
||||
}
|
||||
}
|
||||
.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 {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text(label.uppercased())
|
||||
.font(.caption.weight(.semibold))
|
||||
.tracking(1)
|
||||
.foregroundStyle(.white.opacity(0.5))
|
||||
FuelPriceWidgetContent(entry: mockEntry(family: family), family: family, isWidget: false)
|
||||
.padding(14)
|
||||
.frame(maxWidth: .infinity, 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user