diff --git a/lib/main.dart b/lib/main.dart index f11730e..658892f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ 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'; @@ -13,7 +12,9 @@ import 'services/cloud_service.dart'; import 'services/melo_logger.dart'; import 'services/audio_handler.dart'; import 'services/musik_scanner.dart'; +import 'services/sync_service.dart'; import 'utils/farb_theme.dart'; +import 'utils/user_effekte.dart'; import 'screens/home_screen.dart'; import 'screens/login_screen.dart'; @@ -28,13 +29,20 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); // Logger startet sofort – zeichnet ALLES auf - MeloLogger().init('2.35'); + MeloLogger().init('2.52.3'); // ── Notifications initialisieren (Issue #9) ── const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher'); const initSettings = InitializationSettings(android: androidInit); await notificationsPlugin.initialize( settings: initSettings, + // Tipp auf die Sync-Abschluss-/Fehler-Notification → Cloud-Tab öffnen + onDidReceiveNotificationResponse: (response) { + if (response.payload == 'sync_fertig' || + response.payload == 'sync_fehler') { + SyncService.syncBenachrichtigungGetippt.value++; + } + }, ); // Notification-Channel für Downloads (Fortschritt + Abschluss) @@ -51,6 +59,20 @@ void main() async { AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(downloadChannel); + // Notification-Channel für den Cloud-Sync (Fortschritt + Abschluss) + const syncChannel = AndroidNotificationChannel( + 'de.baka.melo.sync', + 'Melo Sync', + description: 'Cloud-Sync-Fortschritt und Abschluss', + importance: Importance.low, + playSound: false, + enableVibration: false, + ); + await notificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(syncChannel); + // ── Background Service initialisieren (Issue #3) ── await FlutterBackgroundService().configure( iosConfiguration: IosConfiguration(), @@ -108,8 +130,13 @@ void main() async { await Permission.notification.request(); } - // Auto-Sync beim Start (wenn eingeloggt und Auto-Sync aktiv) - _starteAutoSyncFallsNoetig(); + // Per-User-Akzent (überschreibbar in den Einstellungen) + Begrüßungs-Sound + // beim App-Start (wenn Token vorhanden). Fehler sind unkritisch. + await UserEffekt.anwenden(AuthService().benutzer); + + // App-weiter Auto-Sync-Timer (F3): Intervall aus `cloud_interval` + // (0 = Aus), erster Tick erst NACH dem Intervall — kein Sync beim Start. + unawaited(SyncService.starteAutoSyncTimer()); runApp(const MeloApp()); } @@ -134,50 +161,6 @@ void _starteAutoScan() { }); } -/// 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.statusDaten(); - 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 { diff --git a/lib/screens/erweitert_screen.dart b/lib/screens/erweitert_screen.dart index d3963ae..6a504b1 100644 --- a/lib/screens/erweitert_screen.dart +++ b/lib/screens/erweitert_screen.dart @@ -419,7 +419,7 @@ class _EntwicklerScreenState extends State { padding: const EdgeInsets.all(16), children: [ _infoKarte([ - ('Version', 'v2.51'), + ('Version', 'v2.52.3'), ('Session', MeloLogger().sessionId.isEmpty ? '—' : MeloLogger().sessionId), ('Songs in DB', _songAnzahl < 0 ? '…' : '$_songAnzahl'), ('Auth-Server', AppConfig.authUrl), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index de30516..f79af46 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -20,6 +20,7 @@ import 'erweitert_screen.dart'; import '../services/cloud_service.dart'; import '../services/player_service.dart'; import '../services/auth_service.dart'; +import '../services/sync_service.dart'; import '../config/app_config.dart'; class MeloHome extends StatefulWidget { @@ -54,14 +55,23 @@ class _MeloHomeState extends State { }); // Cloud-Verbindung beim App-Start herstellen (feuern-und-vergessen). _cloud.verbinde(); + // Tipp auf die Sync-Abschluss-/Fehler-Notification → Cloud-Tab öffnen + SyncService.syncBenachrichtigungGetippt.addListener(_syncNotifGetippt); } @override void dispose() { + SyncService.syncBenachrichtigungGetippt.removeListener(_syncNotifGetippt); _vm.dispose(); super.dispose(); } + /// Öffnet den Cloud-Tab, wenn die Sync-Notification getippt wurde. + void _syncNotifGetippt() { + if (!mounted) return; + setState(() => _aktiverTab = 3); + } + // ─── Navigation ───────────────────────────────────── /// Öffnet den Now-Playing-Fullscreen mit Slide-up-Animation @@ -415,7 +425,9 @@ class _MeloHomeState extends State { @override Widget build(BuildContext context) { return ListenableBuilder( - listenable: _vm, + // _vm (Songs/Status), _cloud (Verbindung) UND SyncService.laeuftNotifier + // (☁️-Chip pulsiert bei laufendem Sync, auch im Hintergrund) + listenable: Listenable.merge([_vm, _cloud, SyncService.laeuftNotifier]), builder: (_, _) { if (_vm.ladt) { return const Scaffold( @@ -454,7 +466,11 @@ class _MeloHomeState extends State { onSearch: _zeigeSuche, onServer: _zeigeMusikserverDetail, serverVerbunden: _vm.navidrome.istVerbunden, - serverSyncLaeuft: _cloud.status == CloudStatus.verbinde || _vm.serverLadt, + // Chip pulsiert während des Syncs (SyncService-Läuft-Signal), + // beim Verbinden und solange der Server lädt. + serverSyncLaeuft: SyncService.laeuftNotifier.value || + _cloud.status == CloudStatus.verbinde || + _vm.serverLadt, ), Expanded( child: ListView( @@ -1059,7 +1075,7 @@ class _MeloHomeState extends State { ), child: Column( children: [ - _infoZeile('Version', '2.51'), + _infoZeile('Version', '2.52.3'), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Theme', 'Schwarz + Rot'), const Divider(color: MeloTheme.dunkel2, height: 1), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 5dd8596..9797ed4 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -138,7 +138,7 @@ class _SettingsScreenState extends State { ), child: Column( children: [ - _infoZeile('Version', '2.51'), + _infoZeile('Version', '2.52.3'), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Theme', 'Schwarz + Rot'), const Divider(color: MeloTheme.dunkel2, height: 1), diff --git a/lib/services/sync_service.dart b/lib/services/sync_service.dart index 4707ea9..5a6381a 100644 --- a/lib/services/sync_service.dart +++ b/lib/services/sync_service.dart @@ -60,6 +60,12 @@ class SyncService { /// ValueNotifier, damit der Home-Screen (ListenableBuilder) mitrebuildet. static final ValueNotifier laeuftNotifier = ValueNotifier(false); + /// Wird inkrementiert, wenn die Sync-Abschluss-/Fehler-Notification getippt + /// wird — MeloHome hört darauf und öffnet den Cloud-Tab (main.dart + /// verdrahtet den Tipp via `onDidReceiveNotificationResponse`). + static final ValueNotifier syncBenachrichtigungGetippt = + ValueNotifier(0); + // ─── Auto-Sync-Timer (app-weit, F3) ─── static Timer? _autoSyncTimer;