// 心情页面 — 心情统计 + 趋势图 + 连续天数 import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:nuanji_app/core/theme/app_colors.dart'; import 'package:nuanji_app/data/models/journal_entry.dart'; import 'package:nuanji_app/data/remote/api_client.dart'; import '../bloc/mood_bloc.dart'; /// 心情页面 — 统计卡片 + 心情分布饼图 + 详情列表 class MoodPage extends StatefulWidget { const MoodPage({super.key}); @override State createState() => _MoodPageState(); } class _MoodPageState extends State { late final MoodBloc _bloc; @override void initState() { super.initState(); _bloc = MoodBloc(api: context.read()); _bloc.load(); } @override void dispose() { _bloc.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; return ListenableBuilder( listenable: _bloc, builder: (context, _) { final state = _bloc.state; if (state.isLoading) { return const Center(child: CircularProgressIndicator()); } if (state.errorMessage != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48, color: colorScheme.error), const SizedBox(height: 12), Text(state.errorMessage!, style: theme.textTheme.bodyMedium), const SizedBox(height: 16), FilledButton.tonal( onPressed: _bloc.load, child: const Text('重试'), ), ], ), ); } return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 统计概览卡片 _StatsOverviewCard(stats: state.stats, colorScheme: colorScheme), const SizedBox(height: 16), // 周期选择器 _PeriodSelector( selectedPeriod: state.selectedPeriod, onPeriodChanged: _bloc.changePeriod, ), const SizedBox(height: 16), // 心情分布饼图 _MoodDistributionChart( moodCounts: state.stats.moodCounts, colorScheme: colorScheme, ), const SizedBox(height: 24), // 心情详情列表 Text( '心情详情', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), ...state.stats.moodCounts.map((mc) => _MoodCountTile(mc: mc)), const SizedBox(height: 24), // 连续天数鼓励卡片 _StreakCard(streakDays: state.stats.streakDays), ], ), ); }, ); } } /// 统计概览卡片 class _StatsOverviewCard extends StatelessWidget { const _StatsOverviewCard({ required this.stats, required this.colorScheme, }); final MoodStats stats; final ColorScheme colorScheme; @override Widget build(BuildContext context) { final theme = Theme.of(context); final dominantEmoji = stats.dominantMood != null ? _moodEmoji(stats.dominantMood!) : '📝'; return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(22), ), color: colorScheme.primaryContainer, child: Padding( padding: const EdgeInsets.all(20), child: Row( children: [ // 主导心情图标 Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.primary.withValues(alpha: 0.15), ), alignment: Alignment.center, child: Text(dominantEmoji, style: const TextStyle(fontSize: 28)), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '心情概览', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( '共 ${stats.totalJournals} 篇日记 · 连续 ${stats.streakDays} 天', style: theme.textTheme.bodySmall?.copyWith( color: colorScheme.onSurface.withValues(alpha: 0.7), ), ), ], ), ), ], ), ), ); } } /// 周期选择器 class _PeriodSelector extends StatelessWidget { const _PeriodSelector({ required this.selectedPeriod, required this.onPeriodChanged, }); final StatsPeriod selectedPeriod; final ValueChanged onPeriodChanged; @override Widget build(BuildContext context) { return SegmentedButton( segments: const [ ButtonSegment(value: StatsPeriod.week, label: Text('周')), ButtonSegment(value: StatsPeriod.month, label: Text('月')), ButtonSegment(value: StatsPeriod.quarter, label: Text('季')), ], selected: {selectedPeriod}, onSelectionChanged: (set) => onPeriodChanged(set.first), ); } } /// 心情分布饼图 class _MoodDistributionChart extends StatelessWidget { const _MoodDistributionChart({ required this.moodCounts, required this.colorScheme, }); final List moodCounts; final ColorScheme colorScheme; @override Widget build(BuildContext context) { if (moodCounts.isEmpty) { return const SizedBox.shrink(); } return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(22), side: BorderSide(color: colorScheme.outlineVariant), ), child: Padding( padding: const EdgeInsets.all(20), child: SizedBox( height: 200, child: PieChart( PieChartData( sections: moodCounts.map((mc) { final color = AppColors.moodColors[mc.mood.value] ?? colorScheme.primary; return PieChartSectionData( value: mc.count.toDouble(), color: color, radius: 50, title: '${mc.percentage.toStringAsFixed(0)}%', titleStyle: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: colorScheme.onPrimary, ), ); }).toList(), sectionsSpace: 2, centerSpaceRadius: 40, ), ), ), ), ); } } /// 心情计数列表项 class _MoodCountTile extends StatelessWidget { const _MoodCountTile({required this.mc}); final MoodCount mc; @override Widget build(BuildContext context) { final theme = Theme.of(context); final color = AppColors.moodColors[mc.mood.value] ?? theme.colorScheme.primary; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Text(_moodEmoji(mc.mood), style: const TextStyle(fontSize: 20)), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _moodLabel(mc.mood), style: theme.textTheme.bodyMedium, ), const SizedBox(height: 4), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: mc.percentage / 100, backgroundColor: color.withValues(alpha: 0.15), color: color, minHeight: 6, ), ), ], ), ), const SizedBox(width: 12), SizedBox( width: 48, child: Text( '${mc.count} 篇', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.5), ), textAlign: TextAlign.end, ), ), ], ), ); } } /// 连续天数鼓励卡片 class _StreakCard extends StatelessWidget { const _StreakCard({required this.streakDays}); final int streakDays; @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(22), ), color: AppColors.tertiary.withValues(alpha: 0.15), child: Padding( padding: const EdgeInsets.all(20), child: Row( children: [ const Text('🔥', style: TextStyle(fontSize: 32)), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '连续 $streakDays 天', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( streakDays >= 7 ? '太棒了!你已经坚持了一周 ✨' : '继续加油,坚持就是胜利!', style: theme.textTheme.bodySmall?.copyWith( color: colorScheme.onSurface.withValues(alpha: 0.7), ), ), ], ), ), ], ), ), ); } } // ===== 辅助函数 ===== String _moodEmoji(Mood mood) => switch (mood) { Mood.happy => '😊', Mood.calm => '😌', Mood.sad => '😢', Mood.angry => '😠', Mood.thinking => '🤔', }; String _moodLabel(Mood mood) => switch (mood) { Mood.happy => '开心', Mood.calm => '平静', Mood.sad => '难过', Mood.angry => '生气', Mood.thinking => '思考', };