fix(devices): cache platform device names, dedupe placeholder ids, purge collided EQ entries
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m47s

Dart half of bt-device-identity. EstadoEcualizador now caches each
device's platform-reported name in memory so the settings screen
shows the device's own Bluetooth name instead of its raw id when no
custom rename exists, and skips auto-creating preset entries for the
composite-placeholder sentinel. Enabling multi-device EQ triggers the
Bluetooth permission request through the new channel contract. A
flag-guarded one-time migration purges only entries keyed by the
exact literal placeholder id from the three per-device preference
maps, since those collided entries cannot be attributed to a device.

Work unit 2/2 of bt-device-identity (Dart state + migration).
This commit is contained in:
2026-07-11 00:25:00 +02:00
parent 224763bca3
commit b17c582572
11 changed files with 711 additions and 7 deletions
+132 -1
View File
@@ -64,6 +64,7 @@ void main() {
Future<EstadoRadio> crearEstado({
bool eqMultiDeviceEnabled = false,
Map<String, PresetEcualizador> presetsDispositivo = const {},
FakeServicioDispositivoAudio? dispositivoAudio,
}) async {
final estado = EstadoRadio(
audio: FakeServicioAudio(),
@@ -76,6 +77,7 @@ void main() {
servicioGrabacion: _FakeGrabacion(),
resolverArchivoCustom: _archivoCustomVacio,
iniciarAutomaticamente: false,
dispositivoAudio: dispositivoAudio,
);
await estado.ecualizador.cargarPersistido();
return estado;
@@ -99,6 +101,7 @@ void main() {
Map<String, PresetEcualizador>? presetsDispositivo,
Map<String, String>? nombresDispositivos,
String? activeDeviceId,
String nombrePlataforma = 'BT Speaker',
}) async {
final fakeDispositivo = activeDeviceId != null
? (FakeServicioDispositivoAudio()
@@ -106,7 +109,7 @@ void main() {
DispositivoAudio(
id: activeDeviceId,
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'BT Speaker',
nombre: nombrePlataforma,
),
))
: null;
@@ -380,6 +383,134 @@ void main() {
);
});
});
// ── bt-device-identity Phase 4: display fix + permission trigger ─────────
group('_SeccionEcualizadorAvanzado — bt-device-identity Phase 4', () {
// 4.1 — new behavior: the cached platform name (not '') now feeds
// nombreVisible, so a device with no custom rename shows its real name.
testWidgets('4.1 platform name displays with no custom rename', (
tester,
) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
const deviceId = 'bt_a2dp:AA:BB:CC:DD:EE:FF';
final estado = await crearEstadoConNombres(
presetsDispositivo: {deviceId: PresetEcualizador.rock},
activeDeviceId: deviceId,
nombrePlataforma: 'AirPods Pro',
);
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);
expect(find.text('AirPods Pro'), findsOneWidget);
expect(find.text(deviceId), findsNothing);
});
// 4.4 — triangulation companion: custom rename still wins even though
// the row now also has a cached platform name available.
testWidgets('4.4 custom rename overrides platform name', (tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
const deviceId = 'bt_a2dp:AA:BB:CC:DD:EE:FF';
final estado = await crearEstadoConNombres(
presetsDispositivo: {deviceId: PresetEcualizador.rock},
activeDeviceId: deviceId,
nombrePlataforma: 'AirPods Pro',
nombresDispositivos: {deviceId: 'My Headphones'},
);
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);
expect(find.text('My Headphones'), findsOneWidget);
expect(find.text('AirPods Pro'), findsNothing);
});
// 4.5 — approval test (regression-lock): a device never seen on the
// stream has no cached platform name, so legacy raw-id fallback holds.
testWidgets('4.5 no platform name yet falls back to raw id', (
tester,
) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
const deviceId = 'bt_a2dp:AA:BB:CC:DD:EE:FF';
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);
expect(find.text(deviceId), findsOneWidget);
});
// 4.6 — permission trigger point: turning the toggle ON requests
// BLUETOOTH_CONNECT; turning it back OFF must not re-fire the request.
testWidgets('4.6 permission call fires on device-management open', (
tester,
) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
final fakeDispositivo = FakeServicioDispositivoAudio();
final estado = await crearEstado(
eqMultiDeviceEnabled: false,
dispositivoAudio: fakeDispositivo,
);
addTearDown(estado.dispose);
await tester.pumpWidget(buildAjustes(estado));
await pumpStable(tester);
await tester.scrollUntilVisible(
find.text('Advanced Equalization Options'),
300,
scrollable: find.byType(Scrollable).first,
);
await pumpStable(tester);
expect(fakeDispositivo.solicitarPermisoBluetoothCalls, equals(0));
// Toggle ON: permission requested exactly once.
await tester.tap(find.byType(Switch).last);
await pumpStable(tester);
expect(estado.ecualizador.eqMultiDeviceEnabled, isTrue);
expect(fakeDispositivo.solicitarPermisoBluetoothCalls, equals(1));
// Toggle OFF again: no further permission request.
await tester.tap(find.byType(Switch).last);
await pumpStable(tester);
expect(estado.ecualizador.eqMultiDeviceEnabled, isFalse);
expect(fakeDispositivo.solicitarPermisoBluetoothCalls, equals(1));
});
});
}
// ── Infrastructure ──────────────────────────────────────────────────────────