From 284e0d5bde79e12ecef61cb0106f5e1037287778 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Mon, 25 May 2026 03:54:41 +0200 Subject: [PATCH 1/3] feat(ui): toggle Hi-Res badge + add Spotify-style label in PlayerBar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related UI bumps: - New `useHiResBadgeVisibility` hook backed by `profile_setting['ui.show_hi_res_badge']` (default ON). All three HiResBadge variants now skip render when the flag is off, so the toggle is a one-line global hide for users who find the audiophile chrome noisy. Per-profile so a kid's profile can stay clean while the audiophile profile keeps the pills. - New `text` variant on HiResBadge — Spotify-style minimal green uppercase text, no pill background. Used under the artist name in the PlayerBar metadata stack so users instantly know whether the current track is being served at Hi-Res 24-bit / DSD spec without pinning the audio-quality footer. - New Settings → Appearance card (`HiResBadgeCard`) hosting the toggle, with i18n across all 17 locales. Implementation is intentionally minimal: the visibility hook lives inside HiResBadge so existing call sites stay unchanged (no prop drilling). The pattern mirrors `useWrappedBannerVisibility` — profile setting + window event for refresh + same default-ON shape. --- docs/features/ui.md | 12 ++++ src/components/common/HiResBadge.tsx | 26 +++++-- src/components/player/PlayerBar.tsx | 14 ++++ src/components/views/SettingsView.tsx | 3 + .../views/settings/HiResBadgeCard.tsx | 49 +++++++++++++ src/hooks/useHiResBadgeVisibility.ts | 69 +++++++++++++++++++ src/i18n/locales/ar.json | 4 ++ src/i18n/locales/de.json | 4 ++ src/i18n/locales/en.json | 4 ++ src/i18n/locales/es.json | 4 ++ src/i18n/locales/fr.json | 4 ++ src/i18n/locales/hi.json | 4 ++ src/i18n/locales/id.json | 4 ++ src/i18n/locales/it.json | 4 ++ src/i18n/locales/ja.json | 4 ++ src/i18n/locales/ko.json | 4 ++ src/i18n/locales/nl.json | 4 ++ src/i18n/locales/pt-BR.json | 4 ++ src/i18n/locales/pt.json | 4 ++ src/i18n/locales/ru.json | 4 ++ src/i18n/locales/tr.json | 4 ++ src/i18n/locales/zh-CN.json | 4 ++ src/i18n/locales/zh-TW.json | 4 ++ 23 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 src/components/views/settings/HiResBadgeCard.tsx create mode 100644 src/hooks/useHiResBadgeVisibility.ts diff --git a/docs/features/ui.md b/docs/features/ui.md index 3b29249a..a9fc991d 100644 --- a/docs/features/ui.md +++ b/docs/features/ui.md @@ -176,6 +176,18 @@ The overflow popover itself is capped at `max-h-[calc(100dvh-7rem)]` with `overf The opt-in audio-quality footer ([`AudioQualityFooter`](../../src/components/player/AudioQualityFooter.tsx), pinned via Settings → Appearance → Player bar layout) is a thin strip below the player bar that surfaces the source file specs in compact form (`48 kHz · 256 kb/s · 6 Mo` on the left, `AAC · 24bit · 48kHz` on the right; bitrates ≥ 1000 kbps render as `Mb/s`). When the engine is resampling — source rate ≠ output device rate — the left chunk renders an arrow instead: `48 kHz → 44.1 kHz · …`, so the user can spot the conversion at a glance without opening the popover. The arrow is gated on the device rate being known (the engine reports `0` before the first stream opens); otherwise we fall back to the source rate alone rather than printing a misleading `48 kHz → null`. The Hi-Res pill surfaces when [`isHiRes`](../../src/lib/hiRes.ts) accepts the source bit depth / sample rate combination. +### Hi-Res / DSD badge + +[`HiResBadge`](../../src/components/common/HiResBadge.tsx) is the green pill that decorates track rows, album grid tiles, and the player-bar metadata when the source qualifies as Hi-Res (`isHiRes` — ≥ 24-bit, ≥ 44.1 kHz) or as DSD (`dsdLabel` returns `DSD64` / `DSD128` / …). Three variants: + +| Variant | Used in | Style | +| --------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| `overlay` | Album / artist grid covers (default). | Absolute-positioned pill in the cover's top-left corner with a drop shadow. | +| `inline` | TrackTable rows, sidebar lists. | Inline rounded pill next to the title. | +| `text` | Player bar — under the artist name. | Spotify-style minimal green uppercase text, no pill background, blends into the metadata stack. | + +All variants are gated by [`useHiResBadgeVisibility`](../../src/hooks/useHiResBadgeVisibility.ts), which reads `profile_setting['ui.show_hi_res_badge']` (default `true`) and re-reads on the `waveflow:hi-res-badge-visibility` window event. Settings → Appearance ships [`HiResBadgeCard`](../../src/components/views/settings/HiResBadgeCard.tsx) to flip the flag — when off, every mounted `HiResBadge` returns `null` in one render, including the player-bar text label. Per-profile so a kid's profile can hide the audiophile chrome while the audiophile profile keeps it. + Hovering (or keyboard-focusing) the footer opens [`AudioPipelinePopover`](../../src/components/player/AudioPipelinePopover.tsx) — an audiophile-grade breakdown of what the engine is actually doing. #### Sections displayed diff --git a/src/components/common/HiResBadge.tsx b/src/components/common/HiResBadge.tsx index 49dc0d0b..f8a085b7 100644 --- a/src/components/common/HiResBadge.tsx +++ b/src/components/common/HiResBadge.tsx @@ -1,4 +1,5 @@ import { dsdLabel, isHiRes } from "../../lib/hiRes"; +import { useHiResBadgeVisibility } from "../../hooks/useHiResBadgeVisibility"; /** * Hi-Res Audio badge — shown when the source file is delivered at a @@ -6,18 +7,25 @@ import { dsdLabel, isHiRes } from "../../lib/hiRes"; * DSD (in which case the badge says "DSD64", "DSD128", etc. instead * of "Hi-Res 24-bit"). * - * Two visual variants: - * - `overlay` is intended to sit on top of an album cover (top-left + * Three visual variants: + * - `overlay` (default) sits on top of an album cover (top-left * corner, drop shadow); * - `inline` is for sidebar / row contexts where the badge sits next - * to text. + * to text; + * - `text` is the minimal Spotify-style green text label used under + * the artist name in the player bar — no background pill so it + * nests cleanly inside dense metadata. + * + * Globally hidden by the per-profile + * `profile_setting['ui.show_hi_res_badge']` toggle — flipping that off + * returns `null` everywhere this component is mounted. */ interface HiResBadgeProps { bitDepth: number | null; sampleRate: number | null; /** Codec label from the scanner (e.g. "FLAC", "DSD128"). */ codec?: string | null; - variant?: "overlay" | "inline"; + variant?: "overlay" | "inline" | "text"; /** Override the visible text. Default is "Hi-Res {bitDepth}-bit". */ label?: string; } @@ -29,13 +37,21 @@ export function HiResBadge({ variant = "overlay", label, }: HiResBadgeProps) { + const { visible } = useHiResBadgeVisibility(); // DSD wins over the generic Hi-Res check — a DSF/DFF file reports // bit_depth=1 but is anything but lossy, and the user expects the // rate label (DSD64/128/...) rather than "Hi-Res 1-bit". const dsd = dsdLabel(codec); const isVisible = dsd !== null || isHiRes(bitDepth, sampleRate); - if (!isVisible) return null; + if (!isVisible || !visible) return null; const text = label ?? dsd ?? `Hi-Res ${bitDepth}-bit`; + if (variant === "text") { + return ( + + {text} + + ); + } if (variant === "inline") { return ( diff --git a/src/components/player/PlayerBar.tsx b/src/components/player/PlayerBar.tsx index 7f623389..3c721f40 100644 --- a/src/components/player/PlayerBar.tsx +++ b/src/components/player/PlayerBar.tsx @@ -13,6 +13,7 @@ import { useSleepTimer } from "../../hooks/useSleepTimer"; import { usePlayerBarLayout } from "../../hooks/usePlayerBarLayout"; import { Artwork } from "../common/Artwork"; import { ArtistLink } from "../common/ArtistLink"; +import { HiResBadge } from "../common/HiResBadge"; import { PlaybackControls } from "./PlaybackControls"; import { ProgressBar } from "./ProgressBar"; import { SleepTimerMenu } from "./SleepTimerMenu"; @@ -166,6 +167,19 @@ export function PlayerBar({ onNavigateToArtist }: PlayerBarProps) { (currentTrack?.album_title ?? t("player.inactive")) )} + {/* Spotify-style minimal quality label under the artist. + HiResBadge renders null when the track isn't Hi-Res / + DSD OR when the user has hidden the badge from + Settings → Appearance, so the row collapses naturally + for lossy / lossless-16-bit content. */} + {currentTrack && ( + + )} {currentTrack && !isSpotify && (