feat(playlist): 歌单变更与歌单内元数据实时刷新

两个边界场景:
1) 新建/重命名/删除歌单、加入/移除歌曲后,主页歌单页不刷新
2) 歌单详情页里歌曲补全元数据后,标题/封面不更新

- 新增 PlaylistEvents(void 事件)
- PlaylistRepository 的创建/重命名/删除/批量加入/移除后 publish
- 主页订阅后重新拉取歌单摘要
- 歌单详情页订阅 MetadataEvents,按 path 就地更新行
This commit is contained in:
2026-11-01 16:32:35 +08:00
parent 653c1f5563
commit e10be527cb
4 changed files with 65 additions and 0 deletions
+10
View File
@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
import '../services/favorite_events.dart'; import '../services/favorite_events.dart';
import '../services/metadata_events.dart'; import '../services/metadata_events.dart';
import '../services/playlist_events.dart';
import '../services/webdav_service.dart'; import '../services/webdav_service.dart';
import '../widgets/magnetic_scroll_physics.dart'; import '../widgets/magnetic_scroll_physics.dart';
import 'webdav_setup_page.dart'; import 'webdav_setup_page.dart';
@@ -168,6 +169,7 @@ class _HomePageState extends BaseState<HomePage> {
final PageController _homePageController = PageController(); final PageController _homePageController = PageController();
StreamSubscription<MetadataUpdate>? _metaSub; StreamSubscription<MetadataUpdate>? _metaSub;
StreamSubscription<String>? _favSub; StreamSubscription<String>? _favSub;
StreamSubscription<void>? _plSub;
List<PlaylistSummary> _playlistSummaries = []; List<PlaylistSummary> _playlistSummaries = [];
// ========== 滚动控制 ========== // ========== 滚动控制 ==========
@@ -207,6 +209,7 @@ class _HomePageState extends BaseState<HomePage> {
_scrollController = ScrollController()..addListener(_onScroll); _scrollController = ScrollController()..addListener(_onScroll);
_metaSub = MetadataEvents().stream.listen(_onMetadataUpdate); _metaSub = MetadataEvents().stream.listen(_onMetadataUpdate);
_favSub = FavoriteEvents().stream.listen((_) => _onFavoriteChanged()); _favSub = FavoriteEvents().stream.listen((_) => _onFavoriteChanged());
_plSub = PlaylistEvents().stream.listen((_) => _onPlaylistChanged());
_initWebDAV(); _initWebDAV();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -218,6 +221,7 @@ class _HomePageState extends BaseState<HomePage> {
void dispose() { void dispose() {
_metaSub?.cancel(); _metaSub?.cancel();
_favSub?.cancel(); _favSub?.cancel();
_plSub?.cancel();
_homePageController.dispose(); _homePageController.dispose();
_scrollController.dispose(); _scrollController.dispose();
super.dispose(); super.dispose();
@@ -249,6 +253,12 @@ class _HomePageState extends BaseState<HomePage> {
} }
} }
/// 歌单数据变化后刷新歌单页
Future<void> _onPlaylistChanged() async {
await _loadPlaylistSummaries();
if (mounted) safeSetState(() {});
}
/// 任何位置收藏/取消收藏后,重新拉一次收藏列表 /// 任何位置收藏/取消收藏后,重新拉一次收藏列表
Future<void> _onFavoriteChanged() async { Future<void> _onFavoriteChanged() async {
await _loadFavoritesFromDb(); await _loadFavoritesFromDb();
+28
View File
@@ -1,3 +1,4 @@
import 'dart:async';
// lib/pages/playlist_detail_page.dart // lib/pages/playlist_detail_page.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -5,6 +6,7 @@ import 'package:provider/provider.dart';
import '../database/song_database.dart'; import '../database/song_database.dart';
import '../repositories/playlist_repository.dart'; import '../repositories/playlist_repository.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
import '../services/metadata_events.dart';
import '../widgets/song_artwork.dart'; import '../widgets/song_artwork.dart';
/// 歌单详情:展示歌单内歌曲,支持播放全部 / 单曲播放 / 移除 /// 歌单详情:展示歌单内歌曲,支持播放全部 / 单曲播放 / 移除
@@ -41,14 +43,40 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
final SongDatabase _db = SongDatabase(); final SongDatabase _db = SongDatabase();
List<_SongRow> _songs = []; List<_SongRow> _songs = [];
StreamSubscription<MetadataUpdate>? _metaSub;
bool _loading = true; bool _loading = true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_metaSub = MetadataEvents().stream.listen(_onMetadataUpdate);
_load(); _load();
} }
@override
void dispose() {
_metaSub?.cancel();
super.dispose();
}
/// 歌单里的歌补全元数据后,就地更新对应行
void _onMetadataUpdate(MetadataUpdate update) {
final index = _songs.indexWhere((item) => item.path == update.path);
if (index < 0 || !mounted) return;
final row = update.row;
final title = (row['title'] as String?)?.trim() ?? '';
final artist = (row['artist'] as String?)?.trim() ?? '';
final artwork = (row['artwork_path'] as String?)?.trim() ?? '';
final old = _songs[index];
setState(() {
_songs[index] = _SongRow(
path: old.path,
title: title.isNotEmpty ? title : old.title,
artist: artist.isNotEmpty ? artist : old.artist,
artworkPath: artwork.isNotEmpty ? artwork : old.artworkPath,
);
});
}
Future<void> _load() async { Future<void> _load() async {
setState(() => _loading = true); setState(() => _loading = true);
try { try {
@@ -3,6 +3,7 @@ import 'dart:math';
import '../database/song_database.dart'; import '../database/song_database.dart';
import '../models/playlist.dart'; import '../models/playlist.dart';
import '../models/playlist_summary.dart'; import '../models/playlist_summary.dart';
import '../services/playlist_events.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
class PlaylistRepository { class PlaylistRepository {
@@ -35,6 +36,7 @@ class PlaylistRepository {
Future<Playlist> createPlaylist(String name) async { Future<Playlist> createPlaylist(String name) async {
final playlist = Playlist(id: _generateId(), name: name); final playlist = Playlist(id: _generateId(), name: name);
await _db.insertPlaylist(playlist.toJson()); await _db.insertPlaylist(playlist.toJson());
PlaylistEvents().publish();
return playlist; return playlist;
} }
@@ -55,6 +57,7 @@ class PlaylistRepository {
existing.add(id); existing.add(id);
added++; added++;
} }
if (added > 0) PlaylistEvents().publish();
return added; return added;
} }
Future<void> updatePlaylistName(String id, String newName) async { Future<void> updatePlaylistName(String id, String newName) async {
@@ -62,6 +65,7 @@ class PlaylistRepository {
'name': newName, 'name': newName,
'updated_at': DateTime.now().millisecondsSinceEpoch, 'updated_at': DateTime.now().millisecondsSinceEpoch,
}); });
PlaylistEvents().publish();
} }
// ⭐ 新增:更新播放模式 // ⭐ 新增:更新播放模式
@@ -78,6 +82,7 @@ class PlaylistRepository {
Future<void> deletePlaylist(String id) async { Future<void> deletePlaylist(String id) async {
await _db.deletePlaylist(id); await _db.deletePlaylist(id);
PlaylistEvents().publish();
} }
// ════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════
@@ -95,6 +100,7 @@ class PlaylistRepository {
Future<void> removeSong(String playlistId, String songId) async { Future<void> removeSong(String playlistId, String songId) async {
await _db.removeSongFromPlaylist(playlistId, songId); await _db.removeSongFromPlaylist(playlistId, songId);
PlaylistEvents().publish();
} }
Future<void> reorderSongs( Future<void> reorderSongs(
+21
View File
@@ -0,0 +1,21 @@
// lib/services/playlist_events.dart
import 'dart:async';
/// 歌单数据变更事件(新建/重命名/删除/加歌/删歌/排序)。
///
/// 主页的歌单页、歌单详情页都靠它实时刷新,
/// 不用等页面重建。
class PlaylistEvents {
static final PlaylistEvents _instance = PlaylistEvents._internal();
factory PlaylistEvents() => _instance;
PlaylistEvents._internal();
final StreamController<void> _controller =
StreamController<void>.broadcast();
Stream<void> get stream => _controller.stream;
void publish() {
_controller.add(null);
}
}