fix(eq): seed device ID at startup and add device management UI

Fix multi-device EQ auto-switching by calling obtenerDispositivoActual()
during cargarPersistido() to seed the initial device ID. Add device
management modal with rename support, EQ preset editing, and connection
status indicator. Translate device UI keys to all 13 locales.
This commit is contained in:
2026-06-28 11:55:15 +02:00
parent 71978de68f
commit 58922de6fc
34 changed files with 1461 additions and 51 deletions
+186
View File
@@ -8,6 +8,7 @@ import 'package:pluriwave/estado/estado_grabacion.dart';
import 'package:pluriwave/estado/estado_idioma.dart';
import 'package:pluriwave/estado/estado_radio.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/dispositivo_audio.dart';
import 'package:pluriwave/modelos/preset_ecualizador.dart';
import 'package:pluriwave/pantallas/pantalla_ajustes.dart';
import 'package:pluriwave/servicios/servicio_grabacion_radio.dart';
@@ -92,6 +93,42 @@ void main() {
await tester.pumpAndSettle(const Duration(milliseconds: 100));
}
// Also update crearEstado to support nombresDispositivos
Future<EstadoRadio> crearEstadoConNombres({
bool eqMultiDeviceEnabled = true,
Map<String, PresetEcualizador>? presetsDispositivo,
Map<String, String>? nombresDispositivos,
String? activeDeviceId,
}) async {
final fakeDispositivo = activeDeviceId != null
? (FakeServicioDispositivoAudio()
..emitirDispositivo(
DispositivoAudio(
id: activeDeviceId,
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'BT Speaker',
),
))
: null;
final estado = EstadoRadio(
audio: FakeServicioAudio(),
favoritos: FakeServicioFavoritos(),
radio: FakeServicioRadio(),
servicioEcualizador: FakeServicioEcualizador(
eqMultiDeviceEnabled: eqMultiDeviceEnabled,
presetsDispositivo: presetsDispositivo ??
{'bt_a2dp:AA:BB': PresetEcualizador.rock},
nombresDispositivos: nombresDispositivos ?? {},
),
servicioGrabacion: _FakeGrabacion(),
resolverArchivoCustom: _archivoCustomVacio,
iniciarAutomaticamente: false,
dispositivoAudio: fakeDispositivo,
);
await estado.ecualizador.cargarPersistido();
return estado;
}
// ── Phase 7 tests ──────────────────────────────────────────────────────────
group('_SeccionEcualizadorAvanzado (Phase 7)', () {
@@ -194,6 +231,155 @@ void main() {
},
);
});
// ── Phase 3 (eq-device-autoswitch-ux): connection indicator + modal ──────────
group('SeccionEcualizadorAvanzado connection indicator Phase 3', () {
// 3.1 RED — active device row shows green connection dot
testWidgets('3.1 active device row shows green connection indicator', (tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
const activeId = 'bt_a2dp:AA:BB';
final estado = await crearEstadoConNombres(
eqMultiDeviceEnabled: true,
presetsDispositivo: {
activeId: PresetEcualizador.rock,
'wired_headset': PresetEcualizador.jazz,
},
activeDeviceId: activeId,
);
addTearDown(estado.dispose);
await tester.pumpWidget(buildAjustes(estado));
await pumpStable(tester);
await tester.scrollUntilVisible(
find.text('Known audio devices'),
300,
scrollable: find.byType(Scrollable).first,
);
await pumpStable(tester);
// Green connection dot should be present (Icon with green color for active device)
final greenIcons = tester.widgetList<Icon>(find.byType(Icon)).where(
(icon) => icon.color == Colors.green,
);
// At least one green icon present
expect(greenIcons, isNotEmpty);
});
// 3.2 RED — tapping device row opens bottom sheet with TextField and EcualizadorWidget
testWidgets('3.2 tapping device row opens modal with TextField', (tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
final estado = await crearEstadoConNombres(
presetsDispositivo: {'bt_a2dp:AA:BB': PresetEcualizador.rock},
);
addTearDown(estado.dispose);
await tester.pumpWidget(buildAjustes(estado));
await pumpStable(tester);
await tester.scrollUntilVisible(
find.text('Known audio devices'),
300,
scrollable: find.byType(Scrollable).first,
);
await pumpStable(tester);
// Tap the device row (tap the edit icon or the row itself)
final editIcons = find.byIcon(Icons.edit_rounded);
expect(editIcons, findsWidgets);
await tester.tap(editIcons.first);
await pumpStable(tester);
// Bottom sheet should appear with a TextField
expect(find.byType(TextField), findsWidgets);
});
// 3.6 RED — renaming in modal and confirming calls renombrarDispositivo
testWidgets('3.6 confirming rename in modal updates device name', (tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
const deviceId = 'bt_a2dp:AA:BB';
final estado = await crearEstadoConNombres(
presetsDispositivo: {deviceId: PresetEcualizador.rock},
);
addTearDown(estado.dispose);
await tester.pumpWidget(buildAjustes(estado));
await pumpStable(tester);
await tester.scrollUntilVisible(
find.text('Known audio devices'),
300,
scrollable: find.byType(Scrollable).first,
);
await pumpStable(tester);
// Open modal
await tester.tap(find.byIcon(Icons.edit_rounded).first);
await pumpStable(tester);
// Enter a new name
final textField = find.byType(TextField).first;
await tester.enterText(textField, 'My Living Room Speaker');
await pumpStable(tester);
// Tap confirm/save button
final saveButton = find.byIcon(Icons.save_rounded);
expect(saveButton, findsWidgets);
await tester.tap(saveButton.first);
await pumpStable(tester);
// Device should now have the new name
expect(
estado.ecualizador.obtenerNombreDispositivo(deviceId),
equals('My Living Room Speaker'),
);
});
// 3.7 RED — dismissing modal without confirming leaves name unchanged
testWidgets('3.7 dismissing modal without confirming leaves name unchanged', (tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
const deviceId = 'bt_a2dp:AA:BB';
final estado = await crearEstadoConNombres(
presetsDispositivo: {deviceId: PresetEcualizador.rock},
nombresDispositivos: {deviceId: 'Original Name'},
);
addTearDown(estado.dispose);
await tester.pumpWidget(buildAjustes(estado));
await pumpStable(tester);
await tester.scrollUntilVisible(
find.text('Known audio devices'),
300,
scrollable: find.byType(Scrollable).first,
);
await pumpStable(tester);
// Open modal
await tester.tap(find.byIcon(Icons.edit_rounded).first);
await pumpStable(tester);
// Change the text but do NOT confirm
final textField = find.byType(TextField).first;
await tester.enterText(textField, 'New Name Not Saved');
await pumpStable(tester);
// Dismiss by pressing back/escape
await tester.tapAt(const Offset(100, 100)); // tap outside bottom sheet
await pumpStable(tester);
// Name should remain unchanged
expect(
estado.ecualizador.obtenerNombreDispositivo(deviceId),
equals('Original Name'),
);
});
});
}
// ── Infrastructure ──────────────────────────────────────────────────────────