A CarPlay scene beside the phone's: In Progress, Unread and Shows as
CPListTemplates from ipx's API, read with the player's credentials, and
a pick played through the same Playback, so it resumes from the saved
place, saves as it goes and marks read at 90%. Now Playing is the
system's template over Playback's now-playing info.
When the car starts a different episode the page is sent
{t:"replaced"} and closes its player bar. Left open, the bar would have
gone on as the old episode while the host's clock belonged to the new
one, saving that time and marking the wrong item read.
The badge's fetch moves into API.get, which CarPlay shares. Build 13.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
142 lines
5.3 KiB
YAML
142 lines
5.3 KiB
YAML
# XcodeGen makes the .xcodeproj from this, so the project is something a diff can show rather
|
|
# than a pbxproj nobody reviews. On a Mac:
|
|
#
|
|
# brew install xcodegen && cd ios && xcodegen generate && open iPX.xcodeproj
|
|
name: iPX
|
|
options:
|
|
bundleIdPrefix: net.sdf1
|
|
deploymentTarget:
|
|
iOS: "17.0"
|
|
createIntermediateGroups: true
|
|
settings:
|
|
base:
|
|
SWIFT_VERSION: "5.9"
|
|
TARGETED_DEVICE_FAMILY: "1,2"
|
|
CODE_SIGN_STYLE: Automatic
|
|
targets:
|
|
iPX:
|
|
type: application
|
|
platform: iOS
|
|
sources:
|
|
- Sources
|
|
- Shared
|
|
- Resources/Assets.xcassets
|
|
dependencies:
|
|
- target: iPXWidgets
|
|
info:
|
|
path: Resources/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: iPX
|
|
CFBundleShortVersionString: $(MARKETING_VERSION)
|
|
CFBundleVersion: "13"
|
|
# Playing on with the screen locked. A plist key, not a signed entitlement.
|
|
UIBackgroundModes: [audio, fetch, remote-notification]
|
|
# The unread badge's refresh while the app is closed (#421).
|
|
BGTaskSchedulerPermittedIdentifiers: [net.sdf1.ipx.refresh]
|
|
# HTTPS only, so TestFlight need not ask the export-compliance question on every build.
|
|
ITSAppUsesNonExemptEncryption: false
|
|
UILaunchScreen: {}
|
|
UIApplicationSceneManifest:
|
|
UIApplicationSupportsMultipleScenes: false
|
|
UISceneConfigurations:
|
|
UIWindowSceneSessionRoleApplication:
|
|
- UISceneConfigurationName: Default Configuration
|
|
UISceneDelegateClassName: $(PRODUCT_MODULE_NAME).SceneDelegate
|
|
# The car's screen (#420), a scene of its own beside the phone's.
|
|
CPTemplateApplicationSceneSessionRoleApplication:
|
|
- UISceneConfigurationName: CarPlay
|
|
UISceneClassName: CPTemplateApplicationScene
|
|
UISceneDelegateClassName: $(PRODUCT_MODULE_NAME).CarPlaySceneDelegate
|
|
UISupportedInterfaceOrientations:
|
|
- UIInterfaceOrientationPortrait
|
|
- UIInterfaceOrientationPortraitUpsideDown
|
|
- UIInterfaceOrientationLandscapeLeft
|
|
- UIInterfaceOrientationLandscapeRight
|
|
# Plain HTTP only for a server on your own network (192.168.1.130:8099); the tunnel is HTTPS.
|
|
NSAppTransportSecurity:
|
|
NSAllowsLocalNetworking: true
|
|
# The page's service worker runs only for these, so iPX opens offline only at them
|
|
# (#435): the server, and Cloudflare Access and Authentik, where its sign-in goes. Ten at
|
|
# most, and a server not here loses the bridge (WebViewController.makeWebView).
|
|
WKAppBoundDomains:
|
|
- ipodderx.sdf1.net
|
|
- rays-sdf1.cloudflareaccess.com
|
|
- authentik.sdf1.net
|
|
# The icon's count pushed by ipx (#433). Development here; Xcode makes it production when it
|
|
# exports for TestFlight.
|
|
entitlements:
|
|
path: Resources/iPX.entitlements
|
|
properties:
|
|
aps-environment: development
|
|
# Shared with the widget (#428), which reads what the app last knew from it.
|
|
com.apple.security.application-groups: [group.net.sdf1.ipx]
|
|
# Granted by Apple on request (2026-11-09); the App ID has it under Additional Capabilities.
|
|
com.apple.developer.carplay-audio: true
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: net.sdf1.ipx
|
|
MARKETING_VERSION: "0.1.0"
|
|
DEVELOPMENT_TEAM: 8743L8D729
|
|
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
|
# The script below writes into the built Info.plist, which the sandbox would refuse.
|
|
ENABLE_USER_SCRIPT_SANDBOXING: NO
|
|
postBuildScripts:
|
|
# A debug build is app-bound to localhost too, so the simulator can test the service
|
|
# worker against a page served on the Mac (#435). Never in a release.
|
|
- name: App-bound localhost (debug)
|
|
basedOnDependencyAnalysis: false
|
|
script: |
|
|
if [ "$CONFIGURATION" = Debug ]; then
|
|
/usr/libexec/PlistBuddy -c "Add :WKAppBoundDomains: string localhost" "$TARGET_BUILD_DIR/$INFOPLIST_PATH"
|
|
fi
|
|
|
|
# The widget (#428): the unread count and the last episode, from what the app writes to the App
|
|
# Group. Its build number has to match the app's, or App Store Connect refuses the upload.
|
|
iPXWidgets:
|
|
type: app-extension
|
|
platform: iOS
|
|
sources:
|
|
- Widgets
|
|
- Shared
|
|
info:
|
|
path: Widgets/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: iPX
|
|
CFBundleShortVersionString: $(MARKETING_VERSION)
|
|
CFBundleVersion: "13"
|
|
NSExtension:
|
|
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
|
entitlements:
|
|
path: Widgets/iPXWidgets.entitlements
|
|
properties:
|
|
com.apple.security.application-groups: [group.net.sdf1.ipx]
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: net.sdf1.ipx.widgets
|
|
MARKETING_VERSION: "0.1.0"
|
|
DEVELOPMENT_TEAM: 8743L8D729
|
|
# Shared/ is built here too, where the app's player does not exist.
|
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS: $(inherited) WIDGET
|
|
|
|
iPXTests:
|
|
type: bundle.unit-test
|
|
platform: iOS
|
|
sources:
|
|
- Tests
|
|
dependencies:
|
|
- target: iPX
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: net.sdf1.ipx.tests
|
|
GENERATE_INFOPLIST_FILE: YES
|
|
|
|
schemes:
|
|
iPX:
|
|
build:
|
|
targets:
|
|
iPX: all
|
|
iPXTests: [test]
|
|
test:
|
|
targets:
|
|
- iPXTests
|