diff --git a/ANDROID.md b/ANDROID.md index ad2a1e5..0eb4049 100644 --- a/ANDROID.md +++ b/ANDROID.md @@ -22,7 +22,7 @@ implements is `research/mobile-mockup/` (gitignored; see its README). | `src/mobile/contacts.svelte.js` | the local view of a person on the network: a petname, a group, a note and the two marks (`contacts_meta_list` / `contact_meta_set`) — this phone only, published nowhere | | `crates/tauri-plugin-media-session/` | a Tauri plugin with a Kotlin foreground service (`MediaService`) that owns the `MediaSession`, the MediaStyle notification, audio focus and the becoming-noisy pause. The audio never passes through it; it is what keeps the process alive in the background and what the lock screen shows | | `src/mobile/Sheet.svelte`, `HalfSheet.svelte`, `Lane.svelte`, `Cover.svelte`, `Release.svelte` | the player (art / lyrics / queue faces, container transform from the mini bar, drags), the bottom sheets (track, record, sort, the page overflow, the sleep timer), the waveform lane, the two card pieces (a long press on a card is its sheet) | -| `src/mobile/screens/` | Home, Search (the library half: records and artists here, tracks through `library_track_search`), Library (albums / artists / tracks / playlists), Album, Artist (the library's shell at once, the catalog dissolving in: portrait, Popular, discography with what is here marked), CatalogAlbum (a catalog record, rows the library has play), Playlist, Community (sign in; rooms, chat, buddies — the Soulseek side; the private layer waits), Room, Thread, You (Appearance switches, real) | +| `src/mobile/screens/` | Home, Search (the library half: records and artists here, tracks through `library_track_search`), Library (albums / artists / tracks / playlists), Album, Artist (the library's shell at once, the catalog dissolving in: portrait, Popular, discography with what is here marked), CatalogAlbum (a catalog record, rows the library has play), Playlist, Community (sign in; rooms, chat, buddies, taste — the Soulseek side; the private layer waits), Room, Thread, You (Appearance switches, real) | | `src/mobile/prefs.svelte.js` | Appearance, kept in localStorage: ambience, accent from artwork, motion | | `src-tauri/tauri.android.conf.json` | merged over `tauri.conf.json` on Android: one window at `mobile.html`, no desktop resources, `minSdkVersion` 26 | | `src-tauri/gen/android/` | the generated Gradle project (`npx tauri android init`); `minSdk = 26`. Gitignored | @@ -176,6 +176,18 @@ dimmed cards that open the catalog record page, the totals remembered in The server's room list on Community is filtered by name and shows 80 at a time with a way to the rest (Soulseek lists hundreds). +Community's fourth tab is **Taste**: the interests this phone has +declared to the Soulseek server (`slsk_interests_mine`, `slsk_interest_set`) +and the people the server ranks as close to them (`slsk_similar_users`), a +row each into the peer sheet. A like or a hate is a chip and tapping one +takes it back; "Add a like" and "Add a hate" declare a new one. Every +declaration asks the server again and the chips stay quiet until it has +answered, because the ranking only moves when its input does. The lists +are this client's own — the backend keeps them and re-declares every one +at login, since the server's copy belongs to the account and another +client signed in elsewhere can change it under us. Mock-walked only: a +live sign-in and the server's own ranking are Kyle's to see. + ## A peer, and their shares A raw result's ⋯ (or a long press on its head) is the peer's sheet: play diff --git a/src/lib/mockLibrary.js b/src/lib/mockLibrary.js index bc44ad0..92c1da5 100644 --- a/src/lib/mockLibrary.js +++ b/src/lib/mockLibrary.js @@ -570,6 +570,9 @@ const cacheSizes = new Map(); // path → the file's size, for stream_cache_le const preloading = new Set(); let xfer = null; // the one fake transfer: { received, total, timer } let posTimer = null; // the fake engine's position ticks +/* the interests this fake account has declared, and the server's match on + them — a declaration moves both, the way the real server does */ +const MY_INTERESTS = { likes: ["Daft Punk", "french house", "Warp Records"], hates: ["loudness war"] }; function mockNet(cmd, args) { if (cmd === "get_credentials") return Promise.resolve({ username: MOCK_USER, has_password: true }); if (cmd === "connect_stored" || cmd === "connect_and_login") { @@ -676,6 +679,22 @@ function mockNet(cmd, args) { return Promise.resolve(t); } if (cmd === "stop_search") return Promise.resolve(); + if (cmd === "slsk_interests_mine") return Promise.resolve({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() }); + if (cmd === "slsk_interest_set") { + const thing = String(args?.thing || "").trim(); + if (!thing) return Promise.reject(new Error("nothing to declare")); + const list = args?.like ? MY_INTERESTS.likes : MY_INTERESTS.hates; + const at = list.findIndex((t) => t.toLowerCase() === thing.toLowerCase()); + if (args?.on && at < 0) list.push(thing); + if (!args?.on && at >= 0) list.splice(at, 1); + return new Promise((r) => setTimeout(() => r({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() }), 200)); + } + if (cmd === "slsk_similar_users") { + /* the server ranks on the likes we declared: one more name per like */ + const pool = ["hifi_hank", "dusty_crates", "mp3_marge", "ada", "cy", "bo"]; + const n = MY_INTERESTS.likes.length; + return new Promise((r) => setTimeout(() => r(pool.slice(0, Math.min(pool.length, n + 1)).map((username, i) => ({ username, rating: Math.max(1, n - i) }))), 300)); + } if (cmd === "player_state") return Promise.resolve({ status: "idle", position: 0 }); if (cmd === "preload_local") return Promise.resolve(); if (cmd === "waveform") { diff --git a/src/mobile/screens/Community.svelte b/src/mobile/screens/Community.svelte index bb358ad..e0053e7 100644 --- a/src/mobile/screens/Community.svelte +++ b/src/mobile/screens/Community.svelte @@ -1,8 +1,9 @@