android: playlists, the import doors, the share sheet, You → Sources — and three bugs the walks found
The desktop's two playlist pages on the phone: the shelf (pinned first, mosaic covers, badges, New playlist, Import) and the page (kind, Pinned chip, description, the meta line's warnings, Play / Shuffle / more, a filter, rows tagged unresolved / set audio / file missing). Sheets for a card, the page's overflow, a row, pick-a-playlist, a prompt and a confirm; Add to playlist on every track and record sheet; a removal undoable for seven seconds from the neutral toast (notice.svelte.js); stream rows play through the resolver as path-less entries, a set slice starts and ends at its cues; Add to library / Download all tracks with the desktop's bulk rules; nav.replace for a review that became the thing it made. The import doors that need no file — a link (YouTube · Spotify · Apple Music · TIDAL · Deezer), a pasted tracklist, a YouTube DJ set — with the review screen and the desktop's catalog verification pass. A share plugin crate (crates/tauri-plugin-share): Share → SoulStream from any app keeps the text in a ShareActivity and the WebView hands it to the import when it comes back to the front. You → Sources: the source-quality window and the priority per mode. Found by the walks and fixed: set_settings takes camelCase arguments, so the phone's normalization and library-folder saves never landed; Cover.svelte's non-eager wrapper has no layout box, so its intersection observer never fired and row thumbnails never loaded (the Artists tab too); a history push while a history.go was still in flight left the nav mirror one entry ahead and the page blank after a confirmed delete — the mirror now applies once per microtask and never pushes during a pending traversal. The harness names unmocked commands, carries console warnings' frames (the smoke bundle is built unminified), probes the history mirror on every step, mocks app_status / play_local / waveform / lyrics_get / player_state and applies settings patches, runs adb steps on the phone (the share intent), and walks playlists, the imports, the share and the settings on both the mock and the phone. ANDROID.md documents all of it.
This commit is contained in:
1 parent
bb828ff2dc
commit
6e39dc43f3
38 files changed
+2383
-88
No files matched your search
@@ -1258,3 +1258,80 @@ html[data-motion="off"] *, html[data-motion="off"] *::before, html[data-motion="
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── playlists: the desktop's shelf and page, at phone scale ─────────── */
|
||||
.plstats { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 0 var(--pad) 12px; }
|
||||
.plstats .t-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.newpl { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 12px 0 10px; font-size: 13px; font-weight: 750; flex: none; }
|
||||
.plsec { padding-top: 6px; }
|
||||
.plsec h2 { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 800; letter-spacing: 0.8px; text-transform: uppercase; color: var(--text-muted); }
|
||||
.plgrid { padding-bottom: 8px; }
|
||||
/* the pinned mark on a card, the desktop's pinned-mark */
|
||||
.cover-wrap .mark { position: absolute; top: 7px; left: 7px; z-index: 2; width: 22px; height: 22px; border-radius: 50%; background: rgba(0, 0, 0, 0.55); color: #fff; display: grid; place-items: center; }
|
||||
/* the page: the record page's shape with a smaller cover (a mosaic reads at any size) */
|
||||
.rec-head.pl .rec-cover { width: 56%; max-width: 260px; }
|
||||
.rec-meta .kind .pin-chip { display: inline-flex; align-items: center; gap: 3px; margin-left: 8px; padding: 2px 7px; border-radius: 999px; background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent-hover); letter-spacing: 0.4px; vertical-align: 1px; }
|
||||
.rec-desc { margin-top: 8px; font-size: 13.5px; line-height: 1.45; color: var(--text-secondary); padding: 0 8px; }
|
||||
.rec-line .warn { color: #f5a524; font-weight: 650; }
|
||||
.sfield.plfilter { margin-top: 10px; }
|
||||
.pl-rows .trk .thumb { width: 38px; height: 38px; border-radius: 6px; overflow: hidden; flex: none; background: rgba(255, 255, 255, 0.06); display: grid; place-items: center; color: var(--text-muted); }
|
||||
.pl-rows .trk .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.pl-rows .trk .n.stamped { width: auto; min-width: 22px; font-size: 11.5px; }
|
||||
.pl-rows .trk.unresolved .t, .pl-rows .trk.missing .t { color: var(--text-secondary); }
|
||||
.pl-rows .trk .t .tag { margin-left: 6px; vertical-align: 1px; }
|
||||
.tag.un { background: rgba(232, 163, 61, 0.16); color: #e8a33d; }
|
||||
.tag.miss { background: rgba(241, 94, 108, 0.16); color: #f15e6c; }
|
||||
.tag.seg { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent-hover); }
|
||||
/* the neutral toast: a fact with one action (Undo); the error toast keeps its red */
|
||||
.toast.notice { display: flex; align-items: center; gap: 12px; background: #1f1f26; color: #fff; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(255, 255, 255, 0.1); }
|
||||
.toast.notice .nt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.toast.notice .na { flex: none; color: var(--accent-hover); font-weight: 800; font-size: 13px; padding: 4px 8px; margin: -4px -8px -4px 0; border-radius: 6px; }
|
||||
.toast.notice .na:active { background: rgba(255, 255, 255, 0.1); }
|
||||
/* the prompt and confirm sheets */
|
||||
.prompt { padding: 4px var(--pad) 16px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.prompt .field.area { height: auto; min-height: 84px; padding: 12px 14px; border-radius: 14px; resize: none; line-height: 1.4; font-family: inherit; }
|
||||
.prompt-acts { display: flex; gap: 8px; }
|
||||
.prompt-acts .btn { flex: 1; }
|
||||
.btn.btn-fill.danger { background: #f15e6c; color: #fff; }
|
||||
.btn.btn-fill.danger:active { background: #d94a58; }
|
||||
|
||||
/* ── the import doors and the review ─────────────────────────────────── */
|
||||
.plbtns { display: flex; gap: 8px; padding: 0 var(--pad) 12px; }
|
||||
.plbtns .newpl { flex: 1; justify-content: center; }
|
||||
.imp-head { padding: 4px var(--pad) 10px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.imp-src { font-size: 14.5px; font-weight: 700; }
|
||||
.imp-counts { font-size: 13px; color: var(--text-muted); }
|
||||
.imp-counts b { font-weight: 800; }
|
||||
.imp-counts .ok { color: #3ddc84; }
|
||||
.imp-counts .st { color: var(--accent-hover); }
|
||||
.imp-counts .un { color: #e8a33d; }
|
||||
.imp-warn { font-size: 12.5px; line-height: 1.5; color: #e8a33d; padding: 8px 12px; border-radius: 10px; background: rgba(232, 163, 61, 0.1); }
|
||||
.imp-rows .trk { min-height: 44px; }
|
||||
.imp-rows .trk .tag { flex: none; }
|
||||
.imp-rows .trk.excluded { opacity: 0.4; }
|
||||
.tag.ok { background: rgba(61, 220, 132, 0.16); color: #3ddc84; }
|
||||
.tag.st { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent-hover); }
|
||||
.tag.dim { opacity: 0.7; }
|
||||
.imp-form { margin: 10px var(--pad) 0; }
|
||||
.imp-foot { display: flex; gap: 8px; padding: 14px var(--pad) 4px; }
|
||||
.imp-foot .btn { flex: 1; }
|
||||
.imp-wait { margin: 40px var(--pad) 0; padding: 26px 18px; border-radius: var(--r-card); background: var(--surface); box-shadow: var(--ring); display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; font-size: 13px; color: var(--text-secondary); }
|
||||
.imp-wait b { color: #fff; font-size: 15px; }
|
||||
.imp-wait .ring { width: 26px; height: 26px; border-radius: 50%; border: 3px solid rgba(255, 255, 255, 0.15); border-top-color: var(--accent); animation: imp-spin 0.9s linear infinite; }
|
||||
@keyframes imp-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ── You → Sources: a window per mode, the priority inside it ─────────── */
|
||||
.pref-head { padding: 12px 14px 2px; font-size: 11px; font-weight: 800; letter-spacing: 0.8px; text-transform: uppercase; color: var(--text-muted); }
|
||||
.pref { padding: 8px 0 12px; }
|
||||
.pref + .pref { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.055); }
|
||||
.pref-l { padding: 0 14px 8px; }
|
||||
.pref-l .t { display: block; font-size: 14.5px; font-weight: 650; }
|
||||
.pref-l .m { display: block; font-size: 12px; color: var(--text-muted); margin-top: 1px; }
|
||||
.pref .chips { padding: 0 14px; }
|
||||
|
||||
/* ── a playlist's bulk run: the desktop's bulkline, under the actions ── */
|
||||
.bulkline { display: inline-flex; align-items: center; gap: 8px; margin: 2px auto 4px; padding: 7px 8px 7px 12px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); font-size: 12.5px; color: var(--text-secondary); max-width: 100%; text-align: left; }
|
||||
.bulkline > span:not(.ring) { min-width: 0; }
|
||||
.bulkline .ring { width: 14px; height: 14px; flex: none; border-radius: 50%; border: 2px solid rgba(255, 255, 255, 0.15); border-top-color: var(--accent); animation: imp-spin 0.9s linear infinite; }
|
||||
.bulkline .bulkx { display: grid; place-items: center; width: 24px; height: 24px; flex: none; border-radius: 50%; color: var(--text-muted); }
|
||||
.bulkline .bulkx:active { background: rgba(255, 255, 255, 0.12); }
|
||||
Reference in new issue
Block a user