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
@@ -0,0 +1,94 @@
|
||||
/* The discography as the desktop files it (CatalogArtist.svelte's tabs):
|
||||
one category per release, the categories' order and labels from the
|
||||
shared TAB_DEFS, editions folded into families by editions.js. The
|
||||
desktop also weighs MusicBrainz type hints and each record's own
|
||||
description before the title heuristics; the phone files on what the
|
||||
catalog page carries — the edition link, the announced date, the
|
||||
mixtape flag, then the same title rules the library uses. */
|
||||
import { categoryForLocal, TAB_DEFS } from "../lib/localReleaseType.js";
|
||||
import { releaseFamilies } from "../lib/editions.js";
|
||||
|
||||
const today = () => new Date().toISOString().slice(0, 10);
|
||||
|
||||
/** An announced full-length: dated, and the date is still ahead. */
|
||||
export const isUpcoming = (a) =>
|
||||
(a.record_type || "").toLowerCase() === "album"
|
||||
&& typeof a.release_date === "string" && a.release_date.length >= 10
|
||||
&& a.release_date.slice(0, 10) > today();
|
||||
|
||||
function ownCategory(album) {
|
||||
if (isUpcoming(album)) return "upcoming";
|
||||
if (album.is_mixtape) return "mixtape";
|
||||
return categoryForLocal(album);
|
||||
}
|
||||
|
||||
/** A linked edition files WITH its base release (one hop). */
|
||||
export function categoryFor(album, albums) {
|
||||
if (album.edition_group != null && album.id !== album.edition_group) {
|
||||
const base = (albums || []).find((o) => o.id === album.edition_group);
|
||||
if (base) return ownCategory(base);
|
||||
}
|
||||
return ownCategory(album);
|
||||
}
|
||||
|
||||
/** [{ key, label, items: families }] — only the tabs with something in them. */
|
||||
export function discoTabs(albums) {
|
||||
const list = albums || [];
|
||||
const by = {};
|
||||
for (const def of TAB_DEFS) by[def.key] = [];
|
||||
for (const a of list) (by[categoryFor(a, list)] ?? by.other).push(a);
|
||||
return TAB_DEFS
|
||||
.map((def) => ({ key: def.key, label: def.label, items: releaseFamilies(def.cats.flatMap((c) => by[c] || []), def.key) }))
|
||||
.filter((t) => t.items.length > 0);
|
||||
}
|
||||
|
||||
/** "1997–present" / "1997–2013": the released records' span. */
|
||||
export function activeYears(albums) {
|
||||
const years = (albums || [])
|
||||
.filter((a) => !isUpcoming(a))
|
||||
.map((a) => Number(String(a.release_date || "").slice(0, 4)))
|
||||
.filter((y) => Number.isFinite(y) && y > 1900);
|
||||
if (!years.length) return null;
|
||||
const first = Math.min(...years);
|
||||
const last = Math.max(...years);
|
||||
const nowYear = new Date().getFullYear();
|
||||
return last >= nowYear - 1 ? `${first}–present` : `${first}–${last}`;
|
||||
}
|
||||
|
||||
/** The newest dated record. */
|
||||
export function latestOf(albums) {
|
||||
let best = null;
|
||||
for (const a of albums || []) {
|
||||
if (!a.release_date) continue;
|
||||
if (!best || a.release_date > best.release_date) best = a;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/** One column per year across the whole span, gaps included — null under
|
||||
* three years, when it would say nothing. */
|
||||
export function timelineOf(albums) {
|
||||
const byYear = new Map();
|
||||
for (const a of albums || []) {
|
||||
const y = String(a.release_date || "").slice(0, 4);
|
||||
if (y) byYear.set(y, (byYear.get(y) ?? 0) + 1);
|
||||
}
|
||||
const years = [...byYear.keys()].sort();
|
||||
if (years.length < 3) return null;
|
||||
const first = Number(years[0]);
|
||||
const last = Number(years[years.length - 1]);
|
||||
const peak = Math.max(...byYear.values());
|
||||
const cols = [];
|
||||
for (let y = first; y <= last; y++) {
|
||||
const n = byYear.get(String(y)) ?? 0;
|
||||
cols.push({ year: y, n, h: n ? Math.max(8, Math.round((n / peak) * 44)) : 2 });
|
||||
}
|
||||
return { cols, first, last, total: (albums || []).length };
|
||||
}
|
||||
|
||||
/** The desktop's social chips: a label per link kind. */
|
||||
export const SOCIAL_LABEL = {
|
||||
website: "Website", wikipedia: "Wikipedia", bandcamp: "Bandcamp", soundcloud: "SoundCloud",
|
||||
youtube: "YouTube", instagram: "Instagram", twitter: "X", x: "X", facebook: "Facebook", tiktok: "TikTok",
|
||||
spotify: "Spotify", apple: "Apple Music", discogs: "Discogs", lastfm: "Last.fm", musicbrainz: "MusicBrainz",
|
||||
};
|
||||
Reference in new issue
Block a user