UI: Alben sind Kategorien — Reiter Lieder/Kategorie/Kuenstler

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
This commit is contained in:
Hermes (Server)
2026-08-21 09:36:46 +02:00
co-authored by Claude Opus 5
parent cd79213bb3
commit c7a7a7c458
17 changed files with 726 additions and 157 deletions
+35 -7
View File
@@ -48,7 +48,7 @@ Future<void> _insert(
void main() {
setUp(() => SharedPreferences.setMockInitialValues({}));
testWidgets('Meine Musik hat die Unterreiter Songs, Künstler und Alben',
testWidgets('Meine Musik hat die Unterreiter Lieder, Kategorie und Künstler',
(tester) async {
final db = MeloDb(NativeDatabase.memory());
final lib = LibraryService(db);
@@ -60,9 +60,11 @@ void main() {
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
await tester.pumpAndSettle();
expect(find.text('Songs'), findsOneWidget);
expect(find.text('Lieder'), findsOneWidget);
expect(find.text('Kategorie'), findsOneWidget);
expect(find.text('Künstler'), findsOneWidget);
expect(find.text('Alben'), findsOneWidget);
// Der Alben-Reiter ist im Kategorie-Reiter aufgegangen.
expect(find.text('Alben'), findsNothing);
handler.dispose();
await db.close();
@@ -87,14 +89,15 @@ void main() {
// Unterreiter wechseln den Inhalt, sie öffnen keinen neuen Bildschirm —
// Suchfeld und Reiter bleiben sichtbar.
expect(find.text('Titel, Künstler und Alben suchen'), findsOneWidget);
expect(find.text('Songs'), findsOneWidget);
expect(find.text('Lieder'), findsOneWidget);
expect(find.text('Shuffle-Wiedergabe'), findsNothing);
handler.dispose();
await db.close();
});
testWidgets('Unterreiter Alben zeigt die Albenliste', (tester) async {
testWidgets('Unterreiter Kategorie zeigt den Album-Titel als Kategorie',
(tester) async {
final db = MeloDb(NativeDatabase.memory());
final lib = LibraryService(db);
final playlists = PlaylistService(db);
@@ -105,12 +108,37 @@ void main() {
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
await tester.pumpAndSettle();
await tester.tap(find.text('Alben'));
// Das Album ist die Kategorie (siehe kategorienAusTags) — der Titel
// taucht deshalb unter "Roses" auf.
await db.setCategories('1', ['Roses']);
await tester.tap(find.text('Kategorie'));
await tester.pumpAndSettle();
expect(find.text('Roses'), findsOneWidget);
expect(find.text('1 Lieder'), findsOneWidget);
expect(find.text('Titel, Künstler und Alben suchen'), findsOneWidget);
expect(find.text('Songs'), findsOneWidget);
expect(find.text('Lieder'), findsOneWidget);
handler.dispose();
await db.close();
});
testWidgets('Titel ohne Kategorie sammeln sich unter "Ohne Kategorie"',
(tester) async {
final db = MeloDb(NativeDatabase.memory());
final lib = LibraryService(db);
final playlists = PlaylistService(db);
final handler = MeloAudioHandler(db: db);
await _insert(db, id: '1', title: 'Song A');
await tester.pumpWidget(
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
await tester.pumpAndSettle();
await tester.tap(find.text('Kategorie'));
await tester.pumpAndSettle();
expect(find.text('Ohne Kategorie'), findsOneWidget);
handler.dispose();
await db.close();