Songtext aus Datei-Tags, Warteschlangen-Aktionen, Kategorie-Cover im

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
This commit is contained in:
2026-08-20 18:41:36 +02:00
co-authored by Claude Opus 5
parent 1325badf90
commit 4ebd0a8dd4
17 changed files with 534 additions and 46 deletions
+23 -1
View File
@@ -30,6 +30,14 @@ class Songs extends Table {
/// ein erneuter Scan sie nicht mehr mit dem Genre-Tag der Datei.
BoolColumn get categoriesEdited => boolean().withDefault(const Constant(false))();
/// Songtext aus dem Tag der Datei — Grundlage für den automatischen
/// Songtext ohne Server.
TextColumn get lyrics => text().nullable()();
/// ReplayGain des Titels in Dezibel, sofern die Datei den Tag mitbringt —
/// Grundlage für "Gleiche Lautstärke".
RealColumn get gainDb => real().nullable()();
@override
Set<Column> get primaryKey => {id};
}
@@ -112,7 +120,7 @@ class MeloDb extends _$MeloDb {
MeloDb([QueryExecutor? executor]) : super(executor ?? _open());
@override
int get schemaVersion => 4;
int get schemaVersion => 6;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -131,6 +139,12 @@ class MeloDb extends _$MeloDb {
await m.addColumn(songs, songs.categoriesEdited);
await m.createTable(songCategories);
}
if (from < 5) {
await m.addColumn(songs, songs.lyrics);
}
if (from < 6) {
await m.addColumn(songs, songs.gainDb);
}
},
);
@@ -324,6 +338,14 @@ class MeloDb extends _$MeloDb {
return query.watch().map((rows) => rows.map((r) => r.readTable(songs)).toList());
}
/// Songtext eines Songs, sofern beim Scan einer im Tag gefunden wurde.
Future<String?> lyricsOf(String songId) async {
final row = await (select(songs)..where((s) => s.id.equals(songId)))
.getSingleOrNull();
final text = row?.lyrics?.trim();
return (text == null || text.isEmpty) ? null : text;
}
// === Kategorien ===
/// Alle Kategorien-Zuordnungen, nach Song gebündelt und in gespeicherter
/// Reihenfolge — die UI braucht sie immer für die ganze sichtbare Liste.