Compare commits
@@ -1,5 +1,8 @@
|
||||
build/
|
||||
build-release/
|
||||
build-sim/
|
||||
build-watch/
|
||||
.DS_Store
|
||||
*.xcuserstate
|
||||
xcuserdata/
|
||||
FuelBoardTests/.build/
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# FuelBoard App Store Submission Plan
|
||||
|
||||
Updated: 2026-08-16 · Target: safe App Store submission.
|
||||
Companion to SECURITY.md (code security) — this file is the *review-gate*
|
||||
checklist: what must happen, who owns it, and the timeline.
|
||||
|
||||
## Status: SUBMITTABLE after the checklist below
|
||||
|
||||
The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
|
||||
- ~~LAN relay data source~~ → **app fetches from public HTTPS
|
||||
(raw.githubusercontent.com mirror) with a bundled real 8,022-station dump
|
||||
as the no-network fallback.** No private IP in the consumer path. Reviewers
|
||||
on Apple's network see real data.
|
||||
- ~~Always-location risk~~ → **Always is requested only when the user enables
|
||||
Alerts** (ProximityMonitor escalates WhenInUse → Always on alert opt-in).
|
||||
Onboarding requests WhenInUse only.
|
||||
|
||||
## Hard blockers (must happen before ANY submission)
|
||||
|
||||
1. **[USER] Apple Developer Program membership** — the app has never been
|
||||
signed (all builds are `CODE_SIGNING_ALLOWED=NO` + ad-hoc re-sign for
|
||||
SideStore). Requires a paid membership ($99/yr, up to 48 h to approve) and
|
||||
a developer account.
|
||||
2. **[USER] Register App ID `com.apt.fuelboard`** in the developer portal,
|
||||
with the **App Group `group.com.apt.fuelboard`** capability AND the
|
||||
**In-App Purchase** capability enabled (the widget + app-group cache
|
||||
depend on the group; the tip jar needs IAP).
|
||||
3. ~~[ME] `PrivacyInfo.xcprivacy`~~ — **DONE 2026-08-16** (app + widget
|
||||
manifests added; UserDefaults `1C8F.1`, Location + Diagnostics declared).
|
||||
4. **[ME] Real Distribution signing** — Distribution certificate + App Store
|
||||
provisioning profile (or Xcode automatic signing once the account is in
|
||||
Xcode); then `xcodebuild archive` (no more `CODE_SIGNING_ALLOWED=NO`).
|
||||
5. **[USER] Privacy policy URL** in App Store Connect — mandatory; the app
|
||||
collects location. Policy text is now in `PRIVACY.md`; publish it via the
|
||||
FuelBoard marketing site or GitHub Pages and paste that public URL into App
|
||||
Store Connect.
|
||||
|
||||
## Required tasks — owner [ME] (technical, do after account exists)
|
||||
|
||||
- [x] **PrivacyInfo.xcprivacy** — app + widget manifests added (2026-08-16):
|
||||
UserDefaults reason `1C8F.1`; app also declares Location (linked, not
|
||||
tracking) + Diagnostics (not linked, not tracking — the dev beacon)
|
||||
- [ ] Distribution archive + TestFlight upload (needs Apple account)
|
||||
- [x] **Version bump** → `1.0.1` (build 2), app + widget plists
|
||||
- [x] **OGL attribution** — Settings → Data source footer now reads "Prices
|
||||
use the UK Fuel Finder dataset (Open Government Licence) and refresh
|
||||
up to twice a day." (2.3.1 hygiene)
|
||||
- [x] **Out-of-UK empty state** — "No X stations within N miles" + UK-only
|
||||
explainer + "Browse all UK stations" button (switches to Closest)
|
||||
- [ ] ~~Gate dev surface behind #if DEBUG~~ **DECLINED — kept in Release on
|
||||
purpose**: the user's home-testing installs the Release IPA and toggles
|
||||
`fuelboard.relayFallback` via Settings → Debug (revealed only by tapping
|
||||
About → Version ×5 — invisible to reviewers; no credentials involved).
|
||||
Gating it out of Release would break that workflow for zero review gain.
|
||||
- [ ] Final pass: `swift test` green (95 tests), archive smoke test on device
|
||||
- [ ] ~~Capture screenshots from simulator~~ **RAW CAPTURES DONE (2026-08-16)** —
|
||||
native 6.9″ 1320×2868 PNGs of all four tabs (Stations, Favourites,
|
||||
Alerts, Settings) at `/tmp/fuelboard-shots/asc/`; assembled into ASC
|
||||
frames by the user in the yuzu-hub App Store Screenshot Generator
|
||||
(https://yuzu-hub.github.io/appscreen/). Screenshot harness: launch
|
||||
args `-skipOnboarding` / `-tab stations|favourites|alerts|settings`
|
||||
(ContentView, same pattern as `-showKeySheet`).
|
||||
|
||||
## Required tasks — owner [USER] (App Store Connect, cannot be done by me)
|
||||
|
||||
- [ ] Apple Developer Program membership + App ID (blockers #1–#2)
|
||||
- [ ] **Tip jar: create the three consumable IAPs** in App Store Connect →
|
||||
In-App Purchases (all type **Consumable**, exact product IDs, UK price
|
||||
tier; tip jars are an accepted App Review pattern):
|
||||
- `com.apt.fuelboard.tip099` — **£0.99** — "Splash & Dash" (Just enough to keep things moving.)
|
||||
- `com.apt.fuelboard.tip299` — **£2.99** — "Half a Tank" (A generous top-up for development.)
|
||||
- `com.apt.fuelboard.tip499` — **£4.99** — "Fill 'Er Up" (Keeping the app on the road.)
|
||||
App-side is ready (`41e4c91`): Settings → Support FuelBoard shows all
|
||||
three tiers with per-tier prices; sideloaded builds gracefully show
|
||||
"not available in this build yet" until the products exist.
|
||||
- [ ] App Store Connect app record: name, bundle ID `com.apt.fuelboard`,
|
||||
category (Utilities), pricing (Free), availability (UK first?)
|
||||
- [ ] Description, keywords, support URL, marketing URL, privacy policy URL
|
||||
- [ ] Privacy nutrition labels questionnaire (location: yes, linked to user
|
||||
for alerts/nearby; diagnostics: yes — the widget-diag beacon, not
|
||||
linked, not used for tracking; no ATT prompt needed)
|
||||
- [ ] Age rating questionnaire (4+; no objectionable content)
|
||||
- [ ] Export compliance: HTTPS-only + keychain → standard encryption
|
||||
exemption (5A992a), no documentation needed
|
||||
- [ ] Approve the TestFlight build and do a real-device pass before submit
|
||||
- [ ] Final "Submit for Review" click + stand by to answer review questions
|
||||
|
||||
## Review notes (copy-paste into App Store Connect)
|
||||
|
||||
> FuelBoard shows the cheapest petrol, diesel, and premium fuel near the user
|
||||
> using the UK government Fuel Finder dataset (Open Government Licence).
|
||||
> Prices are downloaded from the internet up to twice a day; the app filters
|
||||
> 5/10/15-mile radii on-device and works offline from a bundled snapshot.
|
||||
> Location is used to (a) rank stations by distance and (b) power the
|
||||
> optional Alerts feature: when the user enables alerts, the app geofences
|
||||
> the cheapest station in their radius and sends a notification when they
|
||||
> approach it. Always-location is requested only when the user enables
|
||||
> alerts. The app also provides a home-screen widget showing the current
|
||||
> cheapest station nearby, and a price-history Trends chart for starred
|
||||
> stations. All data is UK-wide (Fuel Finder covers England, Scotland, Wales
|
||||
> and Northern Ireland); users outside the UK see an empty state explaining
|
||||
> this.
|
||||
|
||||
## Review risks (know before submitting)
|
||||
|
||||
- **Always-location (5.1.1)** — the #1 question. The feature is visible
|
||||
(Alerts tab) and opt-in; the review notes above say so explicitly.
|
||||
- **Reviewer outside the UK** — empty radius; mitigated by the empty-state
|
||||
task above + review notes.
|
||||
- **"Cheapest" claims (2.3.1)** — mitigated by the OGL/footnote task.
|
||||
- **Live Activities** — reviewers may ask why; answer: price alerts at a
|
||||
glance. It is declared in the plist already.
|
||||
- **Brand logos (Shell, BP, Tesco…)** — Simple Icons (CC0) + Wikimedia,
|
||||
nominative use (cf. GasBuddy); low risk, keep sources documented.
|
||||
- **GitHub raw as data source** — public data, no credentials; bundled dump
|
||||
covers any regional GitHub unreachability.
|
||||
|
||||
## Timeline (best case)
|
||||
|
||||
| Day | Owner | Milestone |
|
||||
|-----|-------|-----------|
|
||||
| 0–2 | USER | Developer Program enrollment + App ID + app group capability |
|
||||
| 2–3 | ME | Signing (automatic), PrivacyInfo.xcprivacy, attribution, empty state, version bump |
|
||||
| 3–4 | ME | Archive → TestFlight; screenshots captured |
|
||||
| 4–5 | USER | TestFlight install + real-device pass (onboarding, alerts, widget, offline) |
|
||||
| 5–6 | USER | App Store Connect metadata + labels + age rating + privacy URL |
|
||||
| 6–7 | USER/ME | Submit for review |
|
||||
| 7–14 | Apple | Review (typically 1–3 days, can be a week); answer questions same-day |
|
||||
|
||||
Rollback note: nothing about submission changes the repo's rollback posture
|
||||
(`main` history is intact; release builds are tagged per submission).
|
||||
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 841 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 444 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 815 KiB |
|
After Width: | Height: | Size: 724 KiB |
|
After Width: | Height: | Size: 686 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 349 KiB |
@@ -0,0 +1,248 @@
|
||||
# FuelBoard Backlog
|
||||
|
||||
FuelBoard — UK-wide petrol-station price widget + app.
|
||||
GitHub mirror (`aptonline/fuelboard-data`, 2×/day 10:00+16:00) holds the
|
||||
full-UK dump (~8,022 stations, all prices); app fetches it once per 12 h and
|
||||
filters/sorts on-device, with a bundled snapshot as the no-network last resort.
|
||||
LAN relay is dev-only (hidden `fuelboard.relayFallback` flag).
|
||||
|
||||
Priority guide: P0 = next build, P1 = soon, P2 = later, P3 = maybe/idea.
|
||||
Status: TODO / IN PROGRESS / DONE / BLOCKED.
|
||||
|
||||
---
|
||||
|
||||
## P1 — Soon
|
||||
|
||||
- [x] **Move relay + mirror off the Mac mini (DONE 2026-08-16)** — dedicated
|
||||
Debian 13 LXC on Proxmox via community-scripts/ProxmoxVE `ct/debian.sh`
|
||||
(CT **111**, hostname **`fuelboard-relay`**, 1 core/512 MB/2 GB, static IP
|
||||
**192.168.1.113**, plain LXC — NO Docker layer). Scope: relay AND mirror
|
||||
both moved (full Mac independence; relays bind **loopback only**, GitHub is
|
||||
the only public surface). What landed: Python 3.13.5 (trixie) boots both
|
||||
relays (relay 8788 demo/CSV + relay-api 8789 `source: api`, 8,024 stations,
|
||||
~2 min first sync, 0 failures); source at `/root/workspace/fuelboard-{relay,relay-api}`
|
||||
(venvs + pip, `.env` chmod 600); deploy key `fuelboard_deploy` +
|
||||
`github-fuelboard-data` ssh config + `fuelboard-data` clone at
|
||||
`/root/workspace/fuelboard-data` (origin = ssh alias, green + synced);
|
||||
`mirror_push.py` → `/root/scripts/mirror_push.py` (RELAY line already
|
||||
`127.0.0.1:8789`); systemd `fuelboard-relay.service` +
|
||||
`fuelboard-relay-api.service` (Restart=always, `--host 127.0.0.1`) +
|
||||
`fuelboard-mirror.service` (oneshot) + `.timer` (10:00 + 16:00, Persistent,
|
||||
armed — next Mon 10:00); first live push VERIFIED from outside
|
||||
(commit `735a292`, latest.json `data_updated 16:50:53Z`); Mac launchd
|
||||
`com.apt.fuelboard-{relay,relay-api,mirror}` unloaded + plists parked in
|
||||
`~/Library/LaunchAgents/fuelboard-migrated-to-ct/`; editor 8790 + IPA 8765
|
||||
stay on the Mac; price watchdog `relay_price_watchdog.py` now fetches via
|
||||
SSH (`~/.ssh/fuelboard_ct` → `/root/scripts/watchdog_fetch.py`). Logs via
|
||||
`journalctl -u fuelboard-*` on the CT; SSH into CT: `ssh -i ~/.ssh/fuelboard_ct root@192.168.1.113`.
|
||||
**Open: rootfs is 2 GB (1.1 GB free) — mirror retention ~940 MB will get
|
||||
tight; rootfs grown to 8 GB on pve 2026-08-16 (5.9 GB free).**
|
||||
|
||||
- [x] **Siri: "Cheapest [fuel] near me"** — `AppShortcutsProvider` + `CheapestFuelIntent`
|
||||
(App Intents, iOS 16+), dialog + price-card snippet, cached full-UK dump first
|
||||
with freshness label ("as of 6am"), last-known-location fallback. Constraint:
|
||||
fresh-data path needs a reachable source off-LAN — cached answers work anywhere
|
||||
the app has previously fetched (the GitHub mirror serves this now). *DONE —
|
||||
radius-scoped
|
||||
lookup `037c241` (real-data regression: GULF HISTON 100.9p 132 mi away),
|
||||
per-fuel literal-word shortcuts `e55078c`, names = app words `a3b6f37`, plus
|
||||
Directions-to-cheapest `caadf18` + spoken hand-off `aed99e3`.*
|
||||
- [ ] **Siri: "Closest [fuel] station"** — same plumbing as cheapest, sort by
|
||||
distance instead of price. Free second phrase in the same `AppShortcutsProvider`.
|
||||
- [x] **Favourites Trends graph** — price history chart for starred stations.
|
||||
Swift Charts (iOS 26 target) line chart in the Favourites tab; one series per
|
||||
favourite, fuel-scoped (station, fuel) pairs map 1:1 to the archive. Default =
|
||||
absolute price lines; toggle "vs cheapest favourite" re-baselines to 0 as signed
|
||||
pence (mirrors the Cheapest/Closest delta pattern users already know). Reuse the
|
||||
capsule fuel picker; range control 7/30/90 days; respects the price-display
|
||||
toggle; honest empty-state copy in Localizable.strings ("Prices are recorded from
|
||||
each refresh — check back in a few days"). No widget in v1 (one-kind rule).
|
||||
Depends on the P0 archive. *DONE 2026-08-15, merged to main 2026-08-16:
|
||||
`Shared/FuelHistory.swift` (FuelHistoryStore: day math, slim day decode w/ shared
|
||||
band, series + deltaSeries, app-group favourites-only cache, parallel per-day
|
||||
fetches) + `FuelBoard/TrendsView.swift` (chart, fuel capsule, 7/30/90, Price/vs
|
||||
cheapest, per-station legend, empty/loading/retry states) + toolbar entry in
|
||||
FavouritesView + strings. 83 tests incl. URL regression (appendingPathComponent
|
||||
— URL(string:relativeTo:) dropped /main, all fetches 404'd). Sim-verified:
|
||||
favourites rows + TOP/deltas; sheet controls + honest empty state w/ live first-
|
||||
snapshot date. Pending: lines render once ≥2 snapshots (archive warming daily,
|
||||
~Aug 17).*
|
||||
|
||||
## P2 — Later
|
||||
|
||||
- [ ] **Shortcuts: station price lookup** — `StationEntity` + `EntityQuery` so
|
||||
Shortcuts/Siri pick a station by name; `ReturnsValue<Double>` enables user
|
||||
automations ("get cheapest diesel → notify").
|
||||
- [x] **Siri: favourite station price** — "How much is my favourite unleaded?"
|
||||
`FavouriteFuelPriceIntent`: TOP favourite per fuel (first in the manual
|
||||
Favourites order — `SiriCheapestLookup.topFavourite`, refresh-vs-dump with
|
||||
offline cached-snapshot fallback); no location required (distance shown only
|
||||
when the saved fix exists). `DirectionsToFavouriteFuelIntent` (Maps
|
||||
hand-off, station announced first) stays in code but carries no voice
|
||||
phrases — every favourite price card has the interactive Directions button
|
||||
(`6d7254e`), and the voice slots went to Directions-to-Cheapest (`8a4c472`).
|
||||
*DONE — favourites already carried station snapshots, so no `StationEntity`
|
||||
dependency.*
|
||||
- [ ] **Siri: on-screen awareness (View Annotations + StationEntity)** — iOS
|
||||
26/27 Siri prefers the visible screen over the App Shortcut when the app is
|
||||
frontmost; without view annotations it answers from the rendered screen
|
||||
(unstructured "screenshot" parse) — visible tab/sort/radius, not the full
|
||||
dataset or the intent's logic. Fix: `StationEntity` (shared with the station
|
||||
lookup P2 above) + `.appEntityIdentifier` on station rows (collection
|
||||
annotation on the List) + `NSUserActivity` on the station detail/map +
|
||||
App Schema conformance (optional `IndexedEntity` for Spotlight semantic
|
||||
search). Verify annotations with AppIntentsTesting (P3). Off-screen/locked
|
||||
requests already route to `CheapestFuelIntent` correctly; this closes the
|
||||
frontmost gap. WWDC26: "Build intelligent Siri experiences with App Schemas"
|
||||
+ "Explore advanced App Intents features".
|
||||
- [ ] **Lock screen widgets** — new `FuelPriceLockScreen` kind (fuel-only config
|
||||
intent, reuses `FuelPriceTimelineProvider`): `accessoryRectangular` (cheapest
|
||||
station + price + distance), `accessoryCircular` (price ring), `accessoryInline`
|
||||
("⛽ 139.9p"). Provider + `containerBackground` already exist; must branch the
|
||||
view for accessory families (current `else` renders `stationList`). Bonus:
|
||||
StandBy mode. ~1 session.
|
||||
|
||||
- [ ] **CarPlay** — widgets render but can't launch Maps; consider a proper CarPlay
|
||||
scene (CPListTemplate) for navigation deep-links.
|
||||
- [ ] **Alert region tuning** — make alert trigger radius + dedup window
|
||||
user-configurable (currently fixed 300 m trigger / 1 per station per hour).
|
||||
- [ ] **Price-drop alerts: threshold control + Check now** — keep the current
|
||||
cache-first / 12 h refresh policy, but add a per-alert sensitivity control
|
||||
for favourite price-drop alerts instead of a fetch-frequency slider. Scope:
|
||||
`Minimum change` picker (e.g. 0.5p / 1.0p / 2.0p), plus a `Check now`
|
||||
action in the Alerts tab that forces a fetch and immediately re-evaluates the
|
||||
cheapest-favourite alert. Copy should set expectation honestly: standard
|
||||
mode checks when FuelBoard refreshes prices (up to twice daily).
|
||||
- [ ] **Price-drop alerts: optional frequent-check mode** — if the app ever
|
||||
relaxes the current "twice daily max" data policy, expose this as a clearly
|
||||
separate mode rather than a freeform frequency slider. Example framing:
|
||||
`Standard` (current behaviour) vs `Frequent` (best-effort extra checks),
|
||||
with conservative caps (e.g. every 4–6 h, not hourly) and copy that does not
|
||||
promise exact timing under iOS background scheduling.
|
||||
- [ ] **Pull-to-refresh spinner state** — surface refresh in-flight state
|
||||
(currently `refreshable` fires but no visible progress in the row list).
|
||||
- [x] **Offline first-run** — bundled REAL 8,022-station dump (`FuelBoardDump`,
|
||||
refreshed by `scripts/refresh_bundled_dump.sh`) is the no-network last resort
|
||||
in the live chain (P0, 2026-08-16); first launch demos the full dataset offline.
|
||||
- [ ] **Localization** — UK English only today; audit string catalog for l10n.
|
||||
- [ ] **Dark mode polish** — verify brand logos (white-circle clips) and RAG dot
|
||||
contrast under dark appearance on-device.
|
||||
- [ ] **Trends: national average overlay + per-day min/max** — the full dump is
|
||||
already held, so the UK/regional average line is computable locally; archive can
|
||||
store per-day min/max so the chart shows intraday spikes, not just the daily point.
|
||||
|
||||
## P3 — Ideas / Research
|
||||
|
||||
- [ ] **Siri: create fuel alert** — voice-create an alert for a station. Deferred:
|
||||
alert semantics (cheapest-within-radius + hourly dedup) are hard to express in
|
||||
a dialog; needs the alert engine surfaced as an intent.
|
||||
- [ ] **App Intents testing** — AppIntentsTesting (iOS 27) for intent coverage
|
||||
instead of XCUITest when the Siri features land.
|
||||
|
||||
- [ ] **Fuel-price prediction** — "price tends up/down" arrow derived from the
|
||||
price-history archive (P0). Cheap once the archive + Trends graph exist; keep a
|
||||
separate phase. Backlog ancestry: this was the original "relay could keep a
|
||||
rolling archive" idea — the archive is now P0 and the Trends graph (P1) is its
|
||||
visible half.
|
||||
- [ ] **Foresight Live Activity (heading-aware next station)** — investigate a
|
||||
driving-time surface that predicts the likely next station ahead of the user
|
||||
from cached data only (no extra source-data refresh on motion). Best v1 shape:
|
||||
Live Activity option, not widget-first, because widget space/refresh budgets
|
||||
are tight and the lock-screen/CarPlay compact slot better matches the use case.
|
||||
Prefer ONE FuelBoard Live Activity with a mode selector (e.g. `Nearby cheapest`
|
||||
vs `Foresight / next ahead`) rather than trying to run two simultaneous
|
||||
FuelBoard activities; iOS can support multiple activities, but FuelBoard's
|
||||
current implementation is single-activity and the compact surfaces are too
|
||||
constrained for two competing driving modes anyway. Viable MVP: infer heading
|
||||
from recent motion/course, keep only stations in a forward cone, score
|
||||
candidates by forward distance + lateral offset, and show the next-ahead
|
||||
station with all three fuel prices or a compact rotating/focused fuel
|
||||
treatment if space allows. Key caveat: heading is not full route awareness,
|
||||
so avoid overpromising "the next station you will reach" on motorways/opposite
|
||||
carriageways; frame as "next ahead" / "likely next stop" unless route-quality
|
||||
heuristics improve. Must include hysteresis/speed thresholds to avoid flapping,
|
||||
and should be tested specifically against compact Live Activity/CarPlay layouts
|
||||
where space is extremely limited.
|
||||
- [ ] **Widget sparkline** — medium-widget mini sparkline of a favourite's 30-day
|
||||
trend, after the Trends UI lands (one-kind rule preserved).
|
||||
- [ ] **Price alerts for a specific station** — notify when a starred station's
|
||||
price drops below a user threshold.
|
||||
- [ ] **Price-drop alerts: mode = Cheapest favourite / All favourites** — keep
|
||||
`Cheapest favourite` as the default low-noise mode, but explore an optional
|
||||
`All favourites` mode for one fuel. Guardrails if built: threshold required,
|
||||
per-station dedupe based on last-alerted price (not just time), and combine
|
||||
multiple drops from one refresh into a single summary notification rather
|
||||
than one alert per station.
|
||||
- [ ] **Share sheet** — share "Cheapest Unleaded near me" as text/image card.
|
||||
- [ ] **Watch app v1 — favourites + Check now** — small Apple Watch companion,
|
||||
not a phone-app clone. Favourites-first home screen: fuel picker, `Cheapest
|
||||
favourite` summary card, top few favourites for that fuel, last-updated time,
|
||||
and a prominent `Check now` action that forces a refresh + re-evaluates
|
||||
favourite prices. Keep the watch UX pull-based and glanceable; avoid implying
|
||||
high-frequency background refresh. Shared-store architecture preferred over a
|
||||
second watch-only data model.
|
||||
- [ ] **Watch complications / Smart Stack** — complication/widget surfaces for
|
||||
the same watch concept: show the cheapest favourite price, fuel, and short
|
||||
station name at a glance. Keep copy minimal and honest about freshness.
|
||||
- [ ] **Watch app v2 — favourite detail + handoff** — tap a favourite row for a
|
||||
detail screen (station, brand, price, last updated) with actions like
|
||||
`Directions` and `Open on iPhone`. Reuse existing maps handoff patterns where
|
||||
possible instead of inventing watch-only navigation logic.
|
||||
- [ ] **App Clip** — 10 MB budget for "find cheapest fuel nearby" without install.
|
||||
- [ ] **Widget picker: dual-unit distance labels** — the Edit-Widget Distance row
|
||||
label is cached by the system at sheet-render time and only re-resolves on
|
||||
picker interaction, so "mirror the app unit" goes stale. Fix: label options
|
||||
with both units ("5 miles (8 km)") so nothing can go stale; widget rows keep
|
||||
mirroring the live unit.
|
||||
- [ ] **Localizable.strings: normalize keys to match copy** — today 8 onboarding
|
||||
keys diverge from their values (key = old source literal, value = tightened
|
||||
copy, e.g. "…updated twice a day" key vs "…updated daily" value). Option to
|
||||
rename keys so key == value everywhere. Requires sweeping every Swift
|
||||
occurrence of the old literal (Text/NSLocalizedString) AND updating key +
|
||||
value together in one commit + rebuild — renaming the file entry alone
|
||||
silently reverts copy to the Swift literal at next build. Not recommended:
|
||||
keys are invisible anchors; value edits already change copy without code
|
||||
changes, and normalization reintroduces divergence on the next value edit.
|
||||
|
||||
## Done (recent)
|
||||
|
||||
- [x] **GitHub price-history mirror + LIVE provider chain (P0, DONE 2026-08-16)** —
|
||||
push side (relay → public `aptonline/fuelboard-data`, daily 10:00 launchd, SSH
|
||||
deploy key, 180-day retention) + history read side (Trends, P1) + **live chain
|
||||
(`feature/p0-live-chain`)**: `MirrorFuelProvider` fetches CURRENT prices from
|
||||
raw.githubusercontent (latest.json → history/<day>.json, app-group day-cache) →
|
||||
**GitHub FIRST everywhere** (alert checks + widget too — the relay is
|
||||
unreachable from any consumer phone and attempting it would fire the Local
|
||||
Network prompt); LAN relay joins ONLY behind the hidden dev flag
|
||||
`fuelboard.relayFallback` (Settings → Debug, off by default); bundled REAL
|
||||
dump (`FuelBoardDump` dataset, refreshed by `scripts/refresh_bundled_dump.sh`)
|
||||
is the app's no-network last resort. `FuelBeacon` fire-and-forget X-Client app
|
||||
ping fires only with the dev flag on. **Local Network permission removed from
|
||||
onboarding entirely** — data page is informational, replay on cellular can no
|
||||
longer hang. 95 tests.
|
||||
- [x] Switch dataset to govuk API data — official Fuel Finder OAuth API via
|
||||
relay-api :8789 (source: api, ~8,012 stations); app baseURL → :8789; credentials
|
||||
live in relay `.env` only, never in the IPA
|
||||
- [x] Widget configuration — per-widget fuel/sort/distance via
|
||||
`WidgetConfigurationIntent` (`AppIntentConfiguration`), distance options as
|
||||
`WidgetDistance` AppEntity (`26e724f`); small widget gained Favourites
|
||||
(`d0bd4e7`) + `.systemMedium` (`b94f769`)
|
||||
- [x] Unit test target for the app — FuelBoardShared XCTest target, 37 tests
|
||||
(title sanitizer, decode price guard, RAG thresholds, cheapest/closest baseline,
|
||||
miles↔km)
|
||||
- [x] Backlog hygiene — implemented items marked DONE, obsolete pruned
|
||||
- [x] Sync retry with exponential backoff — relay-api retries failed syncs
|
||||
15s→15min cap; `/health` exposes retry state (`6a58275`)
|
||||
- [x] England-wide full-UK dump, on-device filtering (relay `142061b`, app `cf9765b`)
|
||||
- [x] Cache-first, 12 h gate, pull-to-refresh only
|
||||
- [x] Distance filter 5/10/15 miles, local-only changes (no re-fetch)
|
||||
- [x] Closest mode = whole country, distance picker disabled
|
||||
- [x] Delta/TOP baseline = cheapest-within-radius in both modes
|
||||
- [x] Three-layer price sanitisation (relay band 50–500p / app decode guard / title-case)
|
||||
- [x] Title-case station names (chunk-aware sanitizer)
|
||||
- [x] Native large-title navigation (Mail-style collapse) — `313fe84`
|
||||
- [x] E10/E5 bracket labels in picker + descriptions — `05f3ef0`
|
||||
- [x] Gitea remotes created + pushed (`apt/fuelboard`, `apt/fuelboard-relay`)
|
||||
- [x] Brand logos: proper 2x/3x assets, 75% smaller (315 KB → 79 KB)
|
||||
- [x] Security audit (see SECURITY.md)
|
||||
- [x] App Store approval gotcha review (see APPSTORE.md)
|
||||
@@ -17,17 +17,37 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.apt.fuelboard</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fuelboard</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>FuelBoard uses your location to find the cheapest nearby petrol stations.</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>FuelBoard can optionally connect to the FuelBoard Relay on your local network for faster alerts during development.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>FuelBoard uses Always location to alert you when you approach the cheapest station nearby.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>FuelBoard uses your location to find the cheapest nearby petrol stations.</string>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.apt.fuelboard.smart-refresh</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>location</string>
|
||||
</array>
|
||||
<key>UILaunchScreen</key>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.apt.fuelboard</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>FuelBoardWatch</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>WKAppBundleIdentifier</key>
|
||||
<string>com.apt.fuelboard.watchapp</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.watchkit</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>FuelBoard</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>FuelBoard</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>com.apt.fuelboard</string>
|
||||
<key>WKWatchKitApp</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -17,15 +17,15 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>FuelBoard uses your location to show the cheapest nearby petrol stations.</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>FuelBoard uses your location to show the cheapest nearby petrol stations.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"fill-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "light",
|
||||
"value" : {
|
||||
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"glass" : false,
|
||||
"hidden" : false,
|
||||
"image-name" : "0-currency_pound-2.png",
|
||||
"name" : "currency_pound 2"
|
||||
},
|
||||
{
|
||||
"glass" : false,
|
||||
"hidden" : false,
|
||||
"image-name" : "1-water_drop.png",
|
||||
"name" : "water_drop"
|
||||
}
|
||||
],
|
||||
"name" : "Layers",
|
||||
"shadow" : {
|
||||
"kind" : "none",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"specular" : true,
|
||||
"translucency" : {
|
||||
"enabled" : false,
|
||||
"value" : 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"glass" : false,
|
||||
"hidden" : false,
|
||||
"image-name" : "Background.png",
|
||||
"name" : "Background"
|
||||
}
|
||||
],
|
||||
"name" : "Background",
|
||||
"shadow" : {
|
||||
"kind" : "none",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"specular" : false,
|
||||
"translucency" : {
|
||||
"enabled" : false,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
@@ -1,7 +1,4 @@
|
||||
// !$*UTF8*$!
|
||||
// FuelBoard — hand-crafted project (objectVersion 77, synchronized folders).
|
||||
// Targets: FuelBoard (app) + FuelBoardWidgets (widget extension, embedded).
|
||||
// Shared/ is a synchronized root group included in BOTH targets.
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
@@ -10,10 +7,26 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
ADCE5B5A3033A32C008C131C /* FuelBoardWatch Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = ADCE5B4F3033A329008C131C /* FuelBoardWatch Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
ADCE5B623033A32D008C131C /* FuelBoardWatch Watch App Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = ADCE5B633033A32D008C131C /* FuelBoardWatch Watch App Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
BB0000000000000000000001 /* FuelBoardWidgets.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000002 /* FuelBoardWidgets.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
ADCE5B643033A32D008C131C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = BB0000000000000000000004 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = ADCE5B4E3033A329008C131C;
|
||||
remoteInfo = "FuelBoardWatch Watch App Extension";
|
||||
};
|
||||
ADCE5B6F3033A32F008C131C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = BB0000000000000000000004 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = ADCE5B673033A32D008C131C;
|
||||
remoteInfo = "FuelBoardWatch Watch App";
|
||||
};
|
||||
BB0000000000000000000003 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = BB0000000000000000000004 /* Project object */;
|
||||
@@ -24,6 +37,28 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
ADCE5B5B3033A32C008C131C /* Embed Watch Content */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
ADCE5B5A3033A32C008C131C /* FuelBoardWatch Watch App.app in Embed Watch Content */,
|
||||
);
|
||||
name = "Embed Watch Content";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
ADCE5B653033A32D008C131C /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
ADCE5B623033A32D008C131C /* FuelBoardWatch Watch App Extension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
BB0000000000000000000006 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -38,11 +73,23 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
ADCE5B4F3033A329008C131C /* FuelBoardWatch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "FuelBoardWatch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
ADCE5B633033A32D008C131C /* FuelBoardWatch Watch App Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "FuelBoardWatch Watch App Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BB0000000000000000000002 /* FuelBoardWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FuelBoardWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BB0000000000000000000007 /* FuelBoard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FuelBoard.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
ADCE5B503033A329008C131C /* FuelBoardWatch Watch App */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = "FuelBoardWatch Watch App";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
ADCE5B663033A32D008C131C /* FuelBoardWatch Wrapper */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = "FuelBoardWatch Wrapper";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BB0000000000000000000008 /* FuelBoard */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = FuelBoard;
|
||||
@@ -61,6 +108,20 @@
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
ADCE5B4C3033A329008C131C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
ADCE5B6D3033A32E008C131C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
BB000000000000000000000B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -84,6 +145,8 @@
|
||||
BB0000000000000000000008 /* FuelBoard */,
|
||||
BB0000000000000000000009 /* FuelBoardWidgets */,
|
||||
BB000000000000000000000A /* Shared */,
|
||||
ADCE5B663033A32D008C131C /* FuelBoardWatch Wrapper */,
|
||||
ADCE5B503033A329008C131C /* FuelBoardWatch Watch App */,
|
||||
BB000000000000000000000E /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -93,6 +156,8 @@
|
||||
children = (
|
||||
BB0000000000000000000007 /* FuelBoard.app */,
|
||||
BB0000000000000000000002 /* FuelBoardWidgets.appex */,
|
||||
ADCE5B4F3033A329008C131C /* FuelBoardWatch Watch App.app */,
|
||||
ADCE5B633033A32D008C131C /* FuelBoardWatch Watch App Extension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -100,30 +165,50 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
BB000000000000000000000F /* FuelBoard */ = {
|
||||
ADCE5B4E3033A329008C131C /* FuelBoardWatch Watch App Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = BB0000000000000000000010 /* Build configuration list for PBXNativeTarget "FuelBoard" */;
|
||||
buildConfigurationList = ADCE5B593033A32B008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App Extension" */;
|
||||
buildPhases = (
|
||||
BB0000000000000000000011 /* Sources */,
|
||||
BB000000000000000000000B /* Frameworks */,
|
||||
BB0000000000000000000012 /* Resources */,
|
||||
BB0000000000000000000006 /* Embed App Extensions */,
|
||||
ADCE5B4B3033A329008C131C /* Sources */,
|
||||
ADCE5B4C3033A329008C131C /* Frameworks */,
|
||||
ADCE5B4D3033A329008C131C /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
BB0000000000000000000013 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
BB0000000000000000000008 /* FuelBoard */,
|
||||
BB000000000000000000000A /* Shared */,
|
||||
ADCE5B503033A329008C131C /* FuelBoardWatch Watch App */,
|
||||
);
|
||||
name = FuelBoard;
|
||||
name = "FuelBoardWatch Watch App Extension";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = FuelBoard;
|
||||
productReference = BB0000000000000000000007 /* FuelBoard.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
productName = "FuelBoardWatch Watch App Extension";
|
||||
productReference = ADCE5B633033A32D008C131C /* FuelBoardWatch Watch App Extension.appex */;
|
||||
productType = "com.apple.product-type.watchkit2-extension";
|
||||
};
|
||||
ADCE5B673033A32D008C131C /* FuelBoardWatch Watch App */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = ADCE5B6A3033A32D008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App" */;
|
||||
buildPhases = (
|
||||
ADCE5B6E3033A32E008C131C /* Resources */,
|
||||
ADCE5B653033A32D008C131C /* Embed App Extensions */,
|
||||
ADCE5B6D3033A32E008C131C /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
ADCE5B683033A32D008C131C /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
ADCE5B663033A32D008C131C /* FuelBoardWatch Wrapper */,
|
||||
);
|
||||
name = "FuelBoardWatch Watch App";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "FuelBoardWatch Watch App";
|
||||
productReference = ADCE5B4F3033A329008C131C /* FuelBoardWatch Watch App.app */;
|
||||
productType = "com.apple.product-type.application.watchapp2";
|
||||
};
|
||||
BB0000000000000000000005 /* FuelBoardWidgets */ = {
|
||||
isa = PBXNativeTarget;
|
||||
@@ -148,6 +233,33 @@
|
||||
productReference = BB0000000000000000000002 /* FuelBoardWidgets.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
BB000000000000000000000F /* FuelBoard */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = BB0000000000000000000010 /* Build configuration list for PBXNativeTarget "FuelBoard" */;
|
||||
buildPhases = (
|
||||
BB0000000000000000000011 /* Sources */,
|
||||
BB000000000000000000000B /* Frameworks */,
|
||||
BB0000000000000000000012 /* Resources */,
|
||||
ADCE5B5B3033A32C008C131C /* Embed Watch Content */,
|
||||
BB0000000000000000000006 /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
ADCE5B703033A32F008C131C /* PBXTargetDependency */,
|
||||
BB0000000000000000000013 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
BB0000000000000000000008 /* FuelBoard */,
|
||||
BB000000000000000000000A /* Shared */,
|
||||
);
|
||||
name = FuelBoard;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = FuelBoard;
|
||||
productReference = BB0000000000000000000007 /* FuelBoard.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -155,15 +267,18 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1600;
|
||||
LastSwiftUpdateCheck = 2630;
|
||||
LastUpgradeCheck = 1600;
|
||||
TargetAttributes = {
|
||||
BB000000000000000000000F = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
ADCE5B4E3033A329008C131C = {
|
||||
CreatedOnToolsVersion = 26.3;
|
||||
};
|
||||
BB0000000000000000000005 = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
};
|
||||
BB000000000000000000000F = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = BB0000000000000000000017 /* Build configuration list for PBXProject "FuelBoard" */;
|
||||
@@ -182,11 +297,27 @@
|
||||
targets = (
|
||||
BB000000000000000000000F /* FuelBoard */,
|
||||
BB0000000000000000000005 /* FuelBoardWidgets */,
|
||||
ADCE5B673033A32D008C131C /* FuelBoardWatch Watch App */,
|
||||
ADCE5B4E3033A329008C131C /* FuelBoardWatch Watch App Extension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
ADCE5B4D3033A329008C131C /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
ADCE5B6E3033A32E008C131C /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
BB0000000000000000000012 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -204,6 +335,13 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
ADCE5B4B3033A329008C131C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
BB0000000000000000000011 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -221,6 +359,16 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
ADCE5B683033A32D008C131C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = ADCE5B4E3033A329008C131C /* FuelBoardWatch Watch App Extension */;
|
||||
targetProxy = ADCE5B643033A32D008C131C /* PBXContainerItemProxy */;
|
||||
};
|
||||
ADCE5B703033A32F008C131C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = ADCE5B673033A32D008C131C /* FuelBoardWatch Watch App */;
|
||||
targetProxy = ADCE5B6F3033A32F008C131C /* PBXContainerItemProxy */;
|
||||
};
|
||||
BB0000000000000000000013 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = BB0000000000000000000005 /* FuelBoardWidgets */;
|
||||
@@ -229,6 +377,266 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
ADCE5B573033A32B008C131C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/WatchExtension-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp.watchkitextension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 26.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
ADCE5B583033A32B008C131C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/WatchExtension-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp.watchkitextension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 26.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
ADCE5B6B3033A32D008C131C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/WatchWrapper-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 26.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
ADCE5B6C3033A32D008C131C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/WatchWrapper-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 26.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
BB0000000000000000000018 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -251,7 +659,7 @@
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -276,7 +684,7 @@
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@@ -288,16 +696,18 @@
|
||||
BB000000000000000000001A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Config/App-Info.plist;
|
||||
INFOPLIST_FILE = "Config/App-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@@ -309,16 +719,18 @@
|
||||
BB000000000000000000001B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Config/App-Info.plist;
|
||||
INFOPLIST_FILE = "Config/App-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@@ -333,15 +745,16 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Config/Widget-Info.plist;
|
||||
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -357,15 +770,16 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Config/Widget-Info.plist;
|
||||
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -378,11 +792,20 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
BB0000000000000000000017 /* Build configuration list for PBXProject "FuelBoard" */ = {
|
||||
ADCE5B593033A32B008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
BB0000000000000000000018 /* Debug */,
|
||||
BB0000000000000000000019 /* Release */,
|
||||
ADCE5B573033A32B008C131C /* Debug */,
|
||||
ADCE5B583033A32B008C131C /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
ADCE5B6A3033A32D008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
ADCE5B6B3033A32D008C131C /* Debug */,
|
||||
ADCE5B6C3033A32D008C131C /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
@@ -405,6 +828,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
BB0000000000000000000017 /* Build configuration list for PBXProject "FuelBoard" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
BB0000000000000000000018 /* Debug */,
|
||||
BB0000000000000000000019 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = BB0000000000000000000004 /* Project object */;
|
||||
|
||||
@@ -1,58 +1,204 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Alerts tab — enables the cheapest-station proximity alerts and shows what's
|
||||
/// being monitored.
|
||||
/// Alerts tab — enables the cheapest-station proximity alerts. The fuel being
|
||||
/// monitored is chosen HERE, independently of the Stations-tab selection:
|
||||
/// users can browse any fuel without re-targeting their alerts (and vice
|
||||
/// versa). The Live Activity section also lives here — it mirrors this page's
|
||||
/// shape (toggle + fuel + distance) so the two "notify me while driving"
|
||||
/// surfaces sit together. Geofence plumbing (count, 18-limit) lives in
|
||||
/// Settings → Debug; the user-facing page keeps a plain-language explainer
|
||||
/// plus a 0-stations hint.
|
||||
struct AlertsView: View {
|
||||
@Binding var enabled: Bool
|
||||
@Binding var radius: Double
|
||||
@Binding var radius: Double // stored in km (monitor + storage)
|
||||
@Binding var fuel: FuelType // the fuel alerts monitor for
|
||||
@Binding var favouriteDropEnabled: Bool
|
||||
@Binding var favouriteDropFuel: FuelType
|
||||
/// Whether alerts mirror the Stations-tab search distance (capped at 8 mi).
|
||||
@Binding var followsSearch: Bool
|
||||
/// Whether the "cheapest nearby" Live Activity is shown on the Lock Screen
|
||||
/// / Dynamic Island. Tracks its OWN fuel + radius (below) — independent of
|
||||
/// the Stations tab.
|
||||
@Binding var liveActivityEnabled: Bool
|
||||
/// Fuel the Live Activity tracks (independent of the Stations tab).
|
||||
@Binding var liveActivityFuel: FuelType
|
||||
/// Search radius (miles, 5/10/15) the Live Activity uses.
|
||||
@Binding var liveActivityRadiusMiles: Int
|
||||
/// Whether the Live Activity mirrors the Stations-tab search distance.
|
||||
@Binding var liveActivityFollowsSearch: Bool
|
||||
/// Current Stations-tab search distance (miles) — the value "Follow search"
|
||||
/// mirrors.
|
||||
let stationLimit: Int
|
||||
let distanceUnit: DistanceUnit
|
||||
let monitoredCount: Int
|
||||
let lastAlert: String?
|
||||
let onCheckFavouriteDropNow: () -> Void
|
||||
|
||||
/// The Stations-tab search distance shown in the user's unit, e.g.
|
||||
/// "5 miles" or "8 km" — for the "Follow search" rows.
|
||||
private var searchInUnitLabel: String {
|
||||
let shown = distanceUnit.displayMiles(stationLimit) // stationLimit is always miles
|
||||
return "\(shown) \(distanceUnit.label(for: Double(shown)))"
|
||||
}
|
||||
|
||||
/// A miles value (5/10/15) shown truthfully in the user's unit, e.g.
|
||||
/// "5 miles" (miles unit) or "8 km" (km unit) — picker labels must match
|
||||
/// what the option really means.
|
||||
private func distanceLabel(forMiles miles: Int) -> String {
|
||||
let shown = distanceUnit.displayMiles(miles)
|
||||
return "\(shown) \(distanceUnit.label(for: Double(shown)))"
|
||||
}
|
||||
|
||||
/// Situational guidance for the currently-selected alert radius, shown
|
||||
/// under the picker so users know what each choice is best for. Labels
|
||||
/// mirror the picker row text (unit-aware) and explain the trade-off:
|
||||
/// smaller radii ping more often but later; larger radii ping rarely in
|
||||
/// town but earlier from afar.
|
||||
private var alertRadiusGuidance: String {
|
||||
switch alertChoice.wrappedValue {
|
||||
case .followSearch:
|
||||
return "Set-and-forget: alerts behave exactly like the Stations-tab search distance (capped at 8 miles for reliable geofencing)."
|
||||
case .fixed(let value):
|
||||
let optionLabel = "\(value) \(distanceUnit.label(for: Double(value)))"
|
||||
let body: String
|
||||
switch value {
|
||||
case 1:
|
||||
body = "Town driving. Fires most often — you're rarely inside the circle for long, so pings are meaningful. Best for stop-start local trips."
|
||||
case 2:
|
||||
body = "City or suburb commute. Slightly earlier heads-up than the smallest option; still fires regularly around town."
|
||||
case 3:
|
||||
body = "Mixed driving. Middle ground: heads-up a few minutes out, still works in most towns."
|
||||
case 5:
|
||||
body = "A-roads and motorway exits. Wide early warning for longer trips — but in town it rarely fires, because you're usually already inside the circle."
|
||||
default:
|
||||
body = "Long-distance journeys. Maximum reach: a ping when a cheap station is coming up from far out. In town this essentially never fires."
|
||||
}
|
||||
return "\(optionLabel) — \(body)"
|
||||
}
|
||||
}
|
||||
|
||||
private enum AlertRadiusChoice: Hashable {
|
||||
case followSearch
|
||||
case fixed(Int)
|
||||
}
|
||||
|
||||
/// The radius picker works in the user's chosen unit; the stored value
|
||||
/// stays km so ProximityMonitor and persistence never change. Options are
|
||||
/// snapped to the nearest picker value so any stored radius still selects.
|
||||
/// "Follow search" mirrors the Stations-tab distance (capped at 8 miles).
|
||||
private var alertChoice: Binding<AlertRadiusChoice> {
|
||||
Binding(
|
||||
get: {
|
||||
if followsSearch { return .followSearch }
|
||||
let inUnit = distanceUnit.fromKM(radius)
|
||||
let snapped = FuelStore.alertRadiusOptions
|
||||
.min(by: { abs(Double($0) - inUnit) < abs(Double($1) - inUnit) }) ?? 3
|
||||
return .fixed(snapped)
|
||||
},
|
||||
set: { choice in
|
||||
switch choice {
|
||||
case .followSearch:
|
||||
followsSearch = true
|
||||
case .fixed(let value):
|
||||
followsSearch = false
|
||||
radius = distanceUnit.toKM(Double(value))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private enum LADistanceChoice: Hashable {
|
||||
case followSearch
|
||||
case fixed(Int)
|
||||
}
|
||||
|
||||
private var laChoice: Binding<LADistanceChoice> {
|
||||
Binding(
|
||||
get: {
|
||||
if liveActivityFollowsSearch { return .followSearch }
|
||||
return .fixed(liveActivityRadiusMiles)
|
||||
},
|
||||
set: { choice in
|
||||
switch choice {
|
||||
case .followSearch:
|
||||
liveActivityFollowsSearch = true
|
||||
case .fixed(let miles):
|
||||
liveActivityFollowsSearch = false
|
||||
liveActivityRadiusMiles = miles
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
Toggle("Cheapest-station alerts", isOn: $enabled)
|
||||
Toggle("Cheapest favourite", isOn: $favouriteDropEnabled)
|
||||
Picker("Fuel", selection: $favouriteDropFuel) {
|
||||
ForEach(FuelType.allCases) { fuel in
|
||||
Text(fuel.displayName).tag(fuel)
|
||||
}
|
||||
}
|
||||
Button("Check now", action: onCheckFavouriteDropNow)
|
||||
.disabled(!favouriteDropEnabled)
|
||||
} header: {
|
||||
Text("Price-drop alerts")
|
||||
} footer: {
|
||||
Text("When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed.")
|
||||
Text("Watches your cheapest saved favourite for the fuel you pick here. FuelBoard alerts when a different favourite becomes the cheapest, or when your current cheapest favourite drops by at least 1.0p. This is separate from the nearby-geofence alert, so you can monitor favourites without extra location noise.")
|
||||
}
|
||||
|
||||
if enabled {
|
||||
Section("Trigger radius") {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("Radius")
|
||||
Spacer()
|
||||
Text("\(Int(radius)) km")
|
||||
Section {
|
||||
Toggle("Cheapest station", isOn: $enabled)
|
||||
Picker("Fuel", selection: $fuel) {
|
||||
ForEach(FuelType.allCases) { fuel in
|
||||
Text(fuel.displayName).tag(fuel)
|
||||
}
|
||||
}
|
||||
Picker("Radius", selection: alertChoice) {
|
||||
Text("Follow search (\(searchInUnitLabel))").tag(AlertRadiusChoice.followSearch)
|
||||
ForEach(FuelStore.alertRadiusOptions, id: \.self) { value in
|
||||
Text("\(value) \(distanceUnit.label(for: Double(value)))").tag(AlertRadiusChoice.fixed(value))
|
||||
}
|
||||
}
|
||||
Text(alertRadiusGuidance)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.monospacedDigit()
|
||||
}
|
||||
Slider(value: $radius, in: 1...10, step: 1)
|
||||
}
|
||||
}
|
||||
|
||||
Section("Monitoring") {
|
||||
if monitoredCount == 0 {
|
||||
if enabled && monitoredCount == 0 {
|
||||
Text("No stations monitored yet — open the Stations tab to load prices first.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
LabeledContent("Geofenced stations", value: "\(monitoredCount)")
|
||||
Text("Your favourites get priority, then the closest stations fill the rest (18 max, iOS region limit).")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Text("Alerts are checked against the cheapest station within \(Int(radius)) km for the selected fuel. Each station alerts at most once per hour.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} header: {
|
||||
Text("Proximity alerts")
|
||||
} footer: {
|
||||
Text("When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling \(fuel.displayName.lowercased()) are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing. Smaller radii ping more often but later; larger radii ping rarely in town but earlier from afar.")
|
||||
}
|
||||
|
||||
if let lastAlert {
|
||||
if enabled, let lastAlert {
|
||||
Section("Last alert") {
|
||||
Label(lastAlert, systemImage: "bell.badge.fill")
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle("Cheapest station", isOn: $liveActivityEnabled)
|
||||
Picker("Fuel", selection: $liveActivityFuel) {
|
||||
ForEach(FuelType.allCases) { fuel in
|
||||
Text(fuel.displayName).tag(fuel)
|
||||
}
|
||||
}
|
||||
Picker("Distance", selection: laChoice) {
|
||||
Text("Follow search (\(searchInUnitLabel))").tag(LADistanceChoice.followSearch)
|
||||
ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in
|
||||
Text(distanceLabel(forMiles: miles)).tag(LADistanceChoice.fixed(miles))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Live Activity")
|
||||
} footer: {
|
||||
Text("Shows the cheapest station for the fuel and distance you pick here on the Lock Screen and Dynamic Island — independent of the Stations tab (choose Follow search to mirror its distance instead). Updates as you drive; tap to open directions. Live Activities can be disabled in System Settings → Live Activities.")
|
||||
}
|
||||
}
|
||||
.navigationTitle("Alerts")
|
||||
|
||||
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 747 KiB |
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "AppIcon.png",
|
||||
"idiom": "universal",
|
||||
"platform": "ios",
|
||||
"size": "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"filename": "AppIcon-dark.png",
|
||||
"idiom": "universal",
|
||||
"platform": "ios",
|
||||
"size": "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "tinted"
|
||||
}
|
||||
],
|
||||
"filename": "AppIcon-tinted.png",
|
||||
"idiom": "universal",
|
||||
"platform": "ios",
|
||||
"size": "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-NotificationCenter-48.png",
|
||||
"idiom": "watch",
|
||||
"role": "notificationCenter",
|
||||
"scale": "2x",
|
||||
"size": "24x24",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-NotificationCenter-55.png",
|
||||
"idiom": "watch",
|
||||
"role": "notificationCenter",
|
||||
"scale": "2x",
|
||||
"size": "27.5x27.5",
|
||||
"subtype": "42mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-NotificationCenter-66.png",
|
||||
"idiom": "watch",
|
||||
"role": "notificationCenter",
|
||||
"scale": "2x",
|
||||
"size": "33x33",
|
||||
"subtype": "45mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-CompanionSettings-58.png",
|
||||
"idiom": "watch",
|
||||
"role": "companionSettings",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-CompanionSettings-87.png",
|
||||
"idiom": "watch",
|
||||
"role": "companionSettings",
|
||||
"scale": "3x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-AppLauncher-80.png",
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "40x40",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-AppLauncher-88.png",
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "44x44",
|
||||
"subtype": "40mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-AppLauncher-92.png",
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "46x46",
|
||||
"subtype": "41mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-AppLauncher-100.png",
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "50x50",
|
||||
"subtype": "44mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-AppLauncher-102.png",
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "51x51",
|
||||
"subtype": "45mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-AppLauncher-108.png",
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "54x54",
|
||||
"subtype": "49mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-QuickLook-172.png",
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "86x86",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-QuickLook-196.png",
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "98x98",
|
||||
"subtype": "42mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-QuickLook-216.png",
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "108x108",
|
||||
"subtype": "44mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-QuickLook-234.png",
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "117x117",
|
||||
"subtype": "45mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-QuickLook-258.png",
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "129x129",
|
||||
"subtype": "49mm"
|
||||
},
|
||||
{
|
||||
"filename": "Watch-Marketing-1024.png",
|
||||
"idiom": "watch-marketing",
|
||||
"scale": "1x",
|
||||
"size": "1024x1024"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 747 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 747 KiB |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIconArtwork.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 747 KiB |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIconPreview.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 208 KiB |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIconPreviewDark.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"data" : [
|
||||
{
|
||||
"filename" : "fuelboard_dump.json",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "PreviewWallpaper.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 177 KiB |
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_applegreen.png",
|
||||
"filename": "brand_applegreen_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_applegreen_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_asda.png",
|
||||
"filename": "brand_asda_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_asda_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
|
||||
|
Before Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_bp.png",
|
||||
"filename": "brand_bp_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_bp_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
|
||||
|
Before Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_circle_k_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_circle_k_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_essar_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_essar_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_esso.png",
|
||||
"filename": "brand_esso_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_esso_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
|
||||
|
Before Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_gleaner_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_gleaner_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_gulf.png",
|
||||
"filename": "brand_gulf_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_gulf_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_highland_fuels_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_highland_fuels_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 636 B |
|
After Width: | Height: | Size: 970 B |
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_jet.png",
|
||||
"filename": "brand_jet_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_jet_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "brand_maxol_2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"filename": "brand_maxol_3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 7.8 KiB |