/// 时间: 2025-03-22 /// 功能: 点赞足迹页面 /// 介绍: 显示用户点赞的诗词列表,支持删除操作 /// 最新变化: 新增点赞足迹管理功能 import 'dart:async' show StreamSubscription; import 'package:flutter/material.dart'; import 'package:flutter_application_2/controllers/history_controller.dart'; import '../../../constants/app_constants.dart'; import '../../../utils/http/poetry_api.dart'; import '../../../services/network_listener_service.dart'; import '../home/home_components.dart'; /// 点赞足迹页面 class FootprintPage extends StatefulWidget { const FootprintPage({super.key}); @override State createState() => _FootprintPageState(); } class _FootprintPageState extends State with NetworkListenerMixin { List _likedPoetryList = []; bool _isLoading = false; String _errorMessage = ''; StreamSubscription? _networkSubscription; @override void initState() { super.initState(); _loadLikedPoetry(); _setupNetworkListener(); } void _setupNetworkListener() { // 监听网络事件 _networkSubscription = networkEvents.listen((event) { if (mounted) { switch (event.type) { case NetworkEventType.like: case NetworkEventType.unlike: // 当有点赞或取消点赞事件时,刷新列表 _loadLikedPoetry(); break; case NetworkEventType.refresh: // 刷新事件 _loadLikedPoetry(); break; default: break; } } }); } @override void dispose() { _networkSubscription?.cancel(); super.dispose(); } Future _loadLikedPoetry() async { setState(() { _isLoading = true; _errorMessage = ''; }); try { // 从SQLite加载点赞列表 final likedListJson = await HistoryController.getLikedHistory(); final likedList = likedListJson .map((item) => PoetryData.fromJson(item)) .toList(); if (mounted) { setState(() { _likedPoetryList = likedList; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _errorMessage = '加载点赞列表失败'; _isLoading = false; }); } } } Future _showPoetryDetails(PoetryData poetry) async { try { // 从SQLite获取完整数据 final likedList = await HistoryController.getLikedHistory(); // 查找对应的诗词数据,优先使用存储的完整数据 Map poetryData; try { poetryData = likedList.firstWhere( (item) => item['id'].toString() == poetry.id.toString(), orElse: () => poetry.toJson(), ); } catch (e) { poetryData = poetry.toJson(); } if (mounted) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => Container( height: MediaQuery.of(context).size.height * 0.8, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), ), child: Column( children: [ // 顶部拖拽指示器 Container( margin: const EdgeInsets.only(top: 8), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), // 标题栏 Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon( Icons.article, color: AppConstants.primaryColor, size: 24, ), const SizedBox(width: 8), const Text( '诗词详情', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const Spacer(), // 加载图标(不可点击) GestureDetector( onTap: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('开发中'), duration: Duration(seconds: 1), ), ); }, child: Container( padding: const EdgeInsets.all(8), child: Icon( Icons.more_horiz, color: Colors.grey[600], size: 20, ), ), ), const SizedBox(width: 8), // 关闭按钮 IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), color: Colors.grey[600], ), ], ), ), // 内容区域 Expanded( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 诗词名称 - 单独一行 _buildDetailCard( '精选诗句', poetryData['name'] ?? poetry.name, Icons.format_quote, AppConstants.primaryColor, ), // 时间和标签 - 同一行 Row( children: [ // 点赞时间 if (poetryData['liked_time'] != null) ...[ Expanded( child: _buildDetailCard( '点赞时间', '${poetryData['liked_date'] ?? ''} ${poetryData['liked_time'] ?? ''}', Icons.favorite, Colors.orange, ), ), ], // 标签 if (poetryData['keywords'] != null && poetryData['keywords'] .toString() .isNotEmpty) ...[ const SizedBox(width: 8), Expanded( child: _buildDetailCard( '标签', poetryData['keywords'], Icons.local_offer, Colors.green, ), ), ], ], ), // 朝代和出处 - 同一行 Row( children: [ // 朝代 if (poetryData['alias'] != null && poetryData['alias'].toString().isNotEmpty) ...[ Expanded( child: _buildDetailCard( '朝代', poetryData['alias'], Icons.history, Colors.blue, ), ), ], // 出处 if (poetryData['url'] != null && poetryData['url'].toString().isNotEmpty) ...[ const SizedBox(width: 8), Expanded( child: _buildDetailCard( '出处', poetryData['url'], Icons.link, Colors.teal, ), ), ], ], ), // 译文/介绍 - 单独一行 if (poetryData['introduce'] != null && poetryData['introduce'].toString().isNotEmpty) _buildDetailCard( '译文/介绍', poetryData['introduce'], Icons.translate, AppConstants.primaryColor, ), // 原文 - 单独一行 if (poetryData['drtime'] != null && poetryData['drtime'].toString().isNotEmpty) _buildDetailCard( '原文', poetryData['drtime'], Icons.menu_book, Colors.purple, ), const SizedBox(height: 20), ], ), ), ), ], ), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('加载详情失败: $e'), backgroundColor: AppConstants.errorColor, duration: const Duration(seconds: 2), ), ); } } } Widget _buildDetailCard( String title, String content, IconData icon, Color accentColor, ) { return Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: accentColor.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: accentColor.withValues(alpha: 0.2), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题栏 Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: accentColor.withValues(alpha: 0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Row( children: [ Icon(icon, size: 18, color: accentColor), const SizedBox(width: 8), Text( title, style: TextStyle( fontSize: 14, color: accentColor, fontWeight: FontWeight.w600, ), ), ], ), ), // 内容区域 Padding( padding: const EdgeInsets.all(12), child: Text( content, style: const TextStyle( fontSize: 15, color: Colors.black87, height: 1.5, ), ), ), ], ), ); } Future _removeLikedPoetry(String poetryId) async { try { // 从SQLite中删除 await HistoryController.removeLikedPoetry(poetryId); // 重新加载列表 await _loadLikedPoetry(); if (mounted) { PoetryStateManager.showSnackBar( context, '已取消点赞', backgroundColor: AppConstants.successColor, duration: const Duration(milliseconds: 200), ); } } catch (e) { if (mounted) { PoetryStateManager.showSnackBar( context, '操作失败,请重试', backgroundColor: AppConstants.errorColor, duration: const Duration(milliseconds: 200), ); } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[50], appBar: AppBar( title: const Text('点赞足迹'), backgroundColor: AppConstants.primaryColor, foregroundColor: Colors.white, elevation: 0, ), body: _buildBody(), ); } Widget _buildBody() { if (_isLoading) { return const Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(AppConstants.primaryColor), ), ); } if (_errorMessage.isNotEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: AppConstants.errorColor), const SizedBox(height: 16), Text( _errorMessage, style: TextStyle(fontSize: 16, color: AppConstants.errorColor), ), const SizedBox(height: 16), ElevatedButton( onPressed: _loadLikedPoetry, style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, foregroundColor: Colors.white, ), child: const Text('重试'), ), ], ), ); } if (_likedPoetryList.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.favorite_border, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( '暂无点赞记录', style: TextStyle(fontSize: 16, color: Colors.grey[600]), ), const SizedBox(height: 8), Text( '去主页点赞喜欢的诗词吧', style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ], ), ); } return RefreshIndicator( onRefresh: _loadLikedPoetry, child: Column( children: [ // 添加不可点击的刷新按钮 if (_likedPoetryList.isNotEmpty) ...[ Container( margin: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Row( children: [ Icon( Icons.refresh, size: 20, color: AppConstants.primaryColor, ), const SizedBox(width: 8), Text( '下拉刷新列表', style: TextStyle( fontSize: 14, color: AppConstants.primaryColor, fontWeight: FontWeight.w500, ), ), const Spacer(), Text( '共 ${_likedPoetryList.length} 条记录', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), ], Expanded( child: ListView.builder( padding: const EdgeInsets.all(16), itemCount: _likedPoetryList.length, itemBuilder: (context, index) { final poetry = _likedPoetryList[index]; return _buildLikedPoetryCard(poetry); }, ), ), ], ), ); } Widget _buildLikedPoetryCard(PoetryData poetry) { return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // URL字段 (出处) Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Text( "出处: ${poetry.url}", style: const TextStyle( fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black, fontStyle: FontStyle.italic, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), // Name字段 (精选诗句) - 与主页样式一致 Container( width: double.infinity, padding: const EdgeInsets.all(16), margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppConstants.primaryColor.withValues(alpha: 0.1), AppConstants.primaryColor.withValues(alpha: 0.05), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), border: Border.all( color: AppConstants.primaryColor.withValues(alpha: 0.2), width: 1, ), boxShadow: [ BoxShadow( color: AppConstants.primaryColor.withValues(alpha: 0.1), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.format_quote, color: AppConstants.primaryColor, size: 20, ), const SizedBox(width: 8), Expanded( child: Text( '精选诗句', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: AppConstants.primaryColor, ), ), ), ], ), const SizedBox(height: 8), Text( poetry.name, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.4, ), textAlign: TextAlign.center, ), ], ), ), // drtime字段 (原文) if (poetry.drtime.isNotEmpty) ...[ const SizedBox(height: 12), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFF8F8F8), borderRadius: BorderRadius.circular(8), ), child: Text( poetry.drtime, style: const TextStyle( fontSize: 14, height: 1.6, color: Colors.black87, ), maxLines: 3, overflow: TextOverflow.ellipsis, ), ), ), ], // 操作按钮 Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () => _removeLikedPoetry(poetry.id.toString()), icon: const Icon(Icons.favorite_border, size: 18), label: const Text('取消点赞'), style: OutlinedButton.styleFrom( foregroundColor: AppConstants.errorColor, side: BorderSide(color: AppConstants.errorColor), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( onPressed: () { _showPoetryDetails(poetry); }, icon: const Icon(Icons.visibility, size: 18), label: const Text('查看详情'), style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, foregroundColor: Colors.white, elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ], ), ), ], ), ); } }