Files
AutoTime-Tracker/lib/screens/today_screen.dart
2025-11-13 15:45:28 +08:00

408 lines
13 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.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fl_chart/fl_chart.dart';
import '../theme/app_theme.dart';
import '../models/daily_stats.dart';
import '../models/app_usage.dart';
import '../providers/statistics_provider.dart';
import '../widgets/empty_state_widget.dart';
import '../widgets/error_state_widget.dart';
class TodayScreen extends ConsumerWidget {
const TodayScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final todayStatsAsync = ref.watch(todayStatsProvider);
final topAppsAsync = ref.watch(todayTopAppsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('AutoTime Tracker'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
ref.invalidate(todayStatsProvider);
ref.invalidate(todayTopAppsProvider);
},
),
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
// 设置页面通过底部导航栏访问
},
),
],
),
body: todayStatsAsync.when(
data: (stats) {
// 检查是否为空数据总时长为0且没有应用数据
final isEmpty = stats.totalTime == 0;
if (isEmpty) {
return _buildEmptyContent(context, ref);
}
return _buildContent(context, ref, stats, topAppsAsync);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => ErrorStateWidget.dataLoad(
message: _getErrorMessage(error),
onRetry: () {
ref.invalidate(todayStatsProvider);
ref.invalidate(todayTopAppsProvider);
},
),
),
);
}
Widget _buildContent(
BuildContext context,
WidgetRef ref,
DailyStats stats,
AsyncValue<List<AppUsage>> topAppsAsync,
) {
final theme = Theme.of(context);
return RefreshIndicator(
onRefresh: () async {
ref.invalidate(todayStatsProvider);
ref.invalidate(todayTopAppsProvider);
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 总时长显示
_buildTotalTimeSection(stats, theme),
const SizedBox(height: 24),
// 效率评分
_buildEfficiencySection(stats, theme),
const SizedBox(height: 24),
// 分类时间分布(饼图)
_buildCategoryChart(stats, theme),
const SizedBox(height: 24),
// 分类标签
_buildCategoryTags(theme),
const SizedBox(height: 24),
// Top 应用列表
_buildTopAppsSection(context, ref, theme, topAppsAsync),
],
),
),
);
}
Widget _buildTotalTimeSection(DailyStats stats, ThemeData theme) {
return Center(
child: Column(
children: [
Text(
stats.formattedTotalTime,
style: theme.textTheme.displayLarge?.copyWith(
fontSize: 48,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
const SizedBox(height: 8),
Text(
'今日总时长',
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
);
}
Widget _buildEfficiencySection(DailyStats stats, ThemeData theme) {
final score = stats.efficiencyScore ?? 0;
final color = stats.efficiencyColor;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'效率评分',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
'$score%',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(width: 8),
...List.generate(5, (index) {
return Icon(
index < (score / 20).floor()
? Icons.star
: Icons.star_border,
size: 20,
color: color,
);
}),
],
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
_getEfficiencyText(score),
style: TextStyle(
color: color,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
String _getEfficiencyText(int score) {
if (score >= 80) return '优秀';
if (score >= 60) return '良好';
if (score >= 40) return '一般';
return '需改进';
}
Widget _buildCategoryChart(DailyStats stats, ThemeData theme) {
final categoryData = [
{'category': 'work', 'time': stats.workTime, 'color': AppTheme.workColor},
{'category': 'study', 'time': stats.studyTime, 'color': AppTheme.studyColor},
{'category': 'entertainment', 'time': stats.entertainmentTime, 'color': AppTheme.entertainmentColor},
{'category': 'social', 'time': stats.socialTime, 'color': AppTheme.socialColor},
{'category': 'tool', 'time': stats.toolTime, 'color': AppTheme.toolColor},
].where((item) => item['time'] as int > 0).toList();
return Container(
height: 300,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'分类时间分布',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
Expanded(
child: PieChart(
PieChartData(
sections: categoryData.map((item) {
final time = item['time'] as int;
final total = stats.totalTime;
final percentage = (time / total * 100);
return PieChartSectionData(
value: time.toDouble(),
title: '${percentage.toStringAsFixed(1)}%',
color: item['color'] as Color,
radius: 80,
titleStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.white,
),
);
}).toList(),
sectionsSpace: 2,
centerSpaceRadius: 60,
),
),
),
],
),
);
}
Widget _buildCategoryTags(ThemeData theme) {
final categories = ['work', 'study', 'entertainment', 'social', 'tool'];
return Wrap(
spacing: 8,
runSpacing: 8,
children: categories.map((category) {
return FilterChip(
label: Text(AppTheme.getCategoryName(category)),
selected: false,
onSelected: (selected) {
// 筛选该分类
},
backgroundColor: AppTheme.getCategoryColor(category).withOpacity(0.1),
selectedColor: AppTheme.getCategoryColor(category),
labelStyle: TextStyle(
color: AppTheme.getCategoryColor(category),
fontWeight: FontWeight.w500,
),
);
}).toList(),
);
}
Widget _buildTopAppsSection(BuildContext context, WidgetRef ref, ThemeData theme, AsyncValue<List<AppUsage>> topAppsAsync) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Top Apps Today',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
topAppsAsync.when(
data: (apps) {
if (apps.isEmpty) {
return EmptyStateWidget.noApps(
onAction: () {
ref.invalidate(todayTopAppsProvider);
},
);
}
return Column(
children: apps.map((app) => _buildAppItem(app, theme)).toList(),
);
},
loading: () => const Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
),
error: (error, stack) => Padding(
padding: const EdgeInsets.all(16),
child: ErrorStateWidget.dataLoad(
message: _getErrorMessage(error),
onRetry: () {
ref.invalidate(todayTopAppsProvider);
},
),
),
),
],
);
}
Widget _buildEmptyContent(BuildContext context, WidgetRef ref) {
return RefreshIndicator(
onRefresh: () async {
ref.invalidate(todayStatsProvider);
ref.invalidate(todayTopAppsProvider);
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: EmptyStateWidget.firstTime(
onAction: () {
// 可以导航到权限设置页面
Navigator.of(context).pushNamed('/permission');
},
),
),
);
}
String _getErrorMessage(Object error) {
final errorString = error.toString().toLowerCase();
if (errorString.contains('permission') || errorString.contains('权限')) {
return '需要授予应用使用权限';
} else if (errorString.contains('network') || errorString.contains('网络')) {
return '网络连接失败,请检查网络';
} else if (errorString.contains('database') || errorString.contains('数据库')) {
return '数据库操作失败';
}
return '加载失败,请稍后重试';
}
Widget _buildAppItem(AppUsage app, ThemeData theme) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppTheme.getCategoryColor(app.category).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.phone_android,
color: AppTheme.getCategoryColor(app.category),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
app.appName,
style: theme.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
AppTheme.getCategoryName(app.category),
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
),
Text(
app.formattedDuration,
style: theme.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
color: AppTheme.primaryColor,
),
),
],
),
);
}
}