Compare commits
@@ -1,4 +1,6 @@
|
||||
build/
|
||||
Builds/
|
||||
build-*/
|
||||
build-release/
|
||||
build-sim/
|
||||
build-watch/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# FuelBoard App Store Submission Plan
|
||||
|
||||
Updated: 2026-08-16 · Target: safe App Store submission.
|
||||
Updated: 2026-09-26 · 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.
|
||||
|
||||
@@ -13,10 +13,108 @@ The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
|
||||
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.
|
||||
Onboarding requests WhenInUse only. FuelBoard does **not** declare the
|
||||
persistent real-time background location mode; optional alerts use
|
||||
CoreLocation region monitoring/significant-change behaviour instead.
|
||||
|
||||
## StoreKit sandbox: verified working configuration and caveats
|
||||
|
||||
### Verified configuration
|
||||
|
||||
The FuelBoard consumable tip products are:
|
||||
|
||||
- `com.apt.fuelboard.tip099`
|
||||
- `com.apt.fuelboard.tip299`
|
||||
- `com.apt.fuelboard.tip499`
|
||||
|
||||
The app requests these exact **Product IDs** with StoreKit 2
|
||||
`Product.products(for:)`; the UI names are display labels and are not used for
|
||||
lookup. The app bundle ID, App Store Connect app, explicit App ID, development
|
||||
team, and Sandbox Apple Account must belong to the same developer account.
|
||||
|
||||
Two test environments must remain distinct:
|
||||
|
||||
- **Local StoreKit testing:** select `/Users/apt/workspace/fuelboard/FuelBoardTips.storekit`
|
||||
in Xcode, under Product > Scheme > Edit Scheme > Run > Options. This bypasses
|
||||
App Store Connect and confirms the app's StoreKit loading and purchase code.
|
||||
- **Apple sandbox testing:** select **None** for StoreKit Configuration, then
|
||||
use Product > Run on a physical device with the development-signed build and
|
||||
a Sandbox Apple Account signed in under Settings > Developer. This exercises
|
||||
the real App Store Connect catalog.
|
||||
|
||||
The local configuration returning all three products does not prove that the
|
||||
Apple sandbox catalog is ready. Conversely, a successful local run showing `$`
|
||||
does not mean the App Store Connect price is USD: `Product.displayPrice` is
|
||||
formatted for the active StoreKit/device storefront. Local StoreKit prices are
|
||||
placeholders and do not upload to App Store Connect.
|
||||
|
||||
### Availability versus pricing
|
||||
|
||||
App Store Connect has separate controls:
|
||||
|
||||
- **Availability** controls the storefronts where the IAP is offered. The
|
||||
verified FuelBoard configuration uses **United Kingdom only**.
|
||||
- **Price schedule** may show generated prices for **175 countries and
|
||||
regions**. This is expected and does not make the IAP available in all 175
|
||||
storefronts; it is Apple's price conversion schedule.
|
||||
|
||||
The Sandbox Apple Account's App Store Country or Region must match an enabled
|
||||
IAP storefront. After changing the sandbox tester's storefront, sign out and
|
||||
back in under Settings > Developer so the storefront change activates.
|
||||
|
||||
### Important propagation/cache caveat
|
||||
|
||||
During sandbox diagnosis, `Product.products(for:)` completed successfully but
|
||||
returned zero products with the IAP availability set to UK. Temporarily
|
||||
changing one product's availability to all regions caused it to appear in the
|
||||
sandbox catalog; changing it back to UK left it working. This indicates an
|
||||
Apple-side catalog refresh or propagation/cache issue, not a change required in
|
||||
the StoreKit code. Apple documents that product metadata changes can take up to
|
||||
approximately one hour to appear in sandbox.
|
||||
|
||||
If an otherwise valid product returns zero products:
|
||||
|
||||
1. Confirm the Sandbox Apple Account belongs to the same developer team.
|
||||
2. Confirm its storefront is an enabled availability region.
|
||||
3. Sign out and back in to the sandbox account on the device.
|
||||
4. Delete/reinstall the development build and retry with StoreKit Configuration
|
||||
set to **None**.
|
||||
5. If necessary, temporarily broaden one product's availability to all
|
||||
regions, allow propagation, verify the product, then restore the intended
|
||||
availability and allow propagation again.
|
||||
|
||||
Record whether the diagnostic result is **request failed**, **completed with
|
||||
zero products**, or **a partial product set**. These are different failure
|
||||
classes. Do not add fallback prices or change product lookup to display names.
|
||||
|
||||
Sandbox testing does not require prior App Review approval of the IAP. It does
|
||||
require active developer membership, an active Paid Applications Agreement,
|
||||
complete banking and tax information, valid product metadata and pricing, the
|
||||
correct app/team association, and a matching Sandbox Apple Account.
|
||||
|
||||
## Hard blockers (must happen before ANY submission)
|
||||
|
||||
0. **[USER] Paid Applications Agreement in effect** — REQUIRED for paid IAP
|
||||
configuration and sandbox testing. App Store Connect →
|
||||
Business > Paid Applications Agreement → View and Agree. **This is the most
|
||||
common cause of a 2.1(b) "unable to purchase item / error message" rejection
|
||||
— without it every sandbox purchase fails.** (REJECTED once for this:
|
||||
review date 2026-09-25, version 1.0 (21), iPad Air 11″ iPadOS 27.)
|
||||
**Status 2026-09-25: agreement clicked Agree but shows "Pending user info" —
|
||||
this is NOT in effect yet.** Complete the Banking (account + sort code/IBAN)
|
||||
and Tax (W-8/W-9/UK tax ref, e.g. sole-trader UTR) sections under Business >
|
||||
Payments & Tax until the agreement shows "Active" and every tab is green.
|
||||
Only then do sandbox IAPs start working.
|
||||
0. **[USER] Tip IAPs configured and available to the sandbox** — the three
|
||||
consumables may exist but must ALSO have complete metadata, a current price,
|
||||
and an enabled storefront availability. Prior App Review approval or
|
||||
`Cleared for Sale` status is **not required for sandbox product discovery**.
|
||||
They must be backed by the **In-App Purchase capability on the App ID**
|
||||
`com.apt.fuelboard` (developer portal → Identifiers → App ID capability).
|
||||
3-tier tip code is already correct (retry lookup `7cb23e0`); do NOT churn
|
||||
SettingsView.swift. Test product discovery and purchase with a Sandbox
|
||||
Apple Account first: Settings → Support FuelBoard.
|
||||
|
||||
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
|
||||
@@ -25,31 +123,32 @@ The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
|
||||
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).
|
||||
3. ~~[ME] `PrivacyInfo.xcprivacy`~~ — **DONE** (app, widget, watch app, and
|
||||
watch wrapper manifests added; UserDefaults `1C8F.1`; app declares Location).
|
||||
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. A one-page GitHub Pages privacy policy is fine.
|
||||
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)
|
||||
- [x] **PrivacyInfo.xcprivacy** — app, widget, watch app, and watch wrapper
|
||||
manifests added: UserDefaults reason `1C8F.1`; app also declares Location
|
||||
(linked, not tracking). Diagnostic network beacon removed with LAN relay.
|
||||
- [ ] Distribution archive + TestFlight upload (needs Apple account)
|
||||
- [x] **Version bump** → `1.0.1` (build 2), app + widget plists
|
||||
- [x] **Version/build aligned** → `1.0.0` (build 23), app + widget + watch targets
|
||||
- [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.
|
||||
- [x] **Remove LAN relay fallback entirely** — no private IP, Local Network
|
||||
permission string, relay toggle, or diagnostic network beacon remains in
|
||||
the app/widget/watch binary. Hidden Debug still exists for local notification
|
||||
and widget diagnostics only.
|
||||
- [ ] 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,
|
||||
@@ -75,8 +174,7 @@ The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
|
||||
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)
|
||||
for alerts/nearby; no tracking; no ATT prompt needed)
|
||||
- [ ] Age rating questionnaire (4+; no objectionable content)
|
||||
- [ ] Export compliance: HTTPS-only + keychain → standard encryption
|
||||
exemption (5A992a), no documentation needed
|
||||
@@ -90,19 +188,23 @@ The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
|
||||
> 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.
|
||||
> optional Alerts feature: when the user enables alerts, the app registers
|
||||
> CoreLocation regions around the cheapest station in their radius and sends
|
||||
> a notification when they approach it. FuelBoard does not use persistent
|
||||
> real-time background location tracking; it relies on iOS region monitoring
|
||||
> and significant-change behaviour for optional alerts. 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.
|
||||
- **Always-location (5.1.1 / 2.5.4)** — the feature is visible (Alerts tab)
|
||||
and opt-in; the app does not declare persistent background location mode,
|
||||
and the review notes describe region monitoring/significant-change rather
|
||||
than continuous tracking.
|
||||
- **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.
|
||||
@@ -110,8 +212,8 @@ The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
|
||||
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.
|
||||
- **GitHub raw as data source** — public HTTPS data, no credentials or Local
|
||||
Network permission; bundled dump covers any regional GitHub unreachability.
|
||||
|
||||
## Timeline (best case)
|
||||
|
||||
|
||||
|
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 |
@@ -4,7 +4,7 @@ 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).
|
||||
LAN relay fallback has been removed from the app binary; the public GitHub mirror is the only live network data source.
|
||||
|
||||
Priority guide: P0 = next build, P1 = soon, P2 = later, P3 = maybe/idea.
|
||||
Status: TODO / IN PROGRESS / DONE / BLOCKED.
|
||||
@@ -211,15 +211,12 @@ Status: TODO / IN PROGRESS / DONE / BLOCKED.
|
||||
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.
|
||||
**GitHub mirror only everywhere** (alert checks + widget too); no LAN relay
|
||||
fallback, private IP, Local Network permission, or diagnostic network beacon
|
||||
ships in the app binary. Bundled REAL dump (`FuelBoardDump` dataset, refreshed
|
||||
by `scripts/refresh_bundled_dump.sh`) is the app's no-network last resort.
|
||||
Local Network permission removed entirely — data page is informational, replay
|
||||
on cellular can no longer hang. 107+ 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
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -30,11 +30,9 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<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>
|
||||
@@ -48,7 +46,6 @@
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>location</string>
|
||||
</array>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>FuelBoardWatch</string>
|
||||
<string>FuelBoard</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<string>FuelBoard</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -10,6 +10,17 @@
|
||||
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, ); }; };
|
||||
FBA000000000000000000001 /* DataRefreshMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000101 /* DataRefreshMode.swift */; };
|
||||
FBA000000000000000000002 /* FuelHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000102 /* FuelHistory.swift */; };
|
||||
FBA000000000000000000003 /* FuelPriceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000103 /* FuelPriceProvider.swift */; };
|
||||
FBA000000000000000000004 /* FuelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000104 /* FuelStore.swift */; };
|
||||
FBA000000000000000000005 /* MirrorFuelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000105 /* MirrorFuelProvider.swift */; };
|
||||
FBA000000000000000000006 /* SiriCheapestLookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000106 /* SiriCheapestLookup.swift */; };
|
||||
FBA000000000000000000007 /* WidgetLocationFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000107 /* WidgetLocationFetcher.swift */; };
|
||||
FBA000000000000000000008 /* FuelBoardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000108 /* FuelBoardTests.swift */; };
|
||||
FBA000000000000000000009 /* FuelHistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA000000000000000000109 /* FuelHistoryTests.swift */; };
|
||||
FBA00000000000000000000A /* LiveChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA00000000000000000010A /* LiveChainTests.swift */; };
|
||||
FBA00000000000000000000B /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA00000000000000000010B /* XCTest.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -37,17 +48,6 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
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;
|
||||
};
|
||||
ADCE5B5B3033A32C008C131C /* Embed Watch Content */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -59,6 +59,17 @@
|
||||
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;
|
||||
@@ -73,10 +84,23 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
AD22FCFF30682512009859D4 /* FuelBoardTips.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = FuelBoardTips.storekit; sourceTree = "<group>"; };
|
||||
AD64FC4C30739A4B008F27A9 /* FuelBoardSharedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FuelBoardSharedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
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; };
|
||||
FBA000000000000000000101 /* DataRefreshMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataRefreshMode.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000102 /* FuelHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuelHistory.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000103 /* FuelPriceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuelPriceProvider.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000104 /* FuelStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuelStore.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000105 /* MirrorFuelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MirrorFuelProvider.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000106 /* SiriCheapestLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiriCheapestLookup.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000107 /* WidgetLocationFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetLocationFetcher.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000108 /* FuelBoardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuelBoardTests.swift; sourceTree = "<group>"; };
|
||||
FBA000000000000000000109 /* FuelHistoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuelHistoryTests.swift; sourceTree = "<group>"; };
|
||||
FBA00000000000000000010A /* LiveChainTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveChainTests.swift; sourceTree = "<group>"; };
|
||||
FBA00000000000000000010B /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = System/Library/Frameworks/XCTest.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
@@ -136,18 +160,37 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FBA000000000000000000302 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FBA00000000000000000000B /* XCTest.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
AD64FC4B30739A4B008F27A9 /* Recovered References */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBA00000000000000000010B /* XCTest.framework */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BB000000000000000000000D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBA000000000000000000200 /* FuelBoardTests */,
|
||||
AD22FCFF30682512009859D4 /* FuelBoardTips.storekit */,
|
||||
BB0000000000000000000008 /* FuelBoard */,
|
||||
BB0000000000000000000009 /* FuelBoardWidgets */,
|
||||
BB000000000000000000000A /* Shared */,
|
||||
ADCE5B663033A32D008C131C /* FuelBoardWatch Wrapper */,
|
||||
ADCE5B503033A329008C131C /* FuelBoardWatch Watch App */,
|
||||
BB000000000000000000000E /* Products */,
|
||||
AD64FC4B30739A4B008F27A9 /* Recovered References */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -158,16 +201,52 @@
|
||||
BB0000000000000000000002 /* FuelBoardWidgets.appex */,
|
||||
ADCE5B4F3033A329008C131C /* FuelBoardWatch Watch App.app */,
|
||||
ADCE5B633033A32D008C131C /* FuelBoardWatch Watch App Extension.appex */,
|
||||
AD64FC4C30739A4B008F27A9 /* FuelBoardSharedTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBA000000000000000000200 /* FuelBoardTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBA000000000000000000201 /* Sources */,
|
||||
FBA000000000000000000202 /* Tests */,
|
||||
);
|
||||
path = FuelBoardTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBA000000000000000000201 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBA000000000000000000101 /* DataRefreshMode.swift */,
|
||||
FBA000000000000000000102 /* FuelHistory.swift */,
|
||||
FBA000000000000000000103 /* FuelPriceProvider.swift */,
|
||||
FBA000000000000000000104 /* FuelStore.swift */,
|
||||
FBA000000000000000000105 /* MirrorFuelProvider.swift */,
|
||||
FBA000000000000000000106 /* SiriCheapestLookup.swift */,
|
||||
FBA000000000000000000107 /* WidgetLocationFetcher.swift */,
|
||||
);
|
||||
name = Sources;
|
||||
path = Sources/FuelBoardShared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBA000000000000000000202 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBA000000000000000000108 /* FuelBoardTests.swift */,
|
||||
FBA000000000000000000109 /* FuelHistoryTests.swift */,
|
||||
FBA00000000000000000010A /* LiveChainTests.swift */,
|
||||
);
|
||||
name = Tests;
|
||||
path = Tests/FuelBoardSharedTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
ADCE5B4E3033A329008C131C /* FuelBoardWatch Watch App */ = {
|
||||
ADCE5B4E3033A329008C131C /* FuelBoardWatch Watch App Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = ADCE5B593033A32B008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App" */;
|
||||
buildConfigurationList = ADCE5B593033A32B008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App Extension" */;
|
||||
buildPhases = (
|
||||
ADCE5B4B3033A329008C131C /* Sources */,
|
||||
ADCE5B4C3033A329008C131C /* Frameworks */,
|
||||
@@ -260,6 +339,23 @@
|
||||
productReference = BB0000000000000000000007 /* FuelBoard.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
FBA000000000000000000300 /* FuelBoardSharedTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = FBA000000000000000000400 /* Build configuration list for PBXNativeTarget "FuelBoardSharedTests" */;
|
||||
buildPhases = (
|
||||
FBA000000000000000000301 /* Sources */,
|
||||
FBA000000000000000000302 /* Frameworks */,
|
||||
FBA000000000000000000303 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = FuelBoardSharedTests;
|
||||
productName = FuelBoardSharedTests;
|
||||
productReference = AD64FC4C30739A4B008F27A9 /* FuelBoardSharedTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -278,6 +374,11 @@
|
||||
};
|
||||
BB000000000000000000000F = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
SystemCapabilities = {
|
||||
com.apple.InAppPurchase = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -299,6 +400,7 @@
|
||||
BB0000000000000000000005 /* FuelBoardWidgets */,
|
||||
ADCE5B673033A32D008C131C /* FuelBoardWatch Watch App */,
|
||||
ADCE5B4E3033A329008C131C /* FuelBoardWatch Watch App Extension */,
|
||||
FBA000000000000000000300 /* FuelBoardSharedTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -332,6 +434,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FBA000000000000000000303 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -356,6 +465,23 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FBA000000000000000000301 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FBA000000000000000000001 /* DataRefreshMode.swift in Sources */,
|
||||
FBA000000000000000000002 /* FuelHistory.swift in Sources */,
|
||||
FBA000000000000000000003 /* FuelPriceProvider.swift in Sources */,
|
||||
FBA000000000000000000004 /* FuelStore.swift in Sources */,
|
||||
FBA000000000000000000005 /* MirrorFuelProvider.swift in Sources */,
|
||||
FBA000000000000000000006 /* SiriCheapestLookup.swift in Sources */,
|
||||
FBA000000000000000000007 /* WidgetLocationFetcher.swift in Sources */,
|
||||
FBA000000000000000000008 /* FuelBoardTests.swift in Sources */,
|
||||
FBA000000000000000000009 /* FuelHistoryTests.swift in Sources */,
|
||||
FBA00000000000000000000A /* LiveChainTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
@@ -405,8 +531,9 @@
|
||||
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 = 2;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -418,14 +545,13 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
||||
INFOPLIST_FILE = Config/WatchExtension-Info.plist;
|
||||
INFOPLIST_FILE = "Config/WatchExtension-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp.watchkitextension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -471,8 +597,9 @@
|
||||
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 = 2;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -484,14 +611,13 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/WatchApp.entitlements;
|
||||
INFOPLIST_FILE = Config/WatchExtension-Info.plist;
|
||||
INFOPLIST_FILE = "Config/WatchExtension-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp.watchkitextension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -537,7 +663,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -549,13 +675,13 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Config/WatchWrapper-Info.plist;
|
||||
INFOPLIST_FILE = "Config/WatchWrapper-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -602,7 +728,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -614,13 +740,13 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Config/WatchWrapper-Info.plist;
|
||||
INFOPLIST_FILE = "Config/WatchWrapper-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.watchapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -698,8 +824,9 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/App-Info.plist";
|
||||
@@ -707,9 +834,10 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
@@ -721,8 +849,9 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/App.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/App-Info.plist";
|
||||
@@ -730,9 +859,10 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
@@ -745,7 +875,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
||||
@@ -754,7 +884,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -770,7 +900,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Config/Widget.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = "Config/Widget-Info.plist";
|
||||
@@ -779,7 +909,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -789,10 +919,56 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
FBA000000000000000000401 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FuelBoardSharedTests;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.sharedtests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
FBA000000000000000000402 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
DEVELOPMENT_TEAM = F3BE6NE7U3;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FuelBoardSharedTests;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apt.fuelboard.sharedtests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
ADCE5B593033A32B008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App" */ = {
|
||||
ADCE5B593033A32B008C131C /* Build configuration list for PBXNativeTarget "FuelBoardWatch Watch App Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
ADCE5B573033A32B008C131C /* Debug */,
|
||||
@@ -837,6 +1013,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
FBA000000000000000000400 /* Build configuration list for PBXNativeTarget "FuelBoardSharedTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
FBA000000000000000000401 /* Debug */,
|
||||
FBA000000000000000000402 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = BB0000000000000000000004 /* Project object */;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2600"
|
||||
version = "1.8">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BB000000000000000000000F"
|
||||
BuildableName = "FuelBoard.app"
|
||||
BlueprintName = "FuelBoard"
|
||||
ReferencedContainer = "container:FuelBoard.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FBA000000000000000000300"
|
||||
BuildableName = "FuelBoardSharedTests.xctest"
|
||||
BlueprintName = "FuelBoardSharedTests"
|
||||
ReferencedContainer = "container:FuelBoard.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<StoreKitConfigurationFileReference
|
||||
identifier = "../../../FuelBoardTips.storekit">
|
||||
</StoreKitConfigurationFileReference>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = "en"
|
||||
region = "GB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersion = "1.0"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BB000000000000000000000F"
|
||||
BuildableName = "FuelBoard.app"
|
||||
BlueprintName = "FuelBoard"
|
||||
ReferencedContainer = "container:FuelBoard.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersion = "1.0"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BB000000000000000000000F"
|
||||
BuildableName = "FuelBoard.app"
|
||||
BlueprintName = "FuelBoard"
|
||||
ReferencedContainer = "container:FuelBoard.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -12,6 +12,9 @@ struct AlertsView: View {
|
||||
@Binding var enabled: Bool
|
||||
@Binding var radius: Double // stored in km (monitor + storage)
|
||||
@Binding var fuel: FuelType // the fuel alerts monitor for
|
||||
@Binding var alertMode: AlertMode
|
||||
@Binding var bestValueThresholdGBP: Double
|
||||
let bestValueConfigured: Bool
|
||||
@Binding var favouriteDropEnabled: Bool
|
||||
@Binding var favouriteDropFuel: FuelType
|
||||
/// Whether alerts mirror the Stations-tab search distance (capped at 8 mi).
|
||||
@@ -38,7 +41,7 @@ struct AlertsView: View {
|
||||
/// "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)))"
|
||||
return String(format: String(localized: "%lld %@", comment: "Distance label"), shown, distanceUnit.label(for: Double(shown)))
|
||||
}
|
||||
|
||||
/// A miles value (5/10/15) shown truthfully in the user's unit, e.g.
|
||||
@@ -46,7 +49,7 @@ struct AlertsView: View {
|
||||
/// what the option really means.
|
||||
private func distanceLabel(forMiles miles: Int) -> String {
|
||||
let shown = distanceUnit.displayMiles(miles)
|
||||
return "\(shown) \(distanceUnit.label(for: Double(shown)))"
|
||||
return String(format: String(localized: "%lld %@", comment: "Distance label"), shown, distanceUnit.label(for: Double(shown)))
|
||||
}
|
||||
|
||||
/// Situational guidance for the currently-selected alert radius, shown
|
||||
@@ -57,23 +60,23 @@ struct AlertsView: View {
|
||||
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)."
|
||||
return String(localized: "Set-and-forget: alerts behave exactly like the Stations-tab search distance (capped at 8 miles for reliable geofencing).", comment: "Alert radius guidance for following search distance")
|
||||
case .fixed(let value):
|
||||
let optionLabel = "\(value) \(distanceUnit.label(for: Double(value)))"
|
||||
let optionLabel = String(format: String(localized: "%lld %@", comment: "Distance label"), 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."
|
||||
body = String(localized: "Town driving. Fires most often — you're rarely inside the circle for long, so pings are meaningful. Best for stop-start local trips.", comment: "Alert radius guidance")
|
||||
case 2:
|
||||
body = "City or suburb commute. Slightly earlier heads-up than the smallest option; still fires regularly around town."
|
||||
body = String(localized: "City or suburb commute. Slightly earlier heads-up than the smallest option; still fires regularly around town.", comment: "Alert radius guidance")
|
||||
case 3:
|
||||
body = "Mixed driving. Middle ground: heads-up a few minutes out, still works in most towns."
|
||||
body = String(localized: "Mixed driving. Middle ground: heads-up a few minutes out, still works in most towns.", comment: "Alert radius guidance")
|
||||
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."
|
||||
body = String(localized: "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.", comment: "Alert radius guidance")
|
||||
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."
|
||||
body = String(localized: "Long-distance journeys. Maximum reach: a ping when a cheap station is coming up from far out. In town this essentially never fires.", comment: "Alert radius guidance")
|
||||
}
|
||||
return "\(optionLabel) — \(body)"
|
||||
return String(format: String(localized: "%@ — %@", comment: "Alert radius guidance with option label"), optionLabel, body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,74 +137,99 @@ struct AlertsView: View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
Toggle("Cheapest favourite", isOn: $favouriteDropEnabled)
|
||||
Picker("Fuel", selection: $favouriteDropFuel) {
|
||||
Toggle(String(localized: "Cheapest favourite", comment: "Favourite price-drop alert toggle"), isOn: $favouriteDropEnabled)
|
||||
Picker(String(localized: "Fuel", comment: "Fuel picker title"), selection: $favouriteDropFuel) {
|
||||
ForEach(FuelType.allCases) { fuel in
|
||||
Text(fuel.displayName).tag(fuel)
|
||||
}
|
||||
}
|
||||
Button("Check now", action: onCheckFavouriteDropNow)
|
||||
Button(String(localized: "Check now", comment: "Manual favourite price-drop check button"), action: onCheckFavouriteDropNow)
|
||||
.disabled(!favouriteDropEnabled)
|
||||
} header: {
|
||||
Text("Price-drop alerts")
|
||||
Text(String(localized: "Price-drop alerts", comment: "Favourite price-drop alerts section title"))
|
||||
} footer: {
|
||||
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.")
|
||||
Text(String(localized: "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.", comment: "Favourite price-drop alerts section footer"))
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle("Cheapest station", isOn: $enabled)
|
||||
Picker("Fuel", selection: $fuel) {
|
||||
Toggle(String(localized: "Cheapest station", comment: "Proximity alert toggle"), isOn: $enabled)
|
||||
Picker(String(localized: "Alert type", comment: "Proximity alert type picker title"), selection: $alertMode) {
|
||||
ForEach(AlertMode.allCases.filter { mode in mode != .bestValue || bestValueConfigured }) { mode in
|
||||
Text(mode.displayName).tag(mode)
|
||||
}
|
||||
}
|
||||
if !bestValueConfigured {
|
||||
Text(String(localized: "Set up Best Value first so Best Value alerts can account for your fill size, MPG and travel cost.", comment: "Best Value alert unavailable hint"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Picker(String(localized: "Fuel", comment: "Fuel picker title"), selection: $fuel) {
|
||||
ForEach(FuelType.allCases) { fuel in
|
||||
Text(fuel.displayName).tag(fuel)
|
||||
}
|
||||
}
|
||||
Picker("Radius", selection: alertChoice) {
|
||||
Text("Follow search (\(searchInUnitLabel))").tag(AlertRadiusChoice.followSearch)
|
||||
Picker(String(localized: "Radius", comment: "Alert radius picker title"), selection: alertChoice) {
|
||||
Text(String(format: String(localized: "Follow search (%@)", comment: "Follow current station search distance option"), searchInUnitLabel)).tag(AlertRadiusChoice.followSearch)
|
||||
ForEach(FuelStore.alertRadiusOptions, id: \.self) { value in
|
||||
Text("\(value) \(distanceUnit.label(for: Double(value)))").tag(AlertRadiusChoice.fixed(value))
|
||||
Text(String(format: String(localized: "%lld %@", comment: "Distance label"), value, distanceUnit.label(for: Double(value)))).tag(AlertRadiusChoice.fixed(value))
|
||||
}
|
||||
}
|
||||
Text(alertRadiusGuidance)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
if alertMode == .bestValue {
|
||||
Picker(String(localized: "Minimum saving", comment: "Best Value alert threshold picker title"), selection: $bestValueThresholdGBP) {
|
||||
ForEach(FuelStore.bestValueAlertThresholdOptionsGBP, id: \.self) { value in
|
||||
Text(FuelSavingsFormat.money(value)).tag(value)
|
||||
}
|
||||
}
|
||||
if !bestValueConfigured {
|
||||
Text(String(localized: "Set up Best Value first so FuelBoard can account for your fill size, MPG and travel cost.", comment: "Best Value alert setup hint"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
if enabled && monitoredCount == 0 {
|
||||
Text("No stations monitored yet — open the Stations tab to load prices first.")
|
||||
Text(String(localized: "No stations monitored yet — open the Stations tab to load prices first.", comment: "No monitored stations hint"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} header: {
|
||||
Text("Proximity alerts")
|
||||
Text(String(localized: "Proximity alerts", comment: "Proximity alerts section title"))
|
||||
} 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.")
|
||||
let modeDescription = alertMode == .bestValue
|
||||
? String(format: String(localized: "When you approach the Best Value station within the radius, FuelBoard sends a notification if it could leave you at least %@ better off after extra return travel. Nearby stations selling %@ are watched — favourites get priority — and each station alerts at most once per hour.", comment: "Best Value proximity alerts section footer"), FuelSavingsFormat.money(bestValueThresholdGBP), fuel.displayName.lowercased())
|
||||
: String(format: String(localized: "When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling %@ 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.", comment: "Proximity alerts section footer"), fuel.displayName.lowercased())
|
||||
Text(modeDescription)
|
||||
}
|
||||
|
||||
if enabled, let lastAlert {
|
||||
Section("Last alert") {
|
||||
Section(String(localized: "Last alert", comment: "Last alert section title")) {
|
||||
Label(lastAlert, systemImage: "bell.badge.fill")
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle("Cheapest station", isOn: $liveActivityEnabled)
|
||||
Picker("Fuel", selection: $liveActivityFuel) {
|
||||
Toggle(String(localized: "Cheapest station", comment: "Live Activity toggle"), isOn: $liveActivityEnabled)
|
||||
Picker(String(localized: "Fuel", comment: "Fuel picker title"), selection: $liveActivityFuel) {
|
||||
ForEach(FuelType.allCases) { fuel in
|
||||
Text(fuel.displayName).tag(fuel)
|
||||
}
|
||||
}
|
||||
Picker("Distance", selection: laChoice) {
|
||||
Text("Follow search (\(searchInUnitLabel))").tag(LADistanceChoice.followSearch)
|
||||
Picker(String(localized: "Distance", comment: "Distance picker title"), selection: laChoice) {
|
||||
Text(String(format: String(localized: "Follow search (%@)", comment: "Follow current station search distance option"), searchInUnitLabel)).tag(LADistanceChoice.followSearch)
|
||||
ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in
|
||||
Text(distanceLabel(forMiles: miles)).tag(LADistanceChoice.fixed(miles))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Live Activity")
|
||||
Text(String(localized: "Live Activity", comment: "Live Activity section title"))
|
||||
} 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.")
|
||||
Text(String(localized: "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.", comment: "Live Activity section footer"))
|
||||
}
|
||||
}
|
||||
.navigationTitle("Alerts")
|
||||
.navigationTitle(String(localized: "Alerts", comment: "Alerts tab title"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,138 @@
|
||||
"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": {
|
||||
|
||||
|
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 |