P3-Liste aus dem UX-Review: Player nimmt Cover-Farbe auf, "Teilen" aus YouTube trägt die Adresse in den Downloader ein, Android Auto blättert durch die Bibliothek, Laufweite der Typografie geschärft, Überschriften vereinheitlicht. 533 Tests grün, flutter analyze ohne Befund.
400 lines
20 KiB
Markdown
400 lines
20 KiB
Markdown
# Design- & Feature-Review: Melo vs. Apple Music / Spotify
|
||
|
||
**Review-ID:** melo_vollwertigkeit_20260823
|
||
**Datum:** 2026-08-23
|
||
**Umfang:** gesamte App (`lib/`, 56 Dateien, 14.721 Zeilen), Android-Manifest, Referenz-Bilder
|
||
**Fokus:** Visual · Usability · Funktionsumfang · Code
|
||
**Werkzeuge:** ui-ux-pro-max (design-system + flutter stack + pro-rules), ui-design:design-review
|
||
|
||
**Verifizierter Ist-Zustand:** `flutter analyze` → *No issues found*. `flutter test` → *328 bestanden, 1 übersprungen* (der bekannte Hänger aus BACKLOG.md).
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
Melo ist technisch überdurchschnittlich sauber: echtes Design-Token-System, WCAG-geprüfte
|
||
Textfarben, Tombstone-fähiges Schema ab Tag 1, hohe Testabdeckung, deutschsprachige
|
||
Erklär-Kommentare an genau den Stellen, wo früher Bugs saßen. Das ist deutlich besser als
|
||
der Durchschnitt eines Hobby-Projekts.
|
||
|
||
Als **vollwertige Musik-App im Sinne von Apple Music ist sie es aber noch nicht.** Es fehlen
|
||
nicht Details, sondern drei tragende Säulen: **die Album-Detailansicht** (siehe die Korrektur
|
||
unter K4), **echtes Offline-Management** und **Cover-Art für Server-Titel**. Dazu kommen zwei stille Fehler, die man
|
||
der App nicht ansieht: der Offline-Schalter tut nichts, und ohne `POST_NOTIFICATIONS`
|
||
erscheint auf Android 13+ keine Wiedergabe-Benachrichtigung.
|
||
|
||
**Gefundene Punkte:** 26 → 5 kritisch · 10 groß · 7 klein · 4 Vorschläge
|
||
|
||
> **Stand 2026-08-23, nach der Umsetzung.** Erledigt: alle fünf P0-Punkte
|
||
> (K1–K5) sowie G1–G10. Details im CHANGELOG.
|
||
>
|
||
> Zwei Teilforderungen aus **G2** sind bewusst **nicht** umgesetzt: den
|
||
> Server-Bereich aus dem Download-Tab zu lösen widerspricht Commit
|
||
> `34d6d8e` („Auf Wunsch von Dustin"), und eine Server-Suche widerspricht
|
||
> der Vorgabe, dass die Suche rein lokal bleibt.
|
||
>
|
||
> Aus **G3** bleibt der schwere Teil offen: Wiedergabezähler, Historie und
|
||
> Kategorien für reine Server-Titel bräuchten eine Zeile in der Bibliothek.
|
||
>
|
||
> **Nachtrag 2026-08-24:** Die **P2-Liste** ist ebenfalls abgearbeitet
|
||
> (Hero-Übergang, Wischgesten, Künstlerseite mit Alben, Reduced Motion
|
||
> app-weit, Querformat/Tablet, Alphabet-Sprung, Mehrfachauswahl).
|
||
>
|
||
> **Nachtrag 2026-08-24 (zweiter):** Auch die **P3-Liste** ist abgearbeitet —
|
||
> Cover-Farbe im Player, Teilen-Intent, Android Auto. Bei V2 (eigene Schrift)
|
||
> hat Dustin entschieden, bei der System-Schrift zu bleiben und stattdessen
|
||
> die Typografie zu schärfen; das ist umgesetzt.
|
||
>
|
||
> Offen bleibt damit **K4** in seiner
|
||
> korrigierten Fassung (Album-Detailansicht **lokal**, mit Tracknummer und
|
||
> Schema-Erweiterung — die Server-Seite davon ist mit G2 erledigt).
|
||
|
||
---
|
||
|
||
## Kritische Punkte (P0)
|
||
|
||
### K1 — Offline-Modus ist ein toter Schalter
|
||
**Ort:** `lib/services/offline_mode.dart` · `lib/settings/settings_screen.dart:190`
|
||
**Kategorie:** Usability / Korrektheit
|
||
|
||
`OfflineMode` speichert ein Bool in den SharedPreferences und ruft `notifyListeners()`.
|
||
Das war's. Verifiziert per Volltextsuche: **kein einziger Leser** außerhalb von
|
||
`main.dart` (Registrierung) und dem Settings-Schalter selbst. `MeloAudioHandler`,
|
||
`NavidromeService` und `SyncService` fragen ihn nie ab.
|
||
|
||
**Wirkung:** Der Nutzer schaltet "Nur gecachte Lieder abspielen" ein, die App streamt
|
||
trotzdem und verbraucht Mobilfunkdaten. Ein Versprechen, das die UI gibt und der Code
|
||
bricht.
|
||
|
||
**Empfehlung:** `OfflineMode` in `loadPlaylist` auslesen. Ist er an, nur Titel mit
|
||
Cache-Treffer oder lokalem Pfad in die Queue nehmen und die übersprungenen sichtbar
|
||
machen ("3 Titel übersprungen — nicht offline verfügbar").
|
||
|
||
---
|
||
|
||
### K2 — Keine `POST_NOTIFICATIONS`-Berechtigung
|
||
**Ort:** `android/app/src/main/AndroidManifest.xml`
|
||
**Kategorie:** Plattform
|
||
|
||
Verifiziert im **gemergten** Manifest
|
||
(`build/app/intermediates/merged_manifests/release/.../AndroidManifest.xml`):
|
||
`POST_NOTIFICATIONS` ist nicht dabei. `targetSdk` folgt Flutter (≥ 33).
|
||
|
||
**Wirkung:** Auf Android 13+ zeigt das System die Media-Notification erst nach
|
||
erteilter Laufzeit-Berechtigung. Ohne sie fehlt die zentrale Bedienfläche einer
|
||
Musik-App im Alltag. Der Vordergrunddienst läuft, aber unsichtbar.
|
||
|
||
**Empfehlung:** Permission ins Manifest, beim ersten Start der Wiedergabe abfragen
|
||
(`permission_handler` ist bereits als Abhängigkeit da).
|
||
|
||
---
|
||
|
||
### K3 — Server-Titel haben nie ein Coverbild
|
||
**Ort:** `lib/shared/cover.dart:38` · `lib/downloads/downloads_screen.dart:309`
|
||
**Kategorie:** Visual
|
||
|
||
`CoverImage` rendert **ausschließlich** `artUri.scheme == 'file'`. Für alles andere
|
||
kommt der Noten-Platzhalter. Navidrome-Titel bekommen in `_playAlbum`/`_playArtist`
|
||
gar kein `artUri` mitgegeben — obwohl `NavidromeService.coverUrl` (Zeile 265) existiert
|
||
und `SubsonicAlbum.coverId` geparst wird.
|
||
|
||
**Wirkung:** Jeder Server-Titel ist in Liste, Mini-Player, Vollbild-Player, Notification
|
||
und Sperrbildschirm ein graues Notensymbol. Das ist der augenfälligste Unterschied zu
|
||
Apple Music — dort trägt das Cover die gesamte visuelle Identität.
|
||
|
||
**Empfehlung:** `CoverImage` um `Image.network` für http/https erweitern (mit
|
||
`cacheWidth`, Fehler-Fallback auf den Platzhalter), und in beiden `_play*`-Methoden
|
||
`artUri: _nav.coverUrl(s.coverId)` setzen. Bild-Cache lokal ablegen, damit es offline
|
||
bleibt.
|
||
|
||
---
|
||
|
||
### K4 — ~~Alben fehlen als Browse-Achse~~ → **korrigiert: Album-Detailansicht fehlt**
|
||
**Ort:** `lib/library/category_list.dart:12` · `lib/library/categories.dart:26`
|
||
**Kategorie:** Funktionsumfang
|
||
|
||
> **Korrektur vom 2026-08-23 (nach der P0-Runde).** Die ursprüngliche Fassung
|
||
> dieses Punktes war falsch. Sie behauptete, die Alben-Achse sei
|
||
> „verlorengegangen". Sie wurde **bewusst ersetzt**: `category_list.dart`
|
||
> dokumentiert das im Klassenkommentar, und `kategorienAusTags` setzt den
|
||
> Album-Namen beim Scan als **erste Kategorie** jedes Titels. Der Reiter
|
||
> „Kategorie" *ist* der Alben-Reiter — nur allgemeiner, weil ein Titel
|
||
> mehreren Kategorien angehören darf. Der Fehler entstand, weil nur
|
||
> `song_grouping.dart` und `my_music_screen.dart` gelesen wurden.
|
||
|
||
**Was wirklich fehlt**, ist nicht die Achse, sondern die **Ansicht eines
|
||
Albums als Album**:
|
||
|
||
1. **Kein Album-Kopf.** `_KategorieLiederScreen` ist eine `AppBar` über einer
|
||
Titelliste. Es fehlen großes Cover, Künstler, Jahr, Titelzahl,
|
||
Gesamtdauer und ein „Abspielen / Zufällig"-Paar. Bei Apple Music trägt
|
||
genau dieser Kopf die Identität eines Albums.
|
||
2. **Falsche Reihenfolge.** Innerhalb einer Kategorie wird nach Titel
|
||
sortiert. Ein Album gehört in **Tracknummer-Reihenfolge** — sonst beginnt
|
||
jedes Konzeptalbum mit dem Lied, das alphabetisch vorn steht.
|
||
3. **Die Tracknummer wird gar nicht gelesen.** Verifiziert: weder `Songs` noch
|
||
der Scan kennen `trackNumber`. Punkt 2 braucht also zuerst eine
|
||
Schema-Erweiterung plus Auslesen des Tags.
|
||
4. **Kein Weg vom Künstler zu seinen Alben.** `_ArtistSongsScreen` zeigt alle
|
||
Titel flach.
|
||
|
||
**Empfehlung (ersetzt die alte):** Keinen vierten Reiter bauen — der würde
|
||
denselben Bestand doppelt zeigen. Stattdessen `_KategorieLiederScreen` zu
|
||
einem richtigen Detail-Screen ausbauen und die Tracknummer nachrüsten. Der
|
||
Aufwand liegt damit **nicht** bei „ein Reiter wie der Künstler-Reiter",
|
||
sondern bei Schema-Migration + Scan + Screen.
|
||
|
||
|
||
---
|
||
|
||
### K5 — Warteschlange neu ordnen startet die Wiedergabe neu
|
||
**Ort:** `lib/player/queue_screen.dart:87,103` · `lib/player/audio_handler.dart:169`
|
||
**Kategorie:** Usability / Korrektheit
|
||
|
||
Der Kommentar im Code ist ehrlich: der Handler bietet keine Live-Mutation, also ruft
|
||
jedes Entfernen und jedes Verschieben `loadPlaylist()` auf. Das führt
|
||
`_player.setAudioSources(...)` + `play()` + `seek()` aus.
|
||
|
||
Drei Folgen:
|
||
1. **Hörbarer Aussetzer** bei jeder Queue-Änderung — bei Apple Music passiert das lautlos.
|
||
2. `loadPlaylist` setzt `_lastCountedIndex = null` (Zeile 173) → der laufende Titel wird
|
||
**erneut als Wiedergabe gezählt**. Die Sortierung "Wie oft abgespielt" wird durch
|
||
Queue-Bedienung verfälscht.
|
||
3. Bei Server-Titeln werden alle Cache-Treffer neu ermittelt (`await` in der Schleife).
|
||
|
||
**Empfehlung:** `removeQueueItem`/`insertQueueItem` auf `_player.removeAudioSourceAt` /
|
||
`moveAudioSource` abbilden. `just_audio` kann das ohne Neuaufbau.
|
||
|
||
---
|
||
|
||
## Große Punkte (P1)
|
||
|
||
### G1 — Suche findet nur lokale Titel, nur flach
|
||
**Ort:** `lib/library/search_screen.dart` (59 Zeilen)
|
||
`db.searchSongs()` per LIKE über Titel/Künstler/Album. Kein Server (`search3.view` wird
|
||
nirgends aufgerufen), keine Playlisten, keine Kategorien, keine Gruppierung der Treffer
|
||
nach Typ, keine letzten Suchen, keine Vorschläge, kein Entprellen. Apple Music gruppiert
|
||
Ergebnisse in Titel/Alben/Künstler/Playlists und zeigt bei leerem Feld die Historie.
|
||
Der ganze Tab ist heute ein Textfeld über einer Liste.
|
||
|
||
### G2 — Navidrome ist im Download-Tab versteckt
|
||
**Ort:** `lib/downloads/downloads_screen.dart:56`
|
||
Der komplette Server-Browser ist ein Unterreiter von "Download". Ein Nutzer, der seine
|
||
Server-Bibliothek hören will, sucht sie nicht unter "Download". Im Referenz-Screenshot
|
||
hieß der Tab noch "Online" — treffender.
|
||
|
||
Dazu: Tippen auf ein Album **spielt sofort ab**. Es gibt keinen Album-Screen, keine
|
||
Titelliste, keine Möglichkeit, einen einzelnen Server-Titel zu wählen. Nur 50 Alben,
|
||
kein Nachladen, keine Server-Suche.
|
||
|
||
### G3 — Server-Titel sind Bürger zweiter Klasse
|
||
**Ort:** `lib/library/song_media.dart` vs. `lib/downloads/downloads_screen.dart:309`
|
||
Ein `MediaItem` vom Server trägt nur `navidromeId` — **kein** `songId`. Damit gilt für
|
||
Server-Titel: kein Favorit, keine Playlist-Zuordnung, kein lokaler Wiedergabezähler,
|
||
keine Kategorie, kein Cover, kein Eintrag in der Historie.
|
||
|
||
**Konkreter Fehler daraus:** `now_playing_screen.dart:45` gibt
|
||
`FavoriteButton(songId: item.extras?['songId'] ?? '')` — bei Server-Titeln also den
|
||
**leeren String**. `FavoriteButton` prüft nicht darauf, `toggleFavorite('')` legt eine
|
||
Favoriten-Zeile mit leerer ID an. Das Herz füllt sich, in der Favoritenliste (INNER JOIN
|
||
auf `songs`) taucht nichts auf. Falsche Rückmeldung an den Nutzer.
|
||
|
||
### G4 — Kein gezielter Offline-Download
|
||
**Ort:** `lib/services/cache_manager.dart` · `lib/player/audio_handler.dart:203`
|
||
Gecacht wird nur, was zufällig abgespielt wurde. Es gibt kein "Album offline verfügbar
|
||
machen", keine Anzeige, welche Titel offline sind, kein Verwalten pro Titel.
|
||
`CacheManager` hat außerdem **keine Größenbegrenzung und keine Verdrängung** — nur
|
||
"alles löschen". Der Cache wächst unbegrenzt. Alle Dateien werden `.mp3` benannt,
|
||
unabhängig vom tatsächlichen Codec (Navidrome liefert oft Opus/FLAC).
|
||
|
||
### G5 — Kein "läuft gerade"-Zustand in der Liste
|
||
**Ort:** `lib/library/song_list.dart:157`
|
||
Die Referenz-UI zeigt den laufenden Titel farbig hervorgehoben mit animierten Balken.
|
||
Melos `SongList` hat keinerlei Bezug zum `mediaItem`-Stream. Der Nutzer scrollt durch die
|
||
Liste und sieht nicht, wo er gerade ist.
|
||
|
||
### G6 — Zwei verschiedene Rot-Töne
|
||
**Ort:** 10 Vorkommen von `Colors.redAccent` (#FF5252), meist in `settings_screen.dart`
|
||
und `downloads_screen.dart`
|
||
Die Marke ist `MeloTheme.red` = #C0392B (gedecktes Ziegelrot). `Colors.redAccent` ist ein
|
||
grelles Signalrot. Beide stehen im selben Screen nebeneinander. Insgesamt **58
|
||
hartcodierte `Colors.*`-Werte** außerhalb des Themes, 18 davon allein in den
|
||
Einstellungen — die sind visuell der schwächste Screen der App.
|
||
|
||
### G7 — Emoji als Icon
|
||
**Ort:** `downloads_screen.dart:234,247` (📀 Alben / 🎤 Künstler),
|
||
`settings_screen.dart:190` (📴 Offline-Modus), `now_playing_screen.dart:362` (📝 Songtext)
|
||
Verstößt gegen die erste Regel der Pre-Delivery-Checkliste von ui-ux-pro-max: Emoji
|
||
rendern je nach Hersteller-Font unterschiedlich, lassen sich nicht einfärben, folgen
|
||
keinem Design-Token und sind für Screenreader Unsinn. Material-Icons sind vorhanden
|
||
(`Icons.album`, `Icons.mic`, `Icons.cloud_off`, `Icons.lyrics`).
|
||
|
||
### G8 — Songtext ist ein statischer Textblock
|
||
**Ort:** `lib/player/now_playing_screen.dart:309`
|
||
Der Text wird als eine Wand angezeigt. Kein Zeilen-Sync (LRC), kein Mitlaufen, kein
|
||
Antippen zum Springen. Navidromes `getLyricsBySongId` liefert synchronisierte Texte.
|
||
Das ist bei Apple Music eine der meistgenutzten Funktionen.
|
||
|
||
---
|
||
|
||
### G9 — Nach `stop()` merkt sich die App keine Positionen mehr *(bei der P0-Umsetzung gefunden)*
|
||
**Ort:** `lib/player/audio_handler.dart:258` (`stop`)
|
||
**Kategorie:** Korrektheit · **Status:** nicht behoben, außerhalb des P0-Auftrags
|
||
|
||
`stop()` ruft `_positionRecordTimer.cancel()`. Der Timer ist `Timer.periodic`
|
||
und wird nur **einmal im Konstruktor** angelegt — einmal abgebrochen, kommt er
|
||
für den Rest der App-Sitzung nicht zurück.
|
||
|
||
`stop()` löst nicht nur der Nutzer aus: das System ruft es beim Wegwischen der
|
||
Benachrichtigung und über die Stop-Aktion auf. Danach speichert die App keine
|
||
Wiedergabepositionen mehr und scrobbelt nicht mehr zum Server — bis zum
|
||
Neustart, und ohne jeden Hinweis. „Fortsetzen an alter Stelle" hört damit
|
||
still auf zu funktionieren.
|
||
|
||
**Empfehlung:** Den Timer in `stop()` nicht abbrechen, sondern in der Callback
|
||
prüfen (`if (!_player.playing) return;` steht bereits drin — der Abbruch ist
|
||
also gar nicht nötig). Abbrechen gehört allein in `dispose()`.
|
||
|
||
**Vorbestehend**, nicht durch die P0-Änderungen entstanden: der alte
|
||
`QueueScreen` rief in derselben Lage ebenfalls `handler.stop()` auf.
|
||
|
||
---
|
||
|
||
### G10 — Server-Cover haben keinen dauerhaften Zwischenspeicher *(Folge der K3-Umsetzung)*
|
||
**Ort:** `lib/shared/cover.dart:55`
|
||
**Kategorie:** Performance · **Status:** offen, bewusst nicht in P0
|
||
|
||
`Image.network` legt nur im Arbeitsspeicher ab (Flutters `ImageCache`, ~100 MB,
|
||
beim Beenden weg). Beim nächsten Kaltstart fragt jede sichtbare Zeile ihr Cover
|
||
erneut beim Server an. Außerdem laufen diese Anfragen auch im Offline-Modus —
|
||
sie schlagen dann fehl und fallen auf den Platzhalter zurück, kosten aber
|
||
Versuche.
|
||
|
||
Ein dauerhafter Cache bräuchte entweder `cached_network_image` als neue
|
||
Abhängigkeit oder eine Erweiterung des vorhandenen `CacheManager` auf Bilder.
|
||
Beides ist eine Entscheidung, keine Fehlerbehebung — deshalb nicht im P0-Fix.
|
||
Der Unterschied zu vorher bleibt trotzdem groß: bisher gab es **überhaupt kein**
|
||
Server-Cover.
|
||
|
||
---
|
||
|
||
## Kleine Punkte (P2)
|
||
|
||
| # | Punkt | Ort |
|
||
|---|---|---|
|
||
| P1 | AppBar der Einstellungen heißt **"Settings"** — einziges englisches Wort in einer deutschen App | `settings_screen.dart:65` |
|
||
| P2 | Kein `Hero`-Übergang Mini-Player → Vollbild (0 Treffer in `lib/`). Der Wechsel springt, statt das Cover wandern zu lassen | `mini_player.dart:70` |
|
||
| P3 | Kein Wischen am Mini-Player (nach unten = schließen, seitlich = nächster Titel) | `mini_player.dart` |
|
||
| P4 | Künstlerliste ist eine nackte `ListTile`-Liste ohne Bilder; kein Album-Zwischenschritt | `artist_list.dart:31` |
|
||
| P5 | Keine Reduced-Motion-Unterstützung (0 Treffer für `disableAnimations`), obwohl `MeloMotion` sauber getokent ist | `theme.dart:41` |
|
||
| P6 | Keine Querformat-/Tablet-Anpassung (`LayoutBuilder` nur in `cover.dart`, `MediaQuery.size` nur im Equalizer) | app-weit |
|
||
| P7 | Kein Alphabet-Schnellscroller und keine Mehrfachauswahl in langen Listen | `song_list.dart` |
|
||
|
||
---
|
||
|
||
## Vorschläge (P3)
|
||
|
||
- **V1 — Farbe aus dem Cover ziehen.** Apple Music und Spotify färben den Player nach dem
|
||
Albumcover. In Flutter mit `PaletteGenerator` + Verlauf hinter dem Cover. Größter
|
||
optischer Sprung pro Zeile Code.
|
||
- **V2 — Eigene Schrift.** Die App nutzt System-Roboto. Die ui-ux-pro-max-Abfrage für
|
||
"music entertainment" schlägt Righteous/Poppins vor; für Schwarz/Rot passt eher eine
|
||
kräftige Grotesk (Outfit, Sora) für Überschriften bei Roboto/Inter im Fließtext.
|
||
- **V3 — YouTube-Share-Intent.** Aktuell muss man die URL kopieren und einfügen. Ein
|
||
`ACTION_SEND`-Intent-Filter im Manifest macht "Teilen → Melo" möglich. Sehr kleine
|
||
Änderung, spürbarer Alltagsgewinn.
|
||
- **V4 — Android Auto / Assistant.** Der `MediaBrowserService` ist im Manifest deklariert,
|
||
aber `MeloAudioHandler` überschreibt `getChildren`/`getMediaItem` nicht — Browsen im Auto
|
||
funktioniert also nicht. Kein Widget, keine Wear-Unterstützung.
|
||
|
||
---
|
||
|
||
## Was gut ist (nicht ändern)
|
||
|
||
- **Design-Token-System** (`theme.dart`): `MeloSpace`, `MeloRadius`, `MeloMotion` mit
|
||
begründeten Werten. Die Textfarben sind nicht geschätzt, sondern per
|
||
`theme_kontrast_test.dart` gegen 4,5:1 nachgerechnet. Das machen wenige.
|
||
- **Haarlinie statt Schatten** auf Schwarz — richtige Entscheidung für OLED, deckt sich mit
|
||
der "Dark Mode (OLED)"-Empfehlung des Style-Katalogs.
|
||
- **Datenmodell**: UUID + `updatedAtMs` + `deleted`-Tombstone ab Schema 1, 9 saubere
|
||
Migrationen. Sync war von Anfang an mitgedacht statt nachgerüstet.
|
||
- **Reine Funktionen ausgelagert** (`shouldResumeAt`, `playNextIndex`, `reorderQueue`,
|
||
`planeSync`, `newPlayingIndex`) — deshalb 328 Tests ohne Emulator.
|
||
- **Kommentare erklären das Warum**, oft mit dem konkreten Bug, der dahinterstand
|
||
(`navidromeIdOf`, `cacheSchluessel`, `loadPlaylist`). Genau die Stellen aus
|
||
`ui-referenz/bugs/` sind so dokumentiert.
|
||
- **Tooltips überall** an Icon-Buttons → in Flutter automatisch Semantics-Label.
|
||
|
||
---
|
||
|
||
## Priorisierter Plan
|
||
|
||
### P0 — bevor irgendetwas Neues gebaut wird
|
||
1. `POST_NOTIFICATIONS` ins Manifest + Laufzeit-Abfrage *(K2, ~1 h)*
|
||
2. Offline-Modus wirklich verdrahten oder den Schalter entfernen *(K1, ~3 h)*
|
||
3. `CoverImage` für http + `artUri` bei Server-Titeln setzen *(K3, ~3 h)*
|
||
4. Queue ohne Neuladen mutieren, Doppelzählung beenden *(K5, ~4 h)*
|
||
5. Leere `songId` in `FavoriteButton` abfangen *(G3-Teil, ~30 min)*
|
||
|
||
### P1 — das macht sie "vollwertig"
|
||
6. Alben-Achse: `groupByAlbum`, vierter Unterreiter, Album-Screen *(K4, ~1 Tag)*
|
||
7. Suche umbauen: nach Typ gruppiert, Server einbeziehen, letzte Suchen *(G1, ~1,5 Tage)*
|
||
8. Server-Bereich aus "Download" lösen → eigener Tab "Online" mit Album-Detail *(G2, ~1,5 Tage)*
|
||
9. Offline-Download pro Album/Playlist + Cache-Grenze mit LRU *(G4, ~1,5 Tage)*
|
||
10. "Läuft gerade"-Markierung in allen Listen *(G5, ~3 h)*
|
||
|
||
### P2 — Politur, die man sofort sieht
|
||
11. `Colors.redAccent` → `MeloTheme.red`, Emoji → Material-Icons, "Settings" → "Einstellungen" *(G6, G7, P1 — zusammen ~3 h)*
|
||
12. Hero-Übergang + Wischgesten am Mini-Player *(P2, P3, ~4 h)*
|
||
13. Synchronisierter Songtext (LRC) *(G8, ~1 Tag)*
|
||
14. Künstler- und Kategorieliste visuell aufwerten *(P4, ~4 h)*
|
||
15. Reduced-Motion + Querformat *(P5, P6, ~4 h)*
|
||
|
||
### P3 — danach
|
||
16. Cover-Farbe im Player *(V1)*
|
||
17. Eigene Schrift *(V2)*
|
||
18. YouTube-Share-Intent *(V3)*
|
||
19. Android Auto: `getChildren` implementieren *(V4)*
|
||
|
||
---
|
||
|
||
## Feature-Matrix gegen Apple Music
|
||
|
||
| Bereich | Apple Music | Melo | Bewertung |
|
||
|---|---|---|---|
|
||
| Wiedergabe, Hintergrund, Sperrbildschirm | ✅ | ✅ | **da** (Notification-Permission fehlt → K2) |
|
||
| Warteschlange ansehen/umordnen | ✅ | ✅ | **schwach** — Neuladen bei jeder Änderung |
|
||
| Als Nächstes / Ans Ende | ✅ | ✅ | **da** |
|
||
| Shuffle & Repeat | ✅ | ✅ | **da** |
|
||
| Sleep-Timer | ✅ | ✅ | **da** (Melo besser als Apple Music) |
|
||
| Equalizer | ⚠️ nur System | ✅ 5-Band + Bass + Virtualizer | **besser als Apple Music** |
|
||
| Lautstärke-Angleich | ✅ | ✅ ReplayGain | **da** |
|
||
| Fortsetzen an alter Stelle | ✅ | ✅ lokal + Server-Bookmark | **da** |
|
||
| Favoriten | ✅ | ✅ | **da**, aber nicht für Server-Titel |
|
||
| Playlisten anlegen/ordnen | ✅ | ✅ | **da** |
|
||
| Alben durchsuchen | ✅ | ❌ | **fehlt** |
|
||
| Künstler-Seite mit Alben | ✅ | ⚠️ nackte Titelliste | **schwach** |
|
||
| Suche | ✅ typgruppiert, serverweit | ⚠️ lokal, flach | **schwach** |
|
||
| Songtext | ✅ zeilensynchron | ⚠️ statischer Block | **schwach** |
|
||
| Offline verfügbar machen | ✅ gezielt | ⚠️ nur Zufalls-Cache | **schwach** |
|
||
| Cover-Art | ✅ | ⚠️ nur lokale Dateien | **schwach** |
|
||
| Geräte-Sync der Bibliothek | ✅ iCloud | ✅ Melo-Cloud + Tombstones | **da** |
|
||
| Scrobble / Wiedergabestatistik | ✅ | ✅ lokal + Subsonic | **da** |
|
||
| Musikerkennung (Shazam) | ✅ | ✅ ACRCloud | **da** |
|
||
| YouTube-Import | ❌ | ✅ | **Alleinstellung** |
|
||
| Kategorien / eigene Tags | ❌ | ✅ | **Alleinstellung** |
|
||
| Empfehlungen, Radio, Charts | ✅ | ❌ | **fehlt** (bewusst? Melo hat keinen Katalog) |
|
||
| Teilen | ✅ | ❌ | **fehlt** |
|
||
| Homescreen-Widget | ✅ | ❌ | **fehlt** |
|
||
| Android Auto | ✅ | ❌ | **fehlt** (Service da, Browsing nicht) |
|
||
| Cast / Sonos | ✅ | ❌ | **fehlt** |
|
||
| Crossfade / Gapless | ✅ | ❌ | **fehlt** |
|
||
|
||
**Zählung:** 13 × da · 7 × schwach · 8 × fehlt · 3 × besser als Apple Music
|
||
|
||
---
|
||
|
||
_Erzeugt mit ui-ux-pro-max 2.13.0 (design-system, --stack flutter, pro-rules) und
|
||
ui-design:design-review. Alle Code-Stellen im Repo verifiziert, `flutter analyze` und
|
||
`flutter test` zum Review-Zeitpunkt ausgeführt._
|