Files
wushu/lib/views/home/home_part.dart
Developer cba04235c8 release
2026-04-03 03:26:06 +08:00

763 lines
26 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 时间: 2025-03-22
/// 功能: 诗词页面组件部分
/// 介绍: 包含诗词卡片、操作按钮、统计信息等组件的独立文件
/// 最新变化: 2026-04-02 支持深色模式
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../constants/app_constants.dart';
import '../../../services/get/theme_controller.dart';
import '../../../utils/http/poetry_api.dart';
import '../../../utils/audio_manager.dart';
import 'set/home_components.dart';
import 'components/skeleton_widgets.dart';
/// 诗词卡片组件 - 优化版本,防止拉伸和处理文本溢出
class PoetryCard extends StatefulWidget {
final PoetryData poetryData;
final List<String> keywordList;
final VoidCallback? onTap;
final Map<String, bool>? sectionLoadingStates;
final GlobalKey? repaintKey;
final bool isDark;
const PoetryCard({
super.key,
required this.poetryData,
required this.keywordList,
this.onTap,
this.sectionLoadingStates,
this.repaintKey,
this.isDark = false,
});
@override
State<PoetryCard> createState() => _PoetryCardState();
}
class _PoetryCardState extends State<PoetryCard> {
bool _showCopyTip = true;
bool _showRecommendation = false;
bool _globalTipsEnabled = true;
@override
void initState() {
super.initState();
_loadGlobalTipsSettings();
}
String _getTimeOfDayGreeting() {
final hour = DateTime.now().hour;
if (hour >= 5 && hour < 7) {
return '清晨了,呼吸新鲜空气';
} else if (hour >= 7 && hour < 9) {
return '早上好,元气满满';
} else if (hour >= 9 && hour < 12) {
return '上午好,努力工作';
} else if (hour >= 12 && hour < 14) {
return '中午了,吃顿好的';
} else if (hour >= 14 && hour < 17) {
return '下午了,喝杯咖啡';
} else if (hour >= 17 && hour < 19) {
return '傍晚了,休息一下';
} else if (hour >= 19 && hour < 22) {
return '晚上好,放松身心';
} else {
return '深夜了,注意身体';
}
}
String _getRecommendation() {
final hour = DateTime.now().hour;
if (hour >= 5 && hour < 7) {
return '推荐:山水田园诗';
} else if (hour >= 7 && hour < 9) {
return '推荐:励志诗词';
} else if (hour >= 9 && hour < 12) {
return '推荐:送别诗';
} else if (hour >= 12 && hour < 14) {
return '推荐:思乡诗';
} else if (hour >= 14 && hour < 17) {
return '推荐:咏史诗';
} else if (hour >= 17 && hour < 19) {
return '推荐:边塞诗';
} else if (hour >= 19 && hour < 22) {
return '推荐:爱情诗';
} else {
return '推荐:婉约词';
}
}
Future<void> _loadGlobalTipsSettings() async {
try {
final prefs = await SharedPreferences.getInstance();
if (mounted) {
setState(() {
_globalTipsEnabled = prefs.getBool('global_tips_enabled') ?? true;
});
}
} catch (e) {
if (mounted) {
setState(() {
_globalTipsEnabled = true;
});
}
}
}
@override
Widget build(BuildContext context) {
final isDark = widget.isDark;
final themeController = Get.find<ThemeController>();
final themeColor = themeController.currentThemeColor;
final card = GestureDetector(
onTap: () {
AudioManager().playClickSound();
widget.onTap?.call();
},
child: Stack(
children: [
Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E1E) : Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(isDark ? 40 : 10),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
bottom: 20,
top: 50,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildTitleSection(isDark, themeColor),
if (widget.poetryData.drtime.isNotEmpty) ...[
_buildContentSection(context, isDark, themeColor),
const SizedBox(height: 3),
],
Align(
alignment: Alignment.centerRight,
child: Container(
margin: EdgeInsets.zero,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 2,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'精选诗句',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: themeColor,
),
),
const SizedBox(width: 4),
Icon(Icons.format_quote, color: themeColor, size: 14),
],
),
),
),
_buildNameSection(isDark, themeColor),
const SizedBox(height: 12),
if (widget.keywordList.isNotEmpty) ...[
_buildKeywordSection(isDark, themeColor),
const SizedBox(height: 16),
],
if (widget.poetryData.introduce.isNotEmpty) ...[
_buildIntroductionSection(context, isDark, themeColor),
],
],
),
),
),
_buildTimeBar(isDark, themeColor),
_buildCopyTip(isDark, themeColor),
],
),
);
if (widget.repaintKey != null) {
return RepaintBoundary(key: widget.repaintKey, child: card);
}
return card;
}
Widget _buildTimeBar(bool isDark, Color themeColor) {
return Positioned(
top: 16,
left: 16,
right: 16,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
_showRecommendation = !_showRecommendation;
});
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [themeColor.withAlpha(204), themeColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Row(
children: [
Icon(
_showRecommendation ? Icons.lightbulb : Icons.wb_sunny,
color: Colors.white,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Text(
_showRecommendation
? _getRecommendation()
: _getTimeOfDayGreeting(),
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
Icon(
_showRecommendation ? Icons.expand_less : Icons.expand_more,
color: Colors.white70,
size: 20,
),
],
),
),
),
);
}
Widget _buildCopyTip(bool isDark, Color themeColor) {
if (!_globalTipsEnabled || !_showCopyTip) {
return const SizedBox.shrink();
}
return Positioned(
top: 80,
right: 24,
child: GestureDetector(
onTap: () => setState(() => _showCopyTip = false),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: themeColor,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: themeColor.withAlpha(76),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.info_outline, color: Colors.white, size: 16),
SizedBox(width: 6),
Text(
'点击任意区域加载下一条,长按复制,下拉刷新',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
SizedBox(width: 4),
Icon(Icons.close, color: Colors.white, size: 14),
],
),
),
),
);
}
Widget _buildTitleSection(bool isDark, Color themeColor) {
final isLoading = widget.sectionLoadingStates?['title'] ?? false;
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
return SizedBox(
height: 28,
child: Row(
children: [
Expanded(
child: Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.url,
'诗人和标题',
isDark: isDark,
),
child: isLoading
? SkeletonContainer(
width: double.infinity,
height: 20,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
)
: Text(
"出处: ${widget.poetryData.url}",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
color: isDark ? Colors.grey[200] : Colors.black,
fontStyle: FontStyle.italic,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
),
],
),
);
}
Widget _buildNameSection(bool isDark, Color themeColor) {
final isLoading = widget.sectionLoadingStates?['name'] ?? false;
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
margin: EdgeInsets.zero,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isDark
? [const Color(0xFF2A2A2A), const Color(0xFF252525)]
: [themeColor.withAlpha(26), themeColor.withAlpha(13)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isDark ? Colors.grey[700]! : themeColor.withAlpha(51),
width: 1,
),
boxShadow: [
BoxShadow(
color: isDark
? Colors.black.withAlpha(40)
: themeColor.withAlpha(26),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.name,
'诗词',
isDark: isDark,
),
child: isLoading
? Center(
child: Column(
children: [
SkeletonContainer(
width: 120,
height: 28,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 8),
SkeletonContainer(
width: 80,
height: 16,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
),
)
: Text(
_formatPoetryText(widget.poetryData.name),
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: isDark ? Colors.grey[100] : Colors.black87,
height: 1.4,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
String _formatPoetryText(String text) {
if (text.length < 10) {
return text;
}
final commaIndex = text.indexOf('');
if (commaIndex != -1) {
return text.replaceFirst('', '\n');
}
return text;
}
Widget _buildKeywordSection(bool isDark, Color themeColor) {
final isLoading = widget.sectionLoadingStates?['keywords'] ?? false;
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
return Column(
children: [
Row(
children: [
Expanded(
child: isLoading
? Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.center,
children: [
SkeletonContainer(
width: 60,
height: 24,
borderRadius: 12,
baseColor: baseColor,
highlightColor: highlightColor,
),
SkeletonContainer(
width: 80,
height: 24,
borderRadius: 12,
baseColor: baseColor,
highlightColor: highlightColor,
),
SkeletonContainer(
width: 70,
height: 24,
borderRadius: 12,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
)
: Wrap(
spacing: 8,
runSpacing: 4,
children: [
if (widget.keywordList.isNotEmpty)
...widget.keywordList.map(
(keyword) => Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
keyword,
'关键词',
isDark: isDark,
),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: isDark
? themeColor.withAlpha(40)
: themeColor.withAlpha(26),
borderRadius: BorderRadius.circular(8),
),
child: Text(
keyword,
style: TextStyle(
color: themeColor,
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
),
),
if (widget.poetryData.alias.isNotEmpty)
Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.alias,
'朝代',
isDark: isDark,
),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: isDark
? themeColor.withAlpha(40)
: themeColor.withAlpha(26),
borderRadius: BorderRadius.circular(12),
),
child: Text(
widget.poetryData.alias,
style: TextStyle(
color: themeColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
),
],
),
),
if (!isLoading)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
PoetryDataUtils.generateStars(widget.poetryData.star),
style: const TextStyle(fontSize: 14),
),
const SizedBox(width: 4),
Text(
PoetryDataUtils.generateLikeText(widget.poetryData.like),
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[400] : Colors.grey,
),
),
const SizedBox(width: 4),
Text(
PoetryDataUtils.generateViewText(
widget.poetryData.hitsTotal,
),
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[400] : Colors.grey,
),
),
],
),
],
),
],
);
}
Widget _buildContentSection(
BuildContext context,
bool isDark,
Color themeColor,
) {
final isLoading = widget.sectionLoadingStates?['content'] ?? false;
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
return Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.drtime,
'时间',
isDark: isDark,
),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
margin: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
color: isDark
? Colors.grey[800]!.withAlpha(80)
: themeColor.withAlpha(20),
borderRadius: BorderRadius.circular(8),
),
child: isLoading
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SkeletonContainer(
width: 80,
height: 18,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 12),
SkeletonContainer(
width: double.infinity,
height: 16,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 8),
SkeletonContainer(
width: double.infinity,
height: 16,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 8),
SkeletonContainer(
width: 150,
height: 16,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'原文|赏析',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isDark ? Colors.grey[300] : themeColor,
),
),
const SizedBox(height: 8),
Text(
widget.poetryData.drtime,
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[400] : Colors.grey[600],
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
Widget _buildIntroductionSection(
BuildContext context,
bool isDark,
Color themeColor,
) {
final isLoading = widget.sectionLoadingStates?['introduction'] ?? false;
return Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.introduce,
'简介',
isDark: isDark,
),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF252525) : Colors.grey[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isDark ? Colors.grey[700]! : Colors.grey[200]!,
),
),
child: isLoading
? Center(
child: Column(
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
),
const SizedBox(height: 8),
Text(
'简介加载中...',
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[400] : Colors.grey[600],
),
),
],
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.info_outline,
size: 16,
color: isDark ? Colors.grey[400] : Colors.grey[600],
),
const SizedBox(width: 4),
Text(
'简介',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: isDark ? Colors.grey[300] : Colors.grey[700],
),
),
],
),
const SizedBox(height: 8),
Text(
widget.poetryData.introduce,
style: TextStyle(
fontSize: 14,
height: 1.5,
color: isDark ? Colors.grey[400] : Colors.grey[600],
),
),
],
),
),
),
);
}
}