feat(audio): audio session integration and runtime robustness
- Integrate audio_session (new servicio_audio_session.dart): incoming calls pause the radio and resume on end, headphone unplug pauses without auto-resume, permanent focus loss never auto-resumes, duck lowers volume - Add play-intent flag to ServicioAudio so interruption handling and future reconnect logic can distinguish user pause from system-driven stops - Eliminate read-modify-write race in ServicioAlarmas with an in-memory cache and single-writer queue across all mutations; recalcularTodas persists only when state actually changed - Convert ServicioAlarmasAndroid static StreamController/handler to injectable instance fields, restoring test isolation - Inject a single cached SharedPreferences from main.dart across services and state (removes 23 inline getInstance() calls) - Move configurarLocalizaciones out of MiniReproductor.build() (was running on every rebuild during playback) - Bound the alarm fire-dedup set (cap 200 entries, 24h pruning) - 12 new tests (89 total green), flutter analyze clean
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/servicios/servicio_alarmas_android.dart';
|
||||
|
||||
/// S3-R2: the event controller and handler flag must be instance state so
|
||||
/// two bridges created independently never share events.
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
Future<void> emitirAlarmFired(String canal, Map<String, Object?> payload) {
|
||||
final mensaje = const StandardMethodCodec().encodeMethodCall(
|
||||
MethodCall('alarmFired', payload),
|
||||
);
|
||||
return TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.handlePlatformMessage(canal, mensaje, (_) {});
|
||||
}
|
||||
|
||||
test('dos instancias no comparten el stream de eventos (S3-R2-A)', () async {
|
||||
final servicioA = ServicioAlarmasAndroid(
|
||||
channel: const MethodChannel('pluriwave/alarm_scheduler_test_a'),
|
||||
);
|
||||
final servicioB = ServicioAlarmasAndroid(
|
||||
channel: const MethodChannel('pluriwave/alarm_scheduler_test_b'),
|
||||
);
|
||||
|
||||
final eventosA = <EventoAlarmaAndroid>[];
|
||||
final eventosB = <EventoAlarmaAndroid>[];
|
||||
final subA = servicioA.eventosAlarma.listen(eventosA.add);
|
||||
final subB = servicioB.eventosAlarma.listen(eventosB.add);
|
||||
addTearDown(subA.cancel);
|
||||
addTearDown(subB.cancel);
|
||||
|
||||
await emitirAlarmFired('pluriwave/alarm_scheduler_test_a', {
|
||||
'alarmId': 'solo-a',
|
||||
'alarmTitle': 'Alarma A',
|
||||
'alarmAction': 'es.freetimelab.pluriwave.ALARM_FIRE',
|
||||
});
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(eventosA.map((e) => e.alarmaId), ['solo-a']);
|
||||
expect(eventosB, isEmpty, reason: 'B no debe ver los eventos de A');
|
||||
|
||||
await emitirAlarmFired('pluriwave/alarm_scheduler_test_b', {
|
||||
'alarmId': 'solo-b',
|
||||
'alarmTitle': 'Alarma B',
|
||||
'alarmAction': 'es.freetimelab.pluriwave.ALARM_FIRE',
|
||||
});
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(eventosA.map((e) => e.alarmaId), ['solo-a']);
|
||||
expect(eventosB.map((e) => e.alarmaId), ['solo-b']);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user