Files
wushu/lib/views/home/home_part.dart
2026-03-30 21:30:11 +08:00

901 lines
29 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
/// 功能: 诗词页面组件部分
/// 介绍: 包含诗词卡片、操作按钮、统计信息等组件的独立文件
/// 最新变化: 重构代码结构,使用工具类简化代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../constants/app_constants.dart';
import '../../../utils/http/poetry_api.dart';
import '../../../utils/flutter_compatibility_fix.dart';
import '../../../utils/audio_manager.dart';
import 'home_components.dart';
/// 诗词卡片组件 - 优化版本,防止拉伸和处理文本溢出
class PoetryCard extends StatefulWidget {
final PoetryData poetryData;
final List<String> keywordList;
final VoidCallback? onTap;
final Map<String, bool>? sectionLoadingStates;
const PoetryCard({
super.key,
required this.poetryData,
required this.keywordList,
this.onTap,
this.sectionLoadingStates,
});
@override
State<PoetryCard> createState() => _PoetryCardState();
}
class _PoetryCardState extends State<PoetryCard> {
bool _showCopyTip = true;
bool _showRecommendation = false;
bool _globalTipsEnabled = true; // 添加全局Tips开关状态
@override
void initState() {
super.initState();
_loadGlobalTipsSettings(); // 加载全局Tips设置
}
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 '推荐:婉约词';
}
}
// 加载全局Tips设置
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) {
return GestureDetector(
onTap: () async {
// 播放点击音效
await AudioManager().playClickSound();
// 调用原始的onTap回调
widget.onTap?.call();
},
child: Stack(
children: [
Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(10),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildTimeBar(),
Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildTitleSection(),
if (widget.poetryData.drtime.isNotEmpty) ...[
_buildContentSection(context),
const SizedBox(height: 16),
],
_buildNameSection(),
const SizedBox(height: 12),
if (widget.keywordList.isNotEmpty) ...[
_buildKeywordSection(),
const SizedBox(height: 16),
],
if (widget.poetryData.introduce.isNotEmpty) ...[
_buildIntroductionSection(context),
],
],
),
),
],
),
),
if (_showCopyTip) _buildCopyTip(),
],
),
);
}
Widget _buildTimeBar() {
return 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: [
AppConstants.primaryColor.withAlpha(204),
AppConstants.primaryColor,
],
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() {
return Positioned(
top: 56,
right: 24,
child: GestureDetector(
onTap: () => setState(() => _showCopyTip = false),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppConstants.primaryColor,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.withAlpha(76),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.info_outline, color: Colors.white, size: 16),
const SizedBox(width: 6),
Text(
_globalTipsEnabled ? '点击任意区域加载下一条,长按复制,下拉刷新' : '',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 4),
const Icon(Icons.close, color: Colors.white, size: 14),
],
),
),
),
);
}
Widget _buildTitleSection() {
final isLoading = widget.sectionLoadingStates?['title'] ?? false;
return SizedBox(
height: 28,
child: Row(
children: [
Expanded(
child: Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.url,
'诗人和标题',
),
child: isLoading
? Row(
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
),
),
const SizedBox(width: 8),
Text(
'出处加载中...',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
],
)
: Text(
"出处: ${widget.poetryData.url}",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
color: Colors.black,
fontStyle: FontStyle.italic,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
),
],
),
);
}
Widget _buildNameSection() {
final isLoading = widget.sectionLoadingStates?['name'] ?? false;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor.withAlpha(26),
AppConstants.primaryColor.withAlpha(13),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppConstants.primaryColor.withAlpha(51),
width: 1,
),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.withAlpha(26),
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),
GestureDetector(
onLongPress: () =>
CopyUtils.showCopyDialog(context, widget.poetryData.name, '诗词'),
child: isLoading
? Center(
child: Column(
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
),
),
const SizedBox(height: 8),
Text(
'诗句加载中...',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
)
: Text(
widget.poetryData.name,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black87,
height: 1.4,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
Widget _buildKeywordSection() {
final isLoading = widget.sectionLoadingStates?['keywords'] ?? false;
return Column(
children: [
// 第一行关键词和朝代左边vs 星星和点赞(右边)
Row(
children: [
// 左边:关键词和朝代
Expanded(
child: isLoading
? Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
),
),
const SizedBox(width: 8),
Text(
'关键词加载中...',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
)
: Wrap(
spacing: 8,
runSpacing: 4,
children: [
if (widget.keywordList.isNotEmpty)
...widget.keywordList
.map(
(keyword) => Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
keyword,
'关键词',
),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: AppConstants.secondaryColor
.withAlpha(26),
borderRadius: BorderRadius.circular(8),
),
child: Text(
keyword,
style: TextStyle(
color: AppConstants.secondaryColor,
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
),
)
.toList(),
if (widget.poetryData.alias.isNotEmpty)
Builder(
builder: (context) => GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.alias,
'朝代',
),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(
26,
),
borderRadius: BorderRadius.circular(12),
),
child: Text(
widget.poetryData.alias,
style: TextStyle(
color: AppConstants.primaryColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
),
],
),
),
// 右边:星星和点赞
if (!isLoading &&
(widget.poetryData.star != null ||
widget.poetryData.like != null ||
widget.poetryData.hitsTotal != null))
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.poetryData.star != null) ...[
Text(
PoetryDataUtils.generateStars(widget.poetryData.star),
style: const TextStyle(fontSize: 14),
),
const SizedBox(width: 4),
],
if (widget.poetryData.like != null) ...[
Text(
PoetryDataUtils.generateLikeText(widget.poetryData.like),
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
if (widget.poetryData.hitsTotal != null) ...[
Text(
PoetryDataUtils.generateViewText(
widget.poetryData.hitsTotal,
),
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
],
),
],
),
],
);
}
Widget _buildContentSection(BuildContext context) {
final isLoading = widget.sectionLoadingStates?['content'] ?? false;
return GestureDetector(
onLongPress: () =>
CopyUtils.showCopyDialog(context, widget.poetryData.drtime, '原文'),
child: Container(
width: double.infinity,
constraints: const BoxConstraints(maxHeight: 200),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(12),
),
child: isLoading
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
),
),
const SizedBox(height: 8),
Text(
'原文加载中...',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
],
),
)
: SingleChildScrollView(
child: Text(
widget.poetryData.drtime,
style: const TextStyle(
fontSize: 16,
height: 1.6,
color: Colors.black87,
),
),
),
),
);
}
Widget _buildIntroductionSection(BuildContext context) {
final isLoading = widget.sectionLoadingStates?['introduction'] ?? false;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'译文',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppConstants.primaryColor,
),
),
const SizedBox(height: 8),
GestureDetector(
onLongPress: () => CopyUtils.showCopyDialog(
context,
widget.poetryData.introduce,
'译文',
),
child: Container(
width: double.infinity,
constraints: const BoxConstraints(maxHeight: 150),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppConstants.infoColor.withAlpha(13),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppConstants.infoColor.withAlpha(51)),
),
child: isLoading
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
),
),
const SizedBox(height: 8),
Text(
'译文加载中...',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
)
: SingleChildScrollView(
child: Text(
widget.poetryData.introduce,
style: const TextStyle(
fontSize: 14,
height: 1.6,
color: Colors.black87,
),
),
),
),
),
],
);
}
}
/// 悬浮上一条按钮组件
class FloatingPreviousButton extends StatelessWidget {
final VoidCallback onPrevious;
const FloatingPreviousButton({super.key, required this.onPrevious});
@override
Widget build(BuildContext context) {
return Tooltip(
message: 'beta功能',
child: 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();
onPrevious();
},
child: const Center(
child: Icon(Icons.arrow_back, color: Colors.white, size: 28),
),
),
),
),
);
}
}
/// 悬浮下一条按钮组件
class FloatingNextButton extends StatelessWidget {
final VoidCallback onNext;
const FloatingNextButton({super.key, required this.onNext});
@override
Widget build(BuildContext context) {
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: AppConstants.primaryColor,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.withAlpha(76),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(28),
onTap: () {
HapticFeedback.lightImpact();
onNext();
},
child: const Center(
child: Icon(Icons.arrow_forward, color: Colors.white, size: 28),
),
),
),
);
}
}
/// 悬浮点赞按钮组件
class FloatingLikeButton extends StatelessWidget {
final bool isLiked;
final bool isLoadingLike;
final VoidCallback onToggleLike;
const FloatingLikeButton({
super.key,
required this.isLiked,
required this.isLoadingLike,
required this.onToggleLike,
});
@override
Widget build(BuildContext context) {
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: isLiked ? AppConstants.errorColor : AppConstants.primaryColor,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color:
(isLiked ? AppConstants.errorColor : AppConstants.primaryColor)
.withAlpha(76),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(28),
onTap: isLoadingLike
? null
: () {
HapticFeedback.mediumImpact();
onToggleLike();
},
child: Center(
child: isLoadingLike
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white70),
),
)
: Icon(
isLiked ? Icons.favorite : Icons.favorite_border,
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)),
],
),
);
}
}