layout improvements
This commit is contained in:
+72
-33
@@ -17,13 +17,49 @@
|
||||
import MiniPlayer from "$lib/components/player/MiniPlayer.svelte";
|
||||
import SleepTimerModal from "$lib/components/player/SleepTimerModal.svelte";
|
||||
import BottomNav from "$lib/components/BottomNav.svelte";
|
||||
import { isInitialized, pendingSyncCount, isAndroid, showSleepTimerModal } from "$lib/stores/appState";
|
||||
import { isInitialized, pendingSyncCount, isAndroid, showSleepTimerModal, bottomUiHeight } from "$lib/stores/appState";
|
||||
// Shuffle/repeat/next/previous come from the event-driven queue store, the
|
||||
// single source of truth (updated instantly on queue_changed).
|
||||
import { isShuffle as shuffle, repeatMode as repeat, hasNext, hasPrevious } from "$lib/stores/queue";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
// The fixed bottom UI (mini player stacked over the bottom nav) is measured in
|
||||
// real time and its height published to `bottomUiHeight`, so pages can reserve
|
||||
// exactly that much space instead of guessing fixed rem values.
|
||||
let bottomUiEl = $state<HTMLElement | null>(null);
|
||||
|
||||
// Route-level visibility for the fixed bottom UI (the mini player itself also
|
||||
// self-gates on playback state; when it renders nothing the in-flow slot
|
||||
// collapses to 0 and the ResizeObserver shrinks the reserved padding).
|
||||
const pathname = $derived($page.url.pathname);
|
||||
const showBottomNav = $derived(
|
||||
$isAuthenticated && !pathname.startsWith('/player/') && !pathname.startsWith('/login')
|
||||
);
|
||||
const showGlobalMiniPlayer = $derived(
|
||||
!pathname.startsWith('/player/') &&
|
||||
!pathname.startsWith('/login') &&
|
||||
!pathname.startsWith('/settings') &&
|
||||
($isAndroid || !pathname.startsWith('/library'))
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
const el = bottomUiEl;
|
||||
if (!el) {
|
||||
bottomUiHeight.set(0);
|
||||
return;
|
||||
}
|
||||
const ro = new ResizeObserver((entries) => {
|
||||
bottomUiHeight.set(entries[0]?.contentRect.height ?? el.offsetHeight);
|
||||
});
|
||||
ro.observe(el);
|
||||
bottomUiHeight.set(el.offsetHeight);
|
||||
return () => {
|
||||
ro.disconnect();
|
||||
bottomUiHeight.set(0);
|
||||
};
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
// Detect platform first (synchronously, before any await) so the global
|
||||
// mini player's Android visibility gate is correct from the first render.
|
||||
@@ -131,40 +167,43 @@
|
||||
<!-- Toast notifications (global) -->
|
||||
<Toast />
|
||||
|
||||
<!-- Bottom Navigation (mobile only - show everywhere except player and login) -->
|
||||
{#if $isAuthenticated && !$page.url.pathname.startsWith('/player/') && !$page.url.pathname.startsWith('/login')}
|
||||
<BottomNav />
|
||||
{/if}
|
||||
<!-- Fixed bottom UI: mini player stacked over the bottom nav, both in normal
|
||||
flow inside one measured wrapper. The wrapper's height is observed and
|
||||
published to `bottomUiHeight` so pages reserve exactly this much space.
|
||||
Mini player is first (visually on top, above the nav). -->
|
||||
{#if showBottomNav || showGlobalMiniPlayer}
|
||||
<div bind:this={bottomUiEl} class="fixed bottom-0 left-0 right-0 z-40 flex flex-col">
|
||||
{#if showGlobalMiniPlayer}
|
||||
<MiniPlayer
|
||||
media={$currentMedia}
|
||||
isPlaying={$isPlaying}
|
||||
position={$playbackPosition}
|
||||
duration={$playbackDuration}
|
||||
shuffle={$shuffle}
|
||||
repeat={$repeat}
|
||||
hasNext={$hasNext}
|
||||
hasPrevious={$hasPrevious}
|
||||
className="flex-shrink-0"
|
||||
onExpand={() => {
|
||||
// Navigate to player page when mini player is expanded
|
||||
if ($currentMedia) {
|
||||
goto(`/player/${$currentMedia.id}`);
|
||||
}
|
||||
}}
|
||||
onSleepTimerClick={() => showSleepTimerModal.set(true)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Mini Player - show everywhere except on full player page, login and settings -->
|
||||
<!-- Android: Show on all routes (except player/login/settings) -->
|
||||
<!-- Desktop: Show on non-library routes (library layout has its own MiniPlayer) -->
|
||||
{#if !$page.url.pathname.startsWith('/player/') && !$page.url.pathname.startsWith('/login') && !$page.url.pathname.startsWith('/settings')}
|
||||
{#if $isAndroid || !$page.url.pathname.startsWith('/library')}
|
||||
<MiniPlayer
|
||||
media={$currentMedia}
|
||||
isPlaying={$isPlaying}
|
||||
position={$playbackPosition}
|
||||
duration={$playbackDuration}
|
||||
shuffle={$shuffle}
|
||||
repeat={$repeat}
|
||||
hasNext={$hasNext}
|
||||
hasPrevious={$hasPrevious}
|
||||
onExpand={() => {
|
||||
// Navigate to player page when mini player is expanded
|
||||
if ($currentMedia) {
|
||||
goto(`/player/${$currentMedia.id}`);
|
||||
}
|
||||
}}
|
||||
onSleepTimerClick={() => showSleepTimerModal.set(true)}
|
||||
/>
|
||||
{#if showBottomNav}
|
||||
<BottomNav className="flex-shrink-0" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Sleep Timer Modal -->
|
||||
<SleepTimerModal
|
||||
isOpen={$showSleepTimerModal}
|
||||
onClose={() => showSleepTimerModal.set(false)}
|
||||
/>
|
||||
{/if}
|
||||
<!-- Sleep Timer Modal -->
|
||||
<SleepTimerModal
|
||||
isOpen={$showSleepTimerModal}
|
||||
onClose={() => showSleepTimerModal.set(false)}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex items-center justify-center h-screen">
|
||||
|
||||
Reference in New Issue
Block a user