Auf Wunsch von Dustin: seine beste Freundin sortiert ihre Sammlung ueber
das Album-Feld. In Melo ist der Album-Titel deshalb ab jetzt die Kategorie
— sie muss nichts neu machen.
1) Album = Kategorie
- kategorienAusTags(): Album-Titel steht VORN in der Kategorienliste (die
erste Kategorie bestimmt das Coverbild), Genres dahinter.
- Beide Scans (android_scan + scan_service) tragen ihn ein; von Hand
gepflegte Kategorien (categoriesEdited) bleiben unberuehrt.
- DB-Schema 9: einmalige Nachruestung bestehender Bibliotheken, damit das
nicht erst beim naechsten vollstaendigen Scan sichtbar wird (der auf
Android nur laeuft, wenn sich die Dateianzahl aendert).
2) Reiter: 'Songs/Kuenstler/Alben' -> 'Lieder/Kategorie/Kuenstler'
- Neu: library/category_list.dart mit groupByCategory(); Lieder ohne
Kategorie sammeln sich am Ende unter "Ohne Kategorie".
- Entfernt: library/album_list.dart, groupByAlbum(), albumArtistLabel() —
mit dem Alben-Reiter tot geworden. Das Album-FELD bleibt erhalten.
3) YouTube-Downloads ohne Original-Album
- Feld "Kategorie (optional)" im YouTube-Bereich, mit Vorschlaegen aus der
Bibliothek und freier Eingabe.
- ordneDownloadEin() verwirft nach dem Scan das Album-Tag (yt-dlp leitet es
aus Kanal/Playlist ab — als Kategorie waere das Unsinn) und setzt
stattdessen die gewaehlte Kategorie. Beides als "von Hand gesetzt"
markiert, damit der naechste Scan es nicht zurueckholt.
- MeloDb.songByPath() und MeloDb.verwirfAlbum() neu.
- CategoryService.alleNamen: Kategorienamen ohne zusaetzliche Abfrage —
ohne das flackerte die Vorschlagsliste und im Widget-Test blieb ein
Aufraeum-Timer von drift haengen.
295 Tests gruen (19 neue, 1 uebersprungen), flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
BUG 1 — Lieder vom Handy waren nicht abspielbar
("Wiedergabe fehlgeschlagen: (0) Source error" / "Loading interrupted")
Wurzel-Ursache: MeloAudioHandler.loadPlaylist hat jeden Warteschlangen-
Eintrag durch NavidromeService.streamAndCacheToLocal(item.id) geschickt,
sobald Zugangsdaten existierten. item.id ist aber NIE eine Navidrome-Song-ID
— lokal ist es file:///storage/..., beim Server die fertige Stream-Adresse.
Der Server bekam also 'file:///...' als Song-ID, antwortete mit einem Fehler,
und diese Fehlerantwort wurde an just_audio weitergereicht (Source error) —
bzw. als .mp3 in den Cache geschrieben, wodurch der Titel dauerhaft kaputt
blieb.
Zweite Ursache: die Schleife lud die GANZE Warteschlange seriell vorab
herunter (30s Timeout je Titel), bevor setAudioSources lief. Bei hunderten
Titeln startete die Wiedergabe deshalb nie; ein zweiter Tipp brach den
laufenden Ladevorgang ab ("Loading interrupted").
Fix:
- Server-Titel tragen ihre ID in MediaItem.extras['navidromeId'] statt sie
aus der Abspiel-Adresse zu raten. Neue reine Funktionen navidromeIdOf,
songIdOf, nutztServerCache, quelleFuer.
- loadPlaylist baut die Quellen ohne Netzzugriff; Caching des laufenden
Titels im Hintergrund (unawaited).
- Resume/Scrobble nur noch mit der jeweils passenden ID (Server bzw. lokal).
- ladeInCache() ersetzt streamAndCacheToLocal(): .part-Datei, Pruefung des
Inhaltstyps (istAudioAntwort), stabiler Cache-Schluessel ueber die
Song-ID statt der Stream-Adresse (die trug Token+Salt und war je Sitzung
anders — der Cache war nie wiederauffindbar), Client wird geschlossen.
BUG 2 — kein Abgleich zwischen Handy und Server
Neu: services/melo_cloud_service.dart + services/sync_service.dart gegen
cloud.baka-net.de (Bearer-JWT ueber BakaAuth). Server-Titel herunterladen
(offline verfuegbar), eigene Dateien hochladen, Loeschungen in beide
Richtungen (Tombstones), Favoriten und Wiedergabe-Verlauf. Automatisch beim
App-Start und bei Rueckkehr in die App (max. alle 15 Min), plus Knopf unter
Einstellungen -> Geraete-Abgleich. Reine Planungsfunktion planeSync().
Bewusst NICHT ueber Navidrome: die Subsonic-API kennt keinen Upload-
Endpunkt. Navidrome bleibt die Streaming-Bibliothek, die Melo-Cloud ist der
gemeinsame Speicher.
DB-Schema 8: songs.cloud_id verbindet Geraet und Server.
Nebenbei behoben (blockierte Build bzw. Tests):
- database.g.dart war veraltet — das Projekt liess sich nicht uebersetzen.
- metadataEdited wurde nirgends gesetzt/beachtet: von Hand korrigierte
Metadaten wurden vom naechsten Scan ueberschrieben. Jetzt in beiden
Scans respektiert; metadatenUebernahme() setzt die Markierung.
- song_detail_sheet_test.dart haengt beim Oeffnen des Modal-Sheets und
blockierte den gesamten Testlauf — vorerst uebersprungen (TODO im Code);
der Zweck wird von metadaten_uebernahme_test.dart abgedeckt.
Enthaelt ausserdem die bis dahin nicht committete Arbeit der Vorsitzung
(Musikerkennung/ACRCloud, MusicBrainz-Metadaten, MediaStore-Datentraeger).
267 Tests gruen (1 uebersprungen), flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
- AutoScan: beim Zurueckkehren in die App wird die Zahl der Musikdateien
mit der Bibliothek verglichen; nur bei Abweichung laeuft ein Scan, und
hoechstens alle 5 Minuten. Gleich viele Loeschungen wie Neuzugaenge
bleiben unbemerkt - dafuer gibt es den Scan von Hand.
- ServerNeuheiten: meldet neu auf Navidrome aufgetauchte Alben im
Online-Tab. Der erste Abruf meldet bewusst nichts, sonst waere die ganze
Sammlung "neu"; er nutzt die ohnehin geladenen Alben statt eines zweiten
Serveraufrufs.
- Neu: MeloDb.countSongs(), countAndroidMediaStoreSongs().
189 Tests gruen, flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
Untere Navigation folgt jetzt den Referenzbildern:
Meine Musik / Online / Suchen / Favoriten. Der bisherige "Download"-Tab
heisst "Online" und bekommt als Naechstes den YouTube-Downloader.
In "Meine Musik" gibt es Unterreiter (Songs/Kuenstler/Alben) im Pillen-Stil
der Referenz. Sie wechseln nur den Inhalt statt einen Bildschirm zu oeffnen;
die Schnellzugriff-Kacheln "Kuenstler"/"Alben" entfallen dadurch, ebenso die
damit tot gewordenen ArtistsScreen/AlbumsScreen-Huellen.
158 Tests gruen, flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
Sperrbildschirm und ReplayGain
- DB-Schema 6: lyrics + gain_db
- Songtext bevorzugt lokalen Tag, Server nur als Rueckfall; Song-ID-Bug
beim Lyrics-Aufruf behoben
- "Als Naechstes spielen" / "Zur Warteschlange hinzufuegen" ohne Eingriff
in Favoriten oder Wiedergabelisten
- songToMediaItem nimmt eine Cover-Vorgabe: Sperrbildschirm zeigt das
Kategorie-Cover
- ReplayGain: Tags werden gelesen, laute Titel abgesenkt, abschaltbar
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011snPXPafPC5i87o68H14W7
- Meine Musik als Startbildschirm ohne Topbar: Kopfzeile (Einstellungen,
Suche, Musikerkennung), Schnellzugriffe, Shuffle + Sortieren, Liederliste
- Sortierung nach Zuletzt hinzugefuegt / Name (A-Z-#) / Wie oft abgespielt,
auf- und absteigend, pro Liste gespeichert (SortStore)
- Favoriten als eigener Tab mit gleicher Shuffle-/Sortier-Leiste
- Download-Tab uebernimmt den Navidrome-Server-Browser
- DB-Schema 3: playCount, gezaehlt beim Titelstart
- Theme auf #0B0B10 / #c0392b
- Scan-Aktionen aus der entfallenen Topbar jetzt in den Einstellungen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011snPXPafPC5i87o68H14W7
- MeloAudioHandler bekommt MeloDb per Konstruktor injiziert
- Positions-Timer (alle 5s) schreibt db.recordPlayback(songId, positionMs)
während aktiv abgespielt wird (songId aus mediaItem.extras['songId'])
- loadPlaylist liest db.lastPosition() für den Start-Song und seekt vor
dem play() an die gespeicherte Position, statt immer von vorne zu starten
- shouldResumeAt() als reine, testbare Funktion extrahiert: kein Resume bei
Position 0 oder innerhalb der letzten 3s einer bekannten Track-Dauer
- dispose() zum gefahrlosen Abbrechen des Positions-Timers in Tests ohne
vollen App-Lifecycle (super.stop() lässt sich in Widget-Tests wegen der
aktiven playbackState-Pipe nicht gefahrlos aufrufen)
- library_screen_test.dart: MeloAudioHandler-Konstruktion angepasst (db
jetzt Pflichtparameter) + handler.dispose() im Teardown
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Library screen now has a TabBar (Alle / Künstler / Alben) inside a
DefaultTabController; scan/permission/error banners stay above the
TabBarView so they remain visible regardless of the active sub-tab
- New lib/library/song_grouping.dart: pure, unit-tested functions
groupByArtist/groupByAlbum (null artist/album -> "Unbekannt" /
"Unbekanntes Album", alphabetically sorted) plus albumArtistLabel
("Verschiedene Interpreten" when an album mixes artists)
- New lib/library/artist_list.dart + album_list.dart: StreamBuilder on
MeloDb.watchSongs(), grouped client-side, tapping a row pushes a
SongList screen for that artist/album; album rows show a CoverImage
thumbnail from the first song's coverPath
- Existing "Alle" tab content (recently-added strip, full song list,
empty state) preserved unchanged, moved into _AllSongsTab
Genre browsing is intentionally out of scope: the Songs table has no
genre column and neither scanner extracts genre metadata — needs a
schema migration + scanner work in a future task.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
PlaylistDetailScreen zeigt die Songs einer Playlist (watchPlaylistSongs),
erlaubt Entfernen einzelner Songs und Umsortieren per Drag & Drop
(ReorderableListView.builder). Tippen auf einen Song spielt die Playlist
ab dieser Stelle. Leer-Zustand mit Hinweistext.
Da bestehende position-Werte nach Einfügereihenfolge lückenhaft sein
können, schreibt das Umsortieren nicht nur den verschobenen Song um,
sondern vergibt für die gesamte Playlist neue lückenlose Positionen
(0..N-1): MeloDb.reorderAllPlaylistSongs (in einer Transaktion) +
PlaylistService.reorderAll als dünner Wrapper.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- songToMediaItem trägt jetzt die Song-UUID in extras['songId'], damit
Downstream-Code (Favoriten-Button, später Playback-History) den echten
Song wiederfinden kann statt nur die file-URI zu haben
- NowPlayingScreen: Schließen-Button entfernt (AppBar-leading), da der
Screen jetzt als eigener Tab läuft statt gepusht zu werden
- FavoriteButton für den aktuellen Song in die AppBar-Actions eingebaut,
reaktiv über einen verschachtelten StreamBuilder<MediaItem?>; ohne
gültige songId (z.B. nichts spielt) wird kein Button gerendert
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>