- 新增 SongArtwork 组件:内存字节 > 本地封面文件 > 图标兜底,圆角裁剪 - WebDAV 文件列表:前导图标换成封面(来自 songs.artwork_path) - 播放列表页:批量查封面路径(getSongsByPaths),正在播放的歌优先用内存里的封面字节; 切歌后重新拉一次,保证新补全的封面能显示 - 没有封面的歌仍回退为音符/播放图标,不影响列表结构
73 lines
1.7 KiB
Dart
73 lines
1.7 KiB
Dart
// lib/widgets/song_artwork.dart
|
|
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// 歌曲封面:优先进内存字节,其次本地封面文件,最后回退图标。
|
|
///
|
|
/// 列表页一次会构建几十行,这里用 `existsSync` 判断一次即可,
|
|
/// 图片本身由 Flutter 按需解码。
|
|
class SongArtwork extends StatelessWidget {
|
|
final Uint8List? bytes;
|
|
final String? filePath;
|
|
final double size;
|
|
final IconData fallbackIcon;
|
|
final Color? fallbackColor;
|
|
|
|
const SongArtwork({
|
|
super.key,
|
|
this.bytes,
|
|
this.filePath,
|
|
this.size = 44,
|
|
this.fallbackIcon = Icons.music_note,
|
|
this.fallbackColor,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Widget child;
|
|
if (bytes != null && bytes!.isNotEmpty) {
|
|
child = Image.memory(
|
|
bytes!,
|
|
width: size,
|
|
height: size,
|
|
fit: BoxFit.cover,
|
|
gaplessPlayback: true,
|
|
errorBuilder: (_, __, ___) => _fallback(),
|
|
);
|
|
} else if (filePath != null &&
|
|
filePath!.isNotEmpty &&
|
|
File(filePath!).existsSync()) {
|
|
child = Image.file(
|
|
File(filePath!),
|
|
width: size,
|
|
height: size,
|
|
fit: BoxFit.cover,
|
|
gaplessPlayback: true,
|
|
errorBuilder: (_, __, ___) => _fallback(),
|
|
);
|
|
} else {
|
|
child = _fallback();
|
|
}
|
|
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(6),
|
|
child: SizedBox(width: size, height: size, child: child),
|
|
);
|
|
}
|
|
|
|
Widget _fallback() {
|
|
return Container(
|
|
width: size,
|
|
height: size,
|
|
color: const Color(0xFF232928),
|
|
child: Icon(
|
|
fallbackIcon,
|
|
size: size * 0.55,
|
|
color: fallbackColor ?? const Color(0xFFB8D4D0),
|
|
),
|
|
);
|
|
}
|
|
}
|