History review requested by the owner: when and why did the Android Auto UI stop working. ANSWER: 31 July, commit2540556, "give the equalizer actions distinct, state-aware icons".9eff760(31-07) androidIcon: 'drawable/ic_stat_pluriwave' -> in the APK2540556(31-07) androidIcon: 'drawable/ic_auto_eq_on' -> NEVER in it That commit swapped a drawable that shipped for two that the stale CI resource cache never included. From that moment getResourceId returned 0, PlaybackStateCompat.CustomAction.Builder threw, and the throw aborted AudioService.setState before the session was published -- so every Android Auto symptom chased since is one line of that commit. The bitter part is that2540556was itself a fix for a report about two identical icons. Three changes. 1. CI guard. The build now unzips the release APK and fails if a drawable resolved by NAME at runtime is missing. Resolution by name cannot fail at compile time -- it fails in the car, silently, with id 0. This class of bug shipped undetected for a week; it cannot ship again. 2. Skipping stations now walks the favourites GROUP first, as requested: group -> all favourites -> my stations -> catalogue. Two deliberate exclusions, both tested: `sinAsignarId` is the ABSENCE of a group, not a group, so those walk all favourites; and a one-member group falls through too, or both buttons would be dead ends. The group is read from the FAVOURITE record, never from the playing station -- that one is rebuilt by emisoraDesdeMediaItem, which carries no group id and would always report "unfiled". 3. Diagnostics on the station skip. It was reported as doing nothing for radio, and every early return in that method is silent: an empty list and a single-entry list look identical from outside. The log now names which one fired, so the next capture answers it instead of another hypothesis. Tests: 1161 -> 1165.
176 lines
5.3 KiB
Dart
176 lines
5.3 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/modelos/emisora.dart';
|
|
import 'package:pluriwave/modelos/grupo_favoritos.dart';
|
|
import 'package:pluriwave/servicios/navegacion_auto.dart';
|
|
|
|
/// Requested: the Android Auto playback screen must offer previous/next for
|
|
/// stations too, not only for a local-music queue. Those buttons appear
|
|
/// because `skipToPrevious`/`skipToNext` are advertised in `systemActions` —
|
|
/// so they have to actually move, or the car shows two dead buttons.
|
|
void main() {
|
|
Emisora emisora(String uuid) =>
|
|
Emisora(uuid: uuid, nombre: uuid, url: 'https://example.com/$uuid');
|
|
|
|
final a = emisora('a');
|
|
final b = emisora('b');
|
|
final c = emisora('c');
|
|
|
|
group('emisoraVecina', () {
|
|
test('avanza y retrocede dentro de la lista', () {
|
|
expect(emisoraVecina(a, [a, b, c], haciaAtras: false), b);
|
|
expect(emisoraVecina(b, [a, b, c], haciaAtras: true), a);
|
|
});
|
|
|
|
test('da la vuelta en los dos extremos', () {
|
|
// A dead button at the end of a list reads as a broken app on a car
|
|
// screen, where there is no visible list position to explain it.
|
|
expect(emisoraVecina(c, [a, b, c], haciaAtras: false), a);
|
|
expect(emisoraVecina(a, [a, b, c], haciaAtras: true), c);
|
|
});
|
|
|
|
test('identifica por uuid, no por instancia: un snapshot refrescado trae '
|
|
'objetos distintos', () {
|
|
final copiaDeB = Emisora(
|
|
uuid: 'b',
|
|
nombre: 'otro nombre',
|
|
url: 'https://example.com/cambiada',
|
|
);
|
|
expect(emisoraVecina(copiaDeB, [a, b, c], haciaAtras: false), c);
|
|
});
|
|
|
|
test('no hace nada sin contexto suficiente', () {
|
|
expect(emisoraVecina(null, [a, b], haciaAtras: false), isNull);
|
|
expect(emisoraVecina(a, [a], haciaAtras: false), isNull);
|
|
expect(emisoraVecina(a, const [], haciaAtras: false), isNull);
|
|
expect(
|
|
emisoraVecina(emisora('fuera'), [a, b], haciaAtras: false),
|
|
isNull,
|
|
reason: 'una emisora que no está en la lista no debe saltar a ciegas',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('listaParaSaltoEmisora', () {
|
|
test('favoritos gana: "siguiente" desde un favorito va al siguiente '
|
|
'favorito, no a la entrada 4318 del catálogo', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: a,
|
|
favoritos: [a, b],
|
|
misEmisoras: [a, c],
|
|
todas: [a, b, c],
|
|
),
|
|
[a, b],
|
|
);
|
|
});
|
|
|
|
test('mis emisoras cuando no es favorita', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: c,
|
|
favoritos: [a, b],
|
|
misEmisoras: [c, a],
|
|
todas: [a, b, c],
|
|
),
|
|
[c, a],
|
|
);
|
|
});
|
|
|
|
test('cae al catálogo completo para una emisora llegada por búsqueda', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: c,
|
|
favoritos: [a],
|
|
misEmisoras: [b],
|
|
todas: [a, b, c],
|
|
),
|
|
[a, b, c],
|
|
);
|
|
});
|
|
|
|
test('vacía si no está en ninguna: el salto queda en no-op', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: emisora('huerfana'),
|
|
favoritos: [a],
|
|
misEmisoras: [b],
|
|
todas: [a, b],
|
|
),
|
|
isEmpty,
|
|
);
|
|
});
|
|
});
|
|
|
|
group('navegación por GRUPO de favoritos (pedido por el dueño)', () {
|
|
Emisora favorita(String uuid, String grupo) => Emisora(
|
|
uuid: uuid,
|
|
nombre: uuid,
|
|
url: 'https://example.com/$uuid',
|
|
grupoFavoritosId: grupo,
|
|
);
|
|
|
|
final rock1 = favorita('rock1', 'g-rock');
|
|
final rock2 = favorita('rock2', 'g-rock');
|
|
final jazz1 = favorita('jazz1', 'g-jazz');
|
|
final suelta = favorita('suelta', GrupoFavoritos.sinAsignarId);
|
|
|
|
test('sonando una favorita de un grupo, se recorre SOLO ese grupo', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: rock1,
|
|
favoritos: [rock1, jazz1, rock2, suelta],
|
|
misEmisoras: const [],
|
|
todas: [rock1, jazz1, rock2, suelta],
|
|
),
|
|
[rock1, rock2],
|
|
);
|
|
});
|
|
|
|
test('el grupo se lee del registro de FAVORITOS, no de lo que suena: la '
|
|
'emisora reconstruida desde el MediaItem no lleva grupo', () {
|
|
// emisoraDesdeMediaItem no puede saber el grupo -> llega "sin asignar".
|
|
final reconstruida = Emisora(
|
|
uuid: 'rock1',
|
|
nombre: 'Rock 1',
|
|
url: 'https://example.com/rock1',
|
|
);
|
|
expect(reconstruida.grupoFavoritosId, GrupoFavoritos.sinAsignarId);
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: reconstruida,
|
|
favoritos: [rock1, jazz1, rock2],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
[rock1, rock2],
|
|
reason: 'si se leyera de `actual` caeríamos a todos los favoritos',
|
|
);
|
|
});
|
|
|
|
test('"sin asignar" NO es un grupo: recorre todos los favoritos', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: suelta,
|
|
favoritos: [rock1, suelta, jazz1],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
[rock1, suelta, jazz1],
|
|
);
|
|
});
|
|
|
|
test('un grupo de UNA sola emisora cae a todos los favoritos, para no '
|
|
'dejar los dos botones muertos', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: jazz1,
|
|
favoritos: [rock1, rock2, jazz1],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
[rock1, rock2, jazz1],
|
|
);
|
|
});
|
|
});
|
|
}
|