feat(escuchar): replace discovery browser with embedded player and favorites grid
Restructures PantallaInicio's top of screen: a new _EscucharHero (square art, live/offline pill, VisualizadorAudio at barras: 30 / altura: 26 / color: liveGreen, a 5-action transport row - favorite, EQ toggle, stop, play/pause, sleep - plus a tool-tray entry chip opening the full player) replaces the old PluriScreenHeader hero, and a new "Tus emisoras" section (favorites preview, capped, "Ver todas") follows it. Per design ADR-7, EstadoRadio stays the single source of truth: the hero is a StatelessWidget with no cached fields, reading emisoraActual via context.select (uuid-based equality scopes rebuilds to real station changes) and the fast-changing playback status via StreamBuilder, the same pattern _Controles/MiniReproductor already use. The still-present discovery sections (_seccionCercanas onward, including the old grid) are deliberately left in place - WU6 relocates them to Buscar and deletes them from here; removing them now would leave that content nowhere until WU6 lands. MiniReproductor gains a `visible` parameter (default true) and a measured `static const double altura`. app.dart passes `visible: indice != RaizPluriWave.escuchar.index`, hiding it visually only (SizedBox.shrink()) while Escuchar is active, since the hero already shows the same station - the State stays mounted so its didChangeDependencies side effect (configurarLocalizaciones, S3-R3) keeps running regardless of tab. altura was measured empirically (72.0, via tester.getSize) rather than guessed, backing a new derived PluriLayout.escucharBottomChromeInset constant now wired into PantallaInicio's own bottom padding. "Ver todas" switches roots via EstadoNavegacionRaiz.irA(favoritos), verified via a NavigatorObserver asserting the push count is unchanged (switches tabs, does not push). Fixed a pre-existing test-infrastructure gap while writing the anti-cache test: no test in this codebase had ever exercised ServicioAudio.androidAudioSessionIdStream against a bare FakeServicioAudio (pantalla_reproductor.dart has always read it but has no test file at all) - the real getter needs registrarHandler() (main.dart, production only) and threw otherwise. Added an empty stream override to FakeServicioAudio, matching VisualizadorAudio's own documented no-native-session fallback. Tests: 614 -> 618 (2 skipped, unchanged). flutter analyze unchanged at 1 pre-existing info. git diff empty for visualizador_audio.dart and estado_radio.dart - this WU touches neither.
This commit is contained in:
@@ -14,7 +14,21 @@ import 'visualizador_audio.dart';
|
||||
/// Barra inferior persistente con controles básicos de reproducción.
|
||||
/// Toca la barra para abrir PantallaReproductor completa.
|
||||
class MiniReproductor extends StatefulWidget {
|
||||
const MiniReproductor({super.key});
|
||||
const MiniReproductor({super.key, this.visible = true});
|
||||
|
||||
/// Design ADR-7(b): on Escuchar, the embedded hero already shows the same
|
||||
/// station, so `_PaginaPrincipal` passes `visible: false` there to avoid
|
||||
/// showing it twice. Hidden VISUALLY only (`build` returns
|
||||
/// `SizedBox.shrink()`) — the widget stays in the tree and mounted, so
|
||||
/// `didChangeDependencies`'s `configurarLocalizaciones` call (S3-R3) keeps
|
||||
/// running on every locale change regardless of which tab is active.
|
||||
final bool visible;
|
||||
|
||||
/// Measured (not guessed) from this widget's actual laid-out height with a
|
||||
/// representative station name, default text scale and theme — see
|
||||
/// `mini_reproductor_configurar_test.dart`'s measurement assertion. Backs
|
||||
/// `PluriLayout.escucharBottomChromeInset` (ADR-7(b)).
|
||||
static const double altura = 72;
|
||||
|
||||
@override
|
||||
State<MiniReproductor> createState() => _MiniReproductorState();
|
||||
@@ -43,7 +57,7 @@ class _MiniReproductorState extends State<MiniReproductor> {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final emisora = estado.emisoraActual;
|
||||
|
||||
if (emisora == null) return const SizedBox.shrink();
|
||||
if (!widget.visible || emisora == null) return const SizedBox.shrink();
|
||||
|
||||
final t = context.pluriTokens;
|
||||
final stationName = localizedStationName(l10n, emisora.nombre);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../tema/pluriwave_theme.dart';
|
||||
import 'mini_reproductor.dart';
|
||||
|
||||
abstract final class PluriLayout {
|
||||
static const double horizontal = 16;
|
||||
@@ -9,6 +10,12 @@ abstract final class PluriLayout {
|
||||
static const double compactGap = 8;
|
||||
static const double bottomChromeInset = 146;
|
||||
|
||||
/// ADR-7(b): `bottomChromeInset` assumes `MiniReproductor` is visible.
|
||||
/// Escuchar hides it (design's one exception — the embedded hero already
|
||||
/// shows the same station), so its content needs less bottom padding.
|
||||
static const double escucharBottomChromeInset =
|
||||
bottomChromeInset - MiniReproductor.altura;
|
||||
|
||||
static const EdgeInsets pageListPadding = EdgeInsets.fromLTRB(
|
||||
0,
|
||||
0,
|
||||
|
||||
Reference in New Issue
Block a user