The storage plugin gains pick_image — the document picker limited to images, the picked content copied into the app's cache so the Rust side gets a plain path. playlist_cover_set(id, path) is the desktop pick's install step split out and shared (squared, the master cover, the row's revision moved). A playlist's sheet offers Choose cover photo and Remove custom cover; the mock walk exercises both.
139 lines
4.1 KiB
Rust
139 lines
4.1 KiB
Rust
//! Storage on the phone.
|
|
//!
|
|
//! The library on Android lives in the app's private folder unless the user
|
|
//! points it somewhere else. Pointing it somewhere else needs two things the
|
|
//! WebView cannot do: a folder picker (Android's document tree picker, whose
|
|
//! answer is a content URI — resolved here to a real path when the volume
|
|
//! allows, which the Rust scanner and the download pipeline work on), and
|
|
//! for a folder in shared storage, Android's all-files access, which only
|
|
//! the user can grant on a system screen.
|
|
//!
|
|
//! Three commands: `pick_folder` → `{ uri, path }` (path null when the tree
|
|
//! is not on a volume with a path), `all_files_access` → `{ granted }`, and
|
|
//! `request_all_files_access`, which opens the system screen. On the desktop
|
|
//! the plugin is registered so the permission set resolves, and every
|
|
//! command answers "not here".
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use tauri::{
|
|
plugin::{Builder, TauriPlugin},
|
|
Runtime,
|
|
};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PickedFolder {
|
|
pub uri: String,
|
|
pub path: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct AccessState {
|
|
pub granted: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PickedImage {
|
|
pub path: Option<String>,
|
|
}
|
|
|
|
#[cfg(mobile)]
|
|
pub struct Storage<R: Runtime>(tauri::plugin::PluginHandle<R>);
|
|
|
|
#[tauri::command]
|
|
async fn pick_folder<R: Runtime>(app: tauri::AppHandle<R>) -> Result<PickedFolder, String> {
|
|
#[cfg(mobile)]
|
|
{
|
|
use tauri::Manager;
|
|
let storage = app.state::<Storage<R>>();
|
|
storage
|
|
.0
|
|
.run_mobile_plugin::<PickedFolder>("pickFolder", ())
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
#[cfg(not(mobile))]
|
|
{
|
|
let _ = app;
|
|
Err("the folder picker here is the desktop's dialog".into())
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn all_files_access<R: Runtime>(app: tauri::AppHandle<R>) -> Result<AccessState, String> {
|
|
#[cfg(mobile)]
|
|
{
|
|
use tauri::Manager;
|
|
let storage = app.state::<Storage<R>>();
|
|
storage
|
|
.0
|
|
.run_mobile_plugin::<AccessState>("allFilesAccess", ())
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
#[cfg(not(mobile))]
|
|
{
|
|
let _ = app;
|
|
Ok(AccessState { granted: true })
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn request_all_files_access<R: Runtime>(app: tauri::AppHandle<R>) -> Result<(), String> {
|
|
#[cfg(mobile)]
|
|
{
|
|
use tauri::Manager;
|
|
let storage = app.state::<Storage<R>>();
|
|
storage
|
|
.0
|
|
.run_mobile_plugin::<serde_json::Value>("requestAllFilesAccess", ())
|
|
.map(|_| ())
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
#[cfg(not(mobile))]
|
|
{
|
|
let _ = app;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A photo from the phone: the document picker limited to images, the
|
|
/// picked content copied into the app's cache — a plain path for the Rust
|
|
/// side (a playlist's cover, for one).
|
|
#[tauri::command]
|
|
async fn pick_image<R: Runtime>(app: tauri::AppHandle<R>) -> Result<PickedImage, String> {
|
|
#[cfg(mobile)]
|
|
{
|
|
use tauri::Manager;
|
|
let storage = app.state::<Storage<R>>();
|
|
storage
|
|
.0
|
|
.run_mobile_plugin::<PickedImage>("pickImage", ())
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
#[cfg(not(mobile))]
|
|
{
|
|
let _ = app;
|
|
Err("the image picker here is the desktop's dialog".into())
|
|
}
|
|
}
|
|
|
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
Builder::new("storage")
|
|
.invoke_handler(tauri::generate_handler![pick_folder, all_files_access, request_all_files_access, pick_image])
|
|
.setup(|app, api| {
|
|
#[cfg(target_os = "android")]
|
|
{
|
|
use tauri::Manager;
|
|
let handle = api.register_android_plugin("com.soulstream.storage", "StoragePlugin")?;
|
|
app.manage(Storage(handle));
|
|
}
|
|
#[cfg(not(target_os = "android"))]
|
|
{
|
|
let _ = (app, api);
|
|
}
|
|
Ok(())
|
|
})
|
|
.build()
|
|
}
|