Files
xianyan/lib/features/solar_term/solar_term_page.dart
Developer f91be94e9c refactor: 完成项目架构重构,统一模块导入路径
- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
2026-06-12 08:53:57 +08:00

421 lines
14 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.
/// ============================================================
/// 闲言APP — 节气日历页面
/// 创建时间: 2026-05-02
/// 更新时间: 2026-05-02
/// 作用: 二十四节气展示 + 当前节气 + 诗词关联 + 季节切换
/// 上次更新: 修复 AppRadius 类型
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/theme/app_theme.dart';
import '../../core/theme/app_spacing.dart';
import '../../core/theme/app_typography.dart';
import '../../core/theme/app_radius.dart';
import 'solar_term_core.dart';
import '../../../shared/widgets/adaptive/adaptive_back_button.dart';
class SolarTermPage extends ConsumerWidget {
const SolarTermPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final ext = AppTheme.ext(context);
final state = ref.watch(solarTermProvider);
return CupertinoPageScaffold(
backgroundColor: ext.bgPrimary,
navigationBar: CupertinoNavigationBar(
leading: const AdaptiveBackButton(),
middle: Text(
'🌿 节气日历',
style: AppTypography.title3.copyWith(color: ext.textPrimary),
),
backgroundColor: ext.bgElevated.withValues(alpha: 0.85),
border: null,
),
child: SafeArea(
child: ListView(
padding: const EdgeInsets.all(AppSpacing.md),
children: [
_buildCurrentTermCard(ext, state),
const SizedBox(height: AppSpacing.md),
_buildNextTermCard(ext, state),
const SizedBox(height: AppSpacing.md),
_buildSeasonTabs(ext, ref, state),
const SizedBox(height: AppSpacing.md),
_buildTermGrid(ext, state),
if (state.isTodayTerm && state.todayTerm != null) ...[
const SizedBox(height: AppSpacing.md),
_buildTodayTermBanner(ext, state.todayTerm!),
],
const SizedBox(height: AppSpacing.lg),
],
),
),
);
}
Widget _buildCurrentTermCard(AppThemeExtension ext, SolarTermState state) {
final term = state.currentTerm;
if (term == null) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
ext.accent.withValues(alpha: 0.15),
ext.accent.withValues(alpha: 0.05),
],
),
borderRadius: AppRadius.lgBorder,
border: Border.all(color: ext.accent.withValues(alpha: 0.2)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
term.emoji,
style: const TextStyle(fontSize: 36),
).animate().scale(duration: 600.ms, curve: Curves.elasticOut),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'当前节气',
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
),
),
Text(
term.name,
style: AppTypography.title1.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w700,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: AppSpacing.xs,
),
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.15),
borderRadius: AppRadius.smBorder,
),
child: Text(
'${term.season.label} ${term.season.emoji}',
style: AppTypography.caption1.copyWith(
color: ext.accent,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: AppSpacing.md),
Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: ext.bgCard.withValues(alpha: 0.6),
borderRadius: AppRadius.mdBorder,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${term.poem}',
style: AppTypography.body.copyWith(
color: ext.textPrimary,
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 4),
Text(
'—— ${term.poemAuthor}',
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
),
),
],
),
),
const SizedBox(height: AppSpacing.sm),
Text(
term.description,
style: AppTypography.caption1.copyWith(color: ext.textSecondary),
),
const SizedBox(height: 4),
Text(
'民俗: ${term.customs}',
style: AppTypography.caption1.copyWith(color: ext.textHint),
),
],
),
).animate().fadeIn(duration: 400.ms);
}
Widget _buildNextTermCard(AppThemeExtension ext, SolarTermState state) {
final term = state.nextTerm;
if (term == null) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: ext.bgCard,
borderRadius: AppRadius.mdBorder,
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.1),
borderRadius: AppRadius.smBorder,
),
child: Center(
child: Text(term.emoji, style: const TextStyle(fontSize: 24)),
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'下一节气',
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
),
),
Text(
term.name,
style: AppTypography.body.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: AppSpacing.xs,
),
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.1),
borderRadius: AppRadius.smBorder,
),
child: Text(
'${state.daysUntilNext}天后',
style: AppTypography.caption1.copyWith(
color: ext.accent,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
Widget _buildSeasonTabs(
AppThemeExtension ext,
WidgetRef ref,
SolarTermState state,
) {
return Row(
children: SolarTermSeason.values.map((season) {
final isSelected = state.selectedSeason == season;
return Expanded(
child: GestureDetector(
onTap: () => ref.read(solarTermProvider.notifier).setSeason(season),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 2),
padding: const EdgeInsets.symmetric(vertical: AppSpacing.sm),
decoration: BoxDecoration(
color: isSelected
? ext.accent.withValues(alpha: 0.15)
: ext.bgCard,
borderRadius: AppRadius.smBorder,
border: isSelected
? Border.all(color: ext.accent.withValues(alpha: 0.3))
: null,
),
child: Center(
child: Text(
'${season.emoji} ${season.label}',
style: AppTypography.caption1.copyWith(
color: isSelected ? ext.accent : ext.textSecondary,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.normal,
),
),
),
),
),
);
}).toList(),
);
}
Widget _buildTermGrid(AppThemeExtension ext, SolarTermState state) {
final season =
state.selectedSeason ??
state.currentTerm?.season ??
SolarTermSeason.spring;
final terms = SolarTermService.getTermsBySeason(season);
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: AppSpacing.sm,
crossAxisSpacing: AppSpacing.sm,
childAspectRatio: 1.4,
),
itemCount: terms.length,
itemBuilder: (context, index) {
final term = terms[index];
final isCurrent = state.currentTerm?.name == term.name;
return _buildTermCard(ext, term, isCurrent, index);
},
);
}
Widget _buildTermCard(
AppThemeExtension ext,
SolarTermInfo term,
bool isCurrent,
int index,
) {
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: isCurrent ? ext.accent.withValues(alpha: 0.12) : ext.bgCard,
borderRadius: AppRadius.mdBorder,
border: isCurrent
? Border.all(color: ext.accent.withValues(alpha: 0.3))
: null,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(term.emoji, style: const TextStyle(fontSize: 20)),
const SizedBox(width: 6),
Text(
term.name,
style: AppTypography.body.copyWith(
color: isCurrent ? ext.accent : ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
if (isCurrent) ...[
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 1,
),
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'当前',
style: AppTypography.caption1.copyWith(
color: ext.accent,
fontSize: 10,
),
),
),
],
],
),
const Spacer(),
Text(
term.poem.length > 12
? '${term.poem.substring(0, 12)}'
: term.poem,
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
fontStyle: FontStyle.italic,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
term.description,
style: AppTypography.caption1.copyWith(
color: ext.textHint,
fontSize: 11,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
).animate().fadeIn(duration: 300.ms, delay: (index * 50).ms);
}
Widget _buildTodayTermBanner(AppThemeExtension ext, SolarTermInfo term) {
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
ext.accent.withValues(alpha: 0.2),
ext.accent.withValues(alpha: 0.05),
],
),
borderRadius: AppRadius.mdBorder,
),
child: Row(
children: [
const Text('🎉', style: TextStyle(fontSize: 24)),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'今日是${term.name}',
style: AppTypography.body.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
Text(
SolarTermService.getSeasonGreeting(term.season),
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
),
),
],
),
),
],
),
).animate().shimmer(duration: 2000.ms);
}
}