android: the downloads say so in the shade
A Save still running with the app in the background now posts a progress notice — how many files, the one it is on, a bar, Stop all and Open — and takes it down when the queue empties. It sits on its own low-importance channel so it can be silenced without silencing the player's card, and rides its own dataSync foreground service rather than the media one: a mediaPlayback service stops itself when the bar empties, a download is not playback, and nothing else was keeping the process alive for a Save with nothing playing — which is how the first real Save died, four tracks in. Stop all goes through the same cancel(id) a row's tap takes, and Transfers grew the matching button beside Clear finished. The walk pushes two rows straight into the transfer store and reads the real notification back out of `dumpsys notification`, without fetching anything.
This commit is contained in:
1 parent
4fc6e9c630
commit
5738c2d7bf
31 files changed
+1358
-6
No files matched your search
@@ -0,0 +1,110 @@
|
||||
//! 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()
|
||||
}
|
||||
Reference in new issue
Block a user