Many improvemtns and fixes related to decoupling of svelte and rust on android.
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 18s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been skipped
Traceability Validation / Check Requirement Traces (push) Failing after 2s

This commit is contained in:
2026-02-28 19:50:47 +01:00
parent 07f3bf04ca
commit e8e37649fa
53 changed files with 2309 additions and 792 deletions
+41 -81
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import type { MediaItem } from "$lib/api/types";
import { auth } from "$lib/stores/auth";
import CachedImage from "$lib/components/common/CachedImage.svelte";
interface Props {
items: MediaItem[];
@@ -13,7 +13,6 @@
let currentIndex = $state(0);
let intervalId: number | null = null;
let heroImageUrl = $state<string>("");
// Touch/swipe state
let touchStartX = $state(0);
@@ -22,82 +21,45 @@
const currentItem = $derived(items[currentIndex] ?? null);
// Load hero image URL asynchronously based on item priority
async function loadHeroImageUrl(): Promise<void> {
if (!currentItem) {
heroImageUrl = "";
return;
// Compute the best image source for the hero banner (no fetch, pure derivation)
const heroImageSource = $derived.by(() => {
if (!currentItem) return null;
// 1. Try backdrop image first (best for hero display)
if (currentItem.backdropImageTags?.[0]) {
return { itemId: currentItem.id, imageType: "Backdrop" as const, tag: currentItem.backdropImageTags[0] };
}
try {
const repo = auth.getRepository();
// 1. Try backdrop image first (best for hero display)
if (currentItem.backdropImageTags?.[0]) {
heroImageUrl = await repo.getImageUrl(currentItem.id, "Backdrop", {
maxWidth: 1920,
tag: currentItem.backdropImageTags[0],
});
return;
// 2. For episodes, try series/season backdrops
if (currentItem.type === "Episode") {
if (currentItem.seriesId && currentItem.parentBackdropImageTags?.[0]) {
return { itemId: currentItem.seriesId, imageType: "Backdrop" as const, tag: currentItem.parentBackdropImageTags[0] };
}
// 2. For episodes, try to use series backdrop from parent
if (currentItem.type === "Episode") {
// First try parent backdrop tags (includes image tag for caching)
if (currentItem.seriesId && currentItem.parentBackdropImageTags?.[0]) {
heroImageUrl = await repo.getImageUrl(currentItem.seriesId, "Backdrop", {
maxWidth: 1920,
tag: currentItem.parentBackdropImageTags[0],
});
return;
}
// Fallback: try series backdrop without tag (may not be cached optimally)
if (currentItem.seriesId) {
heroImageUrl = await repo.getImageUrl(currentItem.seriesId, "Backdrop", {
maxWidth: 1920,
});
return;
}
// Last resort for episodes: try season backdrop
if (currentItem.seasonId) {
heroImageUrl = await repo.getImageUrl(currentItem.seasonId, "Backdrop", {
maxWidth: 1920,
});
return;
}
if (currentItem.seriesId) {
return { itemId: currentItem.seriesId, imageType: "Backdrop" as const, tag: undefined };
}
// 3. For music tracks, try album backdrop first, then primary
if (currentItem.type === "Audio" && currentItem.albumId) {
// Try album backdrop first (more cinematic for hero)
heroImageUrl = await repo.getImageUrl(currentItem.albumId, "Backdrop", {
maxWidth: 1920,
});
return;
if (currentItem.seasonId) {
return { itemId: currentItem.seasonId, imageType: "Backdrop" as const, tag: undefined };
}
// 4. Fall back to primary image (poster, album art, episode thumbnail)
if (currentItem.primaryImageTag) {
heroImageUrl = await repo.getImageUrl(currentItem.id, "Primary", {
maxWidth: 1920,
tag: currentItem.primaryImageTag,
});
return;
}
// 5. Last resort for audio: try album primary image
if (currentItem.type === "Audio" && currentItem.albumId) {
heroImageUrl = await repo.getImageUrl(currentItem.albumId, "Primary", {
maxWidth: 1920,
});
return;
}
heroImageUrl = "";
} catch {
heroImageUrl = "";
}
}
// 3. For music tracks, try album backdrop
if (currentItem.type === "Audio" && currentItem.albumId) {
return { itemId: currentItem.albumId, imageType: "Backdrop" as const, tag: undefined };
}
// 4. Fall back to primary image
if (currentItem.primaryImageTag) {
return { itemId: currentItem.id, imageType: "Primary" as const, tag: currentItem.primaryImageTag };
}
// 5. Last resort for audio: album primary
if (currentItem.type === "Audio" && currentItem.albumId) {
return { itemId: currentItem.albumId, imageType: "Primary" as const, tag: undefined };
}
return null;
});
function next() {
currentIndex = (currentIndex + 1) % items.length;
@@ -143,11 +105,6 @@
touchEndX = 0;
}
// Load hero image whenever current item changes
$effect(() => {
loadHeroImageUrl();
});
// Auto-rotate logic
$effect(() => {
if (autoRotate && items.length > 1) {
@@ -166,10 +123,13 @@
ontouchmove={handleTouchMove}
ontouchend={handleTouchEnd}
>
{#if heroImageUrl}
<img
src={heroImageUrl}
alt={currentItem?.name}
{#if heroImageSource}
<CachedImage
itemId={heroImageSource.itemId}
imageType={heroImageSource.imageType}
tag={heroImageSource.tag}
maxWidth={1920}
alt={currentItem?.name ?? ""}
class="absolute inset-0 w-full h-full object-cover"
/>
{:else}