fix(auto): give the equalizer actions distinct, state-aware icons

On a car head unit the custom actions render icon-first, so two actions
sharing ic_stat_pluriwave were indistinguishable and the toggle gave no
sign of whether the equalizer was on.

Each action now has its own drawable, and the toggle swaps between
ic_auto_eq_on and ic_auto_eq_off so its state is legible at a glance.
This commit is contained in:
2026-07-31 18:11:57 +02:00
parent c8b2c4d2d6
commit 25405564ee
5 changed files with 107 additions and 18 deletions
@@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:ui' show Locale;
import 'package:audio_service/audio_service.dart';
@@ -162,20 +163,53 @@ void main() {
);
});
test('both actions reuse the bundled notification drawable (zero new '
'native assets)', () {
final controles = controlesEcualizadorPersonalizados(
disponible: true,
activo: true,
presetActual: PresetEcualizador.flat,
l10n: l10n,
);
test(
'toggle icon reflects EQ state: ON uses ic_auto_eq_on, OFF uses '
'ic_auto_eq_off -- a car head unit foregrounds the icon over the '
'label, so the icon itself must change, not just the text',
() {
final activado = controlesEcualizadorPersonalizados(
disponible: true,
activo: true,
presetActual: PresetEcualizador.flat,
l10n: l10n,
).firstWhere((c) => c.customAction?.name == accionEqToggle);
final desactivado = controlesEcualizadorPersonalizados(
disponible: true,
activo: false,
presetActual: PresetEcualizador.flat,
l10n: l10n,
).firstWhere((c) => c.customAction?.name == accionEqToggle);
expect(
controles.every((c) => c.androidIcon == 'drawable/ic_stat_pluriwave'),
isTrue,
);
});
expect(activado.androidIcon, 'drawable/ic_auto_eq_on');
expect(desactivado.androidIcon, 'drawable/ic_auto_eq_off');
},
);
test(
'the toggle and the preset-cycle action never share an androidIcon, '
'in either EQ state -- this was the on-device bug: both buttons used '
'the same drawable and were visually indistinguishable',
() {
for (final activo in [true, false]) {
final controles = controlesEcualizadorPersonalizados(
disponible: true,
activo: activo,
presetActual: PresetEcualizador.flat,
l10n: l10n,
);
final iconos = controles.map((c) => c.androidIcon).toSet();
expect(
iconos.length,
controles.length,
reason:
'every custom action must have a distinct androidIcon '
'(activo=$activo)',
);
}
},
);
});
group('action name constants (item 4 — collision-free with car-tree ids)', () {
@@ -186,4 +220,37 @@ void main() {
expect(accionEqToggle, isNot(equals(accionEqPresetSiguiente)));
});
});
group(
'equalizer drawable assets on disk (on-device feedback follow-up: the '
'two custom actions used to share one drawable and were visually '
'indistinguishable)',
() {
test(
'ic_auto_eq_on, ic_auto_eq_off and ic_auto_eq_preset all exist under '
'android/app/src/main/res/drawable/ -- a missing drawable is not a '
'build error, it silently renders blank/default on the head unit, '
'so this is the only safety net that would have caught the original '
'duplication',
() {
for (final nombre in [
'ic_auto_eq_on',
'ic_auto_eq_off',
'ic_auto_eq_preset',
]) {
final archivo = File(
'android/app/src/main/res/drawable/$nombre.xml',
);
expect(
archivo.existsSync(),
isTrue,
reason:
'$nombre.xml must exist under '
'android/app/src/main/res/drawable/',
);
}
},
);
},
);
}