import 'dart:async'; import 'package:flutter/material.dart'; import 'dart:io' show Platform; import 'package:audio_service/audio_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'database/db_helper.dart'; import 'services/favoriten_service.dart'; import 'services/auth_service.dart'; import 'services/cloud_service.dart'; import 'services/melo_logger.dart'; import 'services/audio_handler.dart'; import 'services/musik_scanner.dart'; import 'utils/farb_theme.dart'; import 'screens/home_screen.dart'; import 'screens/login_screen.dart'; /// Globaler Notification-Plugin (wird in main() initialisiert) final FlutterLocalNotificationsPlugin notificationsPlugin = FlutterLocalNotificationsPlugin(); /// Globaler Timer für periodischen Auto-Scan (wird in MeloAppState.dispose gecancelt) Timer? _autoScanTimer; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Logger startet sofort – zeichnet ALLES auf MeloLogger().init('2.35'); // ── Notifications initialisieren (Issue #9) ── const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher'); const initSettings = InitializationSettings(android: androidInit); await notificationsPlugin.initialize( settings: initSettings, ); // Notification-Channel für Downloads (Fortschritt + Abschluss) const downloadChannel = AndroidNotificationChannel( 'de.baka.melo.downloads', 'Melo Downloads', description: 'Download-Fortschritt und Abschluss', importance: Importance.low, playSound: false, enableVibration: false, ); await notificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(downloadChannel); // ── Background Service initialisieren (Issue #3) ── await FlutterBackgroundService().configure( iosConfiguration: IosConfiguration(), androidConfiguration: AndroidConfiguration( onStart: _onServiceStart, autoStart: false, isForegroundMode: true, ), ); try { await DbHelper().db; await FavoritenService().init(); // Hintergrund-Audio-Service starten await AudioService.init( builder: () => MeloAudioHandler(), config: const AudioServiceConfig( androidNotificationChannelId: 'de.baka.melo.audio', androidNotificationChannelName: 'Melo Wiedergabe', androidStopForegroundOnPause: false, ), ); MeloLogger().zustand('start_ok', {'db': 'ok', 'audio': 'ok'}); } catch (e, stack) { MeloLogger().fehler('App-Start', e, stack); } // Auth initialisieren (Token aus SharedPreferences laden + online validieren) await AuthService().initialisieren(); // Android-Berechtigungen beim Start abfragen (Issue #4) // Speicherzugriff für Musik-Scan (READ_MEDIA_AUDIO / Storage) await MusikScanner().frageSpeicherZugriff(); // Auto-Scan: Musikordner beim Start scannen + periodisch alle 3h (Issue #6) _starteAutoScan(); // POST_NOTIFICATIONS für Android 13+ (Notifications für Downloads, Player) if (Platform.isAndroid) { await Permission.notification.request(); } // Auto-Sync beim Start (wenn eingeloggt und Auto-Sync aktiv) _starteAutoSyncFallsNoetig(); runApp(const MeloApp()); } /// Führt Musik-Scan beim Start aus + startet Timer.periodic alle 3 Stunden (Issue #6) void _starteAutoScan() { // Sofort beim Start scannen (asynchron, blockiert nicht) MusikScanner().scanneMusikOrdner().then((songs) { MeloLogger().zustand('auto_scan_start', {'gefunden': songs.length}); }).catchError((e) { MeloLogger().fehler('auto_scan_start', e); }); // Periodischer Scan alle 3 Stunden – Timer wird in MeloAppState.dispose() gecancelt _autoScanTimer = Timer.periodic(const Duration(hours: 3), (_) { MusikScanner().scanneMusikOrdner().then((songs) { MeloLogger().zustand('auto_scan_periodisch', {'gefunden': songs.length}); }).catchError((e) { MeloLogger().fehler('auto_scan_periodisch', e); }); }); } /// Führt Cloud-Auto-Sync beim App-Start aus, falls konfiguriert Future _starteAutoSyncFallsNoetig() async { try { final auth = AuthService(); if (!auth.istEingeloggt) return; // Prüfen, ob Auto-Sync aktiviert ist final SharedPreferences prefs = await SharedPreferences.getInstance(); final autoSync = prefs.getBool('cloud_auto') ?? true; if (!autoSync) return; final cloud = CloudService(); final ok = await cloud.login(auth.benutzer); if (!ok) return; // Letzten Sync prüfen — nur syncen wenn nötig final lastSync = prefs.getString('cloud_last_sync_ts'); final now = DateTime.now(); if (lastSync != null) { final last = DateTime.tryParse(lastSync); if (last != null) { final interval = prefs.getInt('cloud_interval') ?? 6; if (now.difference(last).inHours < interval) { return; // Noch nicht fällig } } } // Sync ausführen MeloLogger().aktion('auto_sync_start', {}); final st = await cloud.status(); if (st != null) { final serverSongs = await cloud.listSongs(); MeloLogger().aktion('auto_sync_done', {'songs': serverSongs.length}); } await prefs.setString('cloud_last_sync_ts', now.toIso8601String()); } catch (e) { MeloLogger().fehler('auto_sync_init', e); } } /// Background Service Callback – hält App im Vordergrund beim Download (Issue #3) @pragma('vm:entry-point') Future _onServiceStart(ServiceInstance service) async { WidgetsFlutterBinding.ensureInitialized(); // Service läuft als Foreground-Service und hält den Prozess am Leben. // Der eigentliche Download läuft im Haupt-Isolate – der Service dient nur // als WakeLock / Foreground-Garantie. service.on('stopService').listen((_) { service.stopSelf(); }); return true; } class MeloApp extends StatefulWidget { const MeloApp({super.key}); @override State createState() => _MeloAppState(); } class _MeloAppState extends State { @override Widget build(BuildContext context) { final auth = AuthService(); return MaterialApp( title: 'Melo', debugShowCheckedModeBanner: false, theme: MeloTheme.theme, home: auth.istEingeloggt ? const Scaffold(body: MeloHome()) : const LoginScreen(), ); } @override void dispose() { _autoScanTimer?.cancel(); _autoScanTimer = null; super.dispose(); } }