feat(metadata): 元数据补全后主动推送界面刷新
之前标签补全后,收藏/文件列表要退出重进才显示新信息。 - 新增 MetadataEvents 事件总线(MetadataUpdate: path + songs 行) - MetadataService 写库成功后 publish - 首页收藏列表订阅:按 path 替换对应 SongItem(标题/艺术家/封面) - WebDAV 文件列表订阅:按 path 更新 _metaByPath 效果:歌曲播完标签抓取完成后,列表里的那一行自动变成标签信息, 不需要返回重进。
This commit is contained in:
@@ -7,6 +7,7 @@ import 'metadata_model.dart';
|
|||||||
import 'metadata_reader.dart';
|
import 'metadata_reader.dart';
|
||||||
import 'metadata_normalizer.dart';
|
import 'metadata_normalizer.dart';
|
||||||
import 'metadata_stability.dart';
|
import 'metadata_stability.dart';
|
||||||
|
import '../services/metadata_events.dart';
|
||||||
import 'metadata_cache.dart';
|
import 'metadata_cache.dart';
|
||||||
import 'file_provider.dart';
|
import 'file_provider.dart';
|
||||||
import '../utils/artwork_helper.dart';
|
import '../utils/artwork_helper.dart';
|
||||||
@@ -189,6 +190,9 @@ class MetadataService {
|
|||||||
await _db.updateSong(songKey, row);
|
await _db.updateSong(songKey, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通知界面局部刷新(收藏/文件列表/歌单等)
|
||||||
|
MetadataEvents().publish(fileId, row);
|
||||||
|
|
||||||
final merged = FinalMetadata(
|
final merged = FinalMetadata(
|
||||||
title: row['title'] as String? ?? '',
|
title: row['title'] as String? ?? '',
|
||||||
artist: row['artist'] as String? ?? '',
|
artist: row['artist'] as String? ?? '',
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../services/audio_service.dart';
|
import '../services/audio_service.dart';
|
||||||
|
import '../services/metadata_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';
|
||||||
@@ -163,6 +165,7 @@ class _HomePageState extends BaseState<HomePage> {
|
|||||||
/// 0 = 我的收藏,1 = 歌单列表(左右滑动切换)
|
/// 0 = 我的收藏,1 = 歌单列表(左右滑动切换)
|
||||||
int _homeTab = 0;
|
int _homeTab = 0;
|
||||||
final PageController _homePageController = PageController();
|
final PageController _homePageController = PageController();
|
||||||
|
StreamSubscription<MetadataUpdate>? _metaSub;
|
||||||
List<PlaylistSummary> _playlistSummaries = [];
|
List<PlaylistSummary> _playlistSummaries = [];
|
||||||
|
|
||||||
// ========== 滚动控制 ==========
|
// ========== 滚动控制 ==========
|
||||||
@@ -240,6 +243,31 @@ class _HomePageState extends BaseState<HomePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 元数据补全后,按 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() {
|
void _onScroll() {
|
||||||
if (mounted) safeSetState(() {});
|
if (mounted) safeSetState(() {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
// lib/pages/webdav_file_list_page.dart
|
// lib/pages/webdav_file_list_page.dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/webdav_service.dart';
|
import '../services/webdav_service.dart';
|
||||||
@@ -6,6 +7,7 @@ import '../base/base_state.dart';
|
|||||||
import '../controllers/playback_controller.dart';
|
import '../controllers/playback_controller.dart';
|
||||||
import '../database/song_database.dart';
|
import '../database/song_database.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';
|
||||||
import '../widgets/add_to_playlist_sheet.dart';
|
import '../widgets/add_to_playlist_sheet.dart';
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ class _WebDAVFileListPageState extends BaseState<WebDAVFileListPage> {
|
|||||||
/// path -> 元数据行(songs 表),用于把文件名替换成标签里的标题/艺术家
|
/// path -> 元数据行(songs 表),用于把文件名替换成标签里的标题/艺术家
|
||||||
Map<String, Map<String, dynamic>> _metaByPath = {};
|
Map<String, Map<String, dynamic>> _metaByPath = {};
|
||||||
String? _lastSyncedSongPath;
|
String? _lastSyncedSongPath;
|
||||||
|
StreamSubscription<MetadataUpdate>? _metaSub;
|
||||||
|
|
||||||
/// 已收藏的歌曲路径
|
/// 已收藏的歌曲路径
|
||||||
Set<String> _favoritePaths = {};
|
Set<String> _favoritePaths = {};
|
||||||
@@ -54,6 +57,14 @@ class _WebDAVFileListPageState extends BaseState<WebDAVFileListPage> {
|
|||||||
super.dispose();
|
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() {
|
void _onPlaybackChanged() {
|
||||||
final path = AudioService().currentSong?.id;
|
final path = AudioService().currentSong?.id;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// lib/services/metadata_events.dart
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
/// 一条元数据更新(path + songs 表的最新行)
|
||||||
|
class MetadataUpdate {
|
||||||
|
final String path;
|
||||||
|
final Map<String, dynamic> 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<MetadataUpdate> _controller =
|
||||||
|
StreamController<MetadataUpdate>.broadcast();
|
||||||
|
|
||||||
|
Stream<MetadataUpdate> get stream => _controller.stream;
|
||||||
|
|
||||||
|
void publish(String path, Map<String, dynamic> row) {
|
||||||
|
if (path.isEmpty) return;
|
||||||
|
_controller.add(MetadataUpdate(path, row));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user