From 3c8989dfa6c3aef4595e8ddeba5df3ea65b16190 Mon Sep 17 00:00:00 2001 From: KeelrLeek <156356974+KeelrLeek@users.noreply.github.com> Date: Sun, 13 Sep 2026 22:03:57 -0500 Subject: [PATCH] android: the hub's filters on the network results, pasted lyrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The raw results take the Soulseek hub's filters — a free slot only, FLAC or MP3 320+ — as chips above the folders. The lyrics sheet has the desktop's paste door: plain text, or LRC with timestamps, kept for the track. --- ANDROID.md | 4 +++- src/lib/mockLibrary.js | 6 ++++++ src/mobile/HalfSheet.svelte | 3 ++- src/mobile/lyrics.svelte.js | 15 +++++++++++++++ src/mobile/screens/Search.svelte | 20 +++++++++++++++++++- tests/mobile/smoke.mjs | 13 +++++++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/ANDROID.md b/ANDROID.md index 4ad9024..eb153a9 100644 --- a/ANDROID.md +++ b/ANDROID.md @@ -213,7 +213,9 @@ Save menu: Save to library (at once when the cache is byte-complete, else through the download pipeline) and Save this record for the streamed record the queue holds. A moment after boot the phone warms the library's thumbnails the desktop's way (`library_art_prewarm` at the cards' edge, one decode at a -time behind the tiles' gate). The nav wears two badges the desktop's chrome has: transfers in flight on +time behind the tiles' gate). The network results carry the hub's filters (a free slot only; FLAC or +MP3 320+), and the lyrics sheet has the desktop's paste door (plain text, +or LRC with timestamps, kept for the track). The nav wears two badges the desktop's chrome has: transfers in flight on You (the title bar's download ring) and unread chats on Community. The Library's count line carries the collection's hours and the sort offers By format (lossless by depth and rate first, then by bitrate); the diff --git a/src/lib/mockLibrary.js b/src/lib/mockLibrary.js index 3238986..e75ed81 100644 --- a/src/lib/mockLibrary.js +++ b/src/lib/mockLibrary.js @@ -623,6 +623,12 @@ function mockNet(cmd, args) { ]), 200)); if (cmd === "lyrics_select") return Promise.resolve({ ...mockLyrics(args?.title), source: "lrclib #" + args?.candidateId }); if (cmd === "lyrics_set_offset") return Promise.resolve(); + if (cmd === "lyrics_save_text") { + const lines = String(args?.text || "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean); + const timed = lines.map((l) => { const m = /^\[(\d+):(\d\d(?:\.\d+)?)\]\s*(.*)$/.exec(l); return m ? { time_ms: Math.round((+m[1] * 60 + +m[2]) * 1000), text: m[3] } : null; }); + const synced = timed.every(Boolean); + return Promise.resolve({ status: synced ? "synced" : "plain", source: "pasted", synced: synced ? timed : [], plain: synced ? null : lines.join("\n"), offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false }); + } if (cmd === "lyrics_clear") return Promise.resolve(); if (cmd === "clear_play_history") return Promise.resolve(); if (cmd === "refresh_album_art") return new Promise((r) => setTimeout(() => r("C:\\mock\\cover.jpg"), 200)); diff --git a/src/mobile/HalfSheet.svelte b/src/mobile/HalfSheet.svelte index ea8d1c3..81a4a01 100644 --- a/src/mobile/HalfSheet.svelte +++ b/src/mobile/HalfSheet.svelte @@ -16,7 +16,7 @@ import { DOORS, start as startImport } from "./importer.svelte.js"; import { slsk, refreshRooms, disconnect, openThread, addBuddy, removeBuddy, leaveRoom } from "./slsk.svelte.js"; import { lib, albumById, albumsBy, tracksOf, loadLibrary, refreshPlaylists, refreshRecents } from "./lib.svelte.js"; - import { lyr, nudge, searchCandidates, closeCandidates, chooseCandidate, forgetLyrics } from "./lyrics.svelte.js"; + import { lyr, nudge, searchCandidates, closeCandidates, chooseCandidate, forgetLyrics, pasteLyrics } from "./lyrics.svelte.js"; import { art, wantArt } from "./art.svelte.js"; import { note } from "./notice.svelte.js"; import { fmtOf, albumFmt, fmtBytes, basename, dirname, longDur, mmss, titleFromName } from "./util.js"; @@ -577,6 +577,7 @@
{/if} {@render row("search", "Wrong lyrics? Search again", "LRCLIB, by this track's name", () => { void searchCandidates(); })} + {@render row("file-text", "Paste lyrics…", "plain text, or LRC with [mm:ss] timestamps", () => openHalf("prompt", { title: "Paste lyrics", placeholder: "[00:12.50] The first line…", ok: "Save", multiline: true, onsubmit: (v) => { void pasteLyrics(v); } }))} {@render row("refresh", "Forget and look again", "", act(() => { void forgetLyrics(); }))} {/if} diff --git a/src/mobile/lyrics.svelte.js b/src/mobile/lyrics.svelte.js index bbcdf7d..29f6746 100644 --- a/src/mobile/lyrics.svelte.js +++ b/src/mobile/lyrics.svelte.js @@ -105,3 +105,18 @@ export async function forgetLyrics() { try { await api.lyricsClear(id.title, id.artist, id.durationSecs); } catch { /* nothing kept */ } await loadLyrics(true); } + +/** Pasted words: LRC with timestamps becomes synced, anything else plain. */ +export async function pasteLyrics(text) { + const id = identity(); + const t = String(text || "").trim(); + if (!id || !t) return false; + try { + const p = await api.lyricsSaveText(id.title, id.artist, id.durationSecs, t); + if (p && p.status) apply(p); else await loadLyrics(true); + return true; + } catch (e) { + player.error = String(e); + return false; + } +} diff --git a/src/mobile/screens/Search.svelte b/src/mobile/screens/Search.svelte index 9497637..5aa3fcf 100644 --- a/src/mobile/screens/Search.svelte +++ b/src/mobile/screens/Search.svelte @@ -263,6 +263,15 @@ netSearch(query).catch(() => {}); inputEl?.blur(); } + /* the hub's filters over the raw results: a free slot only, a format */ + let netFree = $state(false); + let netFmt = $state("any"); + const shownGroups = $derived(net.groups.filter((g) => { + if (netFree && !g.slot) return false; + if (netFmt === "flac") return g.topExt === "flac"; + if (netFmt === "mp3320") return g.topExt !== "flac" && (g.maxBitrate ?? 0) >= 320; + return true; + })); function playPeer(g, i) { const es = groupEntries(g, i); if (es.length) playEntries(es, 0, g.folderName).catch((e) => { player.error = String(e); }); @@ -421,10 +430,19 @@ {#if !net.searching}{/if} {#if net.error}