419 lines
22 KiB
Markdown
419 lines
22 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).
|
||
>
|
||
> **Nachtrag 2026-08-25 (UX-Simulation):** Ergänzend zu diesem Code-Review
|
||
> ist die App per Multi-Agent-Simulation als normaler Nutzer durchgegangen
|
||
> worden — 7 Flows (Erststart, Bibliothek, Download, Suche, Favoriten,
|
||
> Player, Einstellungen), 39 Funde. Details im CHANGELOG. Kurzfassung:
|
||
> Mehrfachauswahl lief nur in zwei von vier Songlisten-Kontexten, der
|
||
> Baka-Login blieb bei Fehlern bis zu 15s stumm, mehrere destruktive
|
||
> Aktionen (Cache leeren, Navidrome-Abmelden) hatten keine Rückfrage — alles
|
||
> behoben. Bewusst nicht angefasst: `ServerFavoriteButton` bleibt wie hier
|
||
> unter G3 beschrieben lokal-only; das ist keine neue Erkenntnis, sondern
|
||
> dieselbe offene Baustelle.
|
||
>
|
||
> **Nachtrag 2026-08-25 (zweiter, UX-Simulation Teil 3):** Auch die zuerst
|
||
> zurückgestellten Kleinigkeiten sind erledigt — Abbrechen-Knopf für
|
||
> YouTube-Downloads, Hintergrund-Punkt am inaktiven Download-Unterreiter,
|
||
> Versionsnummer unter "Über Melo" (neue Abhängigkeit `package_info_plus`)
|
||
> und ein Mini-Player, der beim Wischen sichtbar mitführt und zusätzlich
|
||
> zum Schwung die Strecke auswertet. `ServerFavoriteButton`/G3 bleibt
|
||
> weiterhin bewusst offen.
|
||
|
||
---
|
||
|
||
## 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._
|