The server address and the player token were set from alerts that opened on shaking the phone, which nobody finds. Settings now has a This device section in the app: the server, the player token and its address, and a Make button that creates an API token and hands it straight to the app's keychain. The app sends what it has at ready (appSettings) and takes setServer, setToken and clearToken; a new server rebuilds the web view as the alert did. The first-launch prompt and the Cannot reach alert stay native, since there is no page then. The iPhone is portrait only; the iPad keeps all four orientations, which Split View and Slide Over need (#434 checked the iPad's layout). Build 14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
5.6 KiB
YAML
146 lines
5.6 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: "14"
|
|
# 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
|
|
# Upright only on a phone, where the page's layout is made for portrait; every way on an
|
|
# iPad, which also needs all four for Split View and Slide Over.
|
|
UISupportedInterfaceOrientations~iphone:
|
|
- UIInterfaceOrientationPortrait
|
|
UISupportedInterfaceOrientations~ipad:
|
|
- 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: "14"
|
|
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
|