- 新增模型目录占位文件与翻译类型拆分 - 调整路由配置与桌面端窗口初始化 - 移除多处冗余图表配置项 - 重构右侧面板注册表与三栏布局组件 - 添加智能AppBar、拖拽书签等新功能组件 - 优化安卓编译配置与多平台插件注册 - 新增翻译覆盖率测试与共享组件 - 格式化代码与修复静态分析警告
344 lines
12 KiB
Dart
344 lines
12 KiB
Dart
/// ============================================================
|
|
/// 闲言APP — 概览仪表盘
|
|
/// 创建时间: 2026-05-29
|
|
/// 更新时间: 2026-05-29
|
|
/// 作用: 宽屏分屏右侧面板的空状态页面,显示概览信息
|
|
/// 上次更新: 快捷操作和数据统计添加flutter_staggered_animations交错入场动画
|
|
/// ============================================================
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../theme/app_theme.dart';
|
|
import '../theme/app_spacing.dart';
|
|
import '../../shared/widgets/containers/glass_container.dart';
|
|
import '../../features/home/providers/home_provider.dart';
|
|
import '../../features/home/providers/favorite_provider.dart';
|
|
import '../../features/home/providers/likes_provider.dart';
|
|
import '../../features/auth/providers/auth_provider.dart';
|
|
import '../../features/mine/signin/providers/signin_provider.dart';
|
|
import '../router/app_routes.dart';
|
|
import '../router/app_nav_extension.dart';
|
|
|
|
class OverviewDashboard extends ConsumerWidget {
|
|
const OverviewDashboard({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildGreeting(context),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildTodayRecommend(context, ref),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildQuickActions(context),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildRecentHistory(context),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildStats(context, ref),
|
|
const SizedBox(height: AppSpacing.xxl),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildGreeting(BuildContext context) {
|
|
final ext = AppTheme.ext(context);
|
|
final hour = DateTime.now().hour;
|
|
final greeting = switch (hour) {
|
|
>= 6 && < 12 => '早上好 ☀️',
|
|
>= 12 && < 14 => '中午好 🌤️',
|
|
>= 14 && < 18 => '下午好 🌅',
|
|
>= 18 && < 22 => '晚上好 🌙',
|
|
_ => '夜深了 🌛',
|
|
};
|
|
|
|
return GlassContainer(
|
|
depth: GlassDepth.elevated,
|
|
padding: const EdgeInsets.all(AppSpacing.lg),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
greeting,
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.xs),
|
|
Text(
|
|
'选择左侧内容查看详情',
|
|
style: TextStyle(fontSize: 14, color: ext.textSecondary),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTodayRecommend(BuildContext context, WidgetRef ref) {
|
|
final ext = AppTheme.ext(context);
|
|
final homeState = ref.watch(homeProvider);
|
|
final recommends = homeState.dailySentences;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'✨ 今日推荐',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
SizedBox(
|
|
height: 120,
|
|
child: recommends.isEmpty
|
|
? GlassContainer(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text('📭', style: TextStyle(fontSize: 28)),
|
|
const SizedBox(height: AppSpacing.xs),
|
|
Text(
|
|
'暂无推荐内容',
|
|
style: TextStyle(fontSize: 13, color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
: ListView.separated(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: recommends.length,
|
|
separatorBuilder: (_, __) =>
|
|
const SizedBox(width: AppSpacing.sm),
|
|
itemBuilder: (context, index) {
|
|
final sentence = recommends[index];
|
|
return GlassContainer(
|
|
width: 180,
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
sentence.text,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: ext.textPrimary,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const Spacer(),
|
|
Text(
|
|
sentence.author != null
|
|
? '—— ${sentence.author}'
|
|
: '—— 佚名',
|
|
style: TextStyle(fontSize: 11, color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildQuickActions(BuildContext context) {
|
|
final ext = AppTheme.ext(context);
|
|
final actions = <({String emoji, String label, String route})>[
|
|
(emoji: '🔍', label: '搜索', route: AppRoutes.search),
|
|
(emoji: '⭐', label: '收藏', route: AppRoutes.favorites),
|
|
(emoji: '📖', label: '稍后读', route: AppRoutes.readLater),
|
|
(emoji: '🕐', label: '历史', route: AppRoutes.history),
|
|
(emoji: '✅', label: '签到', route: AppRoutes.signin),
|
|
(emoji: '📊', label: '阅读报告', route: AppRoutes.readingReport),
|
|
(emoji: '🌤️', label: '每日推荐', route: AppRoutes.dailyCard),
|
|
(emoji: '⚙️', label: '设置', route: AppRoutes.generalSettings),
|
|
];
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'🚀 快捷操作',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
AnimationLimiter(
|
|
child: Wrap(
|
|
spacing: AppSpacing.sm,
|
|
runSpacing: AppSpacing.sm,
|
|
children: actions.asMap().entries.map((entry) {
|
|
return AnimationConfiguration.staggeredList(
|
|
position: entry.key,
|
|
duration: const Duration(milliseconds: 375),
|
|
child: SlideAnimation(
|
|
verticalOffset: 50.0,
|
|
child: FadeInAnimation(
|
|
child: GestureDetector(
|
|
onTap: () => context.appPush(entry.value.route),
|
|
child: GlassContainer(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(entry.value.emoji,
|
|
style: const TextStyle(fontSize: 16)),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Text(
|
|
entry.value.label,
|
|
style: TextStyle(
|
|
fontSize: 13, color: ext.textPrimary),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildRecentHistory(BuildContext context) {
|
|
final ext = AppTheme.ext(context);
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'🕐 最近浏览',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
GlassContainer(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Center(
|
|
child: Column(
|
|
children: [
|
|
const Text('📭', style: TextStyle(fontSize: 32)),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Text(
|
|
'暂无浏览记录',
|
|
style: TextStyle(fontSize: 13, color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildStats(BuildContext context, WidgetRef ref) {
|
|
final ext = AppTheme.ext(context);
|
|
final authState = ref.watch(authProvider);
|
|
final favoriteState = ref.watch(favoriteProvider);
|
|
final likesState = ref.watch(likesProvider);
|
|
final signinState = ref.watch(signinProvider);
|
|
|
|
final readCount = authState.user?.signinDays ?? 0;
|
|
final favCount = favoriteState.total;
|
|
final likeCount = likesState.total;
|
|
final streakDays = signinState.continuous;
|
|
|
|
final stats = [
|
|
('📖', '阅读', '$readCount'),
|
|
('⭐', '收藏', '$favCount'),
|
|
('👍', '点赞', '$likeCount'),
|
|
('🔥', '连续', '$streakDays天'),
|
|
];
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'📊 数据统计',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
AnimationLimiter(
|
|
child: Row(
|
|
children: stats.asMap().entries.map((entry) {
|
|
final stat = entry.value;
|
|
return AnimationConfiguration.staggeredList(
|
|
position: entry.key,
|
|
duration: const Duration(milliseconds: 375),
|
|
child: SlideAnimation(
|
|
verticalOffset: 50.0,
|
|
child: FadeInAnimation(
|
|
child: Expanded(
|
|
child: GlassContainer(
|
|
padding: const EdgeInsets.all(AppSpacing.sm),
|
|
margin: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.xs / 2,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Text(stat.$1, style: const TextStyle(fontSize: 20)),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
stat.$3,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
Text(
|
|
stat.$2,
|
|
style: TextStyle(fontSize: 11, color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|