feat: add Listening Mode overlay with voice picker, speed, sleep timer, and chapter list
Some checks failed
Release / Test backend (push) Has been cancelled
Release / Check ui (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
Release / Docker / ui (push) Has been cancelled
Release / Docker / caddy (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled

Adds a full-screen listening mode accessible via the headphones button in the
mini-player bar. Moves voice selector, speed, auto-next, and sleep timer out of
the reader settings panel into the new overlay. Voices are stored in AudioStore
so ListeningMode can read them without prop drilling.
This commit is contained in:
Admin
2026-04-05 17:06:56 +05:00
parent 48bc206c4e
commit a3ad54db70
4 changed files with 485 additions and 66 deletions

View File

@@ -11,6 +11,7 @@
import { cn } from '$lib/utils';
import * as m from '$lib/paraglide/messages.js';
import { locales, getLocale } from '$lib/paraglide/runtime.js';
import ListeningMode from '$lib/components/ListeningMode.svelte';
let { children, data }: { children: Snippet; data: LayoutData } = $props();
@@ -34,6 +35,7 @@
// Chapter list drawer state for the mini-player
let chapterDrawerOpen = $state(false);
let activeChapterEl = $state<HTMLElement | null>(null);
let listeningModeOpen = $state(false);
function setIfActive(node: HTMLElement, isActive: boolean) {
if (isActive) activeChapterEl = node;
@@ -287,7 +289,7 @@
audioEl.currentTime = Math.min(audioEl.duration || 0, audioEl.currentTime + 30);
}
const speedSteps = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0];
const speedSteps = [0.75, 1.0, 1.25, 1.5, 2.0];
function cycleSpeed() {
const idx = speedSteps.indexOf(audioStore.speed);
@@ -993,6 +995,21 @@
</a>
{/if}
<!-- Headphones: open listening mode -->
<Button
variant="ghost"
size="icon"
onclick={() => (listeningModeOpen = true)}
title="Listening mode"
aria-label="Open listening mode"
class="text-(--color-muted) hover:text-(--color-text) flex-shrink-0"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 18v-6a9 9 0 0118 0v6"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 19a2 2 0 01-2 2h-1a2 2 0 01-2-2v-3a2 2 0 012-2h3zM3 19a2 2 0 002 2h1a2 2 0 002-2v-3a2 2 0 00-2-2H3z"/>
</svg>
</Button>
<!-- Dismiss -->
<Button
variant="ghost"
@@ -1008,4 +1025,9 @@
</Button>
</div>
</div>
<!-- Listening mode full-screen overlay -->
{#if listeningModeOpen}
<ListeningMode onclose={() => (listeningModeOpen = false)} />
{/if}
{/if}