/// 时间: 2025-03-22 /// 功能: 诗词页面通用组件和工具函数 /// 介绍: 从 home_page.dart 和 home_part.dart 中提取的公共组件,用于代码复用和简化 import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import '../../constants/app_constants.dart'; import '../../utils/http/poetry_api.dart'; import 'home-load.dart'; /// 加载状态组件 class LoadingWidget extends StatelessWidget { const LoadingWidget({super.key}); @override Widget build(BuildContext context) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( AppConstants.primaryColor, ), ), SizedBox(height: 16), Text( '加载中...', style: TextStyle(fontSize: 16, color: const Color(0xFF757575)), ), ], ), ); } } /// 截图和分享工具类 class ShareImageUtils { /// 将 Widget 截图并分享 static Future captureAndShare( BuildContext context, GlobalKey repaintKey, { String? subject, }) async { try { PoetryStateManager.showSnackBar( context, '正在生成图片...', backgroundColor: AppConstants.primaryColor, ); final boundary = repaintKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) { if (context.mounted) { PoetryStateManager.showSnackBar( context, '生成图片失败', backgroundColor: AppConstants.errorColor, ); } return; } ui.Image image = await boundary.toImage(pixelRatio: 3.0); ByteData? byteData = await image.toByteData( format: ui.ImageByteFormat.png, ); if (byteData == null) { if (context.mounted) { PoetryStateManager.showSnackBar( context, '生成图片失败', backgroundColor: AppConstants.errorColor, ); } return; } Uint8List pngBytes = byteData.buffer.asUint8List(); final directory = await getTemporaryDirectory(); final file = File( '${directory.path}/poetry_${DateTime.now().millisecondsSinceEpoch}.png', ); await file.writeAsBytes(pngBytes); final result = await Share.shareXFiles([ XFile(file.path), ], subject: subject ?? '诗词分享'); if (result.status == ShareResultStatus.success) { if (context.mounted) { PoetryStateManager.showSnackBar( context, '分享成功!', backgroundColor: AppConstants.successColor, ); } } } catch (e) { if (context.mounted) { PoetryStateManager.showSnackBar( context, '分享失败:${e.toString().substring(0, e.toString().length > 50 ? 50 : e.toString().length)}', backgroundColor: AppConstants.errorColor, ); } } } } /// 错误状态组件 class CustomErrorWidget extends StatelessWidget { final String errorMessage; final VoidCallback onRetry; const CustomErrorWidget({ super.key, required this.errorMessage, required this.onRetry, }); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( errorMessage, style: const TextStyle(fontSize: 16, color: Colors.grey), textAlign: TextAlign.center, ), const SizedBox(height: 16), ElevatedButton( onPressed: onRetry, style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, ), child: const Text('重试'), ), ], ), ); } } /// 空状态组件 class EmptyWidget extends StatelessWidget { const EmptyWidget({super.key}); @override Widget build(BuildContext context) { return const Center( child: Text('暂无诗词内容', style: TextStyle(fontSize: 16, color: Colors.grey)), ); } } /// 诗词状态管理工具类 class PoetryStateManager { static void setLoadingState(VoidCallback setState, bool loading) { setState(); } static void setErrorState(VoidCallback setState, String error) { setState(); } static void setSuccessState(VoidCallback setState, PoetryData data) { setState(); } static String formatErrorMessage(String error) { return error.toString().contains('HttpException') ? error.toString().replaceAll('HttpException: ', '') : '获取诗词失败'; } static void showSnackBar( BuildContext context, String message, { Color? backgroundColor, Duration? duration, }) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: backgroundColor ?? AppConstants.successColor, duration: duration ?? const Duration(seconds: 2), ), ); } static void triggerHapticFeedback() { HapticFeedback.lightImpact(); } static void triggerHapticFeedbackMedium() { HapticFeedback.mediumImpact(); } } /// 诗词数据工具类 class PoetryDataUtils { static List extractKeywords(PoetryData? poetryData) { return poetryData?.keywordList ?? []; } static String getStarDisplay(PoetryData? poetryData) { return poetryData?.starDisplay ?? ''; } static String generateStars(int? starCount) { if (starCount == null) return ''; final count = starCount > 5 ? 5 : starCount; if (count == 5) { return ' 🌟$count'; } else { return ' ⭐$count'; } } static String generateLikeText(int? likeCount) { if (likeCount == null) return ''; return ' ❤️ $likeCount'; } static String generateViewText(int? viewCount) { if (viewCount == null) return ''; return ' 🔥 $viewCount'; } static bool isValidPoetryData(PoetryData? poetryData) { return poetryData != null && poetryData.name.isNotEmpty; } } /// 动画工具类 class AnimationUtils { static AnimationController createFadeController(TickerProvider vsync) { return AnimationController( duration: AppConstants.animationDurationMedium, vsync: vsync, ); } static AnimationController createSlideController(TickerProvider vsync) { return AnimationController( duration: AppConstants.animationDurationShort, vsync: vsync, ); } static Animation createFadeAnimation(AnimationController controller) { return CurvedAnimation(parent: controller, curve: Curves.easeIn); } static Animation createSlideAnimation( AnimationController controller, ) { return Tween( begin: const Offset(0.0, 0.3), end: Offset.zero, ).animate(CurvedAnimation(parent: controller, curve: Curves.easeOutCubic)); } } /// 复制工具类 class CopyUtils { static void copyToClipboard( BuildContext context, String content, String contentType, { VoidCallback? onSuccess, }) { try { Clipboard.setData(ClipboardData(text: content)); PoetryStateManager.showSnackBar(context, '已复制$contentType'); DebugInfoManager().showCopySuccess(); onSuccess?.call(); } catch (e) { PoetryStateManager.showSnackBar( context, '复制失败', backgroundColor: AppConstants.errorColor, ); DebugInfoManager().showCopyFailed(); } } static void showCopyDialog( BuildContext context, String content, String contentType, ) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('复制$contentType'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '受隐私权限约束,频繁写入剪切板需告知用户', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), const SizedBox(height: 12), Text( '预览内容:', style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), const SizedBox(height: 4), Container( width: double.infinity, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.grey[300]!), ), child: Text( content.length > 50 ? '${content.substring(0, 50)}...' : content, style: const TextStyle(fontSize: 12, color: Colors.black87), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('返回'), ), ElevatedButton( onPressed: () { Navigator.of(context).pop(); copyToClipboard(context, content, contentType); }, style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, foregroundColor: Colors.white, ), child: Text('复制$contentType'), ), ], ), ); } } /// 悬浮分享按钮组件 class FloatingShareButton extends StatelessWidget { final VoidCallback onShare; const FloatingShareButton({super.key, required this.onShare}); @override Widget build(BuildContext context) { return Container( width: 56, height: 56, decoration: BoxDecoration( color: AppConstants.secondaryColor, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: AppConstants.secondaryColor.withAlpha(76), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(28), onTap: () { HapticFeedback.lightImpact(); onShare(); }, child: const Center( child: Icon(Icons.share, color: Colors.white, size: 28), ), ), ), ); } } /// 统计信息卡片组件 class StatsCard extends StatelessWidget { final PoetryData poetryData; const StatsCard({super.key, required this.poetryData}); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(5), blurRadius: 5, offset: const Offset(0, 1), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( '统计信息', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppConstants.primaryColor, ), ), const SizedBox(height: 12), Row( children: [ _buildStatItem('今日', poetryData.hitsDay.toString()), const SizedBox(width: 20), _buildStatItem('本月', poetryData.hitsMonth.toString()), const SizedBox(width: 20), _buildStatItem('总计', poetryData.hitsTotal.toString()), ], ), ], ), ); } Widget _buildStatItem(String label, String value) { return Expanded( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( value, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 4), Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)), ], ), ); } }