style(ui): 播放页红心与标题居中对齐、标题单行 - playlists_page:歌单列表(封面/名称/歌曲数)、新建、重命名、删除 - playlist_detail_page:歌单内歌曲、播放全部、单曲播放、移除 - 首页"歌单列表"按钮接入 PlaylistsPage - 播放页:标题行改为 center 对齐、标题限制单行, 收藏按钮压到 36x36,红心与标题不再错位
242 lines
8.2 KiB
Dart
242 lines
8.2 KiB
Dart
// lib/pages/playlists_page.dart
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../models/playlist_summary.dart';
|
|
import '../repositories/playlist_repository.dart';
|
|
import '../widgets/song_artwork.dart';
|
|
import 'playlist_detail_page.dart';
|
|
|
|
/// 歌单集合页:展示所有歌单,支持新建 / 重命名 / 删除
|
|
class PlaylistsPage extends StatefulWidget {
|
|
const PlaylistsPage({super.key});
|
|
|
|
@override
|
|
State<PlaylistsPage> createState() => _PlaylistsPageState();
|
|
}
|
|
|
|
class _PlaylistsPageState extends State<PlaylistsPage> {
|
|
final PlaylistRepository _repo = PlaylistRepository();
|
|
List<PlaylistSummary> _playlists = [];
|
|
bool _loading = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_load();
|
|
}
|
|
|
|
Future<void> _load() async {
|
|
setState(() => _loading = true);
|
|
try {
|
|
final list = await _repo.getSummaries();
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_playlists = list;
|
|
_loading = false;
|
|
});
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
setState(() => _loading = false);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('读取歌单失败: $e')),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _create() async {
|
|
final name = await _promptName('新建歌单');
|
|
if (name == null || name.isEmpty) return;
|
|
try {
|
|
await _repo.createPlaylist(name);
|
|
await _load();
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(SnackBar(content: Text('新建失败: $e')));
|
|
}
|
|
}
|
|
|
|
Future<void> _rename(PlaylistSummary playlist) async {
|
|
final name = await _promptName('重命名歌单', initial: playlist.name);
|
|
if (name == null || name.isEmpty) return;
|
|
await _repo.updatePlaylistName(playlist.id, name);
|
|
await _load();
|
|
}
|
|
|
|
Future<void> _delete(PlaylistSummary playlist) async {
|
|
final ok = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
backgroundColor: const Color(0xFF1A1F1E),
|
|
title: const Text('删除歌单', style: TextStyle(color: Colors.white)),
|
|
content: Text(
|
|
'删除「${playlist.name}」?歌单内的歌曲不会从网盘删除。',
|
|
style: const TextStyle(color: Colors.grey),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: const Text('取消'),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
child: const Text('删除', style: TextStyle(color: Colors.redAccent)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
if (ok == true) {
|
|
await _repo.deletePlaylist(playlist.id);
|
|
await _load();
|
|
}
|
|
}
|
|
|
|
Future<String?> _promptName(String title, {String initial = ''}) async {
|
|
final controller = TextEditingController(text: initial);
|
|
return showDialog<String>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
backgroundColor: const Color(0xFF1A1F1E),
|
|
title: Text(title, style: const TextStyle(color: Colors.white)),
|
|
content: TextField(
|
|
controller: controller,
|
|
autofocus: true,
|
|
style: const TextStyle(color: Colors.white),
|
|
decoration: const InputDecoration(hintText: '歌单名称'),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('取消'),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, controller.text.trim()),
|
|
child: const Text('确定'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFF0E1211),
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
title: const Text('歌单'),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.add, color: Color(0xFFB8D4D0)),
|
|
tooltip: '新建歌单',
|
|
onPressed: _create,
|
|
),
|
|
],
|
|
),
|
|
body: _loading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: _playlists.isEmpty
|
|
? _buildEmpty()
|
|
: RefreshIndicator(
|
|
onRefresh: _load,
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16, vertical: 8),
|
|
itemCount: _playlists.length,
|
|
itemBuilder: (context, index) {
|
|
final playlist = _playlists[index];
|
|
return ListTile(
|
|
contentPadding:
|
|
const EdgeInsets.symmetric(horizontal: 4),
|
|
visualDensity: VisualDensity.compact,
|
|
leading: SongArtwork(
|
|
filePath: playlist.coverPath,
|
|
size: 52,
|
|
fallbackIcon: Icons.queue_music,
|
|
),
|
|
title: Text(
|
|
playlist.name,
|
|
style: const TextStyle(
|
|
color: Colors.white, fontSize: 16),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
subtitle: Text(
|
|
'${playlist.songCount} 首',
|
|
style: const TextStyle(
|
|
color: Colors.grey, fontSize: 12),
|
|
),
|
|
trailing: PopupMenuButton<String>(
|
|
icon: const Icon(Icons.more_vert,
|
|
color: Colors.white38, size: 20),
|
|
onSelected: (value) {
|
|
if (value == 'rename') _rename(playlist);
|
|
if (value == 'delete') _delete(playlist);
|
|
},
|
|
itemBuilder: (context) => const [
|
|
PopupMenuItem(
|
|
value: 'rename',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.edit_outlined,
|
|
color: Color(0xFFB8D4D0), size: 18),
|
|
SizedBox(width: 10),
|
|
Text('重命名'),
|
|
],
|
|
),
|
|
),
|
|
PopupMenuItem(
|
|
value: 'delete',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.delete_outline,
|
|
color: Colors.redAccent, size: 18),
|
|
SizedBox(width: 10),
|
|
Text('删除'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
onTap: () async {
|
|
await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => PlaylistDetailPage(
|
|
playlistId: playlist.id,
|
|
playlistName: playlist.name,
|
|
),
|
|
),
|
|
);
|
|
await _load();
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmpty() {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.queue_music, size: 48, 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),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|