// lib/pages/playlist_detail_page.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../database/song_database.dart'; import '../repositories/playlist_repository.dart'; import '../services/audio_service.dart'; import '../widgets/song_artwork.dart'; /// 歌单详情:展示歌单内歌曲,支持播放全部 / 单曲播放 / 移除 class PlaylistDetailPage extends StatefulWidget { final String playlistId; final String playlistName; const PlaylistDetailPage({ super.key, required this.playlistId, required this.playlistName, }); @override State createState() => _PlaylistDetailPageState(); } class _SongRow { final String path; final String title; final String artist; final String? artworkPath; const _SongRow({ required this.path, required this.title, required this.artist, this.artworkPath, }); } class _PlaylistDetailPageState extends State { final PlaylistRepository _repo = PlaylistRepository(); final SongDatabase _db = SongDatabase(); List<_SongRow> _songs = []; bool _loading = true; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() => _loading = true); try { final ids = await _repo.getPlaylistSongIds(widget.playlistId); final meta = await _db.getSongsByPaths(ids); final rows = <_SongRow>[]; for (final id in ids) { final row = meta[id]; final title = (row?['title'] as String?)?.trim() ?? ''; final artist = (row?['artist'] as String?)?.trim() ?? ''; String fallbackName = id.split('/').last; try { fallbackName = Uri.decodeComponent(fallbackName); } catch (_) {} rows.add(_SongRow( path: id, title: title.isNotEmpty ? title : fallbackName.replaceAll(RegExp(r'\.[^.]*$'), ''), artist: artist.isNotEmpty ? artist : '未知艺术家', artworkPath: (row?['artwork_path'] as String?)?.trim().isNotEmpty == true ? (row!['artwork_path'] as String).trim() : null, )); } if (!mounted) return; setState(() { _songs = rows; _loading = false; }); } catch (e) { if (!mounted) return; setState(() => _loading = false); ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('读取歌单失败: $e'))); } } Future _play(int index) async { await context .read() .playPlaylist(widget.playlistId, startIndex: index); } Future _remove(_SongRow song) async { await _repo.removeSong(widget.playlistId, song.path); await _load(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF0E1211), appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, title: Text(widget.playlistName), actions: [ IconButton( icon: const Icon(Icons.play_arrow_rounded, color: Color(0xFFB8D4D0)), tooltip: '播放全部', onPressed: _songs.isEmpty ? null : () => _play(0), ), ], ), body: _loading ? const Center(child: CircularProgressIndicator()) : _songs.isEmpty ? _buildEmpty() : ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), itemCount: _songs.length, itemBuilder: (context, index) { final song = _songs[index]; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 4), visualDensity: VisualDensity.compact, leading: SongArtwork( filePath: song.artworkPath, size: 44, fallbackIcon: Icons.music_note, ), title: Text( song.title, style: const TextStyle(color: Colors.white, fontSize: 16), maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text( song.artist, style: const TextStyle(color: Colors.grey, fontSize: 13), maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: IconButton( icon: const Icon(Icons.close, color: Colors.white24, size: 18), tooltip: '从歌单移除', onPressed: () => _remove(song), ), onTap: () => _play(index), ); }, ), ); } Widget _buildEmpty() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.music_off_outlined, size: 44, color: Colors.grey[700]), const SizedBox(height: 14), Text( '歌单还是空的', style: TextStyle(color: Colors.grey[500], fontSize: 15), ), const SizedBox(height: 6), Text( '在文件列表长按歌曲即可加入', style: TextStyle(color: Colors.grey[700], fontSize: 12), ), ], ), ); } }