Die letzten vier Punkte aus dem Review-Bericht.
G2 — Server-Bereich: Ein Tipp auf ein Album spielte sofort alles ab;
hineinsehen ging nicht. Jetzt Album- und Kuenstler-Ansicht mit Cover,
Kuenstler, Titelzahl, Gesamtdauer, "Abspielen"/"Zufaellig" und der
Titelliste; Antippen startet an dieser Stelle. Dazu "Mehr Alben laden" —
die Liste hoerte bei 50 auf, ohne dass etwas darauf hinwies.
NICHT umgesetzt: den Server-Bereich aus dem Download-Tab loesen (das
widerspricht Commit 34d6d8e, "Auf Wunsch von Dustin") und eine
Server-Suche (die Suche bleibt ausdruecklich rein lokal).
G8 — Songtext: Traegt der Text Zeitmarken, laeuft er mit; die aktive Zeile
ist hervorgehoben, Antippen springt an die Stelle. Ohne Zeitmarken bleibt
es eine Textwand — die App schaut nach, statt zu raten.
G10 — Cover bleiben liegen. Eigener Ordner, einmal geholt. Der Schluessel
haengt an der Album-Kennung, nicht an der Adresse: die traegt bei jedem
App-Start ein neues Token, und der Ordner waere sonst gewachsen, ohne je
einen Treffer zu liefern. Keine neue Abhaengigkeit.
G3 — Das Herz ist bei Server-Titeln nicht mehr ausgegraut, sondern
favorisiert am Server (star/unstar) — dort, wo der Favoriten-Abgleich sie
auch wieder abholt. Offen bleibt der schwere Teil: Wiedergabezaehler,
Historie und Kategorien braeuchten fuer Server-Titel eine Zeile in der
Bibliothek.
Aus dem Code-Review nachgebessert (sieben Punkte): das Herz behielt beim
Titelwechsel seinen Zustand; recycelte Listenzeilen zeigten kurz das Cover
der vorherigen; der neue Songtext bekam bei Server-Titeln die
Abspiel-Adresse statt der Server-Kennung und fand deshalb nie einen Text;
ein einmal fehlgeschlagenes Cover blieb die ganze Sitzung grau; "Server
nicht erreichbar" sah aus wie "Album hat keine Titel", womit der
Wiederholen-Knopf unerreichbar war; die Kuenstler-Ansicht waere immer leer
geblieben, weil getArtist die Titel gar nicht mitliefert; und zwei
gleichzeitige Cover-Abrufe konnten ein zerschnittenes Bild hinterlassen.
Selbst gefunden: das Haekchen "schon geladen" fragte pro Zeile die Platte
ab, bei jedem Positionswechsel neu. Jetzt einmal je Bildschirm.
457 Tests gruen (vorher 433), flutter analyze ohne Befund, Release-APK
gebaut.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xAHJTJM6UUqmjUgk1PUEd
82 lines
2.8 KiB
Dart
82 lines
2.8 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../services/cover_cache.dart';
|
|
import 'theme.dart';
|
|
|
|
/// Cover-Bild aus einer Datei- oder Server-Adresse, mit Noten-Platzhalter
|
|
/// als Fallback.
|
|
///
|
|
/// Der Server-Fall ist nicht nachträglich angeflanscht, sondern der Regelfall
|
|
/// für jeden Titel aus Navidrome: dessen Cover liegt hinter `getCoverArt.view`
|
|
/// und nicht als Datei auf dem Gerät. Vorher kannte diese Klasse nur `file://`
|
|
/// — jeder Server-Titel war deshalb ein graues Notensymbol, in der Liste wie
|
|
/// auf dem Sperrbildschirm.
|
|
class CoverImage extends StatelessWidget {
|
|
const CoverImage({
|
|
super.key,
|
|
required this.artUri,
|
|
this.size,
|
|
this.radius = MeloRadius.element,
|
|
});
|
|
final Uri? artUri;
|
|
final double? size;
|
|
final double radius;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final placeholder = Container(
|
|
width: size,
|
|
height: size,
|
|
decoration: BoxDecoration(
|
|
color: MeloTheme.surface,
|
|
// Haarlinie statt Schatten: auf Schwarz trennt ein Umriss die
|
|
// Fläche sauber, ein Schatten wäre unsichtbar.
|
|
border: radius > 0 ? MeloTheme.hairline : null,
|
|
borderRadius: BorderRadius.circular(radius),
|
|
),
|
|
child: LayoutBuilder(
|
|
builder: (_, c) {
|
|
final s = size ?? (c.hasBoundedWidth ? c.maxWidth : 48.0);
|
|
return Icon(Icons.music_note, size: s * 0.4, color: MeloTheme.red);
|
|
},
|
|
),
|
|
);
|
|
Widget child = switch (artUri?.scheme) {
|
|
'file' => Image.file(
|
|
File(artUri!.toFilePath()),
|
|
width: size,
|
|
height: size,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (_, _, _) => placeholder,
|
|
),
|
|
// Über den Ordner statt direkt: einmal geladen, bleibt das Cover auch
|
|
// nach einem Neustart und ohne Netz da (siehe [CoverCache]). Solange
|
|
// es unterwegs ist, steht der Platzhalter — sonst springt die Zeile in
|
|
// der Liste, sobald das Bild ankommt.
|
|
'http' || 'https' => FutureBuilder<File?>(
|
|
// Schlüssel je Adresse: in einer Liste werden Zeilen samt ihrem
|
|
// Cover-Widget wiederverwendet. Ohne ihn behielte der FutureBuilder
|
|
// das Ergebnis der vorherigen Zeile und zeigte deren Bild, bis das
|
|
// neue geladen ist.
|
|
key: ValueKey(artUri),
|
|
future: CoverCache.hole(artUri!),
|
|
builder: (context, snapshot) {
|
|
final datei = snapshot.data;
|
|
if (datei == null) return placeholder;
|
|
return Image.file(
|
|
datei,
|
|
width: size,
|
|
height: size,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (_, _, _) => placeholder,
|
|
);
|
|
},
|
|
),
|
|
_ => placeholder,
|
|
};
|
|
return ClipRRect(borderRadius: BorderRadius.circular(radius), child: child);
|
|
}
|
|
}
|