feat(reproductor): restructure full player with tool-tray and EQ sheet
Restructure pantalla_reproductor.dart onto PluriPushScaffold (design ADR-2 - this screen is the documented single consumer of titleOverride, a centered live/not-playing status pill, and the non-default keyboard_arrow_down leadingIcon). Square art replaces the old circular hero, favorite moves from the AppBar into the transport row (the redundant live-indicator dot is dropped - the AppBar pill already covers that signal), the old separate info chips collapse into a single subtitle line, and a new quality row surfaces codec/bitrate with a "Cambiar" action that reconnects the current stream (this app has no per-station alternate-quality capability to invoke, so this reuses the same reproducir() call the existing error-state retry button already uses, rather than a dead button or an invented picker). The always-expanded recording panel and the standalone sleep-timer button both become tool-tray tiles (EQ propio / Grabar / sleep timer / Compartir), each opening its own bottom sheet. "EQ propio" opens a sheet hosting EcualizadorWidget - the exact same component WU13 restyled for Settings, bound via the existing presetParaEmisora/guardarPresetPorEmisora per-station persistence path. No second editor was created; the multi-device-eq resolution hierarchy is untouched. pantalla_reproductor.dart had zero test coverage before this commit (907 lines) - writing it first surfaced two pre-existing bugs blocking any coverage at all, both fixed: initState called estado.reproducir() directly, which notifies listeners synchronously before its first await and threw "setState() during build" the instant the screen mounted against a fresh Provider tree (fixed via addPostFrameCallback); and the body Column had no scrollable ancestor and overflowed even a generously tall viewport (fixed by wrapping it in a SingleChildScrollView, a real UX improvement and not just a test workaround). The three protected EQ test files (servicio_ecualizador_test.dart, estado_ecualizador_test.dart, servicio_audio_eq_reapply_test.dart) stay unmodified. Full suite: 730/730 green (2 skipped, unchanged), up from 713. size:exception - realized 1,410 changed lines (25 files including this docs update) against the 450-600 forecast: the restructured screen file alone is 658 lines (a near-total rewrite of a 907-line file, not a patch), its new test file (first-ever coverage) is 519 lines, and a new test fake plus a togglePlay() override account for the rest. Not splittable: the restructure, the tool tray, and the EQ-sheet wiring are one cohesive change to one screen.
This commit is contained in:
@@ -77,6 +77,21 @@ class FakeServicioAudio extends ServicioAudio {
|
||||
emitirEstado(EstadoReproduccion.pausado);
|
||||
}
|
||||
|
||||
// WU14: the real ServicioAudio.togglePlay() reads `_handler.playbackState`
|
||||
// (a real just_audio-backed handler that requires registrarHandler(), same
|
||||
// gap already documented for androidAudioSessionIdStream above) — unsafe
|
||||
// against a bare FakeServicioAudio. Overridden here using only this Fake's
|
||||
// own state machinery so `pantalla_reproductor.dart`'s play/pause control
|
||||
// (previously untested) can be exercised safely.
|
||||
@override
|
||||
Future<void> togglePlay() async {
|
||||
if (_estadoActual == EstadoReproduccion.reproduciendo) {
|
||||
await pausar();
|
||||
} else {
|
||||
emitirEstado(EstadoReproduccion.reproduciendo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setVolumen(double vol) async {
|
||||
volumenesAplicados.add(vol);
|
||||
@@ -639,6 +654,55 @@ class FakeServicioGrabacionRadio extends ServicioGrabacionRadio {
|
||||
Future<void> dispose() => _controller.close();
|
||||
}
|
||||
|
||||
/// WU14: a recording fake that actually responds to `iniciar`/`detener`
|
||||
/// in-memory, never touching real files or platform channels (`iniciar` on
|
||||
/// the real `ServicioGrabacionRadio` opens an HTTP stream to the station's
|
||||
/// URL and writes to disk — unsafe inside a widget test). Records every
|
||||
/// call for assertions.
|
||||
class FakeServicioGrabacionRadioActivable extends ServicioGrabacionRadio {
|
||||
final _controller = StreamController<EstadoGrabacionRadio>.broadcast();
|
||||
EstadoGrabacionRadio _estadoActual = const EstadoGrabacionRadio.inactiva();
|
||||
final List<Duration?> duracionesIniciadas = [];
|
||||
Emisora? ultimaEmisoraIniciada;
|
||||
int detenerCalls = 0;
|
||||
|
||||
@override
|
||||
EstadoGrabacionRadio get estado => _estadoActual;
|
||||
|
||||
@override
|
||||
Stream<EstadoGrabacionRadio> get estadoStream => _controller.stream;
|
||||
|
||||
@override
|
||||
Future<void> inicializar() async {}
|
||||
|
||||
@override
|
||||
Future<void> iniciar(
|
||||
Emisora emisora, {
|
||||
Duration? duracion,
|
||||
String? directorio,
|
||||
}) async {
|
||||
ultimaEmisoraIniciada = emisora;
|
||||
duracionesIniciadas.add(duracion);
|
||||
_estadoActual = EstadoGrabacionRadio(
|
||||
tipo: EstadoGrabacionRadioTipo.grabando,
|
||||
emisora: emisora,
|
||||
inicio: DateTime.now(),
|
||||
duracionObjetivo: duracion,
|
||||
);
|
||||
_controller.add(_estadoActual);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> detener() async {
|
||||
detenerCalls++;
|
||||
_estadoActual = const EstadoGrabacionRadio.inactiva();
|
||||
_controller.add(_estadoActual);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() => _controller.close();
|
||||
}
|
||||
|
||||
Emisora emisoraDemo({
|
||||
required String uuid,
|
||||
required String nombre,
|
||||
|
||||
Reference in New Issue
Block a user