android: the catalog's numbers — Popular's own lane, a count per row, the artist's photo

Popular is asked on its own as well as with the page, so the section can
paint before the full page is built and an artist whose page came back
with none still gets one. A catalog record's rows that the album's own
artist anchor found no count for — a feature's guest, a compilation's
stranger — get the deferred Last.fm pass by their own artist id. The
artist page's ⋯ is its own sheet now: choose a hero photo from this phone,
or put the catalog's own back; set_artist_hero is pick_artist_hero's
install step split out and shared, so the desktop's dialog keeps its door.
You gains the desktop's Refresh artist portraits, and a data: URL is the
image itself in the art gate — a chosen hero comes back from Rust as one,
and the thumbnail fetch used to throw it away.
This commit is contained in:
KeelrLeek committed 2026-09-13 23:46:07 -05:00
1 parent c737809325
commit 1737c5e1b6
12 files changed
+232 -13

No files matched your search

+31 -2
View File
@@ -370,6 +370,9 @@ export function mockInvoke(cmd, args) {
if (cmd === "album_tracks")
return new Promise((r) => setTimeout(() => r(tracksFor(args.albumId)), 60));
if (cmd === "album_art") {
/* not a path: the real one fails to open it, which is why the art
gate has to answer a data: URL itself */
if (String(args.path || "").startsWith("data:")) return Promise.reject(new Error("album_art: not a path"));
const m = /(\d+)$/.exec(args.path || "");
const num = m ? +m[1] : 0;
return Promise.resolve(svgArt((num * 47) % 360, String.fromCharCode(65 + (num % 26))));
@@ -494,6 +497,8 @@ const NEWS = [
{ title: "Infinity Repeating gets a video", url: "https://example.com/news/2", source: "NME", date: Math.floor(Date.now() / 1000) - 86400 * 2 },
];
const RELATED = [["Justice", 411], ["Air", 412], ["Kavinsky", 413], ["Chemical Brothers", 414]].map(([name, id], i) => ({ id, name, picture: i % 2 ? null : svgArt(40 + id, name[0]), album_count: 3, fans: 900000 - id * 100 }));
/* the heroes chosen this session — pick_image → set_artist_hero */
const heroes = {};
let follows = [];
function mockCatalog(cmd, args) {
if (cmd === "catalog_artist") {
@@ -502,7 +507,7 @@ function mockCatalog(cmd, args) {
if (!a) return Promise.reject(new Error("catalog_artist: unknown artist"));
const own = a === CAT_ARTIST;
return new Promise((r) => setTimeout(() => r({
artist: a, banner: null, banners: [], banner_custom: false, logos: [],
artist: a, banner: heroes[a.id] ?? null, banners: heroes[a.id] ? [heroes[a.id]] : [], banner_custom: !!heroes[a.id], logos: [],
popular_tracks: own ? CAT_TRACKS.slice(0, 7).map((t, i) => ({ ...t, listeners: 2_400_000 - i * 310_000 })) : [],
albums: own ? CAT_DISCO : [{ id: 900 + id, title: a.name + " Live", artist: a.name, cover: null, release_date: "2015-01-01", record_type: "album", track_count: 9, edition_group: null, is_soundtrack: false }],
top_genre: "Electronic", socials: [],
@@ -518,6 +523,27 @@ function mockCatalog(cmd, args) {
if (cmd === "catalog_artist_profile") return Promise.resolve({ listeners: 3_100_000, plays: 210_000_000, tags: ["electronic", "house", "french house", "dance"] });
if (cmd === "catalog_artist_bio") return Promise.resolve("Daft Punk were a French electronic music duo formed in Paris in 1993 by Guy-Manuel de Homem-Christo and Thomas Bangalter. They achieved popularity in the late 1990s as part of the French house movement.");
if (cmd === "catalog_related") return Promise.resolve(RELATED);
/* Popular's own lane: ranked, with counts — and for the artists whose
page gives none (the related ones), the only Popular there is */
if (cmd === "catalog_artist_popular") {
const id = Number(args?.artistId);
const a = id === CAT_ARTIST.id ? CAT_ARTIST : RELATED.find((r) => r.id === id);
if (!a) return Promise.resolve([]);
const rows = id === CAT_ARTIST.id
? CAT_TRACKS.slice(0, 7)
: TITLES.slice(0, 4).map((t, i) => ({
id: 4200000 + id * 10 + i, title: t, artist: a.name, artist_id: id, rank: 700000 - i * 1000,
album: a.name + " Live", album_id: 900 + id, cover: null, duration_secs: 210 + i * 13, preview: null, track_no: i + 1,
}));
return new Promise((r) => setTimeout(() => r(rows.map((t, i) => ({ ...t, listeners: 1_900_000 - i * 240_000 }))), 260));
}
/* the deferred pass: a count per row, by that row's own artist id */
if (cmd === "catalog_track_listeners") {
const rows = args?.tracks || [];
return new Promise((r) => setTimeout(() => r(rows.map((t, i) => (t.artistId ? 640_000 - i * 20_000 : null))), 260));
}
if (cmd === "set_artist_hero") { const id = Number(args?.artistId); heroes[id] = svgArt(180, "H"); return Promise.resolve({ data_url: heroes[id], width: 3840, height: 2160 }); }
if (cmd === "clear_artist_hero") { const id = Number(args?.artistId); const had = id in heroes; delete heroes[id]; return Promise.resolve(had); }
if (cmd === "artist_play_stats") return Promise.resolve({ seconds: 7380, last_played_at: Math.floor(Date.now() / 1000) - 86400, top_title: "Track 1", top_count: 12 });
if (cmd === "followed_artists") return Promise.resolve(follows.slice());
if (cmd === "follow_artist") { follows.push({ artist_id: args?.artistId, name: args?.name, portrait: args?.portrait ?? null, followed_at: Date.now() / 1000 }); later(30, () => mockEmit("soulseek:follows-changed", {})); return Promise.resolve(); }
@@ -532,7 +558,9 @@ function mockCatalog(cmd, args) {
if (cmd === "catalog_album") {
const a = CAT_ALBUMS.find((x) => x.id === Number(args?.albumId));
if (!a) return Promise.reject(new Error("catalog_album: unknown album"));
return Promise.resolve({ album: a, tracks: a.id === 302127 ? CAT_TRACKS.map((t, i) => ({ ...t, listeners: 2_400_000 - i * 150_000 })) : [], album_plays: a.id === 302127 ? 310_000_000 : null, album_listeners: a.id === 302127 ? 4_100_000 : null, totals_summed: false });
/* the last two rows are a guest's: the album's own artist anchor
has no count for them, which is what the deferred pass is for */
return Promise.resolve({ album: a, tracks: a.id === 302127 ? CAT_TRACKS.map((t, i) => (i >= CAT_TRACKS.length - 2 ? { ...t, artist: "Daft Punk feat. Todd Edwards", artist_id: 411 } : { ...t, listeners: 2_400_000 - i * 150_000 })) : [], album_plays: a.id === 302127 ? 310_000_000 : null, album_listeners: a.id === 302127 ? 4_100_000 : null, totals_summed: false });
}
if (cmd === "catalog_album_info") {
const t = String(args?.title || "");
@@ -558,6 +586,7 @@ function mockCatalog(cmd, args) {
if (cmd === "rescan_library") return Promise.resolve();
if (cmd === "library_art_prewarm") return Promise.resolve();
if (cmd === "refresh_all_album_art") return new Promise((r) => setTimeout(() => r(140), 300));
if (cmd === "refresh_artist_art") return new Promise((r) => setTimeout(() => r(26), 300));
if (cmd === "catalog_art") return Promise.resolve(svgArt(200, "D"));
if (cmd === "stream_cache_ready") return Promise.resolve(cacheReady.has(args?.path) || (xfer && xfer.path === args?.path && xfer.received >= xfer.total));
if (cmd === "preload_stream") {