import 'dart:io'; import 'package:flutter/material.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, ), // Solange das Bild vom Server unterwegs ist, steht der Platzhalter da — // sonst springt die Zeile in der Liste, sobald es ankommt. 'http' || 'https' => Image.network( artUri!.toString(), width: size, height: size, fit: BoxFit.cover, errorBuilder: (_, _, _) => placeholder, frameBuilder: (_, kind, frame, warAusCache) => frame == null && !warAusCache ? placeholder : kind, ), _ => placeholder, }; return ClipRRect(borderRadius: BorderRadius.circular(radius), child: child); } }