Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55fe50d07d | ||
|
|
2e15d05431 | ||
|
|
e9f47d47c2 |
@@ -37,7 +37,9 @@ class EstadoEcualizador extends ChangeNotifier {
|
|||||||
_presetsPersonalizadosService =
|
_presetsPersonalizadosService =
|
||||||
presetsPersonalizadosService ?? ServicioPresetsPersonalizados(),
|
presetsPersonalizadosService ?? ServicioPresetsPersonalizados(),
|
||||||
_dispositivoAudio = dispositivoAudio,
|
_dispositivoAudio = dispositivoAudio,
|
||||||
_emisoraActualUuid = emisoraActualUuid ?? (() => null);
|
_emisoraActualUuid = emisoraActualUuid ?? (() => null) {
|
||||||
|
_escucharCambiosEqDesdeHandler();
|
||||||
|
}
|
||||||
|
|
||||||
final ServicioAudio audio;
|
final ServicioAudio audio;
|
||||||
final ServicioEcualizador servicio;
|
final ServicioEcualizador servicio;
|
||||||
@@ -84,6 +86,20 @@ class EstadoEcualizador extends ChangeNotifier {
|
|||||||
StreamSubscription<DispositivoAudio>? _deviceSub;
|
StreamSubscription<DispositivoAudio>? _deviceSub;
|
||||||
Future<void>? _refrescoEnCurso;
|
Future<void>? _refrescoEnCurso;
|
||||||
|
|
||||||
|
/// Catches a car/notification-initiated EQ change that bypasses this
|
||||||
|
/// class entirely (eq-sync-superficies): `accionEqToggle` calls
|
||||||
|
/// `PluriWaveAudioHandler.setEcualizadorActivo` directly, and
|
||||||
|
/// `seleccionarPresetEqPorMediaId` calls `aplicarPreset` directly — both
|
||||||
|
/// mutate ONLY the handler's own `_ecualizadorActivo`/`_presetActual`
|
||||||
|
/// fields, never [audio]'s owner ([EstadoEcualizador]). Mirrors the exact
|
||||||
|
/// shape `EstadoRadio._escucharErroresReproduccion` already uses for the
|
||||||
|
/// equivalent `playFromMediaId` gap: on every [ServicioAudio.estadoStream]
|
||||||
|
/// tick (which the handler already re-emits on any EQ change via
|
||||||
|
/// `_actualizarControlesEq()`, regardless of who triggered it), compare
|
||||||
|
/// the handler's current EQ state against our cached copy and adopt it on
|
||||||
|
/// divergence.
|
||||||
|
StreamSubscription<EstadoReproduccion>? _suscripcionEstadoAudioEq;
|
||||||
|
|
||||||
PresetEcualizador get presetActual => _presetActual;
|
PresetEcualizador get presetActual => _presetActual;
|
||||||
PresetEcualizador get presetPrincipal => _presetPrincipal;
|
PresetEcualizador get presetPrincipal => _presetPrincipal;
|
||||||
bool get activo => _activo;
|
bool get activo => _activo;
|
||||||
@@ -337,6 +353,58 @@ class EstadoEcualizador extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subscribes to [ServicioAudio.estadoStream] to catch a
|
||||||
|
/// car/notification-initiated EQ change (see [_suscripcionEstadoAudioEq]
|
||||||
|
/// doc for the full rationale).
|
||||||
|
void _escucharCambiosEqDesdeHandler() {
|
||||||
|
_suscripcionEstadoAudioEq = audio.estadoStream.listen((_) {
|
||||||
|
unawaited(_resincronizarConHandler());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares the handler's live EQ state ([ServicioAudio.ecualizadorActivo],
|
||||||
|
/// [ServicioAudio.presetActual]) against our cached [_activo]/
|
||||||
|
/// [_presetActual] and adopts the handler's value on divergence.
|
||||||
|
///
|
||||||
|
/// Deliberately never calls back into [audio] here (no
|
||||||
|
/// `setEcualizadorActivo`/`aplicarPreset`): doing so would re-trigger the
|
||||||
|
/// handler's own `_actualizarControlesEq()` re-push, which would tick
|
||||||
|
/// [ServicioAudio.estadoStream] again and re-enter this method forever.
|
||||||
|
/// Only a local field write, [servicio] persistence and [notifyListeners]
|
||||||
|
/// happen here, so a divergence is resolved in a single pass.
|
||||||
|
///
|
||||||
|
/// Wrapped in try/catch like every other handler-facing read in this
|
||||||
|
/// class (e.g. [_sembrarDispositivoActual]): a test double or an
|
||||||
|
/// unexpected platform state that makes [audio]'s EQ getters unavailable
|
||||||
|
/// must never crash the stream subscription — it just skips this tick.
|
||||||
|
Future<void> _resincronizarConHandler() async {
|
||||||
|
try {
|
||||||
|
final activoHandler = audio.ecualizadorActivo;
|
||||||
|
final presetHandler = audio.presetActual;
|
||||||
|
|
||||||
|
final activoDiverge = activoHandler != _activo;
|
||||||
|
final presetDiverge = presetHandler != _presetActual;
|
||||||
|
if (!activoDiverge && !presetDiverge) return;
|
||||||
|
|
||||||
|
if (activoDiverge) {
|
||||||
|
_activo = activoHandler;
|
||||||
|
// Closes the persistence gap: `PluriWaveAudioHandler` never
|
||||||
|
// persists anything itself (it must stay headless-constructible,
|
||||||
|
// with zero SharedPreferences/Provider access) — [servicio] is the
|
||||||
|
// only owner of EQ persistence, so a car/notification toggle must
|
||||||
|
// be saved HERE or it is lost on the next process restart.
|
||||||
|
await servicio.guardarActivo(activoHandler);
|
||||||
|
}
|
||||||
|
if (presetDiverge) {
|
||||||
|
_presetActual = presetHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
} catch (_) {
|
||||||
|
// See doc above — never let a resync failure crash the app.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Applies [preset] to the audio engine and tracks it as current
|
/// Applies [preset] to the audio engine and tracks it as current
|
||||||
/// WITHOUT persisting it (used when switching stations).
|
/// WITHOUT persisting it (used when switching stations).
|
||||||
Future<void> aplicarPresetActivo(PresetEcualizador preset) async {
|
Future<void> aplicarPresetActivo(PresetEcualizador preset) async {
|
||||||
@@ -673,6 +741,7 @@ class EstadoEcualizador extends ChangeNotifier {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_deviceSub?.cancel();
|
_deviceSub?.cancel();
|
||||||
|
_suscripcionEstadoAudioEq?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,28 @@ const _bannerAdUnitIdReal = 'ca-app-pub-6038935671414339/5658618378';
|
|||||||
/// Real interstitial unit id, provisioned in the AdMob console (iap-freemium-unlock).
|
/// Real interstitial unit id, provisioned in the AdMob console (iap-freemium-unlock).
|
||||||
const _interstitialAdUnitIdReal = 'ca-app-pub-6038935671414339/4189478248';
|
const _interstitialAdUnitIdReal = 'ca-app-pub-6038935671414339/4189478248';
|
||||||
|
|
||||||
/// Real id in release builds only; test id everywhere else (debug/profile,
|
/// TESTING-PHASE SWITCH. While `true`, release builds serve Google's official
|
||||||
/// including internal-testing-track builds run via `flutter run --release`
|
/// TEST ad units instead of the real ones, so none of the closed-testing
|
||||||
/// on a personal device — see the "never tap your own ads" note above).
|
/// human testers can generate invalid traffic against the AdMob account
|
||||||
|
/// (they cannot be registered as AdMob test devices). Flip to `false` for
|
||||||
|
/// the production release — that is the ONLY change needed to start serving
|
||||||
|
/// real ads. This does NOT affect the AdMob application id in
|
||||||
|
/// `AndroidManifest.xml`, which stays real in every build (it only
|
||||||
|
/// initializes the SDK and carries none of the click risk).
|
||||||
|
const usarAnunciosDePruebaEnRelease = true;
|
||||||
|
|
||||||
|
/// Real id in release builds only, and only once [usarAnunciosDePruebaEnRelease]
|
||||||
|
/// is flipped to `false`; test id everywhere else (debug/profile, including
|
||||||
|
/// internal-testing-track builds run via `flutter run --release` on a
|
||||||
|
/// personal device — see the "never tap your own ads" note above).
|
||||||
const bannerAdUnitId =
|
const bannerAdUnitId =
|
||||||
kReleaseMode ? _bannerAdUnitIdReal : bannerAdUnitIdPrueba;
|
kReleaseMode && !usarAnunciosDePruebaEnRelease
|
||||||
|
? _bannerAdUnitIdReal
|
||||||
|
: bannerAdUnitIdPrueba;
|
||||||
const interstitialAdUnitId =
|
const interstitialAdUnitId =
|
||||||
kReleaseMode ? _interstitialAdUnitIdReal : interstitialAdUnitIdPrueba;
|
kReleaseMode && !usarAnunciosDePruebaEnRelease
|
||||||
|
? _interstitialAdUnitIdReal
|
||||||
|
: interstitialAdUnitIdPrueba;
|
||||||
|
|
||||||
/// Ads port + AdMob adapter (Design "Interfaces / Contracts", ADR-6): owns
|
/// Ads port + AdMob adapter (Design "Interfaces / Contracts", ADR-6): owns
|
||||||
/// the entitlement gate for both surfaces, the interstitial's session
|
/// the entitlement gate for both surfaces, the interstitial's session
|
||||||
|
|||||||
@@ -582,6 +582,13 @@ class ServicioAudio {
|
|||||||
bool get ecualizadorDisponible => _handler.ecualizadorDisponible;
|
bool get ecualizadorDisponible => _handler.ecualizadorDisponible;
|
||||||
PresetEcualizador get presetActual => _handler.presetActual;
|
PresetEcualizador get presetActual => _handler.presetActual;
|
||||||
|
|
||||||
|
/// Forwards the handler's own on/off flag (eq-sync-superficies): a
|
||||||
|
/// car/notification toggle (`accionEqToggle`) mutates
|
||||||
|
/// `PluriWaveAudioHandler._ecualizadorActivo` directly, bypassing
|
||||||
|
/// [setEcualizadorActivo] entirely. [EstadoEcualizador] polls this getter
|
||||||
|
/// on every [estadoStream] tick to detect and resync that divergence.
|
||||||
|
bool get ecualizadorActivo => _handler.ecualizadorActivo;
|
||||||
|
|
||||||
Future<void> aplicarPreset(PresetEcualizador preset) =>
|
Future<void> aplicarPreset(PresetEcualizador preset) =>
|
||||||
_handler.aplicarPreset(preset);
|
_handler.aplicarPreset(preset);
|
||||||
Future<void> setEcualizadorActivo(bool activo) =>
|
Future<void> setEcualizadorActivo(bool activo) =>
|
||||||
|
|||||||
@@ -1728,6 +1728,132 @@ void main() {
|
|||||||
eq.dispose();
|
eq.dispose();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// eq-sync-superficies: car/notification-initiated EQ changes must reach
|
||||||
|
// EstadoEcualizador (and persist through ServicioEcualizador), not just
|
||||||
|
// the audio handler.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
group('EstadoEcualizador — resync with handler-initiated EQ changes '
|
||||||
|
'(eq-sync-superficies)', () {
|
||||||
|
test(
|
||||||
|
'a handler-initiated toggle (car/notification) syncs activo and '
|
||||||
|
'notifies listeners',
|
||||||
|
() async {
|
||||||
|
final fakeAudio = FakeServicioAudio();
|
||||||
|
final eq = EstadoEcualizador(
|
||||||
|
audio: fakeAudio,
|
||||||
|
servicio: FakeServicioEcualizador(activo: true),
|
||||||
|
);
|
||||||
|
await eq.cargarPersistido();
|
||||||
|
expect(eq.activo, isTrue);
|
||||||
|
|
||||||
|
var avisos = 0;
|
||||||
|
eq.addListener(() => avisos++);
|
||||||
|
|
||||||
|
// Simulates `accionEqToggle` calling
|
||||||
|
// `PluriWaveAudioHandler.setEcualizadorActivo` directly, bypassing
|
||||||
|
// `ServicioAudio`/`EstadoEcualizador` entirely.
|
||||||
|
fakeAudio.simularCambioEqDesdeHandler(activo: false);
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
expect(eq.activo, isFalse);
|
||||||
|
expect(avisos, greaterThanOrEqualTo(1));
|
||||||
|
eq.dispose();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'a handler-initiated preset change (Android Auto) syncs presetActual '
|
||||||
|
'and notifies listeners',
|
||||||
|
() async {
|
||||||
|
final fakeAudio = FakeServicioAudio();
|
||||||
|
final eq = EstadoEcualizador(
|
||||||
|
audio: fakeAudio,
|
||||||
|
servicio: FakeServicioEcualizador(principal: PresetEcualizador.flat),
|
||||||
|
);
|
||||||
|
await eq.cargarPersistido();
|
||||||
|
expect(eq.presetActual, equals(PresetEcualizador.flat));
|
||||||
|
|
||||||
|
var avisos = 0;
|
||||||
|
eq.addListener(() => avisos++);
|
||||||
|
|
||||||
|
// Simulates `seleccionarPresetEqPorMediaId` calling
|
||||||
|
// `PluriWaveAudioHandler.aplicarPreset` directly.
|
||||||
|
fakeAudio.simularCambioEqDesdeHandler(preset: PresetEcualizador.jazz);
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
expect(eq.presetActual, equals(PresetEcualizador.jazz));
|
||||||
|
expect(avisos, greaterThanOrEqualTo(1));
|
||||||
|
eq.dispose();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'a handler-initiated toggle is persisted through ServicioEcualizador '
|
||||||
|
'(survives a restart)',
|
||||||
|
() async {
|
||||||
|
final fakeAudio = FakeServicioAudio();
|
||||||
|
final fakeServicio = FakeServicioEcualizador(activo: true);
|
||||||
|
final eq = EstadoEcualizador(audio: fakeAudio, servicio: fakeServicio);
|
||||||
|
await eq.cargarPersistido();
|
||||||
|
|
||||||
|
fakeAudio.simularCambioEqDesdeHandler(activo: false);
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
expect(fakeServicio.config.activo, isFalse);
|
||||||
|
expect(fakeServicio.guardarActivoLlamadas, equals(1));
|
||||||
|
eq.dispose();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'resync does not cause an extra handler write (no feedback loop)',
|
||||||
|
() async {
|
||||||
|
final fakeAudio = FakeServicioAudio();
|
||||||
|
final eq = EstadoEcualizador(
|
||||||
|
audio: fakeAudio,
|
||||||
|
servicio: FakeServicioEcualizador(activo: true),
|
||||||
|
);
|
||||||
|
await eq.cargarPersistido();
|
||||||
|
fakeAudio.cambiosEcualizadorActivo.clear();
|
||||||
|
fakeAudio.presetsAplicados.clear();
|
||||||
|
|
||||||
|
fakeAudio.simularCambioEqDesdeHandler(activo: false);
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
// The resync must only read from `audio` and write to `servicio` —
|
||||||
|
// never write BACK into `audio`, or a handler write would trigger
|
||||||
|
// another stream tick, which would resync again, forever.
|
||||||
|
expect(fakeAudio.cambiosEcualizadorActivo, isEmpty);
|
||||||
|
expect(fakeAudio.presetsAplicados, isEmpty);
|
||||||
|
eq.dispose();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'a UI-initiated toggle still works exactly as before and persists '
|
||||||
|
'exactly once (regression)',
|
||||||
|
() async {
|
||||||
|
final fakeAudio = FakeServicioAudio();
|
||||||
|
final fakeServicio = FakeServicioEcualizador(activo: true);
|
||||||
|
final eq = EstadoEcualizador(audio: fakeAudio, servicio: fakeServicio);
|
||||||
|
await eq.cargarPersistido();
|
||||||
|
fakeAudio.cambiosEcualizadorActivo.clear();
|
||||||
|
|
||||||
|
await eq.cambiarActivo(false);
|
||||||
|
// Give any (harmless, no-op) resync tick a chance to run too.
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
expect(eq.activo, isFalse);
|
||||||
|
expect(fakeServicio.config.activo, isFalse);
|
||||||
|
expect(fakeServicio.guardarActivoLlamadas, equals(1));
|
||||||
|
expect(fakeAudio.cambiosEcualizadorActivo, equals([false]));
|
||||||
|
eq.dispose();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fake whose [guardarActivo] stays pending until released, and releases the
|
/// Fake whose [guardarActivo] stays pending until released, and releases the
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ void main() {
|
|||||||
final porEmisora = {'fav-1': PresetEcualizador.rock};
|
final porEmisora = {'fav-1': PresetEcualizador.rock};
|
||||||
final estado = EstadoRadio(
|
final estado = EstadoRadio(
|
||||||
esPremium: () => true,
|
esPremium: () => true,
|
||||||
audio: FakeServicioAudio(ecualizadorActivo: false),
|
audio: FakeServicioAudio(ecualizadorDisponible: false),
|
||||||
favoritos: FakeServicioFavoritos(),
|
favoritos: FakeServicioFavoritos(),
|
||||||
radio: FakeServicioRadio(),
|
radio: FakeServicioRadio(),
|
||||||
servicioEcualizador: FakeServicioEcualizador(
|
servicioEcualizador: FakeServicioEcualizador(
|
||||||
|
|||||||
+41
-5
@@ -15,11 +15,14 @@ import 'package:pluriwave/servicios/servicio_presets_personalizados.dart';
|
|||||||
import 'package:pluriwave/servicios/servicio_radio.dart';
|
import 'package:pluriwave/servicios/servicio_radio.dart';
|
||||||
|
|
||||||
class FakeServicioAudio extends ServicioAudio {
|
class FakeServicioAudio extends ServicioAudio {
|
||||||
FakeServicioAudio({this.ecualizadorActivo = true}) {
|
FakeServicioAudio({this.ecualizadorDisponible = true}) {
|
||||||
_estadoController.add(EstadoReproduccion.detenido);
|
_estadoController.add(EstadoReproduccion.detenido);
|
||||||
}
|
}
|
||||||
|
|
||||||
final bool ecualizadorActivo;
|
/// Whether the native equalizer is available on this device — NOT whether
|
||||||
|
/// it is currently switched on (see [ecualizadorActivo] for that).
|
||||||
|
@override
|
||||||
|
final bool ecualizadorDisponible;
|
||||||
final _estadoController = StreamController<EstadoReproduccion>.broadcast();
|
final _estadoController = StreamController<EstadoReproduccion>.broadcast();
|
||||||
final List<PresetEcualizador> presetsAplicados = [];
|
final List<PresetEcualizador> presetsAplicados = [];
|
||||||
final List<Emisora> emisorasReproducidas = [];
|
final List<Emisora> emisorasReproducidas = [];
|
||||||
@@ -30,6 +33,35 @@ class FakeServicioAudio extends ServicioAudio {
|
|||||||
Emisora? _emisoraActual;
|
Emisora? _emisoraActual;
|
||||||
EstadoReproduccion _estadoActual = EstadoReproduccion.detenido;
|
EstadoReproduccion _estadoActual = EstadoReproduccion.detenido;
|
||||||
|
|
||||||
|
/// Mirrors `PluriWaveAudioHandler._ecualizadorActivo`/`_presetActual`:
|
||||||
|
/// the handler-side EQ state, settable independently of the
|
||||||
|
/// `ServicioAudio`-forwarded methods below so tests can simulate a
|
||||||
|
/// car/notification-initiated change (eq-sync-superficies).
|
||||||
|
bool _ecualizadorActivoValor = true;
|
||||||
|
PresetEcualizador _presetActualValor = PresetEcualizador.flat;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get ecualizadorActivo => _ecualizadorActivoValor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PresetEcualizador get presetActual => _presetActualValor;
|
||||||
|
|
||||||
|
/// Simulates a car/notification-initiated EQ change: mutates the (fake)
|
||||||
|
/// handler's own state directly, the same way
|
||||||
|
/// `PluriWaveAudioHandler.customAction`/`seleccionarPresetEqPorMediaId`
|
||||||
|
/// call `setEcualizadorActivo`/`aplicarPreset` on the handler WITHOUT
|
||||||
|
/// going through `ServicioAudio` — then re-emits the current playback
|
||||||
|
/// state, mirroring `_actualizarControlesEq()`'s unconditional
|
||||||
|
/// `playbackState.add(...)` republish so a resync listener on
|
||||||
|
/// [estadoStream] picks it up. Deliberately does NOT append to
|
||||||
|
/// [cambiosEcualizadorActivo]/[presetsAplicados]: those track calls that
|
||||||
|
/// arrived through the `ServicioAudio`-forwarded (UI-initiated) path.
|
||||||
|
void simularCambioEqDesdeHandler({bool? activo, PresetEcualizador? preset}) {
|
||||||
|
if (activo != null) _ecualizadorActivoValor = activo;
|
||||||
|
if (preset != null) _presetActualValor = preset;
|
||||||
|
emitirEstado(_estadoActual);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void configurarLocalizaciones(AppLocalizations l10n) {
|
void configurarLocalizaciones(AppLocalizations l10n) {
|
||||||
// No global handler in tests; just record the call.
|
// No global handler in tests; just record the call.
|
||||||
@@ -39,9 +71,6 @@ class FakeServicioAudio extends ServicioAudio {
|
|||||||
@override
|
@override
|
||||||
Emisora? get emisoraActual => _emisoraActual;
|
Emisora? get emisoraActual => _emisoraActual;
|
||||||
|
|
||||||
@override
|
|
||||||
bool get ecualizadorDisponible => ecualizadorActivo;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<EstadoReproduccion> get estadoStream => _estadoController.stream;
|
Stream<EstadoReproduccion> get estadoStream => _estadoController.stream;
|
||||||
|
|
||||||
@@ -105,6 +134,7 @@ class FakeServicioAudio extends ServicioAudio {
|
|||||||
@override
|
@override
|
||||||
Future<void> aplicarPreset(PresetEcualizador preset) async {
|
Future<void> aplicarPreset(PresetEcualizador preset) async {
|
||||||
presetsAplicados.add(preset);
|
presetsAplicados.add(preset);
|
||||||
|
_presetActualValor = preset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -113,6 +143,7 @@ class FakeServicioAudio extends ServicioAudio {
|
|||||||
@override
|
@override
|
||||||
Future<void> setEcualizadorActivo(bool activo) async {
|
Future<void> setEcualizadorActivo(bool activo) async {
|
||||||
cambiosEcualizadorActivo.add(activo);
|
cambiosEcualizadorActivo.add(activo);
|
||||||
|
_ecualizadorActivoValor = activo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -349,6 +380,10 @@ class FakeServicioEcualizador extends ServicioEcualizador {
|
|||||||
ConfiguracionEcualizador _config;
|
ConfiguracionEcualizador _config;
|
||||||
ConfiguracionEcualizador get config => _config;
|
ConfiguracionEcualizador get config => _config;
|
||||||
|
|
||||||
|
/// Number of times [guardarActivo] has been called — lets tests assert a
|
||||||
|
/// persistence write happened exactly once (eq-sync-superficies).
|
||||||
|
int guardarActivoLlamadas = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ConfiguracionEcualizador> cargar() async => _config;
|
Future<ConfiguracionEcualizador> cargar() async => _config;
|
||||||
|
|
||||||
@@ -367,6 +402,7 @@ class FakeServicioEcualizador extends ServicioEcualizador {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> guardarActivo(bool activo) async {
|
Future<void> guardarActivo(bool activo) async {
|
||||||
|
guardarActivoLlamadas++;
|
||||||
_config = ConfiguracionEcualizador(
|
_config = ConfiguracionEcualizador(
|
||||||
principal: _config.principal,
|
principal: _config.principal,
|
||||||
porEmisora: _config.porEmisora,
|
porEmisora: _config.porEmisora,
|
||||||
|
|||||||
@@ -161,6 +161,36 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('usarAnunciosDePruebaEnRelease — interruptor de fase de pruebas', () {
|
||||||
|
test('mientras esta en true, bannerAdUnitId e interstitialAdUnitId son los '
|
||||||
|
'ids oficiales de prueba de Google', () {
|
||||||
|
expect(usarAnunciosDePruebaEnRelease, isTrue);
|
||||||
|
expect(bannerAdUnitId, equals('ca-app-pub-3940256099942544/6300978111'));
|
||||||
|
expect(
|
||||||
|
interstitialAdUnitId,
|
||||||
|
equals('ca-app-pub-3940256099942544/1033173712'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('los ids reales siguen presentes como constantes en el archivo (no se '
|
||||||
|
'pueden perder en un futuro edit)', () {
|
||||||
|
final source =
|
||||||
|
File('lib/servicios/servicio_anuncios.dart').readAsStringSync();
|
||||||
|
expect(
|
||||||
|
source.contains("'ca-app-pub-6038935671414339/5658618378'"),
|
||||||
|
isTrue,
|
||||||
|
reason: 'el id real del banner debe seguir presente en el archivo',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
source.contains("'ca-app-pub-6038935671414339/4189478248'"),
|
||||||
|
isTrue,
|
||||||
|
reason:
|
||||||
|
'el id real del interstitial debe seguir presente en el '
|
||||||
|
'archivo',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('debeMostrarBanner', () {
|
group('debeMostrarBanner', () {
|
||||||
test('free: true', () {
|
test('free: true', () {
|
||||||
final servicio = construir(
|
final servicio = construir(
|
||||||
|
|||||||
Reference in New Issue
Block a user