This commit is contained in:
Developer
2026-04-03 03:26:06 +08:00
parent 3063deb34c
commit cba04235c8
49 changed files with 3955 additions and 1421 deletions

View File

@@ -30,7 +30,7 @@ class _CarePageState extends State<CarePage> {
backgroundColor: isDark ? const Color(0xFF121212) : Colors.white,
appBar: AppBar(
title: Text(
'关怀',
'关怀 Beta',
style: TextStyle(
color: isDark ? Colors.white : primaryColor,
fontWeight: FontWeight.bold,
@@ -183,27 +183,31 @@ class _CarePageState extends State<CarePage> {
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildUserTypeButton(
'儿童',
_careController.userType == '儿童',
isDark,
primaryColor,
Obx(() {
final themeController = Get.find<ThemeController>();
final currentPrimaryColor = themeController.currentThemeColor;
return Row(
children: [
Expanded(
child: _buildUserTypeButton(
'儿童',
_careController.userType == '儿童',
isDark,
currentPrimaryColor,
),
),
),
const SizedBox(width: 16),
Expanded(
child: _buildUserTypeButton(
'学生',
_careController.userType == '学生',
isDark,
primaryColor,
const SizedBox(width: 16),
Expanded(
child: _buildUserTypeButton(
'学生',
_careController.userType == '学生',
isDark,
currentPrimaryColor,
),
),
),
],
),
],
);
}),
const SizedBox(height: 16),
Container(
width: double.infinity,

View File

@@ -10,6 +10,7 @@ import '../../../services/get/theme_controller.dart';
import '../../../services/get/care_controller.dart';
import '../../../constants/app_constants.dart';
import '../home_part.dart';
import '../components/skeleton_widgets.dart';
import 'care_widgets.dart';
import 'pinyin_helper.dart';
@@ -75,15 +76,18 @@ class _CarePoetryPageState extends State<CarePoetryPage>
final isDark = themeController.isDarkModeRx.value;
final primaryColor = themeController.currentThemeColor;
final poetryData = homeController.poetryData.value;
final isLoadingNext = homeController.isLoadingNext.value;
// 当诗词数据变化时,重新播放动画
if (!isLoadingNext) {
_animationController.reset();
_animationController.forward();
}
if (poetryData == null) {
return const Center(child: CircularProgressIndicator());
}
// 当诗词数据变化时,重新播放动画
_animationController.reset();
_animationController.forward();
return Scaffold(
backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50],
body: SafeArea(
@@ -144,7 +148,12 @@ class _CarePoetryPageState extends State<CarePoetryPage>
const SizedBox(height: 16),
// 出处
if (careController.selectedOptions.contains('出处'))
_buildPoetrySource(poetryData, isDark, primaryColor),
_buildPoetrySource(
poetryData,
isDark,
primaryColor,
isLoadingNext,
),
if (careController.selectedOptions.contains('出处'))
const SizedBox(height: 24),
if (!careController.selectedOptions.contains('问候语') &&
@@ -152,7 +161,12 @@ class _CarePoetryPageState extends State<CarePoetryPage>
const SizedBox(height: 24),
// 诗词标题
if (careController.selectedOptions.contains('诗词'))
_buildPoetryTitle(poetryData, isDark, primaryColor),
_buildPoetryTitle(
poetryData,
isDark,
primaryColor,
isLoadingNext,
),
const SizedBox(height: 24),
@@ -170,6 +184,7 @@ class _CarePoetryPageState extends State<CarePoetryPage>
poetryData,
isDark,
primaryColor,
isLoadingNext,
),
),
@@ -186,13 +201,23 @@ class _CarePoetryPageState extends State<CarePoetryPage>
// 译文
if (careController.selectedOptions.contains('译文'))
_buildPoetryTranslation(poetryData, isDark, primaryColor),
_buildPoetryTranslation(
poetryData,
isDark,
primaryColor,
isLoadingNext,
),
const SizedBox(height: 24),
// 更多
if (careController.selectedOptions.contains('更多'))
_buildPoetryMore(poetryData, isDark, primaryColor),
_buildPoetryMore(
poetryData,
isDark,
primaryColor,
isLoadingNext,
),
const SizedBox(height: 48),
],
@@ -209,22 +234,48 @@ class _CarePoetryPageState extends State<CarePoetryPage>
dynamic poetryData,
bool isDark,
Color primaryColor,
bool isLoading,
) {
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
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,
),
child: isLoading
? Center(
child: Column(
children: [
SkeletonContainer(
width: 180,
height: 32,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 8),
SkeletonContainer(
width: 120,
height: 20,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
),
)
: Text(
poetryData.name ?? '未知标题',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: primaryColor,
),
textAlign: TextAlign.center,
),
);
}
@@ -232,6 +283,7 @@ class _CarePoetryPageState extends State<CarePoetryPage>
dynamic poetryData,
bool isDark,
Color primaryColor,
bool isLoading,
) {
final themeController = Get.find<ThemeController>();
final careController = Get.find<CareController>();
@@ -239,6 +291,8 @@ class _CarePoetryPageState extends State<CarePoetryPage>
final content = poetryData.name ?? '';
final lines = content.split('\n');
final showPinyin = careController.pinyinEnabled;
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
return Container(
padding: const EdgeInsets.all(16),
@@ -248,95 +302,128 @@ class _CarePoetryPageState extends State<CarePoetryPage>
: 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(
child: isLoading
? Column(
crossAxisAlignment: CrossAxisAlignment.center,
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(),
),
SkeletonContainer(
width: double.infinity,
height: 28,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 12),
SkeletonContainer(
width: double.infinity,
height: 28,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 12),
SkeletonContainer(
width: 200,
height: 28,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
)
: 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(),
),
);
}).toList(),
),
);
}
@@ -344,8 +431,11 @@ class _CarePoetryPageState extends State<CarePoetryPage>
dynamic poetryData,
bool isDark,
Color primaryColor,
bool isLoading,
) {
final url = poetryData.url ?? '未知作者';
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
return Container(
padding: const EdgeInsets.all(16),
@@ -353,15 +443,25 @@ class _CarePoetryPageState extends State<CarePoetryPage>
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,
),
child: isLoading
? Center(
child: SkeletonContainer(
width: 200,
height: 24,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
)
: Text(
url,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: primaryColor,
),
textAlign: TextAlign.center,
),
);
}
@@ -369,8 +469,11 @@ class _CarePoetryPageState extends State<CarePoetryPage>
dynamic poetryData,
bool isDark,
Color primaryColor,
bool isLoading,
) {
final translation = poetryData.introduce ?? '暂无译文';
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
return Container(
padding: const EdgeInsets.all(16),
@@ -378,46 +481,118 @@ class _CarePoetryPageState extends State<CarePoetryPage>
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,
),
child: isLoading
? Column(
children: [
SkeletonContainer(
width: 100,
height: 20,
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: 200,
height: 16,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
)
: 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) {
Widget _buildPoetryMore(
dynamic poetryData,
bool isDark,
Color primaryColor,
bool isLoading,
) {
final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300];
final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100];
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,
child: isLoading
? Column(
children: [
SkeletonContainer(
width: 120,
height: 22,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 12),
SkeletonContainer(
width: double.infinity,
height: 14,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 8),
SkeletonContainer(
width: 250,
height: 14,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
)
: 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,
),
],
),
),
const SizedBox(height: 12),
Text(
'诗词解析、背景知识等内容将在此显示',
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[300] : Colors.grey[700],
),
textAlign: TextAlign.center,
),
],
),
);
}

