diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 5eb7f1b..72e7392 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -109,19 +109,21 @@ class _SettingsScreenState extends State { await p.setString('sync_modus', modus); // Realtime-Service entsprechend starten/stoppen + final rs = RealtimeSyncService(); switch (modus) { case 'realtime': // SSE sofort starten - final rs = RealtimeSyncService(); await rs.starteWennAktiviert(); break; case 'manual': - // Kein Auto-Sync — Timer stoppen + // Kein Auto-Sync — SSE stoppen, Timer deaktivieren + rs.stoppe(); await p.setInt('cloud_interval', 0); // alten Timer deaktivieren await SyncService.starteAutoSyncTimer(); break; case 'interval': - // Intervall-Timer mit cloud_interval_stunden starten + // Intervall-Timer — SSE stoppen, Timer mit cloud_interval_stunden starten + rs.stoppe(); await p.setInt('cloud_interval', _cloudIntervallStunden); await SyncService.starteAutoSyncTimer(); break; diff --git a/lib/services/realtime_sync_service.dart b/lib/services/realtime_sync_service.dart index 98f68bf..97b2639 100644 --- a/lib/services/realtime_sync_service.dart +++ b/lib/services/realtime_sync_service.dart @@ -33,8 +33,13 @@ enum SyncModus { /// **Lifecycle** (via WidgetsBindingObserver in main.dart): /// - resumed → fortsetzen() (SSE neu verbinden) /// - paused/hidden → pausiere() (Batterie sparen) +/// +/// **Singleton**: Nur EINE Instanz pro App-Lebenszyklus, damit +/// home_screen.dart und settings_screen.dart dieselbe Verbindung teilen. class RealtimeSyncService { - RealtimeSyncService() + static final RealtimeSyncService _instanz = RealtimeSyncService._(); + factory RealtimeSyncService() => _instanz; + RealtimeSyncService._() : _db = DbHelper(), _auth = AuthService(), _favoriten = FavoritenService(); @@ -123,6 +128,9 @@ class RealtimeSyncService { // Fallback: sync_modus auf interval setzen + Auto-Sync-Timer starten final p = await SharedPreferences.getInstance(); await p.setString('sync_modus', 'interval'); + // cloud_interval setzen, damit starteAutoSyncTimer() den Timer startet + final intervallStunden = await cloudIntervallStunden(); + await p.setInt('cloud_interval', intervallStunden); await SyncService.starteAutoSyncTimer(); stoppe(); return; @@ -164,6 +172,7 @@ class RealtimeSyncService { final token = _auth.token; if (token == null || token.isEmpty) { _fehlerBericht('Kein Auth-Token'); + _laeuft = false; return; } @@ -179,6 +188,14 @@ class RealtimeSyncService { _sseResponse = await request.close(); + // Nach await prüfen: pausiere() könnte während des Connects + // aufgerufen worden sein (WidgetsBindingObserver-Race). + if (_pausiert) { + _sseResponse = null; + _laeuft = false; + return; + } + if (_sseResponse!.statusCode != 200) { _fehlerBericht('HTTP ${_sseResponse!.statusCode}'); await _reconnecteOderFallback(); @@ -249,7 +266,8 @@ class RealtimeSyncService { MeloLogger().aktion('realtime_event', { 'type': eventTyp, - 'data': daten, + // Daten nicht loggen — könnten sensitive User-Informationen enthalten + // (Song-IDs, Titel, Künstler). Nur Event-Typ wird geloggt. }); switch (eventTyp) { @@ -391,6 +409,10 @@ class RealtimeSyncService { MeloLogger().aktion('realtime_fallback_to_interval', {}); final p = await SharedPreferences.getInstance(); await p.setString('sync_modus', 'interval'); + // cloud_interval (alter Key) auf Default 48h setzen, damit + // SyncService.starteAutoSyncTimer() den Timer auch wirklich startet. + final intervallStunden = await cloudIntervallStunden(); + await p.setInt('cloud_interval', intervallStunden); await SyncService.starteAutoSyncTimer(); } } @@ -435,6 +457,12 @@ class RealtimeSyncService { if (!_pausiert) return; _pausiert = false; await starteWennAktiviert(); + // Nach await prüfen: pausiere() könnte während starteWennAktiviert() + // aufgerufen worden sein (z.B. schnelles App-Umschalten). + // Falls ja, Verbindung sofort wieder trennen. + if (_pausiert) { + stoppe(); + } } /// Stoppt die SSE-Verbindung endgültig (z. B. Logout, Modus-Wechsel).