Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
111 lines
3.8 KiB
Plaintext
111 lines
3.8 KiB
Plaintext
//! The downloads notice on the phone.
|
|
//!
|
|
//! The Transfers screen only exists inside the app: the desktop says what a
|
|
//! Save is doing with its title-bar ring, the phone with a badge on the nav,
|
|
//! and neither is visible once the app is in the background — which is
|
|
//! exactly when a Save is left to run on its own. So a running Save says so
|
|
//! where the phone says things, in the shade: how many files are left, the
|
|
//! one it is on, a progress bar, Stop all and Open.
|
|
//!
|
|
//! Its own channel, low importance, so it can be silenced without silencing
|
|
//! the player's card. Its own foreground service (`TransferService`,
|
|
//! `dataSync`) rather than the media one, because a download is not
|
|
//! playback: `MediaService` stops itself the moment the bar empties, and a
|
|
//! `mediaPlayback` service with no session is a policy violation. That
|
|
//! service is also what keeps the process alive for a Save with nothing
|
|
//! playing — the way the first real Save died, four tracks in.
|
|
//!
|
|
//! Two commands, both fed by the phone UI (src/mobile/xfernote.svelte.js):
|
|
//! `update` with what is still in flight, `clear` when the queue empties.
|
|
//! The notice's buttons arrive as the plugin event `action`
|
|
//! (`addPluginListener("transfer-notice", "action", …)`): `cancel` stops
|
|
//! everything still going, `open` brings the app forward on Transfers.
|
|
//!
|
|
//! On the desktop the plugin is registered too, so the permission set
|
|
//! resolves on every platform, but its commands answer "not here".
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use tauri::{
|
|
plugin::{Builder, TauriPlugin},
|
|
Runtime,
|
|
};
|
|
|
|
/// What the notice says: how many files are still in flight, the one it is
|
|
/// on, and how far the burst has come. `indeterminate` is a queue whose
|
|
/// sizes no peer has stated yet — a bar with no end to walk towards.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TransferNotice {
|
|
#[serde(default)]
|
|
pub count: u32,
|
|
pub title: Option<String>,
|
|
pub line: Option<String>,
|
|
#[serde(default)]
|
|
pub percent: i32,
|
|
#[serde(default)]
|
|
pub indeterminate: bool,
|
|
}
|
|
|
|
#[cfg(mobile)]
|
|
pub struct TransferNoticePlugin<R: Runtime>(tauri::plugin::PluginHandle<R>);
|
|
|
|
#[tauri::command]
|
|
fn update<R: Runtime>(app: tauri::AppHandle<R>, state: TransferNotice) -> Result<(), String> {
|
|
#[cfg(mobile)]
|
|
{
|
|
use tauri::Manager;
|
|
let notice = app.state::<TransferNoticePlugin<R>>();
|
|
notice
|
|
.0
|
|
.run_mobile_plugin::<serde_json::Value>("update", state)
|
|
.map(|_| ())
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
#[cfg(not(mobile))]
|
|
{
|
|
let _ = (app, state);
|
|
Err("the downloads notice is a phone feature".into())
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn clear<R: Runtime>(app: tauri::AppHandle<R>) -> Result<(), String> {
|
|
#[cfg(mobile)]
|
|
{
|
|
use tauri::Manager;
|
|
let notice = app.state::<TransferNoticePlugin<R>>();
|
|
notice
|
|
.0
|
|
.run_mobile_plugin::<serde_json::Value>("clear", ())
|
|
.map(|_| ())
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
#[cfg(not(mobile))]
|
|
{
|
|
let _ = app;
|
|
Err("the downloads notice is a phone feature".into())
|
|
}
|
|
}
|
|
|
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
Builder::new("transfer-notice")
|
|
.invoke_handler(tauri::generate_handler![update, clear])
|
|
.setup(|app, api| {
|
|
#[cfg(target_os = "android")]
|
|
{
|
|
use tauri::Manager;
|
|
let handle = api.register_android_plugin(
|
|
"com.soulstream.transfernotice",
|
|
"TransferNoticePlugin",
|
|
)?;
|
|
app.manage(TransferNoticePlugin(handle));
|
|
}
|
|
#[cfg(not(target_os = "android"))]
|
|
{
|
|
let _ = (app, api);
|
|
}
|
|
Ok(())
|
|
})
|
|
.build()
|
|
}
|