View File

@@ -61,30 +61,30 @@ class CareModeToggle extends StatelessWidget {
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(
return GestureDetector(
onTap: () => Get.to(() => const CarePage()),
child: 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),
Text(
'关怀',
style: TextStyle(
fontSize: 16,
@@ -92,16 +92,21 @@ class CareModeToggle extends StatelessWidget {
color: isDark ? Colors.white : Colors.black,
),
),
],
),
GestureDetector(
onTap: onToggle,
child: Switch(
value: isEnabled,
onChanged: (_) => onToggle(),
activeColor: primaryColor,
inactiveTrackColor: isDark
? Colors.grey[700]
: Colors.grey[300],
),
],
),
Switch(
value: isEnabled,
onChanged: (_) => onToggle(),
activeColor: primaryColor,
inactiveTrackColor: isDark ? Colors.grey[700] : Colors.grey[300],
),
],
),
],
),
),
);
}

View File

@@ -25,31 +25,24 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
class _HomePageState extends State<HomePage> {
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();
}
@@ -63,35 +56,14 @@ class _HomePageState extends State<HomePage>
return Obx(() {
final isDark = themeController.isDarkModeRx.value;
// 触发动画
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 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)),
),
return Scaffold(
backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50],
body: SafeArea(child: _buildBody(controller, isDark)),
);
});
}
@@ -168,10 +140,16 @@ class _HomePageState extends State<HomePage>
top: 60,
left: 16,
right: 16,
child: CareModeToggle(
isEnabled: _careController.isCareModeEnabled,
onToggle: _careController.toggleCareMode,
isDark: isDark,
child: GestureDetector(
onTap: () => _careController.resetAutoHideTimer(),
child: CareModeToggle(
isEnabled: _careController.isCareModeEnabled,
onToggle: () {
_careController.resetAutoHideTimer();
_careController.toggleCareMode();
},
isDark: isDark,
),
),
)
: const SizedBox.shrink(),

