android: a playlist, out of the phone
The desktop has had Export since playlists v2 and the phone had no door onto it for one honest reason: `tauri-plugin-dialog` does open Android's create-document sheet, but it answers with a `content://` URI and `playlist_export` wrote with `std::fs::write`, which cannot take one. The storage plugin grew a `create_document` whose Kotlin writes the bytes through the ContentResolver — a plugin function called from Rust, not a command, so the file never passes through the WebView and nothing was added to the ACL — and `playlist_export` now builds the file above the platform fork with the same `playlist_formats` writers, so the phone rebuilds no format and the two exports cannot drift. The door is the playlist's own sheet, on the page and on the shelf card, with the desktop's five profiles in the desktop's order; a dismissed sheet says nothing, because nothing happened. The mock walk drives it three ways — a file written where the sheet pointed, a sheet dismissed, a refusal read back as a sentence — and the phone walk opens the real sheet and dismisses it with BACK, writing nothing to the phone. The Kotlin is unbuilt: nothing in this tree compiles it, though it is written against tauri-android's own `PluginHandle`, `Invoke` and `JSObject` rather than against a guess at them.
This commit is contained in:
1 parent
95c2bfeeba
commit
04c87e0db1
9 files changed
+390
-72
No files matched your search
+34
-12
@@ -52,7 +52,10 @@ What the phone does differently at runtime:
|
||||
- the audio output stream asks for an 8192-frame buffer (`open_output_stream`);
|
||||
AAudio's default put it on the fast path with a 10 ms burst, where the mixer's
|
||||
inline decode and file reads underran audibly;
|
||||
- `pick_folder` answers "nothing picked" (desktop-only dialog); `save_file` works;
|
||||
- `pick_folder` answers "nothing picked" (desktop-only dialog); `save_file`
|
||||
opens Android's create-document sheet, but answers a `content://` URI that
|
||||
`std::fs` cannot open — which is why the playlist export goes through the
|
||||
storage plugin's `create_document` instead;
|
||||
- the window-glass command is a no-op (the ground is the artwork);
|
||||
- the aligner and the separator report "not on this platform";
|
||||
- the desktop's Win32 perf counters read 0 / None in the debug overlay.
|
||||
@@ -459,6 +462,25 @@ describe / pin / duplicate / delete, `playlist_add_items`,
|
||||
verdict, the stream rows are verified against the catalog while it is up
|
||||
(the desktop's pass, verbatim), and Create swaps the review for the
|
||||
playlist it made (`nav.replace`).
|
||||
- **Exporting** (`playlist_export`, the playlist's own sheet and the shelf
|
||||
card's): the desktop's five profiles, in the desktop's order and its own
|
||||
words — M3U8, XSPF, PLS, CSV and the backup JSON. The file is built in
|
||||
Rust by `playlist_formats`, ABOVE the platform fork, so the phone never
|
||||
rebuilds a format and the two exports cannot drift. The doors differ
|
||||
because the answers do: `tauri-plugin-dialog` does open Android's
|
||||
create-document sheet, but it answers with a `content://` URI and
|
||||
`std::fs::write` cannot take one, so on the phone the bytes go to
|
||||
`tauri-plugin-storage`'s `create_document` and its Kotlin writes them
|
||||
through the ContentResolver (`openOutputStream(uri, "wt")`), wherever the
|
||||
sheet pointed. That is a plugin function called from Rust, not a command:
|
||||
the file never passes through the WebView, and nothing was added to the
|
||||
ACL. A dismissed sheet resolves nothing and the phone says nothing —
|
||||
which is the desktop's `Ok(None)`. A row with no local file exports
|
||||
exactly as it does on the desktop: named, with a comment where the
|
||||
location would be (M3U8), carried without a location (XSPF, CSV, the
|
||||
backup), or left out (PLS, which holds only real files). The locations
|
||||
that ARE written are this phone's own paths, inside the app's folder —
|
||||
the format behaving correctly, not a bug to rewrite.
|
||||
- **The share sheet** (`crates/tauri-plugin-share`, `share.svelte.js`):
|
||||
Share → SoulStream from any app. The plugin's `ShareActivity` (an
|
||||
ACTION_SEND text/plain target, merged into the app's manifest from the
|
||||
@@ -469,9 +491,9 @@ describe / pin / duplicate / delete, `playlist_add_items`,
|
||||
anything else the pasted-tracklist door. The phone walk sends the intent
|
||||
itself (`adb shell am start -a android.intent.action.SEND …`).
|
||||
|
||||
Not on the phone yet: the playlist-file door and export — both want a file
|
||||
picker the storage plugin has still to grow — the listener door (it needs
|
||||
yt-dlp, a desktop tool), drag reorder, and Share on SoulStream.
|
||||
Not on the phone yet: the playlist-file door — it wants a file picker the
|
||||
storage plugin has still to grow — the listener door (it needs yt-dlp, a
|
||||
desktop tool), drag reorder, and Share on SoulStream.
|
||||
|
||||
## The first run
|
||||
|
||||
@@ -1111,7 +1133,10 @@ all at 915×411) and back → and last, on a reload of its own, a phone that
|
||||
has just been installed (the first-run sheet and its two cards, its
|
||||
Soulseek door, the notes on Home, on both Library tabs and on Search, the
|
||||
preview from You, a folder picked from the tour and the note losing the row
|
||||
it answered), then back to the provisioned device, asserts what each step should show — the Home
|
||||
it answered), then back to the provisioned device, and after that the
|
||||
playlist export door three ways (a file written where the sheet pointed, a
|
||||
dismissed sheet that says nothing at all, and a refusal read back as a
|
||||
sentence), asserts what each step should show — the Home
|
||||
step holds the launch to a budget as well, reading off `__ss.boot` how many
|
||||
commands the app issued before its first frame, how many of those left in the
|
||||
shell's own turn, how long it took, and requiring round 12's art prewarm to
|
||||
@@ -1134,7 +1159,7 @@ list, a null page), `offline(on)` takes the client down — everything the
|
||||
backend funnels through `with_client()` answers "not connected to Soulseek",
|
||||
the two connects answer the server string, and `soulseek:state` goes out — and
|
||||
`clear()` puts all of it back. They reject with a plain string because that is
|
||||
what Tauri rejects with. Nine mock steps drive them, and the phone walk carries
|
||||
what Tauri rejects with. Twelve mock steps drive them, and the phone walk carries
|
||||
one safe step of its own (`trouble-words`: eleven real backend strings through
|
||||
`__ss.say` on the device, every output asserted free of Rust). A mock step can
|
||||
also set the
|
||||
@@ -1203,12 +1228,9 @@ symptom.
|
||||
- **Off by design — external runtimes.** yt-dlp and deno, the vocal
|
||||
separator, the lyric alignment models, and the listener door that needs
|
||||
them.
|
||||
- **Waiting on Rust, not on a picker.** Playlist EXPORT: `tauri-plugin-dialog`
|
||||
does open Android's create-document sheet from Rust, but it answers with a
|
||||
`content://` URI and `playlist_export` writes with `std::fs::write`, which
|
||||
cannot take one. It needs a ContentResolver write, or the bytes handed to
|
||||
an outbound share intent — and `crates/tauri-plugin-share` is inbound-only
|
||||
today. Playlist-FILE import wants a `pick_file` in the storage plugin,
|
||||
- **Waiting on a picker.** The storage plugin can WRITE a document now
|
||||
(`create_document`, which the playlist export goes through) but it cannot
|
||||
read one. Playlist-FILE import wants a `pick_file` in the storage plugin,
|
||||
beside the `pick_folder` and `pick_image` it already has.
|
||||
- **Still desktop-only, smaller.** Drag reorder (Move up / down / to the top
|
||||
stand in), catalog track previews, the full discography page, and the
|
||||
|
||||
Reference in new issue
Block a user