Files
pluriwave/lib/main.dart
T
FreeTLab 8f2bf2bdd6
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m28s
feat(notifications): add branded monochrome icon and color to all notifications
Replace generic system icons (info bubble, stock alarm clock) with a
custom equalizer-bars vector drawable across all 4 notification
builders: pre-notice, snooze countdown, ringing alarm, and the audio
player. Apply the app's cyan brand color to the 3 alarm notifications
that previously had none. Audio notification now explicitly declares
its icon instead of falling back to the full-color launcher icon,
which Android was auto-silhouetting into an illegible status-bar
blob.
2026-07-02 18:53:33 +02:00

110 lines
3.3 KiB
Dart

import 'dart:async';
import 'dart:ui' as ui;
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'app.dart';
import 'servicios/servicio_audio.dart';
import 'servicios/servicio_audio_session.dart';
import 'tema/pluriwave_tokens.dart';
const _anchoMinimoLandscape = 600.0;
/// Branded monochrome status-bar icon, replacing the default full-color
/// launcher silhouette fallback.
const androidNotificationIconResource = 'drawable/ic_stat_pluriwave';
/// S5-R8: media notification accent uses the brand color, not the M3
/// default purple. Top-level const so tests can assert it.
const configuracionAudioService = AudioServiceConfig(
androidNotificationChannelId: 'es.freetimelab.pluriwave.audio',
androidNotificationChannelName: 'PluriWave Radio',
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
notificationColor: PluriWaveTokens.brand,
androidNotificationIcon: androidNotificationIconResource,
);
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await _aplicarPoliticaOrientacion();
// S3-R4: single SharedPreferences instance resolved once at startup and
// injected into every state/service below.
final prefs = await SharedPreferences.getInstance();
final handler = await AudioService.init(
builder: () => PluriWaveAudioHandler(),
config: configuracionAudioService,
);
registrarHandler(handler);
// S3-R1: audio focus — phone calls / transient losses pause or duck the
// radio; headphones unplugged pauses it.
final sesionAudio = ServicioAudioSession(objetivo: handler);
unawaited(sesionAudio.configurar());
runApp(_OrientacionResponsiveApp(child: PluriWaveApp(prefs: prefs)));
}
Future<void> _aplicarPoliticaOrientacion([ui.Display? display]) async {
final vista =
WidgetsBinding.instance.platformDispatcher.views.isNotEmpty
? WidgetsBinding.instance.platformDispatcher.views.first
: null;
final displayActivo = display ?? vista?.display;
if (displayActivo == null) return;
final anchoLogico = displayActivo.size.width / displayActivo.devicePixelRatio;
if (anchoLogico < _anchoMinimoLandscape) {
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return;
}
await SystemChrome.setPreferredOrientations(DeviceOrientation.values);
}
class _OrientacionResponsiveApp extends StatefulWidget {
const _OrientacionResponsiveApp({required this.child});
final Widget child;
@override
State<_OrientacionResponsiveApp> createState() =>
_OrientacionResponsiveAppState();
}
class _OrientacionResponsiveAppState extends State<_OrientacionResponsiveApp>
with WidgetsBindingObserver {
ui.Display? _display;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_display = View.maybeOf(context)?.display;
unawaited(_aplicarPoliticaOrientacion(_display));
}
@override
void didChangeMetrics() {
unawaited(_aplicarPoliticaOrientacion(_display));
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}