View File

@@ -11,6 +11,7 @@ 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 {
@@ -130,65 +131,61 @@ class _PoetryCardState extends State<PoetryCard> {
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildTimeBar(isDark, themeColor),
Padding(
padding: const EdgeInsets.all(20),
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,
),
],
),
),
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,
),
_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),
],
],
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),
],
),
@@ -201,52 +198,57 @@ class _PoetryCardState extends State<PoetryCard> {
}
Widget _buildTimeBar(bool isDark, Color themeColor) {
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: [themeColor.withAlpha(204), themeColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
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)),
),
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,
child: Row(
children: [
Icon(
_showRecommendation ? Icons.lightbulb : Icons.wb_sunny,
color: Colors.white,
size: 20,
),
),
Icon(
_showRecommendation ? Icons.expand_less : Icons.expand_more,
color: Colors.white70,
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,
),
],
),
),
),
);
@@ -258,7 +260,7 @@ class _PoetryCardState extends State<PoetryCard> {
}
return Positioned(
top: 56,
top: 80,
right: 24,
child: GestureDetector(
onTap: () => setState(() => _showCopyTip = false),
@@ -299,6 +301,8 @@ class _PoetryCardState extends State<PoetryCard> {
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,
@@ -314,30 +318,12 @@ class _PoetryCardState extends State<PoetryCard> {
isDark: isDark,
),
child: isLoading
? Row(
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
themeColor,
),
),
),
const SizedBox(width: 8),
Text(
'出处加载中...',
style: TextStyle(
fontSize: 14,
color: isDark
? Colors.grey[400]
: Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
],
? SkeletonContainer(
width: double.infinity,
height: 20,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
)
: Text(
"出处: ${widget.poetryData.url}",
@@ -360,6 +346,8 @@ class _PoetryCardState extends State<PoetryCard> {
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,
@@ -402,23 +390,20 @@ class _PoetryCardState extends State<PoetryCard> {
? Center(
child: Column(
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
themeColor,
),
),
SkeletonContainer(
width: 120,
height: 28,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
const SizedBox(height: 8),
Text(
'诗句加载中...',
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[400] : Colors.grey[600],
),
SkeletonContainer(
width: 80,
height: 16,
borderRadius: 4,
baseColor: baseColor,
highlightColor: highlightColor,
),
],
),
@@ -454,6 +439,8 @@ class _PoetryCardState extends State<PoetryCard> {
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: [
@@ -461,32 +448,33 @@ class _PoetryCardState extends State<PoetryCard> {
children: [
Expanded(
child: isLoading
? Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 16,
height: 8,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
themeColor,
),
),
),
const SizedBox(width: 4),
Text(
'关键词加载中...',
style: TextStyle(
fontSize: 12,
color: isDark
? Colors.grey[400]
: Colors.grey[600],
),
),
],
),
? 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,
@@ -602,6 +590,8 @@ class _PoetryCardState extends State<PoetryCard> {
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(
@@ -622,28 +612,41 @@ class _PoetryCardState extends State<PoetryCard> {
borderRadius: BorderRadius.circular(8),
),
child: isLoading
? Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
),
const SizedBox(width: 8),
Text(
'时间加载中...',
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[400] : Colors.grey[600],
),
),
],
),
? 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,

View File

@@ -30,8 +30,11 @@ class FloatingButtonsVisibilityManager {
Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
_isVisible = prefs.getBool(_key) ?? true;
_visibleNotifier.value = _isVisible;
final visible = prefs.getBool(_key) ?? true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_isVisible = visible;
_visibleNotifier.value = visible;
});
}
bool get isVisible => _isVisible;
@@ -59,10 +62,12 @@ class FloatingButtonsVisibilityManager {
Future<void> _restoreFromFlashing() async {
_cancelAllTimers();
_isFlashing = false;
_flashingNotifier.value = false;
_isVisible = true;
_visibleNotifier.value = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_isFlashing = false;
_flashingNotifier.value = false;
_isVisible = true;
_visibleNotifier.value = true;
});
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_key, true);
}
@@ -107,8 +112,10 @@ class FloatingButtonsVisibilityManager {
void dispose() {
_cancelAllTimers();
_visibleNotifier.value = true;
_flashingNotifier.value = false;
WidgetsBinding.instance.addPostFrameCallback((_) {
_visibleNotifier.value = true;
_flashingNotifier.value = false;
});
}
}