import 'dart:ui'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constants/app_constants.dart'; import '../../config/app_config.dart'; import '../../controllers/history_controller.dart'; import '../../services/network_listener_service.dart'; import '../../utils/http/poetry_api.dart'; import '../../services/get/theme_controller.dart'; import 'collect_notes.dart'; import 'liked_poetry_manager.dart'; /// 时间: 2026-03-26 /// 功能: 全部收藏列表组件 /// 介绍: 同时展示点赞诗词和笔记,支持多种卡片类型 /// 最新变化: 新建组件,统一展示所有收藏内容 /// 卡片类型枚举 enum CardType { like, note } /// 统一卡片数据模型 class UnifiedCard { final CardType type; final dynamic data; final DateTime time; UnifiedCard({required this.type, required this.data, required this.time}); } class AllListPage extends StatefulWidget { final bool? initialSortByTime; final bool? initialLikesFirst; const AllListPage({ super.key, this.initialSortByTime, this.initialLikesFirst, }); @override State createState() => AllListPageState(); } class AllListPageState extends State { List _cards = []; bool _isLoading = false; bool _sortByTime = true; // true: 按时间排序, false: 按分类排序 bool _likesFirst = true; // true: 点赞在前, false: 笔记在前 StreamSubscription? _networkSubscription; final ThemeController _themeController = Get.find(); @override void initState() { super.initState(); // 应用初始化排序参数 if (widget.initialSortByTime != null) { _sortByTime = widget.initialSortByTime!; } if (widget.initialLikesFirst != null) { _likesFirst = widget.initialLikesFirst!; } _loadAllData(); _listenToNetworkEvents(); } @override void dispose() { _networkSubscription?.cancel(); super.dispose(); } // 监听网络事件,实时更新列表 void _listenToNetworkEvents() { _networkSubscription = NetworkListenerService().eventStream.listen((event) { if (event.type == NetworkEventType.like || event.type == NetworkEventType.unlike || event.type == NetworkEventType.noteUpdate) { debugPrint('AllListPage: 收到事件 ${event.type},刷新列表'); _loadAllData(); } }); } // 加载所有数据 Future _loadAllData() async { if (_isLoading) return; setState(() { _isLoading = true; }); try { final List allCards = []; // 并行加载点赞和笔记数据 final results = await Future.wait([ HistoryController.getLikedHistory(), HistoryController.getNotes(), ]); // 处理点赞数据 final likedList = results[0]; for (final item in likedList) { try { final poetry = PoetryData.fromJson(item); final timeStr = item['time'] as String? ?? ''; allCards.add( UnifiedCard( type: CardType.like, data: poetry, time: DateTime.tryParse(timeStr) ?? DateTime.now(), ), ); } catch (e) { debugPrint('解析点赞数据失败: $e'); } } // 处理笔记数据 final notesList = results[1]; for (final note in notesList) { final timeStr = note['time'] as String? ?? ''; allCards.add( UnifiedCard( type: CardType.note, data: note, time: DateTime.tryParse(timeStr) ?? DateTime.now(), ), ); } // 根据状态进行排序 if (_sortByTime) { // 按时间排序(最新的在前) allCards.sort((a, b) => b.time.compareTo(a.time)); } else { // 按分类排序,点赞在前,笔记在后 allCards.sort((a, b) { // 先按类型排序 final typeCompare = a.type.index.compareTo(b.type.index); if (typeCompare != 0) return typeCompare; // 同类型按时间排序 return b.time.compareTo(a.time); }); // 如果笔记在前,则重新排序 if (!_likesFirst) { allCards.sort((a, b) { if (a.type != b.type) { // 笔记在前,笔记类型排在前面 return a.type == CardType.note ? -1 : 1; } return b.time.compareTo(a.time); }); } } if (mounted) { setState(() { _cards = allCards; _isLoading = false; }); } } catch (e) { debugPrint('加载全部数据失败: $e'); if (mounted) { setState(() { _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Obx(() { final isDark = _themeController.isDarkMode; if (_isLoading && _cards.isEmpty) { return Center( child: CircularProgressIndicator( color: isDark ? Colors.white : AppConstants.primaryColor, ), ); } if (_cards.isEmpty) { return _buildEmptyState(isDark); } return RefreshIndicator( onRefresh: _loadAllData, child: ListView.separated( // 添加底部内边距,让内容延伸到导航栏下方,实现玻璃效果 padding: EdgeInsets.fromLTRB( 16, 8, 16, AppConfig.liquidGlassTotalHeight + 16, ), itemCount: _cards.length + 1, separatorBuilder: (context, index) { if (index == _cards.length) return const SizedBox.shrink(); return Container( height: 1, color: isDark ? Colors.white.withValues(alpha: 0.1) : Colors.black.withValues(alpha: 0.1), margin: const EdgeInsets.symmetric(vertical: 4), ); }, itemBuilder: (context, index) { if (index == _cards.length) { return _buildBottomIndicator(isDark); } return _buildCard(_cards[index], isDark); }, ), ); }); } Widget _buildBottomIndicator(bool isDark) { return Container( padding: const EdgeInsets.symmetric(vertical: 24), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 40, height: 1, color: isDark ? Colors.grey[700] : Colors.grey[300], ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( '到底了', style: TextStyle( fontSize: 12, color: isDark ? Colors.grey[500] : Colors.grey[400], ), ), ), Container( width: 40, height: 1, color: isDark ? Colors.grey[700] : Colors.grey[300], ), ], ), ); } Widget _buildEmptyState(bool isDark) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.inbox_outlined, size: 64, color: isDark ? Colors.grey[600] : Colors.grey[400], ), const SizedBox(height: 16), Text( '暂无收藏内容', style: TextStyle( fontSize: 16, color: isDark ? Colors.grey[400] : Colors.grey[600], ), ), const SizedBox(height: 8), Text( '点赞诗词或创建笔记后将显示在这里', style: TextStyle( fontSize: 14, color: isDark ? Colors.grey[500] : Colors.grey[500], ), ), ], ), ); } Widget _buildCard(UnifiedCard card, bool isDark) { switch (card.type) { case CardType.like: return _buildLikeCard(card.data as PoetryData, isDark); case CardType.note: return _buildNoteCard(card.data as Map, isDark); } } Widget _buildLikeCard(PoetryData poetry, bool isDark) { return Container( margin: const EdgeInsets.only(bottom: 0), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, borderRadius: BorderRadius.circular(10), border: Border.all( color: AppConstants.primaryColor.withValues(alpha: 0.2), width: 1, ), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withValues(alpha: 0.2) : Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 1), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(10, 4, 0, 4), child: Row( children: [ if (poetry.url.isNotEmpty) Expanded( child: Text( poetry.url, style: TextStyle( fontSize: 11, color: isDark ? Colors.grey[400] : Colors.grey[600], ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), if (poetry.url.isEmpty) const Spacer(), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: AppConstants.primaryColor.withValues(alpha: 0.1), borderRadius: const BorderRadius.only( topRight: Radius.circular(10), bottomLeft: Radius.circular(10), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.favorite, size: 12, color: AppConstants.primaryColor, ), const SizedBox(width: 3), Text( '点赞诗词', style: TextStyle( fontSize: 11, color: AppConstants.primaryColor, fontWeight: FontWeight.w500, ), ), ], ), ), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(10, 4, 10, 4), child: Text( poetry.name, style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black87, height: 1.3, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), Padding( padding: const EdgeInsets.fromLTRB(8, 0, 8, 6), child: Row( children: [ TextButton.icon( onPressed: () => _showPoetryDetails(poetry), icon: Icon( Icons.visibility, size: 14, color: AppConstants.primaryColor, ), label: Text( '查看详情', style: TextStyle( fontSize: 12, color: AppConstants.primaryColor, ), ), ), const Spacer(), TextButton.icon( onPressed: () => _createNoteFromPoetry(poetry), icon: Icon( Icons.note_add, size: 14, color: isDark ? Colors.orange[300] : Colors.orange[700], ), label: Text( '创建笔记', style: TextStyle( fontSize: 12, color: isDark ? Colors.orange[300] : Colors.orange[700], ), ), ), TextButton.icon( onPressed: () => _removeLikedPoetry(poetry.id.toString()), icon: Icon( Icons.favorite_border, size: 14, color: isDark ? Colors.grey[400] : Colors.grey[600], ), label: Text( '取消', style: TextStyle( fontSize: 12, color: isDark ? Colors.grey[400] : Colors.grey[600], ), ), ), ], ), ), ], ), ); } Widget _buildNoteCard(Map note, bool isDark) { final title = note['title'] as String? ?? ''; final content = note['content'] as String? ?? ''; final category = note['category'] as String? ?? ''; final isPinned = note['isPinned'] == true; final isLocked = note['isLocked'] == true; String displayText; bool hasTitle = title.isNotEmpty; bool hasCategory = category.isNotEmpty && category != '未分类'; if (hasTitle) { displayText = title; } else if (hasCategory) { displayText = '[$category]'; } else { displayText = content; } return Container( margin: const EdgeInsets.only(bottom: 0), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withValues(alpha: 0.2) : Colors.black.withValues(alpha: 0.06), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InkWell( onTap: () => _handleNoteTap(note, isLocked), borderRadius: BorderRadius.circular(10), child: Padding( padding: const EdgeInsets.fromLTRB(10, 28, 10, 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( displayText, style: TextStyle( fontSize: hasTitle ? 14 : 13, fontWeight: hasTitle ? FontWeight.w600 : FontWeight.normal, color: isDark ? Colors.white : Colors.black87, height: 1.4, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( children: [ Text( '${note['charCount'] ?? displayText.length} 字', style: TextStyle( fontSize: 11, color: isDark ? Colors.grey[400] : Colors.grey[500], ), ), if (hasCategory) ...[ const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 4, vertical: 1, ), decoration: BoxDecoration( color: isDark ? Colors.grey[800] : Colors.grey[200], borderRadius: BorderRadius.circular(3), ), child: Text( category, style: TextStyle( fontSize: 9, color: isDark ? Colors.grey[400] : Colors.grey[600], ), ), ), ], ], ), ], ), ), ), ], ), Positioned( top: 0, right: 0, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.orange.withValues(alpha: 0.1), borderRadius: const BorderRadius.only( topRight: Radius.circular(10), bottomLeft: Radius.circular(10), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.note_outlined, size: 12, color: isDark ? Colors.orange[300] : Colors.orange[700], ), const SizedBox(width: 3), Text( '笔记', style: TextStyle( fontSize: 11, color: isDark ? Colors.orange[300] : Colors.orange[700], fontWeight: FontWeight.w500, ), ), if (isPinned) ...[ const SizedBox(width: 6), Icon( Icons.push_pin, size: 10, color: isDark ? Colors.orange[300] : Colors.orange[700], ), ], if (isLocked) ...[ const SizedBox(width: 6), Icon( Icons.lock, size: 10, color: isDark ? Colors.orange[300] : Colors.orange[700], ), ], ], ), ), ), if (isLocked) Positioned.fill( child: GestureDetector( onTap: () => _handleNoteTap(note, isLocked), child: ClipRRect( borderRadius: BorderRadius.circular(10), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Container( color: (isDark ? Colors.black : Colors.white).withValues( alpha: 0.4, ), child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.lock, size: 16, color: isDark ? Colors.orange[300] : Colors.orange[700], ), const SizedBox(width: 6), Text( '已锁定', style: TextStyle( fontSize: 13, color: isDark ? Colors.orange[300] : Colors.orange[700], fontWeight: FontWeight.w500, ), ), ], ), ), ), ), ), ), ), ], ), ); } // 显示诗词详情 - 调用公共方法 Future _showPoetryDetails(PoetryData poetry) async { await LikedPoetryManager.showPoetryDetails(context, poetry); } // 移除点赞 Future _removeLikedPoetry(String poetryId) async { try { await HistoryController.removeLikedPoetry(poetryId); await _loadAllData(); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('已取消点赞'))); } } catch (e) { debugPrint('取消点赞失败: $e'); } } // 从诗词创建笔记 Future _createNoteFromPoetry(PoetryData poetry) async { try { final title = poetry.url.isNotEmpty ? poetry.url : '诗词笔记'; final category = poetry.alias.isNotEmpty ? poetry.alias : '诗词'; final content = poetry.name; final noteId = await HistoryController.saveNote( title: title, content: content, category: category, ); if (noteId != null) { NetworkListenerService().sendSuccessEvent( NetworkEventType.noteUpdate, data: noteId, ); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('已创建笔记'))); } } } catch (e) { debugPrint('创建笔记失败: $e'); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('创建笔记失败: $e'))); } } } // 处理笔记点击 Future _handleNoteTap(Map note, bool isLocked) async { final noteId = note['id'] as String?; if (noteId == null) return; if (isLocked) { final password = await _showPasswordInputDialog(noteId); if (password == null) return; final isValid = await HistoryController.verifyNotePassword( noteId, password, ); if (!isValid) { if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('密码错误'))); } return; } } if (mounted) { Navigator.of(context) .push( MaterialPageRoute( builder: (_) => CollectNotesPage(noteId: noteId), ), ) .then((_) => _loadAllData()); } } // 密码输入对话框 Future _showPasswordInputDialog(String noteId) async { final TextEditingController passwordController = TextEditingController(); return showDialog( context: context, builder: (context) => AlertDialog( title: const Row( children: [ Icon(Icons.lock, size: 20), SizedBox(width: 8), Text('输入密码'), ], ), content: TextField( controller: passwordController, obscureText: true, autofocus: true, decoration: const InputDecoration( hintText: '请输入访问密码', border: OutlineInputBorder(), ), onSubmitted: (value) => Navigator.of(context).pop(value), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(null), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.of(context).pop(passwordController.text), child: const Text('确定'), ), ], ), ); } // 公开的排序方法,供外部调用 void sortByTime(bool sortByTime) { if (_sortByTime != sortByTime) { setState(() { _sortByTime = sortByTime; _applySorting(); }); } } void toggleLikesFirst() { setState(() { _likesFirst = !_likesFirst; _applySorting(); }); } // 应用排序逻辑 void _applySorting() { if (_sortByTime) { // 按时间排序(最新的在前) _cards.sort((a, b) => b.time.compareTo(a.time)); } else { // 按分类排序,点赞在前,笔记在后 _cards.sort((a, b) { // 先按类型排序 final typeCompare = a.type.index.compareTo(b.type.index); if (typeCompare != 0) return typeCompare; // 同类型按时间排序 return b.time.compareTo(a.time); }); // 如果笔记在前,则重新排序 if (!_likesFirst) { _cards.sort((a, b) { if (a.type != b.type) { // 笔记在前,笔记类型排在前面 return a.type == CardType.note ? -1 : 1; } return b.time.compareTo(a.time); }); } } } }