feat(chrome): shared account menu and global app header
Move account actions (Settings, Downloads, Display preferences, Sign out) out of the library-only header into a shared AccountMenu anchored in a global AppHeader, available on every authenticated non-immersive screen. Add a layoutShell helper deciding where chrome shows, expose serverName/serverUrl auth stores, and a display view-mode preference. The settings page also gains the UR-053 WiFi-only toggle. TRACES: UR-054 | DR-075, DR-076, DR-077
This commit is contained in:
@@ -16,16 +16,22 @@
|
||||
import Toast from "$lib/components/Toast.svelte";
|
||||
import SleepTimerModal from "$lib/components/player/SleepTimerModal.svelte";
|
||||
import BottomUi from "$lib/components/BottomUi.svelte";
|
||||
import AppHeader from "$lib/components/AppHeader.svelte";
|
||||
import { isInitialized, pendingSyncCount, isAndroid, showSleepTimerModal } from "$lib/stores/appState";
|
||||
import {
|
||||
showBottomNav as computeShowBottomNav,
|
||||
showGlobalMiniPlayer as computeShowGlobalMiniPlayer,
|
||||
showGlobalHeader as computeShowGlobalHeader,
|
||||
routeOwnsLayout as computeRouteOwnsLayout,
|
||||
} from "$lib/utils/layoutShell";
|
||||
import { registerNavigationTracking } from "$lib/utils/navigation";
|
||||
import { startNetworkReporting } from "$lib/services/networkType";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
/** Teardown for the network-transport reporter (WiFi-only gate). */
|
||||
let stopNetworkReporting: (() => void) | null = null;
|
||||
|
||||
// Track in-app navigation depth so the header "back" affordance knows when a
|
||||
// real in-app Back exists (vs. a stale WebView stack after a background /
|
||||
// restore). Must run during component init — afterNavigate needs a component
|
||||
@@ -47,6 +53,13 @@
|
||||
);
|
||||
const showGlobalMiniPlayer = $derived(computeShowGlobalMiniPlayer({ pathname }));
|
||||
|
||||
// The shared header (with the account menu) is rendered by the root on every
|
||||
// authenticated non-immersive route that doesn't own its own layout. Library
|
||||
// renders its own AppHeader; settings/player/login get none. (UR-054)
|
||||
const showGlobalHeader = $derived(
|
||||
computeShowGlobalHeader({ pathname, isAuthenticated: $isAuthenticated })
|
||||
);
|
||||
|
||||
// Library/settings/player/login own their own full-height flex column
|
||||
// (header + scroller + their own in-flow BottomUi), so the root just clips
|
||||
// and lets them manage layout. Every other route renders into the root's
|
||||
@@ -76,6 +89,11 @@
|
||||
// Initialize download event listener
|
||||
await initDownloadEvents();
|
||||
|
||||
// Report the network transport to the backend and keep it current, so the
|
||||
// WiFi-only download gate has real data to act on (UR-053). No-op on
|
||||
// desktop, where the backend defaults to unmetered.
|
||||
stopNetworkReporting = startNetworkReporting();
|
||||
|
||||
// Prime the downloads store from the DB. The store starts empty each launch,
|
||||
// and download badges (e.g. AlbumDownloadButton) derive purely from it, so
|
||||
// without an initial refresh a previously-downloaded album shows as
|
||||
@@ -102,6 +120,7 @@
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
stopNetworkReporting?.();
|
||||
cleanupPlayerEvents();
|
||||
cleanupDownloadEvents();
|
||||
connectivity.stopMonitoring();
|
||||
@@ -189,6 +208,11 @@
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Shared header (account menu, desktop nav) as a flex-shrink-0 sibling
|
||||
above the scroller, so it never eats into the scroller's bounds. -->
|
||||
{#if showGlobalHeader}
|
||||
<AppHeader />
|
||||
{/if}
|
||||
<!-- Scroller is flex-1/min-h-0; the in-flow BottomUi below is a flex
|
||||
sibling, so the list is physically bounded above it and can never
|
||||
render behind it. No measurement, no reserved padding. -->
|
||||
|
||||
Reference in New Issue
Block a user