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
+188
View File
@@ -1133,6 +1133,194 @@ void main() {
// WARNING-3: FakeServicioEcualizador preserves nombresDispositivos
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// bt-device-identity Phase 3: platform-name cache + duplicate-entry guard
// ---------------------------------------------------------------------------
group('EstadoEcualizador — platform-name cache (bt-device-identity Phase 3)', () {
const stationUuid = 'station-uuid-bt-identity';
test('3.1 platform name is cached from a device-change event', () async {
final fakeDispositivo = FakeServicioDispositivoAudio();
final eq = EstadoEcualizador(
audio: FakeServicioAudio(),
servicio: FakeServicioEcualizador(eqMultiDeviceEnabled: true),
dispositivoAudio: fakeDispositivo,
emisoraActualUuid: () => stationUuid,
);
await eq.cargarPersistido();
fakeDispositivo.emitirDispositivo(
const DispositivoAudio(
id: 'bt_a2dp:AA:BB:CC:DD:EE:FF',
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'AirPods Pro',
),
);
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(
eq.nombrePlataforma('bt_a2dp:AA:BB:CC:DD:EE:FF'),
equals('AirPods Pro'),
);
eq.dispose();
});
test(
'3.4 composite-placeholder sentinel does not create device-list entry',
() async {
final fakeDispositivo = FakeServicioDispositivoAudio();
final eq = EstadoEcualizador(
audio: FakeServicioAudio(),
servicio: FakeServicioEcualizador(eqMultiDeviceEnabled: true),
dispositivoAudio: fakeDispositivo,
emisoraActualUuid: () => stationUuid,
);
await eq.cargarPersistido();
fakeDispositivo.emitirDispositivo(
const DispositivoAudio(
id: 'bt_a2dp:name:AirPods-Pro',
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'AirPods Pro',
),
);
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(
eq.presetsDispositivo.containsKey('bt_a2dp:name:AirPods-Pro'),
isFalse,
);
expect(
eq.nombrePlataforma('bt_a2dp:name:AirPods-Pro'),
equals('AirPods Pro'),
);
eq.dispose();
},
);
test('3.6 multiple denied-permission devices do not collide', () async {
final fakeDispositivo = FakeServicioDispositivoAudio();
final eq = EstadoEcualizador(
audio: FakeServicioAudio(),
servicio: FakeServicioEcualizador(eqMultiDeviceEnabled: true),
dispositivoAudio: fakeDispositivo,
emisoraActualUuid: () => stationUuid,
);
await eq.cargarPersistido();
final baseline = eq.presetsDispositivo.length;
fakeDispositivo.emitirDispositivo(
const DispositivoAudio(
id: 'bt_a2dp:name:AirPods-Pro',
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'AirPods Pro',
),
);
await Future<void>.delayed(const Duration(milliseconds: 50));
fakeDispositivo.emitirDispositivo(
const DispositivoAudio(
id: 'bt_a2dp:name:Sony-WH-1000XM5',
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'Sony WH-1000XM5',
),
);
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(
eq.nombrePlataforma('bt_a2dp:name:AirPods-Pro'),
equals('AirPods Pro'),
);
expect(
eq.nombrePlataforma('bt_a2dp:name:Sony-WH-1000XM5'),
equals('Sony WH-1000XM5'),
);
expect(eq.presetsDispositivo.length, equals(baseline));
eq.dispose();
});
test(
'3.7 repeated event for known id is a no-op on preset creation '
'(regression-lock)',
() async {
final fakeDispositivo = FakeServicioDispositivoAudio();
const device = DispositivoAudio(
id: 'bt_a2dp:AA:BB:CC:DD:EE:FF',
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'AirPods Pro',
);
final eq = EstadoEcualizador(
audio: FakeServicioAudio(),
servicio: FakeServicioEcualizador(eqMultiDeviceEnabled: true),
dispositivoAudio: fakeDispositivo,
emisoraActualUuid: () => stationUuid,
);
await eq.cargarPersistido();
fakeDispositivo.emitirDispositivo(device);
await Future<void>.delayed(const Duration(milliseconds: 50));
final presetTrasPrimerEvento = eq.presetsDispositivo[device.id];
final cantidadTrasPrimerEvento = eq.presetsDispositivo.length;
expect(presetTrasPrimerEvento, isNotNull);
fakeDispositivo.emitirDispositivo(device);
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(
eq.presetsDispositivo[device.id],
equals(presetTrasPrimerEvento),
);
expect(eq.presetsDispositivo.length, equals(cantidadTrasPrimerEvento));
eq.dispose();
},
);
test(
'3.8 transient non-BT id during pairing handshake does not corrupt '
'the BT entry (regression-lock)',
() async {
final fakeDispositivo = FakeServicioDispositivoAudio();
const btDevice = DispositivoAudio(
id: 'bt_a2dp:AA:BB:CC:DD:EE:FF',
tipo: TipoDispositivo.bluetoothA2dp,
nombre: 'AirPods Pro',
);
const builtinSpeaker = DispositivoAudio(
id: 'builtin_speaker',
tipo: TipoDispositivo.altavozInterno,
nombre: 'Speaker',
);
final eq = EstadoEcualizador(
audio: FakeServicioAudio(),
servicio: FakeServicioEcualizador(
principal: PresetEcualizador.rock,
eqMultiDeviceEnabled: true,
),
dispositivoAudio: fakeDispositivo,
emisoraActualUuid: () => stationUuid,
);
await eq.cargarPersistido();
// Transient builtin_speaker report mid-handshake.
fakeDispositivo.emitirDispositivo(builtinSpeaker);
await Future<void>.delayed(const Duration(milliseconds: 50));
// Final BT event once the A2DP profile attaches.
fakeDispositivo.emitirDispositivo(btDevice);
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(eq.presetsDispositivo.containsKey(btDevice.id), isTrue);
expect(eq.presetsDispositivo.containsKey('builtin_speaker'), isFalse);
expect(
eq.presetsDispositivo[btDevice.id],
equals(PresetEcualizador.rock),
);
eq.dispose();
},
);
});
group('FakeServicioEcualizador — nombresDispositivos preservation (WARNING-3)', () {
test(
'guardarPrincipal preserves nombresDispositivos',