fix(visualizador): add a discrete-bar rendering mode

Audit 1.7/2.5 (t4 lines 66-68, 120-122): the prototype draws 30
discrete bottom-anchored bars (radius 2, vertical gradient); the
build's VisualizadorAudio painted a single continuous oscilloscope
stroke, so its `barras: 30` parameter never produced bars.

Adds an opt-in `barrasDiscretas` mode (default false, byte-identical
continuous rendering preserved for any other caller) plus a
`gradienteFinAlpha` knob for the two screens' differing gradient
end-alpha (.3 vs .45). Wires it into the Escuchar hero and the full
player, correcting the player's bar count/height to match the
prototype (26->30 bars, 46->40px) at the same time.
This commit is contained in:
2026-07-30 00:04:03 +02:00
parent e94e64faca
commit fcba592352
6 changed files with 318 additions and 12 deletions
+46
View File
@@ -10,6 +10,7 @@ import 'package:pluriwave/estado/estado_radio.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/emisora.dart';
import 'package:pluriwave/pantallas/pantalla_inicio.dart';
import 'package:pluriwave/widgets/visualizador_audio.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -208,6 +209,51 @@ void main() {
);
});
testWidgets(
'visual fidelity (audit 1.7): the hero visualizer renders 30 discrete '
'bars, not the continuous waveform stroke',
(tester) async {
// Item 16 is exactly the widget the audit warned about
// (visualizador_audio.dart:77, `_controller.repeat()`) — bounded pump
// only, never pumpAndSettle.
_setLargeSurfaceSize(tester);
final favoritos = FakeServicioFavoritos();
final estado = EstadoRadio(
audio: FakeServicioAudio(),
favoritos: favoritos,
radio: FakeServicioRadio(),
servicioEcualizador: FakeServicioEcualizador(),
servicioGrabacion: FakeServicioGrabacionRadio(),
resolverArchivoCustom: _archivoCustomVacio,
iniciarAutomaticamente: false,
);
addTearDown(estado.dispose);
await tester.runAsync(estado.inicializar);
final sonando = emisoraDemo(uuid: 'f1', nombre: 'Favorita Uno');
await favoritos.agregar(sonando);
await estado.cargarFavoritos();
await tester.runAsync(() => estado.reproducir(sonando));
await tester.pumpWidget(
_conProviders(estado, _testApp(const PantallaInicio())),
);
await _pumpBounded(tester);
expect(
find.byKey(const Key('visualizador-barra-0')),
findsOneWidget,
reason: 'prototype t4 line 66-68: 30 discrete bottom-anchored bars',
);
expect(
find.descendant(
of: find.byType(VisualizadorAudio),
matching: find.byType(CustomPaint),
),
findsNothing,
);
},
);
testWidgets(
'visual fidelity (audit 1.6): a station missing every meta field omits '
'the line gracefully, no stray separators',
@@ -412,6 +412,35 @@ void main() {
},
);
testWidgets(
'visual fidelity (audit 2.5): the visualizer renders 30 discrete '
'bars, not the continuous waveform stroke',
(tester) async {
final estado = crearEstado();
addTearDown(estado.dispose);
await montarPantalla(tester, estado);
final visualizador = find.byKey(const Key('player-visualizer'));
expect(visualizador, findsOneWidget);
expect(
find.descendant(
of: visualizador,
matching: find.byKey(const Key('visualizador-barra-0')),
),
findsOneWidget,
reason: 'prototype t4 line 120-122: 30 discrete bars',
);
expect(
find.descendant(
of: visualizador,
matching: find.byType(CustomPaint),
),
findsNothing,
);
},
);
testWidgets(
'exactly 4 tool-tray tiles render: EQ propio, Grabar, sleep timer, Compartir',
(tester) async {