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( // 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); } }