From 6ca31c37ba1c2f213d79796bc03afd0770c38d3a Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Sun, 1 Nov 2026 12:18:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(metadata):=20=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=A1=A5=E5=85=A8=E5=90=8E=E4=B8=BB=E5=8A=A8=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前标签补全后,收藏/文件列表要退出重进才显示新信息。 - 新增 MetadataEvents 事件总线(MetadataUpdate: path + songs 行) - MetadataService 写库成功后 publish - 首页收藏列表订阅:按 path 替换对应 SongItem(标题/艺术家/封面) - WebDAV 文件列表订阅:按 path 更新 _metaByPath 效果:歌曲播完标签抓取完成后,列表里的那一行自动变成标签信息, 不需要返回重进。 --- lib/metadata/metadata_service.dart | 4 ++++ lib/pages/home_page.dart | 28 ++++++++++++++++++++++++++ lib/pages/webdav_file_list_page.dart | 11 ++++++++++ lib/services/metadata_events.dart | 30 ++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 lib/services/metadata_events.dart diff --git a/lib/metadata/metadata_service.dart b/lib/metadata/metadata_service.dart index 92c4c6b..11a720a 100644 --- a/lib/metadata/metadata_service.dart +++ b/lib/metadata/metadata_service.dart @@ -7,6 +7,7 @@ import 'metadata_model.dart'; import 'metadata_reader.dart'; import 'metadata_normalizer.dart'; import 'metadata_stability.dart'; +import '../services/metadata_events.dart'; import 'metadata_cache.dart'; import 'file_provider.dart'; import '../utils/artwork_helper.dart'; @@ -189,6 +190,9 @@ class MetadataService { await _db.updateSong(songKey, row); } + // 通知界面局部刷新(收藏/文件列表/歌单等) + MetadataEvents().publish(fileId, row); + final merged = FinalMetadata( title: row['title'] as String? ?? '', artist: row['artist'] as String? ?? '', diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 69bf117..595250a 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,6 +1,8 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/audio_service.dart'; +import '../services/metadata_events.dart'; import '../services/webdav_service.dart'; import '../widgets/magnetic_scroll_physics.dart'; import 'webdav_setup_page.dart'; @@ -163,6 +165,7 @@ class _HomePageState extends BaseState { /// 0 = 我的收藏,1 = 歌单列表(左右滑动切换) int _homeTab = 0; final PageController _homePageController = PageController(); + StreamSubscription? _metaSub; List _playlistSummaries = []; // ========== 滚动控制 ========== @@ -240,6 +243,31 @@ class _HomePageState extends BaseState { } } + /// 元数据补全后,按 path 更新收藏列表里的对应行 + void _onMetadataUpdate(MetadataUpdate update) { + final index = _favorites.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 = _favorites[index]; + safeSetState(() { + _favorites[index] = SongItem( + path: old.path, + fileName: old.fileName, + title: title.isNotEmpty ? title : old.title, + artist: artist.isNotEmpty ? artist : old.artist, + artworkPath: artwork.isNotEmpty ? artwork : old.artworkPath, + sourceTag: old.sourceTag, + metadataState: (title.isNotEmpty || old.title != null) + ? 'success' + : 'unknown', + ); + }); + } void _onScroll() { if (mounted) safeSetState(() {}); } diff --git a/lib/pages/webdav_file_list_page.dart b/lib/pages/webdav_file_list_page.dart index c4b290e..e1d13e4 100644 --- a/lib/pages/webdav_file_list_page.dart +++ b/lib/pages/webdav_file_list_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; // lib/pages/webdav_file_list_page.dart import 'package:flutter/material.dart'; import '../services/webdav_service.dart'; @@ -6,6 +7,7 @@ import '../base/base_state.dart'; import '../controllers/playback_controller.dart'; import '../database/song_database.dart'; import '../services/audio_service.dart'; +import '../services/metadata_events.dart'; import '../widgets/song_artwork.dart'; import '../widgets/add_to_playlist_sheet.dart'; @@ -27,6 +29,7 @@ class _WebDAVFileListPageState extends BaseState { /// path -> 元数据行(songs 表),用于把文件名替换成标签里的标题/艺术家 Map> _metaByPath = {}; String? _lastSyncedSongPath; + StreamSubscription? _metaSub; /// 已收藏的歌曲路径 Set _favoritePaths = {}; @@ -54,6 +57,14 @@ class _WebDAVFileListPageState extends BaseState { super.dispose(); } + /// 元数据补全后,按 path 更新列表行的标题/艺术家/封面 + void _onMetadataUpdate(MetadataUpdate update) { + if (!_items.any((item) => item.path == update.path)) return; + if (!mounted) return; + safeSetState(() { + _metaByPath = {..._metaByPath, update.path: update.row}; + }); + } /// 播放中的歌一旦补全了元数据,立刻刷新列表里对应的那一行 void _onPlaybackChanged() { final path = AudioService().currentSong?.id; diff --git a/lib/services/metadata_events.dart b/lib/services/metadata_events.dart new file mode 100644 index 0000000..67b60ff --- /dev/null +++ b/lib/services/metadata_events.dart @@ -0,0 +1,30 @@ +// lib/services/metadata_events.dart +import 'dart:async'; + +/// 一条元数据更新(path + songs 表的最新行) +class MetadataUpdate { + final String path; + final Map row; + + const MetadataUpdate(this.path, this.row); +} + +/// 元数据更新事件总线。 +/// +/// 之前各列表页靠"返回时重查"来刷新,歌曲补全标签后要退出重进才看得到。 +/// 现在 MetadataService 写库成功后 publish,订阅的页面按 path 局部更新。 +class MetadataEvents { + static final MetadataEvents _instance = MetadataEvents._internal(); + factory MetadataEvents() => _instance; + MetadataEvents._internal(); + + final StreamController _controller = + StreamController.broadcast(); + + Stream get stream => _controller.stream; + + void publish(String path, Map row) { + if (path.isEmpty) return; + _controller.add(MetadataUpdate(path, row)); + } +}