- cloud_screen.dart: Sync-Popup mit Live-Fortschritt („12/325 synchronisiert"),
„Im Hintergrund fortsetzen"-Button, Ergebnis-Snackbar nach Abschluss
- sync_service.dart: Notification-Kanal auf HIGH + Visibility.public,
Foreground-Service starten/stoppen während Sync (Display-aus-Garantie),
statische syncNotificationDetails() für Tests
- main.dart: Sync-Channel-Importance auf HIGH (überschreibt alte low-Einstellung)
- Tests: +7 (syncNotificationDetails HIGH/public, Lifecycle-Unabhängigkeit)
CRITICAL (C1): RealtimeSyncService jetzt Singleton (Factory-Pattern)
- Verhindert doppelte SSE-Verbindungen durch home_screen + settings
- UI-Notifier (istVerbundenNotifier) app-weit konsistent
HIGH (H1): _reconnecteOderFallback + starteWennAktiviert setzen
cloud_interval vor starteAutoSyncTimer() — sonst bleibt Timer
bei Fallback auf 0 (kein Sync)
HIGH (H2): Race-Condition in _verbinde() nach await request.close()
— _pausiert-Flag re-check; bei true → clean exit mit _laeuft=false
HIGH (H3): fortsetzen() re-checkt _pausiert nach starteWennAktiviert()
— verhindert Leak bei schnellem App-Umschalten
MEDIUM (M1): _syncModusSetzen stoppt SSE bei Wechsel auf manual/interval
(vorher lief die Verbindung weiter)
MEDIUM (M2): Event-Logging loggt nur noch Event-Typ, nicht rohe Daten
(Privacy: Song-IDs/Titel/Künstler nicht im Log)
LOW (L1): _verbinde() setzt _laeuft=false bei Token-Fehler (kein Deadlock)
## MED-1 (Review-Fix)
- sync_service.dart: letzterSync-Snapshot wird VOR listSongs gelesen und am
Sync-Ende persistiert (Sync-BEGINN statt now-am-Ende) — Löschungen, die
zwischen listSongs und Sync-Ende auf dem Server ankommen, werden im
nächsten Sync korrekt angewendet statt als „bereits gesehen“ übersprungen
- cloud_screen.dart: _download schreibt cloud_last_sync_ts nicht mehr
nachträglich mit now-am-Ende (redundant + Race-Wieder-Einführung);
UI-Anzeige liest den von SyncService persistierten Snapshot
- melo_cloud.py: handle_delete stempelt deleted_at IMMER mit Server-Zeit
(datetime('now')) — Client-Timestamps werden ignoriert (Uhr-Drift würde
Löschungen dauerhaft überspringen lassen; konsistente Uhr für alle Geräte)
## Chip-Puls (home_screen)
- ListenableBuilder hört auf Listenable.merge([_vm, _cloud, SyncService.laeuftNotifier]) — der ☁️-Musikserver-Chip pulsiert jetzt bei laufendem Sync (auch im Hintergrund/Auto-Sync), nicht nur beim Verbinden/Laden
- serverSyncLaeuft = SyncService.laeuftNotifier.value || CloudStatus.verbinde || serverLadt
## Auto-Sync-Timer app-weit (main.dart)
- _starteAutoSyncFallsNoetig() entfernt (F3a: kein Sync beim App-Start); stattdessen SyncService.starteAutoSyncTimer() — Timer tickt erst nach dem Intervall, Fälligkeit via istSyncFaellig
- shared_preferences-Import aus main.dart entfernt (Intervall-Logik lebt in SyncService/CloudScreen)
## Sync-Notification
- Sync-Kanal de.baka.melo.sync wird in main() angelegt (Android 8+: Channel nötig, sonst erscheint die Notification nicht)
- onDidReceiveNotificationResponse: Tipp auf 'sync_fertig'/'sync_fehler' → SyncService.syncBenachrichtigungGetippt++ → MeloHome öffnet den Cloud-Tab
## Version
- Versionsanzeige 2.51 → 2.52.3 (settings/home/erweitert), MeloLogger.init('2.35') → '2.52.3'
## MED-3 (cloud_screen.dart)
- Konflikterkennung prüft jetzt Titel ODER Künstler (case-insensitiv)
- Dialog dismissable (Tap außerhalb = Abbruch → lokale Version behalten,
Sync läuft weiter — kein Hängenbleiben)
- Batch-Optionen: Checkbox „Für alle weiteren Konflikte übernehmen“
(_konfliktBatch) + Button „Alle weiteren überspringen“
- Auto-Sync (_autoSyncDurchfuehren) löst Konflikte NICHT interaktiv:
Server-Metadaten gewinnen still (automatisch: true)
## MED-4 (cloud_screen.dart)
- Neuer echter Sync-Guard _syncProzessLaeuft, getrennt vom UI-Flag _syncLaeuft
- „Im Hintergrund fortsetzen“ setzt nur noch _syncLaeuft zurück — der Guard
bleibt bis zum echten Loop-Ende (finally) gesperrt → kein Doppel-Sync
- _download/_autoSyncDurchfuehren prüfen _syncProzessLaeuft; „Jetzt Syncen“
während Hintergrund-Sync zeigt Hinweis
- finally gibt den Guard immer frei + AnimationController-stop try/catch
(Screen kann während Hintergrund-Sync disposed sein)
## LOW (cloud_screen.dart)
- cloud_last_sync/_ts werden nur noch bei erfolgreichem Sync geschrieben
(_syncAlles returned bool; Fehler/Guard → kein Schreiben)
- _syncedItems-Zähler konsistent (Server-Übernahmen/Downloads je ++ statt
Überschreiben)
## MED-1 (settings_screen.dart)
- Erweitert-Push wird awaited; Ergebnis 'scanner' wird an MeloHome weitergereicht
- onScan-Closure poppt nur noch den Erweitert-Screen, SettingsScreen propagiert
das Ergebnis per eigenem Navigator.pop (mit State-Context + mounted-Guard)
## Cloud-Dashboard (cloud_screen.dart)
- Statistik-Karten 2x2: Songs, Playlisten, Favoriten, Letzter Sync (Daten aus statusDaten/syncStatus)
- Speicheranzeige mit Balken — Platzhalter „–" wenn der Server keine Storage-Daten liefert (storage_used/storage_total o.ä.)
- Sync-Historie-Zeile: „Heute: X Dateien, Y Favoriten, Z Playlisten" aus _syncHistorie (heute gefiltert)
- Konflikt-Dialog NUR bei abweichendem Titel (lokal ≠ Server), sonst kein Dialog
- Lint-Fixes: curly_braces (if ohne Block) + unused _syncHistorieHeuteText (jetzt in der Historie-Zeile verdrahtet)
- Sync-Animation während des Syncs: rotierendes ☁️-Icon + Fortschrittsring (CloudStatus/AnimationController)
Keine neuen Packages, flutter analyze 0 Issues, Tests 98/98 grün
## Fix 1 (MED): Glow-Pulsation rebuildet jetzt wirklich
- now_playing_screen.dart: Cover-Container mit AnimatedBuilder(animation: _glowController) gewrappt — BoxShadow (blur 50–70 / spread 6–12) wird jetzt pro Animations-Tick neu gebaut statt nur bei fremden setStates (Pulsation war vorher nur zufällig über positionStream sichtbar und fror bei Pause/Pufferung ein)
## Fix 2 (MED): Echter Crossfade beim Songwechsel
- _songSub-Listener setzt _hatCover/_coverPfad jetzt SYNCHRON auf false/null (im selben setState) — Hintergrund und Front-Cover zeigen beim Songwechsel kein altes Cover mehr, bis die async Existenz-Prüfung durch ist
- _hintergrundBlur-Key auf den tatsächlichen Cover-Pfad gelegt ('cover-$pfad' statt song.id) — Switcher wechselt erst bei geladenem neuem Cover (Crossfade Gradient→Cover), kein Fade alt→alt + harter Cut mehr
flutter analyze: 0 Issues, Tests 98/98 grün, keine neuen Packages
## Fullscreen-Player (now_playing_screen.dart)
- Glow-Farbe folgt MeloTheme.akzentNotifier (Per-User-Akzent) statt fixem Rot; Listener rebuildet bei Akzentwechsel
- Dynamische Glow-Pulsation: AnimationController (Bordmittel) pulsiert blurRadius 50–70 / spreadRadius 6–12 nur bei Wiedergabe, stillstehend bei Pause
- Blur-Hintergrund crossfaded beim Songwechsel (AnimatedSwitcher 600ms, layoutBuilder mit StackFit.expand, ValueKey je Song)
- existsSync aus dem Build-Pfad entfernt: async _ladeCoverStatus mit try/catch, Guard gegen stale Song-Wechsel
## Fix (Code-Review Finding 2, HIGH)
- DbHelper.songZurPlaylist: INSERT OR IGNORE (ConflictAlgorithm.ignore) —
Doppel-Tap-Race verletzt PRIMARY KEY (playlist_id, song_id) nicht mehr
- FavoritenService.umschalten: gibt neuen Status zurück, try/catch mit
MeloLogger, Zustand nach Fehler aus DB neu geladen statt blind geflippt
- NowPlayingScreen._toggleFavorit: In-Flight-Guard _toggleLaeuft gegen
parallele Toggles + Statusübernahme aus DB (Quelle der Wahrheit)
## CloudService
- CloudStatus-Enum (verbinde/verbunden/fehler), CloudService extends ChangeNotifier
- verbinde(): Login+Status beim App-Start, setzt Status, notifyListeners
- Getter status/statusText/istVerbunden/verbindungGestartet
- Bestehende Methoden behalten — nur status() → statusDaten() umbenannt (Namenskonflikt mit neuem status-Getter)
## Home-Screen
- _cloud.verbinde() in initState (feuern-und-vergessen) — Verbindung steht beim App-Start, nicht erst beim Tab-Öffnen
## Cloud-Screen
- initState verbindet nicht mehr selbst — zeigt Service-Status via ListenableBuilder
- Status wechselt live: 'Verbinde...' (gelb) → 'Verbunden' (grün) / 'Keine Verbindung' (rot)
- Serverdaten (Playlists/Favoriten/Zähler) laden automatisch, sobald Service verbunden
- Refresh-Button re-verbindet + lädt Zähler neu
- Fallback: ungestarteter Service (Einstellungen-Screen) wird beim Öffnen gestartet
## Neuer Recap-Screen
- lib/screens/recap_screen.dart: Full-Screen mit 7 animierten Seiten (Intro, Top 5 Songs, Top 3 Künstler, Hörzeit, häufigster Tag, Nachtaktive %, Gesamtzahl + Teilen)
- Wrapped-Stil: Verlaufs-Gradienten, große Zahlen mit Count-up-Animation, Fade+Scale-Seitenwechsel, Fortschritts-Punkte, Weiter-Button
- Teilen-Button kopiert Recap-Text in die Zwischenablage (kein neues Package)
## Datenbasis
- lib/services/recap_service.dart: aggregiert lokale Abspiel-Historie zu Top-5-Songs, Top-3-Künstler, Hörzeit, Wochentag, Nacht-%, Gesamtzahl
- Fallback auf Gesamt-Historie wenn im Zieljahr keine Daten; freundlicher Leer-Zustand ohne History
## DB + Tracking
- db_helper.dart: Migration v6→v7 mit neuer Tabelle abspiel_historie (song_id, played_at) + Backfill aus wiedergabe_verlauf
- player_service.dart: registriert jeden Abspielvorgang in der Historie
## Einstieg
- Home-Screen: Karte 'Jahres-Recap 2026' unter der Statistik-Card
- Einstellungen: Kachel 'Jahres-Recap' in Sektion Daten & Privatsphäre
flutter analyze: 0 Issues, Tests 4/4
- Profil-Dialog: 'Cloud aktivieren' erscheint im Lokal-Modus -> setzt Modus + Login
- Abmelden loescht jetzt wirklich das Token (CloudService.logout)
- _zeigeProfil liest Modus vor dem Dialog
- Erster Start: Dialog 'Cloud (empfohlen)' oder 'Nur lokal' -> wird gespeichert
- Cloud gewaehlt: direkt Login-Dialog (Name + Passwort)
- Lokal gewaehlt: App nutzt nur lokale Musik, kein Login noetig
- Spaeter jederzeit im Cloud-Tab einloggbar