关怀模式

This commit is contained in:
Developer
2026-04-02 22:30:49 +08:00
parent 09fee0694c
commit 7872f2e78a
70 changed files with 4884 additions and 2752 deletions

View File

@@ -0,0 +1,478 @@
/// 时间: 2026-04-02
/// 功能: 关怀页面
/// 介绍: 简约大字风格的关怀功能页面
/// 最新变化: 2026-04-02 添加多个按钮和文本描述区域
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../services/get/theme_controller.dart';
import '../../../services/get/care_controller.dart';
class CarePage extends StatefulWidget {
const CarePage({super.key});
@override
State<CarePage> createState() => _CarePageState();
}
class _CarePageState extends State<CarePage> {
final CareController _careController = Get.find<CareController>();
bool _readAloudEnabled = false;
final List<String> _options = ['诗词', '出处', '译文', '更多', '原文', '问候语'];
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDarkModeRx.value;
final primaryColor = themeController.currentThemeColor;
return Scaffold(
backgroundColor: isDark ? const Color(0xFF121212) : Colors.white,
appBar: AppBar(
title: Text(
'关怀',
style: TextStyle(
color: isDark ? Colors.white : primaryColor,
fontWeight: FontWeight.bold,
),
),
backgroundColor: isDark ? const Color(0xFF1A1A1A) : Colors.white,
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: isDark ? Colors.white : primaryColor,
),
onPressed: () => Navigator.pop(context),
),
actions: [
IconButton(
icon: Icon(
Icons.info_outline,
color: isDark ? Colors.white : primaryColor,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: isDark
? const Color(0xFF2A2A2A)
: Colors.white,
title: Text(
'功能说明',
style: TextStyle(
color: isDark ? Colors.white : Colors.black87,
fontWeight: FontWeight.bold,
),
),
content: Text(
'该功能处于开发状态,限时免费使用\n待后续开发完成后,使用需付费激活\n其他功能不受影响。',
style: TextStyle(
color: isDark ? Colors.grey[300] : Colors.grey[700],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('确定', style: TextStyle(color: primaryColor)),
),
],
),
);
},
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(32),
child: Column(
children: [
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.people, size: 80, color: primaryColor),
const SizedBox(width: 32),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'关怀',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
const SizedBox(height: 16),
Text(
'用心呵护每一位用户',
style: TextStyle(
fontSize: 18,
color: isDark ? Colors.grey[400] : Colors.grey[600],
),
),
],
),
],
),
),
const SizedBox(height: 48),
_buildCareModeToggle(isDark, primaryColor),
const SizedBox(height: 24),
_buildUserTypeSelector(isDark, primaryColor),
const SizedBox(height: 24),
_buildPinyinToggle(isDark, primaryColor),
const SizedBox(height: 24),
_buildMultiSelectOptions(isDark, primaryColor),
const SizedBox(height: 24),
_buildDescriptionArea(isDark),
],
),
),
);
}
Widget _buildCareModeToggle(bool isDark, Color primaryColor) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'关怀模式',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
Obx(
() => Switch(
value: _careController.isCareModeEnabled,
onChanged: (value) => _careController.toggleCareMode(),
activeColor: primaryColor,
),
),
],
),
);
}
Widget _buildUserTypeSelector(bool isDark, Color primaryColor) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'用户类型',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildUserTypeButton(
'儿童',
_careController.userType == '儿童',
isDark,
primaryColor,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildUserTypeButton(
'学生',
_careController.userType == '学生',
isDark,
primaryColor,
),
),
],
),
const SizedBox(height: 16),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF3A3A3A) : Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Obx(
() => Text(
_careController.userType == '儿童'
? '儿童模式:大字体、简单界面、拼音辅助,适合低龄儿童使用。界面更加卡通化,内容简单易懂,帮助儿童轻松学习诗词。'
: '学生模式:适中字体、学习辅助、诗词解析,适合学生群体使用。提供详细的诗词注释、背景知识,助力学生深入理解古典文学。',
style: TextStyle(
fontSize: 14,
height: 1.5,
color: isDark ? Colors.grey[300] : Colors.grey[700],
),
),
),
),
],
),
);
}
Widget _buildUserTypeButton(
String label,
bool isSelected,
bool isDark,
Color primaryColor,
) {
return GestureDetector(
onTap: () => _careController.setUserType(label),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: isSelected
? primaryColor
: (isDark ? const Color(0xFF3A3A3A) : Colors.white),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected ? primaryColor : Colors.transparent,
width: 2,
),
),
child: Center(
child: Text(
label,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: isSelected
? Colors.white
: (isDark ? Colors.white : Colors.black87),
),
),
),
),
);
}
Widget _buildPinyinToggle(bool isDark, Color primaryColor) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'拼音显示',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
Obx(
() => Switch(
value: _careController.pinyinEnabled,
onChanged: (value) => _careController.setPinyinEnabled(value),
activeColor: primaryColor,
),
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'朗读',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
const SizedBox(width: 8),
IconButton(
icon: Icon(
Icons.info_outline,
color: isDark ? Colors.grey[400] : Colors.grey[600],
size: 20,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: isDark
? const Color(0xFF2A2A2A)
: Colors.white,
title: Text(
'功能说明',
style: TextStyle(
color: isDark ? Colors.white : Colors.black87,
fontWeight: FontWeight.bold,
),
),
content: Text(
'你使用的设备暂不支持朗读功能\n 等待上游厂商适配TTS.',
style: TextStyle(
color: isDark
? Colors.grey[300]
: Colors.grey[700],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'确定',
style: TextStyle(color: primaryColor),
),
),
],
),
);
},
),
],
),
Switch(
value: false,
onChanged: null,
activeColor: isDark ? Colors.grey[700] : Colors.grey[400],
inactiveTrackColor: isDark
? Colors.grey[800]
: Colors.grey[300],
),
],
),
],
),
);
}
Widget _buildMultiSelectOptions(bool isDark, Color primaryColor) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'显示选项',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
const SizedBox(height: 16),
Obx(
() => Wrap(
spacing: 12,
runSpacing: 12,
children: _options.map((option) {
final isSelected = _careController.selectedOptions.contains(
option,
);
final isPoetry = option == '诗词';
return GestureDetector(
onTap: isPoetry
? null
: () => _careController.toggleOption(option),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
decoration: BoxDecoration(
color: isSelected
? primaryColor
: (isDark ? const Color(0xFF3A3A3A) : Colors.white),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected ? primaryColor : Colors.transparent,
width: 2,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isSelected)
Icon(Icons.check, color: Colors.white, size: 20),
if (isSelected) const SizedBox(width: 8),
Text(
option,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: isSelected
? Colors.white
: (isDark ? Colors.white : Colors.black87),
),
),
],
),
),
);
}).toList(),
),
),
],
),
);
}
Widget _buildDescriptionArea(bool isDark) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'功能说明',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
const SizedBox(height: 12),
Text(
'关怀模式为儿童和学生用户提供更加友好的界面体验。\n\n'
'开启关怀模式后,应用将根据选择的用户类型自动调整界面显示。',
style: TextStyle(
fontSize: 16,
height: 1.6,
color: isDark ? Colors.grey[300] : Colors.grey[700],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,512 @@
/// 时间: 2026-04-02
/// 功能: 关怀模式诗词页面
/// 介绍: 关怀模式下显示的诗词页面,根据关怀设置显示不同内容
/// 最新变化: 2026-04-02 初始创建
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../services/get/home_controller.dart';
import '../../../services/get/theme_controller.dart';
import '../../../services/get/care_controller.dart';
import '../../../constants/app_constants.dart';
import '../home_part.dart';
import 'care_widgets.dart';
import 'pinyin_helper.dart';
class CarePoetryPage extends StatefulWidget {
const CarePoetryPage({super.key});
@override
State<CarePoetryPage> createState() => _CarePoetryPageState();
}
class _CarePoetryPageState extends State<CarePoetryPage>
with SingleTickerProviderStateMixin {
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 '深夜了,注意身体';
}
}
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final homeController = Get.find<HomeController>();
final themeController = Get.find<ThemeController>();
final careController = Get.find<CareController>();
return Obx(() {
final isDark = themeController.isDarkModeRx.value;
final primaryColor = themeController.currentThemeColor;
final poetryData = homeController.poetryData.value;
if (poetryData == null) {
return const Center(child: CircularProgressIndicator());
}
// 当诗词数据变化时,重新播放动画
_animationController.reset();
_animationController.forward();
return Scaffold(
backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50],
body: SafeArea(
child: GestureDetector(
onTap: () async {
try {
await homeController.loadNextPoetry();
} catch (e) {
print('加载诗词失败: $e');
}
},
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: FadeTransition(
opacity: _fadeAnimation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 关怀模式开关
CarePageHeaderToggle(isDark: isDark),
const SizedBox(height: 16),
// 问候语bar
if (careController.selectedOptions.contains('问候语'))
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [primaryColor.withAlpha(204), primaryColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(Icons.wb_sunny, color: Colors.white, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
_getTimeOfDayGreeting(),
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
),
if (careController.selectedOptions.contains('问候语'))
const SizedBox(height: 16),
// 出处
if (careController.selectedOptions.contains('出处'))
_buildPoetrySource(poetryData, isDark, primaryColor),
if (careController.selectedOptions.contains('出处'))
const SizedBox(height: 24),
if (!careController.selectedOptions.contains('问候语') &&
!careController.selectedOptions.contains('出处'))
const SizedBox(height: 24),
// 诗词标题
if (careController.selectedOptions.contains('诗词'))
_buildPoetryTitle(poetryData, isDark, primaryColor),
const SizedBox(height: 24),
// 诗词内容
if (careController.selectedOptions.contains('诗词'))
GestureDetector(
onTap: () async {
try {
await homeController.loadNextPoetry();
} catch (e) {
print('加载诗词失败: $e');
}
},
child: _buildPoetryContent(
poetryData,
isDark,
primaryColor,
),
),
const SizedBox(height: 24),
// 原文
if (careController.selectedOptions.contains('原文'))
_buildPoetryOriginal(poetryData, isDark, primaryColor),
if (careController.selectedOptions.contains('原文'))
const SizedBox(height: 24),
const SizedBox(height: 24),
// 译文
if (careController.selectedOptions.contains('译文'))
_buildPoetryTranslation(poetryData, isDark, primaryColor),
const SizedBox(height: 24),
// 更多
if (careController.selectedOptions.contains('更多'))
_buildPoetryMore(poetryData, isDark, primaryColor),
const SizedBox(height: 48),
],
),
),
),
),
),
);
});
}
Widget _buildPoetryTitle(
dynamic poetryData,
bool isDark,
Color primaryColor,
) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Text(
poetryData.name ?? '未知标题',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: primaryColor,
),
textAlign: TextAlign.center,
),
);
}
Widget _buildPoetryContent(
dynamic poetryData,
bool isDark,
Color primaryColor,
) {
final themeController = Get.find<ThemeController>();
final careController = Get.find<CareController>();
final accentColor = themeController.currentAccentColor;
final content = poetryData.name ?? '';
final lines = content.split('\n');
final showPinyin = careController.pinyinEnabled;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark
? accentColor.withOpacity(0.2)
: accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: lines.asMap().entries.map<Widget>((entry) {
int index = entry.key;
String line = entry.value;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
if (showPinyin && line.trim().isNotEmpty)
Wrap(
alignment: WrapAlignment.center,
children: PinyinHelper.convertToCharPinyinList(line)
.asMap()
.entries
.map<Widget>((charEntry) {
int charIndex = charEntry.key;
var item = charEntry.value;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: AnimatedOpacity(
opacity: 1.0,
duration: Duration(
milliseconds: 300 + charIndex * 100,
),
curve: Curves.easeOut,
child: Column(
children: [
Text(
item['pinyin'] ?? '',
style: TextStyle(
fontSize: 12,
color: isDark
? Colors.grey[400]
: Colors.grey[600],
height: 1.2,
),
textAlign: TextAlign.center,
),
Text(
item['char'] ?? '',
style: TextStyle(
fontSize: 20,
color: primaryColor,
height: 1.6,
),
textAlign: TextAlign.center,
),
],
),
),
);
})
.toList(),
),
if (!showPinyin || line.trim().isEmpty)
Wrap(
alignment: WrapAlignment.center,
children: line.split('').asMap().entries.map<Widget>((
charEntry,
) {
int charIndex = charEntry.key;
String char = charEntry.value;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: AnimatedOpacity(
opacity: 1.0,
duration: Duration(
milliseconds: 300 + charIndex * 100,
),
curve: Curves.easeOut,
child: Text(
char,
style: TextStyle(
fontSize: 20,
color: primaryColor,
height: 1.6,
),
textAlign: TextAlign.center,
),
),
);
}).toList(),
),
],
),
);
}).toList(),
),
);
}
Widget _buildPoetrySource(
dynamic poetryData,
bool isDark,
Color primaryColor,
) {
final url = poetryData.url ?? '未知作者';
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Text(
url,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: primaryColor,
),
textAlign: TextAlign.center,
),
);
}
Widget _buildPoetryTranslation(
dynamic poetryData,
bool isDark,
Color primaryColor,
) {
final translation = poetryData.introduce ?? '暂无译文';
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Text(
translation,
style: TextStyle(
fontSize: 16,
color: isDark ? Colors.grey[300] : Colors.grey[700],
height: 1.6,
),
textAlign: TextAlign.center,
),
);
}
Widget _buildPoetryMore(dynamic poetryData, bool isDark, Color primaryColor) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
'更多信息',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
const SizedBox(height: 12),
Text(
'诗词解析、背景知识等内容将在此显示',
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[300] : Colors.grey[700],
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildPoetryOriginal(
dynamic poetryData,
bool isDark,
Color primaryColor,
) {
final original = poetryData.drtime ?? '暂无原文';
final careController = Get.find<CareController>();
final showPinyin = careController.pinyinEnabled;
final lines = original.split('\n');
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: lines.asMap().entries.map<Widget>((entry) {
int index = entry.key;
String line = entry.value;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
if (showPinyin && line.trim().isNotEmpty)
Wrap(
alignment: WrapAlignment.center,
children: PinyinHelper.convertToCharPinyinList(line)
.asMap()
.entries
.map<Widget>((charEntry) {
int charIndex = charEntry.key;
var item = charEntry.value;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: AnimatedOpacity(
opacity: 1.0,
duration: Duration(
milliseconds: 300 + charIndex * 100,
),
curve: Curves.easeOut,
child: Column(
children: [
Text(
item['pinyin'] ?? '',
style: TextStyle(
fontSize: 12,
color: isDark
? Colors.grey[400]
: Colors.grey[600],
height: 1.2,
),
textAlign: TextAlign.center,
),
Text(
item['char'] ?? '',
style: TextStyle(
fontSize: 16,
color: primaryColor,
height: 1.6,
),
textAlign: TextAlign.center,
),
],
),
),
);
})
.toList(),
),
if (!showPinyin || line.trim().isEmpty)
Text(
line,
style: TextStyle(
fontSize: 16,
color: primaryColor,
height: 1.6,
),
textAlign: TextAlign.center,
),
],
),
);
}).toList(),
),
);
}
}

