perf: stop per-cover HttpClient churn (Android) + scroll-arrow reflow (web)
Android: MusicRepository.coverArtUrl/streamUrl built a fresh SubsonicClient per call — each spins up (and never closes) a Ktor HttpClient. That ran for every cover on every recomposition, causing heavy GC/alloc churn (and a leak) that showed up as scroll/tab lag. Reuse the cached ClientHolder client for URL building instead. Web: the home scroll-row arrows called getBoundingClientRect on every scroll event (layout thrashing). Split it: only toggle arrow visibility on scroll (rAF-throttled); do the getBoundingClientRect vertical-centering only on resize/content change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
ef25ac3100
commit
d50e22b62d
2 files changed
+17
-9
No files matched your search
@@ -76,8 +76,9 @@ class MusicRepository(private val context: Context) {
|
||||
}
|
||||
}
|
||||
val creds = lastCredentials ?: return null
|
||||
return SubsonicClient(creds.serverUrl, creds.username, creds.password)
|
||||
.coverArtUrl(id ?: return null, size)
|
||||
// Reuse the cached client — building a fresh SubsonicClient here spins up a whole Ktor
|
||||
// HttpClient per cover (and never closes it), which is heavy GC/alloc churn while scrolling.
|
||||
return ClientHolder.get(creds).coverArtUrl(id ?: return null, size)
|
||||
}
|
||||
|
||||
fun streamUrl(id: String): String? {
|
||||
@@ -86,7 +87,7 @@ class MusicRepository(private val context: Context) {
|
||||
if (f.exists()) return Uri.fromFile(f).toString()
|
||||
}
|
||||
val creds = lastCredentials ?: return null
|
||||
return SubsonicClient(creds.serverUrl, creds.username, creds.password).streamUrl(id)
|
||||
return ClientHolder.get(creds).streamUrl(id) // reuse cached client (no per-call HttpClient)
|
||||
}
|
||||
|
||||
private fun offlineRepo(): OfflineRepository? = runCatching {
|
||||
|
||||
+13
-6
@@ -131,24 +131,31 @@ export function enhanceScrollRow(row) {
|
||||
const right = mkBtn('sa-right', _CHEV_R, 'Weiter');
|
||||
|
||||
const EPS = 4;
|
||||
const update = () => {
|
||||
// Cheap per-scroll work — just toggle which arrow is available; rAF-throttled to once per frame.
|
||||
let _rafPending = false;
|
||||
const updateArrows = () => {
|
||||
_rafPending = false;
|
||||
left.classList.toggle('can-scroll', row.scrollLeft > EPS);
|
||||
right.classList.toggle('can-scroll', row.scrollLeft < row.scrollWidth - row.clientWidth - EPS);
|
||||
// Vertically center the arrows on the cover art (fallback: CSS default 72px)
|
||||
};
|
||||
const onScroll = () => { if (!_rafPending) { _rafPending = true; requestAnimationFrame(updateArrows); } };
|
||||
// Expensive (getBoundingClientRect forces layout) — only on size/content change, NOT every scroll.
|
||||
const positionArrows = () => {
|
||||
const art = row.querySelector('.card-art') || row.querySelector('.card');
|
||||
if (art) {
|
||||
const top = art.getBoundingClientRect().top - wrap.getBoundingClientRect().top + art.offsetHeight / 2;
|
||||
if (top > 0) { left.style.top = right.style.top = top + 'px'; }
|
||||
}
|
||||
updateArrows();
|
||||
};
|
||||
const step = () => Math.max(160, row.clientWidth * 0.8);
|
||||
left.addEventListener('click', () => row.scrollBy({ left: -step(), behavior: 'smooth' }));
|
||||
right.addEventListener('click', () => row.scrollBy({ left: step(), behavior: 'smooth' }));
|
||||
|
||||
row.addEventListener('scroll', update, { passive: true });
|
||||
new ResizeObserver(update).observe(row);
|
||||
new MutationObserver(update).observe(row, { childList: true });
|
||||
update();
|
||||
row.addEventListener('scroll', onScroll, { passive: true });
|
||||
new ResizeObserver(positionArrows).observe(row);
|
||||
new MutationObserver(positionArrows).observe(row, { childList: true });
|
||||
positionArrows();
|
||||
}
|
||||
|
||||
/** Enhance every `.scroll-row` currently in the DOM (the static Home rows). */
|
||||
|
||||
Reference in new issue
Block a user