The iOS client is now a screen-for-screen port of the Android app over the
same pstr-android UniFFI surface, with no Rust changes: Library, title page,
History, Shares (public links and Drive folders), Downloads and Settings,
with the same strings, undo snackbars and two-pane layout on wide screens.
- Theme from the engine's palette (Material role names, Inter, the same
type ramp and corners), repainted live from the appearance settings.
- Account sign-in with two-factor code, mailbox password and the Proton
CAPTCHA in a non-persistent WKWebView, the Drive folder browser, and
watch-history sync every five minutes, after sign-in, on player close and
when the app leaves the screen.
- Player over libmpv: resume, progress saved every 5 s, autoplay with the
up-next card, skip and auto-skip from the chapter plan, track, speed,
volume and episode pickers, landscape lock, mini transport, and
lock-screen controls with interruption and route-change handling.
- Offline downloads with the Android record format, free-space floor,
retries, transfer rate and the Wi-Fi-only policy; interrupted transfers
resume at next launch.
- Config, data and download state are excluded from backup, as Android's
allowBackup=false: a restore would bring shares back without their
Keychain secrets and give two devices one sync identity.
Picture-in-picture, the share sheet and background downloads are not
possible the same way on iOS; docs/IOS.md lists why and what replaces them.
Adds XCTest ports of the Android unit tests, a `test` command to
scripts/build-ios.sh, the app icon and its render script, and an iOS
workflow on macos-15 that runs the tests and builds the unsigned .ipa.
- Desktop shows Proton's CAPTCHA instead of failing the sign-in: a wry webview
in a child process of the app (`pstr-app::verify`), confined to
https://*.proton.me, hands the token back and the sign-in is retried. Linux
packages and CI gain WebKitGTK 4.1; the Windows zip and MSI ship
WebView2Loader.dll, without which a -gnu build does not start.
- The desktop keyring splits a secret too long for one entry across several,
so a Proton session with many key salts survives Windows' 1280-unit cap.
Short secrets stay one entry, so every existing secret still reads.
- `WatchSync` runs against a small `HistoryDrive` trait and is tested end to
end against an in-memory Drive: two devices, newer wins, no re-upload of an
unchanged history, a missing file written again, an unreadable own file
never overwritten, a newer-format file left alone.
- An installation's file unwritten for 90 days is retired by the next device
to sync, after its entries are merged and carried in that device's file.
- Removing a share keeps its watch state: sync would bring it back from the
other devices anyway, and re-adding the share picks up where it left off.
- Watch history syncs once more on desktop exit (bounded to 3 s) and, on
Android, through a WorkManager job enqueued when the app leaves the screen.
- The second-factor prompts say security keys are not supported.
Upstream mpv-android moved its build to NDK r30 (30.0.16248370), and each
mpv-android revision pins its own NDK, so the two move together. The new
revision also updates harfbuzz to 14.5.0, unibreak to 8.0, fribidi to
1.0.17, libxml2 to 2.15.4, fontconfig to 2.18.3 and curl to 8.22.0, and
builds libass with assembly on x86_64 (nasm, already installed in CI).
r30 brings an LLVM update with codegen and LTO fixes and sysroots up to
API 37. It is also the NDK current SDK managers install, so a fresh setup
no longer needs r29 installed beside it.
The script change invalidates the cached libmpv tree, so CI rebuilds it
once. The staged libraries are the same set as before, all 16 KiB aligned.
Gradle deletes the declared outputs of a task it has no history for, so
on every fresh checkout buildLibmpvAndroid threw away the libmpv tree just
restored from the cache and rebuilt it from source (~14 min per job). The
task no longer declares outputs; the script's STAMP decides.
rust-cache treats target/android/<triple> as a profile directory and
deletes its release tree before saving, so both cargo-ndk ABIs were
rebuilt from scratch on every run (~6 min per job). It now caches only the
registry and helpers; the target directory goes through actions/cache,
keyed on toolchain and lockfile and restoring from the last lockfile.
- libmpv is built in its own step and saved with actions/cache/save right
after, so a failure later in the job no longer discards a 12 minute
build the way a post-job save did
- lint's OldTargetApi fails the gate whenever a new platform ships, like
the dependency freshness checks already marked informational; raising
targetSdk is a deliberate change with its own device pass
The fontconfig that the pinned mpv-android revision builds requires
meson >= 1.11, and ubuntu-24.04's apt meson is 1.3.2, so buildLibmpvAndroid
failed configuring fontconfig.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every Android run since August failed in :app:buildRustHost: the host
build of pstr-android reaches libdbus-sys through keyring's Secret
Service store, and the runner had no libdbus-1-dev. With no successful
run, nothing was ever cached, so each run rebuilt from nothing.
- libdbus-1-dev in both jobs
- one shared, save-on-failure Rust cache for both jobs
- the staged libmpv tree cached on the build script's hash; the script
stamps its output with the revision and its own hash and skips the
libmpv build when the stamp matches
PLAN.md records the Android parity and UI work that follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The signing secrets are now configured, so the job no longer needs to be
switched off. It runs for v* tags and for a manual dispatch with
signed_release set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>