android: the phone, on the real core — the mockup shell, playback, the media card, the catalog, the resolver, Save, the dev flavour
The same Tauri 2 project builds for Android: mobile.html as a third Vite
entry (src/mobile/, the mockup on real data), tauri.android.conf.json, the
mobile stubs (updater, lyric alignment, vocal separation), rustls reqwest
on Android, the AAudio buffer, and a hand-driven detour build
(src-tauri/android/{env,build,deploy,dev}.sh) while Developer Mode is off.
ANDROID.md is the recipe.
Playback: the media-session plugin crate (lock-screen card, MediaStyle
notification, audio focus, headset buttons, a foreground service that
promotes itself first — Android 14 kills the app otherwise), session
restore across reload and restart, solid chrome after measuring the
backdrop-filter lag.
The catalog: Search is the desktop's top bar (the top-result rule, artist
cards), the artist page and the record page are the desktop's, Home is the
desktop's landing page, Gaps per followed shelf. Playing goes through the
desktop's own source rules, moved verbatim from state.svelte.js into
src/lib/sources.js so both apps share one resolver; the phone readies the
next three tracks and crosses boundaries gapless. Save fetches a record or
a track into the library by the download rules; Transfers shows the
burst. The raw Soulseek search stays, offered on request, a peer's copy
streaming with the lane rising as the bytes arrive. Community is the
Soulseek side (rooms, chat, buddies); the private layer waits.
The opener and storage plugins (social links, the picked library folder,
all-files access), the volume normalization switch, and the harness:
tests/mobile/smoke.mjs walks the mock (a fake network behind
src/lib/events.js) and the real phone over CDP, perf.mjs measures frames.
This commit is contained in:
1 parent
972dd103cb
commit
bb828ff2dc
102 files changed
+12258
-830
No files matched your search
+40
-15
@@ -284,11 +284,22 @@ pub fn forget_credentials(app: tauri::AppHandle) -> Result<(), String> {
|
||||
/// Open a native folder picker; returns the chosen path or null.
|
||||
#[tauri::command]
|
||||
pub async fn pick_folder(app: tauri::AppHandle) -> Result<Option<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
app.dialog().file().pick_folder(move |path| {
|
||||
let _ = tx.send(path.map(|p| p.to_string()));
|
||||
});
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<Option<String>>();
|
||||
// A folder picker is a desktop dialog; Android hands folders over through
|
||||
// the Storage Access Framework, which is its own story (the mockup's
|
||||
// Import step 1). Until then the phone answers "nothing picked".
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
app.dialog().file().pick_folder(move |path| {
|
||||
let _ = tx.send(path.map(|p| p.to_string()));
|
||||
});
|
||||
}
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
let _ = app;
|
||||
let _ = tx.send(None);
|
||||
}
|
||||
Ok(rx.await.unwrap_or(None))
|
||||
}
|
||||
|
||||
@@ -5369,6 +5380,7 @@ pub async fn mini_close(app: tauri::AppHandle) -> Result<(), String> {
|
||||
/// modal move/size loop, and a maximize/restore (Kyle photographed the
|
||||
/// desktop punching through a maximized window on 2026-08-28 — the accent
|
||||
/// did not survive the transition on its own).
|
||||
#[cfg(windows)]
|
||||
pub(crate) static GLASS_ON: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
@@ -5378,12 +5390,14 @@ pub(crate) static GLASS_ON: std::sync::atomic::AtomicBool =
|
||||
/// swaps down to it for the length of a gesture and back up on release
|
||||
/// (see thumbbar.rs). Same struct, one field different.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[cfg(windows)]
|
||||
pub(crate) enum Accent {
|
||||
Off,
|
||||
Blur,
|
||||
Acrylic,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn apply_accent(hwnd: *mut std::ffi::c_void, accent: Accent) -> Result<(), String> {
|
||||
#[repr(C)]
|
||||
struct AccentPolicy {
|
||||
@@ -5451,6 +5465,7 @@ pub(crate) fn apply_accent(hwnd: *mut std::ffi::c_void, accent: Accent) -> Resul
|
||||
/// (2026-08-29). So transitions are now explicitly forced ON (clearing any
|
||||
/// disable a previous build left on this window), and the discrete bracket
|
||||
/// in thumbbar.rs + GROUND_DISCRETE in glass.js covers the detach window.
|
||||
#[cfg(windows)]
|
||||
fn set_transitions_disabled(hwnd: *mut std::ffi::c_void, disabled: bool) {
|
||||
#[allow(non_snake_case)]
|
||||
#[link(name = "kernel32")]
|
||||
@@ -5481,17 +5496,27 @@ fn set_transitions_disabled(hwnd: *mut std::ffi::c_void, disabled: bool) {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn window_glass(window: tauri::WebviewWindow, on: bool) -> Result<(), String> {
|
||||
// Clear any system backdrop a previous toggle/build may have applied.
|
||||
let _ = window.set_effects(None::<tauri::utils::config::WindowEffectsConfig>);
|
||||
let hwnd = window.hwnd().map_err(|e| e.to_string())?.0 as *mut std::ffi::c_void;
|
||||
// The mini calls this too (window_glass acts on the CALLING window), and
|
||||
// the subclass re-asserting from thumbbar.rs is on the MAIN window only —
|
||||
// so only the main window's state is tracked.
|
||||
if window.label() == "main" {
|
||||
GLASS_ON.store(on, std::sync::atomic::Ordering::Relaxed);
|
||||
// The glass is the Windows compositor's acrylic; a phone's ground is the
|
||||
// artwork itself (see mobile.css), so there is nothing to apply here.
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let _ = (window, on);
|
||||
return Ok(());
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Clear any system backdrop a previous toggle/build may have applied.
|
||||
let _ = window.set_effects(None::<tauri::utils::config::WindowEffectsConfig>);
|
||||
let hwnd = window.hwnd().map_err(|e| e.to_string())?.0 as *mut std::ffi::c_void;
|
||||
// The mini calls this too (window_glass acts on the CALLING window), and
|
||||
// the subclass re-asserting from thumbbar.rs is on the MAIN window only —
|
||||
// so only the main window's state is tracked.
|
||||
if window.label() == "main" {
|
||||
GLASS_ON.store(on, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
set_transitions_disabled(hwnd, false);
|
||||
apply_accent(hwnd, if on { Accent::Acrylic } else { Accent::Off })
|
||||
}
|
||||
set_transitions_disabled(hwnd, false);
|
||||
apply_accent(hwnd, if on { Accent::Acrylic } else { Accent::Off })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user