feat(ui): 文件列表收藏红心 + 首页收藏绑定 favorites 表
交互调整(按讨论): - WebDAV 文件列表:整行点击 = 播放(原来的行为),右侧播放按钮改成收藏红心 已收藏实心红,未收藏空心;点击即写/删 favorites 表 - 首页"我的收藏"改为读 favorites 表 + songs 元数据; 从文件列表返回时自动刷新 - 收藏项右侧红心改为真正删除(原来只是内存 removeAt) 修复: - 收藏列表项透明度绑定 _listReveal,首屏为 0 导致条目完全透明 (收藏成功但看不到)。收藏项改为固定 opacity 1.0。 验证(真机): 列表点红心 -> favorites 表 +1 -> 返回首页 -> 收藏区出现该条目
This commit is contained in:
+40
-10
@@ -5,6 +5,7 @@ import '../services/webdav_service.dart';
|
||||
import '../widgets/magnetic_scroll_physics.dart';
|
||||
import 'webdav_setup_page.dart';
|
||||
import 'settings_page.dart';
|
||||
import '../database/song_database.dart';
|
||||
import 'webdav_file_list_page.dart';
|
||||
import '../constants/ui_constants.dart';
|
||||
import '../base/base_state.dart';
|
||||
@@ -236,7 +237,7 @@ class _HomePageState extends BaseState<HomePage> {
|
||||
safeSetState(() => _isLoading = true);
|
||||
try {
|
||||
await WebDAVService.instance.loadCredentials();
|
||||
_favorites = [];
|
||||
await _loadFavoritesFromDb();
|
||||
} catch (e) {
|
||||
_favorites = [];
|
||||
} finally {
|
||||
@@ -244,6 +245,34 @@ class _HomePageState extends BaseState<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 收藏列表直接绑定 favorites 表 + songs 元数据
|
||||
Future<void> _loadFavoritesFromDb() async {
|
||||
final db = SongDatabase();
|
||||
final favRows = await db.getFavorites();
|
||||
final paths = favRows
|
||||
.map((row) => row['song_id'] as String?)
|
||||
.whereType<String>()
|
||||
.toList();
|
||||
final meta = await db.getSongsByPaths(paths);
|
||||
|
||||
final items = <SongItem>[];
|
||||
for (final path in paths) {
|
||||
final row = meta[path];
|
||||
final title = (row?['title'] as String?)?.trim() ?? '';
|
||||
final artist = (row?['artist'] as String?)?.trim() ?? '';
|
||||
final hasTitle = title.isNotEmpty;
|
||||
items.add(SongItem(
|
||||
path: path,
|
||||
fileName: path.split('/').last,
|
||||
title: hasTitle ? title : null,
|
||||
artist: artist.isNotEmpty ? artist : null,
|
||||
sourceTag: '收藏',
|
||||
metadataState: hasTitle ? 'success' : 'unknown',
|
||||
));
|
||||
}
|
||||
_favorites = items;
|
||||
}
|
||||
|
||||
void _playSong(SongItem song) async {
|
||||
try {
|
||||
// 先校验登录,再开始播放。
|
||||
@@ -416,6 +445,7 @@ class _HomePageState extends BaseState<HomePage> {
|
||||
builder: (_) => const WebDAVFileListPage(),
|
||||
),
|
||||
);
|
||||
await _loadFavoritesFromDb();
|
||||
_resetSnapState();
|
||||
safeSetState(() {});
|
||||
} else {
|
||||
@@ -674,12 +704,11 @@ class _HomePageState extends BaseState<HomePage> {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final song = _favorites[index];
|
||||
final itemProgress =
|
||||
(_listReveal * 2 - index / 5)
|
||||
.clamp(0.0, 1.0);
|
||||
|
||||
// 收藏项不做滚动渐显:_listReveal 在首屏为 0,
|
||||
// 会让条目完全透明(看起来"收藏了但不显示")。
|
||||
return AnimatedOpacity(
|
||||
opacity: itemProgress,
|
||||
opacity: 1.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -713,13 +742,14 @@ class _HomePageState extends BaseState<HomePage> {
|
||||
trailing: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.favorite,
|
||||
color: Color(0xFFB8D4D0),
|
||||
color: Color(0xFFE05C5C),
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
safeSetState(() {
|
||||
_favorites.removeAt(index);
|
||||
});
|
||||
onPressed: () async {
|
||||
await SongDatabase()
|
||||
.removeFavorite(song.path);
|
||||
await _loadFavoritesFromDb();
|
||||
if (mounted) safeSetState(() {});
|
||||
},
|
||||
),
|
||||
onTap: () => _playSong(song),
|
||||
|
||||
@@ -27,6 +27,9 @@ class _WebDAVFileListPageState extends BaseState<WebDAVFileListPage> {
|
||||
Map<String, Map<String, dynamic>> _metaByPath = {};
|
||||
String? _lastSyncedSongPath;
|
||||
|
||||
/// 已收藏的歌曲路径
|
||||
Set<String> _favoritePaths = {};
|
||||
|
||||
// 安全解码
|
||||
String _safeDecode(String input) {
|
||||
try {
|
||||
@@ -69,6 +72,30 @@ class _WebDAVFileListPageState extends BaseState<WebDAVFileListPage> {
|
||||
});
|
||||
}
|
||||
|
||||
/// 收藏 / 取消收藏:整行点击是播放,右侧红心专门做收藏
|
||||
Future<void> _toggleFavorite(String path) async {
|
||||
final wasFavorite = _favoritePaths.contains(path);
|
||||
try {
|
||||
await SongDatabase().toggleFavorite(path);
|
||||
if (!mounted) return;
|
||||
safeSetState(() {
|
||||
final next = {..._favoritePaths};
|
||||
if (wasFavorite) {
|
||||
next.remove(path);
|
||||
} else {
|
||||
next.add(path);
|
||||
}
|
||||
_favoritePaths = next;
|
||||
});
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('收藏失败: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 加载目录
|
||||
// ============================================================
|
||||
@@ -94,10 +121,22 @@ class _WebDAVFileListPageState extends BaseState<WebDAVFileListPage> {
|
||||
debugPrint('⚠️ [WebDAVList] 批量读取元数据失败: $e');
|
||||
}
|
||||
|
||||
var favorites = <String>{};
|
||||
try {
|
||||
final favRows = await SongDatabase().getFavorites();
|
||||
favorites = favRows
|
||||
.map((row) => row['song_id'] as String?)
|
||||
.whereType<String>()
|
||||
.toSet();
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ [WebDAVList] 读取收藏失败: $e');
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
safeSetState(() {
|
||||
_items = items;
|
||||
_metaByPath = meta;
|
||||
_favoritePaths = favorites;
|
||||
_isLoading = false;
|
||||
});
|
||||
// 如果当前正在播的歌就在这个目录里,补一次同步
|
||||
@@ -382,8 +421,16 @@ class _WebDAVFileListPageState extends BaseState<WebDAVFileListPage> {
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.play_arrow, color: Color(0xFFB8D4D0)),
|
||||
onPressed: () => _playSong(item),
|
||||
tooltip: '收藏',
|
||||
icon: Icon(
|
||||
_favoritePaths.contains(item.path)
|
||||
? Icons.favorite
|
||||
: Icons.favorite_border,
|
||||
color: _favoritePaths.contains(item.path)
|
||||
? const Color(0xFFE05C5C)
|
||||
: Colors.grey,
|
||||
),
|
||||
onPressed: () => _toggleFavorite(item.path),
|
||||
),
|
||||
onTap: () => _playSong(item),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user