View File

@@ -0,0 +1,171 @@
/// 时间: 2026-04-02
/// 功能: 关怀模式相关组件
/// 介绍: 包含关怀按钮和开关的UI组件
/// 最新变化: 2026-04-02 初始创建
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../services/get/theme_controller.dart';
import '../../../services/get/care_controller.dart';
import 'care-page.dart';
/// 关怀按钮组件
class CareButton extends StatelessWidget {
const CareButton({super.key, required this.onTap, required this.isDark});
final VoidCallback onTap;
final bool isDark;
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final primaryColor = themeController.currentThemeColor;
return GestureDetector(
onTap: onTap,
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(isDark ? 40 : 20),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Center(child: Icon(Icons.people, color: primaryColor, size: 24)),
),
);
}
}
/// 关怀模式开关组件
class CareModeToggle extends StatelessWidget {
const CareModeToggle({
super.key,
required this.isEnabled,
required this.onToggle,
required this.isDark,
});
final bool isEnabled;
final VoidCallback onToggle;
final bool isDark;
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final primaryColor = themeController.currentThemeColor;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(isDark ? 40 : 20),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.people, color: primaryColor, size: 20),
const SizedBox(width: 12),
GestureDetector(
onTap: () => Get.to(() => const CarePage()),
child: Text(
'关怀',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: isDark ? Colors.white : Colors.black,
),
),
),
],
),
Switch(
value: isEnabled,
onChanged: (_) => onToggle(),
activeColor: primaryColor,
inactiveTrackColor: isDark ? Colors.grey[700] : Colors.grey[300],
),
],
),
);
}
}
/// 关怀模式页面顶部开关组件
class CarePageHeaderToggle extends StatelessWidget {
const CarePageHeaderToggle({super.key, required this.isDark});
final bool isDark;
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final careController = Get.find<CareController>();
final primaryColor = themeController.currentThemeColor;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(isDark ? 40 : 20),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.people, color: primaryColor, size: 20),
const SizedBox(width: 12),
GestureDetector(
onTap: () => Get.to(() => const CarePage()),
child: Text(
'关怀模式',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: isDark ? Colors.white : Colors.black,
),
),
),
],
),
Obx(
() => Switch(
value: careController.isCareModeEnabled,
onChanged: (value) {
// 关闭关怀模式并返回正常页面
careController.toggleCareMode();
},
activeColor: primaryColor,
inactiveTrackColor: isDark ? Colors.grey[700] : Colors.grey[300],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,79 @@
/// 时间: 2026-04-02
/// 功能: 拼音处理辅助类
/// 介绍: 使用pinyin库为汉字生成带音调的拼音
/// 最新变化: 2026-04-02 修正API使用方式避免递归调用
import 'package:pinyin/pinyin.dart' as pinyin;
class PinyinHelper {
/// 将汉字转换为带音调的拼音
static String convertToPinyin(String text) {
if (text.isEmpty) return '';
try {
// 使用pinyin库将汉字转换为拼音
return pinyin.PinyinHelper.getPinyin(
text,
separator: ' ',
format: pinyin.PinyinFormat.WITH_TONE_MARK,
);
} catch (e) {
// 无法转换时返回空字符串
return '';
}
}
/// 为诗词内容生成拼音
static String generatePoetryPinyin(String content) {
if (content.isEmpty) return '';
// 按行分割诗词
List<String> lines = content.split('\n');
List<String> pinyinLines = [];
// 为每行生成拼音
for (String line in lines) {
if (line.trim().isNotEmpty) {
String linePinyin = convertToPinyin(line);
pinyinLines.add(linePinyin);
} else {
pinyinLines.add('');
}
}
return pinyinLines.join('\n');
}
/// 将字符串转换为每个汉字及其对应的拼音的列表
static List<Map<String, String>> convertToCharPinyinList(String text) {
if (text.isEmpty) return [];
List<Map<String, String>> result = [];
try {
// 为每个字符生成拼音
for (int i = 0; i < text.length; i++) {
String char = text[i];
String charPinyin = '';
try {
charPinyin = pinyin.PinyinHelper.getPinyin(
char,
separator: ' ',
format: pinyin.PinyinFormat.WITH_TONE_MARK,
);
} catch (e) {
// 无法转换时使用空字符串
charPinyin = '';
}
result.add({'char': char, 'pinyin': charPinyin});
}
} catch (e) {
// 发生错误时返回空列表
return [];
}
return result;
}
}

View File

@@ -14,6 +14,9 @@ import 'home_part.dart';
import 'set/home_components.dart';
import 'set/home-load.dart';
import 'set/home-set.dart';
import '../../services/get/care_controller.dart';
import 'care/care_widgets.dart';
import 'care/care_poetry_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@@ -22,23 +25,31 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
final GlobalKey repaintKey = GlobalKey();
final SecondaryButtonsManager _secondaryButtonsManager =
SecondaryButtonsManager();
final FloatingButtonsVisibilityManager _floatingButtonsVisibilityManager =
FloatingButtonsVisibilityManager();
final CareController _careController = Get.put(CareController());
late AnimationController _animationController;
@override
void initState() {
super.initState();
_secondaryButtonsManager.init();
_floatingButtonsVisibilityManager.init();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
}
@override
void dispose() {
_floatingButtonsVisibilityManager.dispose();
_animationController.dispose();
super.dispose();
}
@@ -47,12 +58,40 @@ class _HomePageState extends State<HomePage> {
Get.lazyPut(() => HomeController());
final controller = Get.find<HomeController>();
final themeController = Get.find<ThemeController>();
final careController = Get.find<CareController>();
return Obx(() {
final isDark = themeController.isDarkModeRx.value;
return Scaffold(
backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50],
body: SafeArea(child: _buildBody(controller, isDark)),
// 触发动画
if (_animationController.status != AnimationStatus.forward) {
_animationController.forward(from: 0);
}
// 关怀模式开启时显示关怀页面,添加过渡动画
if (careController.isCareModeEnabled) {
return FadeTransition(
opacity: Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
),
child: const CarePoetryPage(),
);
}
return FadeTransition(
opacity: Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
),
child: Scaffold(
backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50],
body: SafeArea(child: _buildBody(controller, isDark)),
),
);
});
}
@@ -113,6 +152,30 @@ class _HomePageState extends State<HomePage> {
),
),
),
// 关怀按钮 - 左上角
Positioned(
top: 8,
left: 16,
child: CareButton(
onTap: _careController.toggleCareButtonVisibility,
isDark: isDark,
),
),
// 关怀模式开关
Obx(
() => _careController.isCareButtonVisible.value
? Positioned(
top: 60,
left: 16,
right: 16,
child: CareModeToggle(
isEnabled: _careController.isCareModeEnabled,
onToggle: _careController.toggleCareMode,
isDark: isDark,
),
)
: const SizedBox.shrink(),
),
// 收起/恢复按钮 - 右上角(与下方分享按钮对齐)
Positioned(
top: 8,

View File

@@ -4,8 +4,10 @@
/// 最新变化: 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';
@@ -106,6 +108,8 @@ class _PoetryCardState extends State<PoetryCard> {
@override
Widget build(BuildContext context) {
final isDark = widget.isDark;
final themeController = Get.find<ThemeController>();
final themeColor = themeController.currentThemeColor;
final card = GestureDetector(
onTap: () {
AudioManager().playClickSound();
@@ -129,16 +133,16 @@ class _PoetryCardState extends State<PoetryCard> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildTimeBar(isDark),
_buildTimeBar(isDark, themeColor),
Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildTitleSection(isDark),
_buildTitleSection(isDark, themeColor),
if (widget.poetryData.drtime.isNotEmpty) ...[
_buildContentSection(context, isDark),
_buildContentSection(context, isDark, themeColor),
const SizedBox(height: 3),
],
Align(
@@ -157,27 +161,27 @@ class _PoetryCardState extends State<PoetryCard> {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: AppConstants.primaryColor,
color: themeColor,
),
),
const SizedBox(width: 4),
Icon(
Icons.format_quote,
color: AppConstants.primaryColor,
color: themeColor,
size: 14,
),
],
),
),
),
_buildNameSection(isDark),
_buildNameSection(isDark, themeColor),
const SizedBox(height: 12),
if (widget.keywordList.isNotEmpty) ...[
_buildKeywordSection(isDark),
_buildKeywordSection(isDark, themeColor),
const SizedBox(height: 16),
],
if (widget.poetryData.introduce.isNotEmpty) ...[
_buildIntroductionSection(context, isDark),
_buildIntroductionSection(context, isDark, themeColor),
],
],
),
@@ -185,7 +189,7 @@ class _PoetryCardState extends State<PoetryCard> {
],
),
),
_buildCopyTip(isDark),
_buildCopyTip(isDark, themeColor),
],
),
);
@@ -196,7 +200,7 @@ class _PoetryCardState extends State<PoetryCard> {
return card;
}
Widget _buildTimeBar(bool isDark) {
Widget _buildTimeBar(bool isDark, Color themeColor) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
@@ -209,10 +213,7 @@ class _PoetryCardState extends State<PoetryCard> {
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor.withAlpha(204),
AppConstants.primaryColor,
],
colors: [themeColor.withAlpha(204), themeColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
@@ -251,7 +252,7 @@ class _PoetryCardState extends State<PoetryCard> {
);
}
Widget _buildCopyTip(bool isDark) {
Widget _buildCopyTip(bool isDark, Color themeColor) {
if (!_globalTipsEnabled || !_showCopyTip) {
return const SizedBox.shrink();
}
@@ -264,11 +265,11 @@ class _PoetryCardState extends State<PoetryCard> {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppConstants.primaryColor,
color: themeColor,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.withAlpha(76),
color: themeColor.withAlpha(76),
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -296,7 +297,7 @@ class _PoetryCardState extends State<PoetryCard> {
);
}
Widget _buildTitleSection(bool isDark) {
Widget _buildTitleSection(bool isDark, Color themeColor) {
final isLoading = widget.sectionLoadingStates?['title'] ?? false;
return SizedBox(
@@ -321,7 +322,7 @@ class _PoetryCardState extends State<PoetryCard> {
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
themeColor,
),
),
),
@@ -357,7 +358,7 @@ class _PoetryCardState extends State<PoetryCard> {
);
}
Widget _buildNameSection(bool isDark) {
Widget _buildNameSection(bool isDark, Color themeColor) {
final isLoading = widget.sectionLoadingStates?['name'] ?? false;
return Container(
@@ -368,25 +369,20 @@ class _PoetryCardState extends State<PoetryCard> {
gradient: LinearGradient(
colors: isDark
? [const Color(0xFF2A2A2A), const Color(0xFF252525)]
: [
AppConstants.primaryColor.withAlpha(26),
AppConstants.primaryColor.withAlpha(13),
],
: [themeColor.withAlpha(26), themeColor.withAlpha(13)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isDark
? Colors.grey[700]!
: AppConstants.primaryColor.withAlpha(51),
color: isDark ? Colors.grey[700]! : themeColor.withAlpha(51),
width: 1,
),
boxShadow: [
BoxShadow(
color: isDark
? Colors.black.withAlpha(40)
: AppConstants.primaryColor.withAlpha(26),
: themeColor.withAlpha(26),
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -412,7 +408,7 @@ class _PoetryCardState extends State<PoetryCard> {
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
themeColor,
),
),
),
@@ -456,7 +452,7 @@ class _PoetryCardState extends State<PoetryCard> {
return text;
}
Widget _buildKeywordSection(bool isDark) {
Widget _buildKeywordSection(bool isDark, Color themeColor) {
final isLoading = widget.sectionLoadingStates?['keywords'] ?? false;
return Column(
@@ -475,7 +471,7 @@ class _PoetryCardState extends State<PoetryCard> {
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
themeColor,
),
),
),
@@ -513,18 +509,14 @@ class _PoetryCardState extends State<PoetryCard> {
),
decoration: BoxDecoration(
color: isDark
? AppConstants.secondaryColor.withAlpha(
40,
)
: AppConstants.secondaryColor.withAlpha(
26,
),
? themeColor.withAlpha(40)
: themeColor.withAlpha(26),
borderRadius: BorderRadius.circular(8),
),
child: Text(
keyword,
style: TextStyle(
color: AppConstants.secondaryColor,
color: themeColor,
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
@@ -550,14 +542,14 @@ class _PoetryCardState extends State<PoetryCard> {
),
decoration: BoxDecoration(
color: isDark
? AppConstants.primaryColor.withAlpha(40)
: AppConstants.primaryColor.withAlpha(26),
? themeColor.withAlpha(40)
: themeColor.withAlpha(26),
borderRadius: BorderRadius.circular(12),
),
child: Text(
widget.poetryData.alias,
style: TextStyle(
color: AppConstants.primaryColor,
color: themeColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
@@ -604,7 +596,11 @@ class _PoetryCardState extends State<PoetryCard> {
);
}
Widget _buildContentSection(BuildContext context, bool isDark) {
Widget _buildContentSection(
BuildContext context,
bool isDark,
Color themeColor,
) {
final isLoading = widget.sectionLoadingStates?['content'] ?? false;
return Builder(
@@ -622,7 +618,7 @@ class _PoetryCardState extends State<PoetryCard> {
decoration: BoxDecoration(
color: isDark
? Colors.grey[800]!.withAlpha(80)
: AppConstants.primaryColor.withAlpha(20),
: themeColor.withAlpha(20),
borderRadius: BorderRadius.circular(8),
),
child: isLoading
@@ -635,9 +631,7 @@ class _PoetryCardState extends State<PoetryCard> {
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
),
const SizedBox(width: 8),
@@ -659,9 +653,7 @@ class _PoetryCardState extends State<PoetryCard> {
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isDark
? Colors.grey[300]
: AppConstants.primaryColor,
color: isDark ? Colors.grey[300] : themeColor,
),
),
const SizedBox(height: 8),
@@ -680,7 +672,11 @@ class _PoetryCardState extends State<PoetryCard> {
);
}
Widget _buildIntroductionSection(BuildContext context, bool isDark) {
Widget _buildIntroductionSection(
BuildContext context,
bool isDark,
Color themeColor,
) {
final isLoading = widget.sectionLoadingStates?['introduction'] ?? false;
return Builder(
@@ -710,9 +706,7 @@ class _PoetryCardState extends State<PoetryCard> {
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
),
const SizedBox(height: 8),

View File

@@ -12,6 +12,7 @@ import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import '../../../constants/app_constants.dart';
import '../../../services/get/theme_controller.dart';
import '../../../utils/http/poetry_api.dart';
import 'home-load.dart';
@@ -23,14 +24,14 @@ class LoadingWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final themeColor = themeController.currentThemeColor;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
),
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
const SizedBox(height: 16),
Text(
@@ -145,7 +146,7 @@ class CustomErrorWidget extends StatelessWidget {
ElevatedButton(
onPressed: onRetry,
style: ElevatedButton.styleFrom(
backgroundColor: AppConstants.primaryColor,
backgroundColor: Get.find<ThemeController>().currentThemeColor,
),
child: const Text('重试'),
),
@@ -401,15 +402,17 @@ class FloatingShareButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final themeColor = themeController.currentThemeColor;
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: AppConstants.secondaryColor,
color: themeColor,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppConstants.secondaryColor.withAlpha(76),
color: themeColor.withAlpha(76),
blurRadius: 8,
offset: const Offset(0, 4),
),
@@ -470,7 +473,9 @@ class FloatingPreviousButton extends StatelessWidget {
child: Center(
child: Icon(
Icons.arrow_back,
color: isDark ? Colors.grey[300] : AppConstants.primaryColor,
color: isDark
? Colors.grey[300]
: Get.find<ThemeController>().currentThemeColor,
size: 28,
),
),
@@ -518,7 +523,9 @@ class FloatingNextButton extends StatelessWidget {
child: Center(
child: Icon(
Icons.arrow_forward,
color: isDark ? Colors.grey[300] : AppConstants.primaryColor,
color: isDark
? Colors.grey[300]
: Get.find<ThemeController>().currentThemeColor,
size: 28,
),
),
@@ -581,7 +588,9 @@ class FloatingLikeButton extends StatelessWidget {
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
isDark ? Colors.grey[300]! : AppConstants.primaryColor,
isDark
? Colors.grey[300]!
: Get.find<ThemeController>().currentThemeColor,
),
),
)
@@ -608,6 +617,8 @@ class StatsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final themeColor = themeController.currentThemeColor;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
@@ -631,7 +642,7 @@ class StatsCard extends StatelessWidget {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppConstants.primaryColor,
color: themeColor,
),
),
const SizedBox(height: 12),