Compare commits
3
Commits
62f7804d6d
...
ec6ccb2db8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec6ccb2db8 | ||
|
|
8e00dc0c7c | ||
|
|
f01c0911f7 |
@@ -134,6 +134,36 @@ jobs:
|
||||
- name: Build APK release
|
||||
run: flutter build apk --release
|
||||
|
||||
# Guardian de recursos: el APK debe contener los drawables que el codigo
|
||||
# resuelve POR NOMBRE en runtime (getResources().getIdentifier).
|
||||
#
|
||||
# Un nombre que no resuelve devuelve id 0, y eso no falla la
|
||||
# compilacion: falla en el coche. Concretamente
|
||||
# PlaybackStateCompat.CustomAction.Builder lanza con icono 0, ese throw
|
||||
# aborta AudioService.setState antes de activar la sesion de medios, y
|
||||
# Android Auto se queda con la interfaz congelada. Paso exactamente eso
|
||||
# entre el 31-07 (commit 2540556) y el 07-08 sin que nada lo detectara.
|
||||
#
|
||||
# Anadir un drawable nuevo referenciado por nombre => anadirlo aqui.
|
||||
- name: Verificar recursos criticos en el APK
|
||||
run: |
|
||||
APK=build/app/outputs/flutter-apk/app-release.apk
|
||||
FALTAN=0
|
||||
for RECURSO in ic_auto_eq_on ic_auto_eq_off ic_stat_pluriwave; do
|
||||
if unzip -l "$APK" | grep -q "res/drawable/$RECURSO.xml"; then
|
||||
echo "OK res/drawable/$RECURSO.xml"
|
||||
else
|
||||
echo "FALTA res/drawable/$RECURSO.xml"
|
||||
FALTAN=1
|
||||
fi
|
||||
done
|
||||
if [ "$FALTAN" -ne 0 ]; then
|
||||
echo ""
|
||||
echo "Hay drawables resueltos por nombre que NO estan en el APK."
|
||||
echo "En runtime resolveran a id 0 y tumbaran la sesion de medios."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build AAB release
|
||||
run: flutter build appbundle --release
|
||||
|
||||
|
||||
@@ -936,15 +936,26 @@ Future<void> reproducirPorMediaId(
|
||||
await reproducir(item);
|
||||
}
|
||||
|
||||
/// Which list previous/next should walk for [actual]: the NARROWEST list the
|
||||
/// station actually belongs to, favourites first, then my stations, then the
|
||||
/// full catalogue.
|
||||
/// Which list previous/next should walk for [actual]: the NARROWEST context
|
||||
/// the station belongs to.
|
||||
///
|
||||
/// Narrowest-first is the point. "Next station" while playing a favourite
|
||||
/// should land on the next favourite, not on entry 4,318 of a 50,000-station
|
||||
/// catalogue that happens to sit beside it alphabetically. Falling through to
|
||||
/// [todas] only when the station is in neither curated list keeps the button
|
||||
/// working for a station reached by search.
|
||||
/// Tightest first:
|
||||
/// 1. its FAVOURITES GROUP, when it is a favourite filed under a real group,
|
||||
/// 2. all favourites,
|
||||
/// 3. my stations,
|
||||
/// 4. the full catalogue.
|
||||
///
|
||||
/// The group tier is what the owner asked for: driving with a themed group,
|
||||
/// "next" should stay inside that group rather than wander across every
|
||||
/// favourite. And "next" from a favourite must never land on entry 4,318 of a
|
||||
/// 50,000-station catalogue that happens to sit beside it alphabetically.
|
||||
/// Falling through to [todas] only when the station is in neither curated
|
||||
/// list keeps the button alive for a station reached by search.
|
||||
///
|
||||
/// [GrupoFavoritos.sinAsignarId] is deliberately NOT treated as a group: it
|
||||
/// is the ABSENCE of one, so those stations walk all favourites instead of a
|
||||
/// bucket that only means "unfiled". A group with a single member also falls
|
||||
/// through to all favourites — otherwise both buttons would be dead ends.
|
||||
///
|
||||
/// Returns an empty list when [actual] is in none of them, which
|
||||
/// [emisoraVecina] turns into "do nothing".
|
||||
@@ -954,10 +965,28 @@ List<Emisora> listaParaSaltoEmisora({
|
||||
required List<Emisora> misEmisoras,
|
||||
required List<Emisora> todas,
|
||||
}) {
|
||||
bool contiene(List<Emisora> lista) => lista.any((e) => e.uuid == actual.uuid);
|
||||
if (contiene(favoritos)) return favoritos;
|
||||
if (contiene(misEmisoras)) return misEmisoras;
|
||||
if (contiene(todas)) return todas;
|
||||
Emisora? enLista(List<Emisora> lista) {
|
||||
for (final e in lista) {
|
||||
if (e.uuid == actual.uuid) return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// The FAVOURITE record is the authority on the group, never `actual`: the
|
||||
// playing station is rebuilt from a MediaItem by `emisoraDesdeMediaItem`,
|
||||
// which carries no group id and would always report "sin asignar".
|
||||
final favorita = enLista(favoritos);
|
||||
if (favorita != null) {
|
||||
final grupo = favorita.grupoFavoritosId;
|
||||
if (grupo != GrupoFavoritos.sinAsignarId) {
|
||||
final delGrupo =
|
||||
favoritos.where((e) => e.grupoFavoritosId == grupo).toList();
|
||||
if (delGrupo.length > 1) return delGrupo;
|
||||
}
|
||||
return favoritos;
|
||||
}
|
||||
if (enLista(misEmisoras) != null) return misEmisoras;
|
||||
if (enLista(todas) != null) return todas;
|
||||
return const [];
|
||||
}
|
||||
|
||||
|
||||
@@ -1544,6 +1544,15 @@ class PluriWaveAudioHandler extends BaseAudioHandler
|
||||
todas: await fuente.todas(),
|
||||
);
|
||||
final destino = emisoraVecina(actual, lista, haciaAtras: haciaAtras);
|
||||
// Reported: in the car these buttons did nothing for radio. Every early
|
||||
// return here is silent, so the log has to say WHICH one fired --
|
||||
// an empty list (the station matched none of the three) and a station
|
||||
// that is in a list of one are indistinguishable from outside.
|
||||
debugPrint(
|
||||
'[PluriWave][ServicioAudio] salto emisora atras=$haciaAtras '
|
||||
'actual=${actual.nombre} uuid=${actual.uuid} '
|
||||
'lista=${lista.length} destino=${destino?.nombre ?? "NINGUNO"}',
|
||||
);
|
||||
if (destino == null) return;
|
||||
await playMediaItem(mediaItemParaEmisora(destino, l10n: _textos));
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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
|
||||
@@ -99,4 +100,76 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user