1854 lines
57 KiB
Dart
1854 lines
57 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
|||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
|
|
import '../../../core/utils/responsive_utils.dart';
|
|||
|
|
import '../../../shared/widgets/custom_app_bar.dart';
|
|||
|
|
import '../../../shared/widgets/loading_widget.dart';
|
|||
|
|
|
|||
|
|
class StudyPlanScreen extends ConsumerStatefulWidget {
|
|||
|
|
const StudyPlanScreen({super.key});
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
ConsumerState<StudyPlanScreen> createState() => _StudyPlanScreenState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _StudyPlanScreenState extends ConsumerState<StudyPlanScreen>
|
|||
|
|
with TickerProviderStateMixin {
|
|||
|
|
late TabController _tabController;
|
|||
|
|
bool _isLoading = false;
|
|||
|
|
|
|||
|
|
List<StudyPlan> _studyPlans = [];
|
|||
|
|
StudyPlan? _currentPlan;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_tabController = TabController(length: 3, vsync: this);
|
|||
|
|
_loadStudyPlans();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_tabController.dispose();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
final isMobile = ResponsiveUtils.isMobile(context);
|
|||
|
|
|
|||
|
|
return Scaffold(
|
|||
|
|
appBar: CustomAppBar(
|
|||
|
|
title: '学习计划',
|
|||
|
|
actions: [
|
|||
|
|
IconButton(
|
|||
|
|
icon: const Icon(Icons.add),
|
|||
|
|
onPressed: _showCreatePlanDialog,
|
|||
|
|
),
|
|||
|
|
PopupMenuButton<String>(
|
|||
|
|
icon: const Icon(Icons.more_vert),
|
|||
|
|
onSelected: _handleMenuAction,
|
|||
|
|
itemBuilder: (context) => [
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: 'templates',
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.library_books),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('计划模板'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: 'statistics',
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.analytics),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('学习统计'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: 'settings',
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.settings),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('设置'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
body: Column(
|
|||
|
|
children: [
|
|||
|
|
_buildTabBar(context, isMobile),
|
|||
|
|
Expanded(
|
|||
|
|
child: TabBarView(
|
|||
|
|
controller: _tabController,
|
|||
|
|
children: [
|
|||
|
|
_buildCurrentPlanTab(context, isMobile),
|
|||
|
|
_buildAllPlansTab(context, isMobile),
|
|||
|
|
_buildProgressTab(context, isMobile),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
floatingActionButton: FloatingActionButton(
|
|||
|
|
onPressed: _showCreatePlanDialog,
|
|||
|
|
backgroundColor: Colors.blue,
|
|||
|
|
child: const Icon(Icons.add, color: Colors.white),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildTabBar(BuildContext context, bool isMobile) {
|
|||
|
|
return Container(
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: Colors.white,
|
|||
|
|
border: Border(
|
|||
|
|
bottom: BorderSide(color: Colors.grey.shade200),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
child: TabBar(
|
|||
|
|
controller: _tabController,
|
|||
|
|
labelColor: Colors.blue,
|
|||
|
|
unselectedLabelColor: Colors.grey,
|
|||
|
|
indicatorColor: Colors.blue,
|
|||
|
|
tabs: const [
|
|||
|
|
Tab(
|
|||
|
|
icon: Icon(Icons.today),
|
|||
|
|
text: '当前计划',
|
|||
|
|
),
|
|||
|
|
Tab(
|
|||
|
|
icon: Icon(Icons.list),
|
|||
|
|
text: '所有计划',
|
|||
|
|
),
|
|||
|
|
Tab(
|
|||
|
|
icon: Icon(Icons.trending_up),
|
|||
|
|
text: '学习进度',
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildCurrentPlanTab(BuildContext context, bool isMobile) {
|
|||
|
|
if (_isLoading) {
|
|||
|
|
return const LoadingWidget();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_currentPlan == null) {
|
|||
|
|
return _buildNoPlanState(context, isMobile);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return SingleChildScrollView(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 24.0),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
_buildCurrentPlanHeader(context, _currentPlan!, isMobile),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
_buildTodayTasks(context, _currentPlan!, isMobile),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
_buildWeeklyProgress(context, _currentPlan!, isMobile),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
_buildQuickActions(context, isMobile),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildAllPlansTab(BuildContext context, bool isMobile) {
|
|||
|
|
if (_isLoading) {
|
|||
|
|
return const LoadingWidget();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_studyPlans.isEmpty) {
|
|||
|
|
return _buildEmptyPlansState(context, isMobile);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return ListView.builder(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 24.0),
|
|||
|
|
itemCount: _studyPlans.length,
|
|||
|
|
itemBuilder: (context, index) {
|
|||
|
|
final plan = _studyPlans[index];
|
|||
|
|
return _buildPlanCard(context, plan, isMobile);
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildProgressTab(BuildContext context, bool isMobile) {
|
|||
|
|
if (_isLoading) {
|
|||
|
|
return const LoadingWidget();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return SingleChildScrollView(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 24.0),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
_buildOverallProgress(context, isMobile),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
_buildWeeklyChart(context, isMobile),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
_buildMonthlyStats(context, isMobile),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
_buildAchievements(context, isMobile),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildCurrentPlanHeader(BuildContext context, StudyPlan plan, bool isMobile) {
|
|||
|
|
final progress = plan.completedTasks / plan.totalTasks;
|
|||
|
|
|
|||
|
|
return Card(
|
|||
|
|
elevation: 4,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(16),
|
|||
|
|
),
|
|||
|
|
child: Container(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 20.0 : 24.0),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
gradient: LinearGradient(
|
|||
|
|
colors: [Colors.blue.shade400, Colors.blue.shade600],
|
|||
|
|
begin: Alignment.topLeft,
|
|||
|
|
end: Alignment.bottomRight,
|
|||
|
|
),
|
|||
|
|
borderRadius: BorderRadius.circular(16),
|
|||
|
|
),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
plan.title,
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white,
|
|||
|
|
fontSize: isMobile ? 20 : 24,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text(
|
|||
|
|
plan.description,
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white.withOpacity(0.9),
|
|||
|
|
fontSize: isMobile ? 14 : 16,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
Container(
|
|||
|
|
padding: const EdgeInsets.all(12),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: Colors.white.withOpacity(0.2),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Icon(
|
|||
|
|
_getPlanTypeIcon(plan.type),
|
|||
|
|
color: Colors.white,
|
|||
|
|
size: isMobile ? 28 : 32,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 20),
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'总体进度',
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white.withOpacity(0.9),
|
|||
|
|
fontSize: isMobile ? 14 : 16,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
LinearProgressIndicator(
|
|||
|
|
value: progress,
|
|||
|
|
backgroundColor: Colors.white.withOpacity(0.3),
|
|||
|
|
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
|
|||
|
|
minHeight: 6,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text(
|
|||
|
|
'${(progress * 100).toInt()}% (${plan.completedTasks}/${plan.totalTasks})',
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white,
|
|||
|
|
fontSize: isMobile ? 12 : 14,
|
|||
|
|
fontWeight: FontWeight.w500,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 20),
|
|||
|
|
Column(
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'剩余天数',
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white.withOpacity(0.9),
|
|||
|
|
fontSize: isMobile ? 12 : 14,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
Text(
|
|||
|
|
'${plan.endDate.difference(DateTime.now()).inDays}',
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white,
|
|||
|
|
fontSize: isMobile ? 24 : 28,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildTodayTasks(BuildContext context, StudyPlan plan, bool isMobile) {
|
|||
|
|
final todayTasks = plan.tasks.where((task) {
|
|||
|
|
final today = DateTime.now();
|
|||
|
|
return task.dueDate.year == today.year &&
|
|||
|
|
task.dueDate.month == today.month &&
|
|||
|
|
task.dueDate.day == today.day;
|
|||
|
|
}).toList();
|
|||
|
|
|
|||
|
|
return Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
Icons.today,
|
|||
|
|
color: Colors.blue[700],
|
|||
|
|
size: isMobile ? 24 : 28,
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 8),
|
|||
|
|
Text(
|
|||
|
|
'今日任务',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 18 : 20,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const Spacer(),
|
|||
|
|
Container(
|
|||
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: Colors.blue.withOpacity(0.1),
|
|||
|
|
borderRadius: BorderRadius.circular(20),
|
|||
|
|
),
|
|||
|
|
child: Text(
|
|||
|
|
'${todayTasks.where((t) => t.isCompleted).length}/${todayTasks.length}',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
color: Colors.blue[700],
|
|||
|
|
fontWeight: FontWeight.w500,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
if (todayTasks.isEmpty)
|
|||
|
|
Container(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 20.0 : 24.0),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: Colors.green.withOpacity(0.1),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
border: Border.all(color: Colors.green.withOpacity(0.3)),
|
|||
|
|
),
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
Icons.check_circle,
|
|||
|
|
color: Colors.green[600],
|
|||
|
|
size: isMobile ? 24 : 28,
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 12),
|
|||
|
|
Expanded(
|
|||
|
|
child: Text(
|
|||
|
|
'今日无任务,您可以休息或提前学习明天的内容',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 14 : 16,
|
|||
|
|
color: Colors.green[700],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
)
|
|||
|
|
else
|
|||
|
|
...todayTasks.map((task) => _buildTaskCard(context, task, isMobile)),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildTaskCard(BuildContext context, StudyTask task, bool isMobile) {
|
|||
|
|
return Container(
|
|||
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|||
|
|
child: Card(
|
|||
|
|
elevation: 2,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: InkWell(
|
|||
|
|
onTap: () => _toggleTaskCompletion(task),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 20.0),
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Container(
|
|||
|
|
width: 24,
|
|||
|
|
height: 24,
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
shape: BoxShape.circle,
|
|||
|
|
color: task.isCompleted ? Colors.green : Colors.transparent,
|
|||
|
|
border: Border.all(
|
|||
|
|
color: task.isCompleted ? Colors.green : Colors.grey[400]!,
|
|||
|
|
width: 2,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
child: task.isCompleted
|
|||
|
|
? const Icon(
|
|||
|
|
Icons.check,
|
|||
|
|
color: Colors.white,
|
|||
|
|
size: 16,
|
|||
|
|
)
|
|||
|
|
: null,
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 16),
|
|||
|
|
Expanded(
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
task.title,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 16 : 18,
|
|||
|
|
fontWeight: FontWeight.w600,
|
|||
|
|
color: task.isCompleted ? Colors.grey[500] : Colors.grey[800],
|
|||
|
|
decoration: task.isCompleted ? TextDecoration.lineThrough : null,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
if (task.description.isNotEmpty) ...[
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text(
|
|||
|
|
task.description,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 14 : 16,
|
|||
|
|
color: task.isCompleted ? Colors.grey[400] : Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
_buildTaskTypeChip(task.type, isMobile),
|
|||
|
|
const SizedBox(width: 8),
|
|||
|
|
if (task.estimatedMinutes > 0)
|
|||
|
|
Container(
|
|||
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: Colors.orange.withOpacity(0.1),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Row(
|
|||
|
|
mainAxisSize: MainAxisSize.min,
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
Icons.access_time,
|
|||
|
|
size: 12,
|
|||
|
|
color: Colors.orange[700],
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 4),
|
|||
|
|
Text(
|
|||
|
|
'${task.estimatedMinutes}分钟',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
color: Colors.orange[700],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
if (!task.isCompleted)
|
|||
|
|
IconButton(
|
|||
|
|
icon: const Icon(Icons.play_arrow),
|
|||
|
|
onPressed: () => _startTask(task),
|
|||
|
|
color: Colors.blue,
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildTaskTypeChip(TaskType type, bool isMobile) {
|
|||
|
|
Color color;
|
|||
|
|
String text;
|
|||
|
|
IconData icon;
|
|||
|
|
|
|||
|
|
switch (type) {
|
|||
|
|
case TaskType.vocabulary:
|
|||
|
|
color = Colors.blue;
|
|||
|
|
text = '词汇';
|
|||
|
|
icon = Icons.book;
|
|||
|
|
break;
|
|||
|
|
case TaskType.reading:
|
|||
|
|
color = Colors.green;
|
|||
|
|
text = '阅读';
|
|||
|
|
icon = Icons.article;
|
|||
|
|
break;
|
|||
|
|
case TaskType.listening:
|
|||
|
|
color = Colors.purple;
|
|||
|
|
text = '听力';
|
|||
|
|
icon = Icons.headphones;
|
|||
|
|
break;
|
|||
|
|
case TaskType.speaking:
|
|||
|
|
color = Colors.orange;
|
|||
|
|
text = '口语';
|
|||
|
|
icon = Icons.mic;
|
|||
|
|
break;
|
|||
|
|
case TaskType.writing:
|
|||
|
|
color = Colors.red;
|
|||
|
|
text = '写作';
|
|||
|
|
icon = Icons.edit;
|
|||
|
|
break;
|
|||
|
|
case TaskType.review:
|
|||
|
|
color = Colors.teal;
|
|||
|
|
text = '复习';
|
|||
|
|
icon = Icons.refresh;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return Container(
|
|||
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: color.withOpacity(0.1),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Row(
|
|||
|
|
mainAxisSize: MainAxisSize.min,
|
|||
|
|
children: [
|
|||
|
|
Icon(icon, size: 12, color: color),
|
|||
|
|
const SizedBox(width: 4),
|
|||
|
|
Text(
|
|||
|
|
text,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
color: color,
|
|||
|
|
fontWeight: FontWeight.w500,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildWeeklyProgress(BuildContext context, StudyPlan plan, bool isMobile) {
|
|||
|
|
return Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
Icons.calendar_view_week,
|
|||
|
|
color: Colors.blue[700],
|
|||
|
|
size: isMobile ? 24 : 28,
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 8),
|
|||
|
|
Text(
|
|||
|
|
'本周进度',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 18 : 20,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
Card(
|
|||
|
|
elevation: 2,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 20.0),
|
|||
|
|
child: Column(
|
|||
|
|
children: [
|
|||
|
|
Row(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|||
|
|
children: List.generate(7, (index) {
|
|||
|
|
final date = DateTime.now().subtract(Duration(days: 6 - index));
|
|||
|
|
final dayName = ['一', '二', '三', '四', '五', '六', '日'][date.weekday - 1];
|
|||
|
|
final isToday = date.day == DateTime.now().day;
|
|||
|
|
final hasTask = plan.tasks.any((task) =>
|
|||
|
|
task.dueDate.year == date.year &&
|
|||
|
|
task.dueDate.month == date.month &&
|
|||
|
|
task.dueDate.day == date.day);
|
|||
|
|
final isCompleted = hasTask && plan.tasks
|
|||
|
|
.where((task) =>
|
|||
|
|
task.dueDate.year == date.year &&
|
|||
|
|
task.dueDate.month == date.month &&
|
|||
|
|
task.dueDate.day == date.day)
|
|||
|
|
.every((task) => task.isCompleted);
|
|||
|
|
|
|||
|
|
return Column(
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
dayName,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
color: isToday ? Colors.blue : Colors.grey[600],
|
|||
|
|
fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
Container(
|
|||
|
|
width: 32,
|
|||
|
|
height: 32,
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
shape: BoxShape.circle,
|
|||
|
|
color: isCompleted
|
|||
|
|
? Colors.green
|
|||
|
|
: hasTask
|
|||
|
|
? Colors.orange.withOpacity(0.3)
|
|||
|
|
: Colors.grey.withOpacity(0.2),
|
|||
|
|
border: isToday
|
|||
|
|
? Border.all(color: Colors.blue, width: 2)
|
|||
|
|
: null,
|
|||
|
|
),
|
|||
|
|
child: Center(
|
|||
|
|
child: isCompleted
|
|||
|
|
? const Icon(
|
|||
|
|
Icons.check,
|
|||
|
|
color: Colors.white,
|
|||
|
|
size: 16,
|
|||
|
|
)
|
|||
|
|
: hasTask
|
|||
|
|
? Icon(
|
|||
|
|
Icons.circle,
|
|||
|
|
color: Colors.orange,
|
|||
|
|
size: 8,
|
|||
|
|
)
|
|||
|
|
: null,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text(
|
|||
|
|
'${date.day}',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 10,
|
|||
|
|
color: Colors.grey[500],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildQuickActions(BuildContext context, bool isMobile) {
|
|||
|
|
return Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'快速操作',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 18 : 20,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
GridView.count(
|
|||
|
|
shrinkWrap: true,
|
|||
|
|
physics: const NeverScrollableScrollPhysics(),
|
|||
|
|
crossAxisCount: isMobile ? 2 : 4,
|
|||
|
|
mainAxisSpacing: 12,
|
|||
|
|
crossAxisSpacing: 12,
|
|||
|
|
childAspectRatio: isMobile ? 1.2 : 1.0,
|
|||
|
|
children: [
|
|||
|
|
_buildQuickActionCard(
|
|||
|
|
context,
|
|||
|
|
'开始学习',
|
|||
|
|
Icons.play_arrow,
|
|||
|
|
Colors.green,
|
|||
|
|
() => _startStudySession(),
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
_buildQuickActionCard(
|
|||
|
|
context,
|
|||
|
|
'复习单词',
|
|||
|
|
Icons.refresh,
|
|||
|
|
Colors.blue,
|
|||
|
|
() => _startReview(),
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
_buildQuickActionCard(
|
|||
|
|
context,
|
|||
|
|
'查看统计',
|
|||
|
|
Icons.analytics,
|
|||
|
|
Colors.purple,
|
|||
|
|
() => _showStatistics(),
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
_buildQuickActionCard(
|
|||
|
|
context,
|
|||
|
|
'调整计划',
|
|||
|
|
Icons.edit,
|
|||
|
|
Colors.orange,
|
|||
|
|
() => _editCurrentPlan(),
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildQuickActionCard(
|
|||
|
|
BuildContext context,
|
|||
|
|
String title,
|
|||
|
|
IconData icon,
|
|||
|
|
Color color,
|
|||
|
|
VoidCallback onTap,
|
|||
|
|
bool isMobile,
|
|||
|
|
) {
|
|||
|
|
return Card(
|
|||
|
|
elevation: 2,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: InkWell(
|
|||
|
|
onTap: onTap,
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 12.0 : 16.0),
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
Container(
|
|||
|
|
padding: const EdgeInsets.all(12),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: color.withOpacity(0.1),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Icon(
|
|||
|
|
icon,
|
|||
|
|
color: color,
|
|||
|
|
size: isMobile ? 24 : 28,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
Text(
|
|||
|
|
title,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 12 : 14,
|
|||
|
|
fontWeight: FontWeight.w600,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
textAlign: TextAlign.center,
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildPlanCard(BuildContext context, StudyPlan plan, bool isMobile) {
|
|||
|
|
final progress = plan.completedTasks / plan.totalTasks;
|
|||
|
|
final isActive = plan == _currentPlan;
|
|||
|
|
|
|||
|
|
return Container(
|
|||
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|||
|
|
child: Card(
|
|||
|
|
elevation: isActive ? 4 : 2,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
side: isActive
|
|||
|
|
? BorderSide(color: Colors.blue, width: 2)
|
|||
|
|
: BorderSide.none,
|
|||
|
|
),
|
|||
|
|
child: InkWell(
|
|||
|
|
onTap: () => _selectPlan(plan),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 20.0),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Container(
|
|||
|
|
padding: const EdgeInsets.all(8),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: _getPlanTypeColor(plan.type).withOpacity(0.1),
|
|||
|
|
borderRadius: BorderRadius.circular(8),
|
|||
|
|
),
|
|||
|
|
child: Icon(
|
|||
|
|
_getPlanTypeIcon(plan.type),
|
|||
|
|
color: _getPlanTypeColor(plan.type),
|
|||
|
|
size: isMobile ? 20 : 24,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 12),
|
|||
|
|
Expanded(
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: Text(
|
|||
|
|
plan.title,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 16 : 18,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
if (isActive)
|
|||
|
|
Container(
|
|||
|
|
padding: const EdgeInsets.symmetric(
|
|||
|
|
horizontal: 8,
|
|||
|
|
vertical: 4,
|
|||
|
|
),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: Colors.blue,
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: const Text(
|
|||
|
|
'当前',
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white,
|
|||
|
|
fontSize: 10,
|
|||
|
|
fontWeight: FontWeight.w500,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text(
|
|||
|
|
plan.description,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 14 : 16,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
maxLines: 2,
|
|||
|
|
overflow: TextOverflow.ellipsis,
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
PopupMenuButton<String>(
|
|||
|
|
icon: Icon(
|
|||
|
|
Icons.more_vert,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
onSelected: (value) => _handlePlanAction(value, plan),
|
|||
|
|
itemBuilder: (context) => [
|
|||
|
|
if (!isActive)
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: 'activate',
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.play_arrow, size: 18),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('设为当前'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: 'edit',
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.edit, size: 18),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('编辑'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: 'duplicate',
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.copy, size: 18),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('复制'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: 'delete',
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.delete, size: 18, color: Colors.red),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('删除', style: TextStyle(color: Colors.red)),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'进度',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
LinearProgressIndicator(
|
|||
|
|
value: progress,
|
|||
|
|
backgroundColor: Colors.grey[300],
|
|||
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|||
|
|
_getPlanTypeColor(plan.type),
|
|||
|
|
),
|
|||
|
|
minHeight: 4,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text(
|
|||
|
|
'${(progress * 100).toInt()}% (${plan.completedTasks}/${plan.totalTasks})',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 16),
|
|||
|
|
Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'剩余',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
Text(
|
|||
|
|
'${plan.endDate.difference(DateTime.now()).inDays}天',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 16 : 18,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildOverallProgress(BuildContext context, bool isMobile) {
|
|||
|
|
return Card(
|
|||
|
|
elevation: 4,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(16),
|
|||
|
|
),
|
|||
|
|
child: Container(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 20.0 : 24.0),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
gradient: LinearGradient(
|
|||
|
|
colors: [Colors.purple.shade400, Colors.purple.shade600],
|
|||
|
|
begin: Alignment.topLeft,
|
|||
|
|
end: Alignment.bottomRight,
|
|||
|
|
),
|
|||
|
|
borderRadius: BorderRadius.circular(16),
|
|||
|
|
),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
Icons.trending_up,
|
|||
|
|
color: Colors.white,
|
|||
|
|
size: isMobile ? 28 : 32,
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 12),
|
|||
|
|
Expanded(
|
|||
|
|
child: Text(
|
|||
|
|
'学习统计',
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white,
|
|||
|
|
fontSize: isMobile ? 20 : 24,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 20),
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildStatItem('连续学习', '7天', Icons.local_fire_department, isMobile),
|
|||
|
|
),
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildStatItem('总学习时长', '45小时', Icons.access_time, isMobile),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildStatItem('掌握单词', '328个', Icons.book, isMobile),
|
|||
|
|
),
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildStatItem('完成任务', '156个', Icons.check_circle, isMobile),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildStatItem(String label, String value, IconData icon, bool isMobile) {
|
|||
|
|
return Column(
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
icon,
|
|||
|
|
color: Colors.white.withOpacity(0.8),
|
|||
|
|
size: isMobile ? 24 : 28,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
Text(
|
|||
|
|
value,
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white,
|
|||
|
|
fontSize: isMobile ? 18 : 20,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
Text(
|
|||
|
|
label,
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.white.withOpacity(0.8),
|
|||
|
|
fontSize: isMobile ? 12 : 14,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildWeeklyChart(BuildContext context, bool isMobile) {
|
|||
|
|
return Card(
|
|||
|
|
elevation: 2,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 20.0),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'本周学习时长',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 18 : 20,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 20),
|
|||
|
|
SizedBox(
|
|||
|
|
height: 200,
|
|||
|
|
child: Center(
|
|||
|
|
child: Text(
|
|||
|
|
'图表组件开发中...',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 16 : 18,
|
|||
|
|
color: Colors.grey[500],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildMonthlyStats(BuildContext context, bool isMobile) {
|
|||
|
|
return Card(
|
|||
|
|
elevation: 2,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 16.0 : 20.0),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'月度统计',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 18 : 20,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildMonthlyStatCard(
|
|||
|
|
'学习天数',
|
|||
|
|
'23/30',
|
|||
|
|
Icons.calendar_today,
|
|||
|
|
Colors.blue,
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 12),
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildMonthlyStatCard(
|
|||
|
|
'平均时长',
|
|||
|
|
'1.8小时',
|
|||
|
|
Icons.access_time,
|
|||
|
|
Colors.green,
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 12),
|
|||
|
|
Row(
|
|||
|
|
children: [
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildMonthlyStatCard(
|
|||
|
|
'新学单词',
|
|||
|
|
'156个',
|
|||
|
|
Icons.add_circle,
|
|||
|
|
Colors.orange,
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 12),
|
|||
|
|
Expanded(
|
|||
|
|
child: _buildMonthlyStatCard(
|
|||
|
|
'复习次数',
|
|||
|
|
'89次',
|
|||
|
|
Icons.refresh,
|
|||
|
|
Colors.purple,
|
|||
|
|
isMobile,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildMonthlyStatCard(
|
|||
|
|
String label,
|
|||
|
|
String value,
|
|||
|
|
IconData icon,
|
|||
|
|
Color color,
|
|||
|
|
bool isMobile,
|
|||
|
|
) {
|
|||
|
|
return Container(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 12.0 : 16.0),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: color.withOpacity(0.1),
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Column(
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
icon,
|
|||
|
|
color: color,
|
|||
|
|
size: isMobile ? 24 : 28,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
Text(
|
|||
|
|
value,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 16 : 18,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: color,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
Text(
|
|||
|
|
label,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 12 : 14,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildAchievements(BuildContext context, bool isMobile) {
|
|||
|
|
final achievements = [
|
|||
|
|
Achievement(
|
|||
|
|
title: '连续学习7天',
|
|||
|
|
description: '坚持每天学习,养成良好习惯',
|
|||
|
|
icon: Icons.local_fire_department,
|
|||
|
|
color: Colors.red,
|
|||
|
|
isUnlocked: true,
|
|||
|
|
),
|
|||
|
|
Achievement(
|
|||
|
|
title: '掌握100个单词',
|
|||
|
|
description: '词汇量达到新的里程碑',
|
|||
|
|
icon: Icons.book,
|
|||
|
|
color: Colors.blue,
|
|||
|
|
isUnlocked: true,
|
|||
|
|
),
|
|||
|
|
Achievement(
|
|||
|
|
title: '学习时长达到50小时',
|
|||
|
|
description: '累计学习时间的重要节点',
|
|||
|
|
icon: Icons.access_time,
|
|||
|
|
color: Colors.green,
|
|||
|
|
isUnlocked: false,
|
|||
|
|
),
|
|||
|
|
Achievement(
|
|||
|
|
title: '完成第一个学习计划',
|
|||
|
|
description: '成功完成制定的学习目标',
|
|||
|
|
icon: Icons.emoji_events,
|
|||
|
|
color: Colors.orange,
|
|||
|
|
isUnlocked: false,
|
|||
|
|
),
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
'成就徽章',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 18 : 20,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[800],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
GridView.builder(
|
|||
|
|
shrinkWrap: true,
|
|||
|
|
physics: const NeverScrollableScrollPhysics(),
|
|||
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|||
|
|
crossAxisCount: isMobile ? 2 : 4,
|
|||
|
|
mainAxisSpacing: 12,
|
|||
|
|
crossAxisSpacing: 12,
|
|||
|
|
childAspectRatio: 1.0,
|
|||
|
|
),
|
|||
|
|
itemCount: achievements.length,
|
|||
|
|
itemBuilder: (context, index) {
|
|||
|
|
final achievement = achievements[index];
|
|||
|
|
return _buildAchievementCard(context, achievement, isMobile);
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildAchievementCard(BuildContext context, Achievement achievement, bool isMobile) {
|
|||
|
|
return Card(
|
|||
|
|
elevation: achievement.isUnlocked ? 4 : 1,
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
),
|
|||
|
|
child: Container(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 12.0 : 16.0),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
borderRadius: BorderRadius.circular(12),
|
|||
|
|
color: achievement.isUnlocked
|
|||
|
|
? achievement.color.withOpacity(0.1)
|
|||
|
|
: Colors.grey.withOpacity(0.05),
|
|||
|
|
),
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
Container(
|
|||
|
|
padding: const EdgeInsets.all(12),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
shape: BoxShape.circle,
|
|||
|
|
color: achievement.isUnlocked
|
|||
|
|
? achievement.color
|
|||
|
|
: Colors.grey.withOpacity(0.3),
|
|||
|
|
),
|
|||
|
|
child: Icon(
|
|||
|
|
achievement.icon,
|
|||
|
|
color: achievement.isUnlocked ? Colors.white : Colors.grey,
|
|||
|
|
size: isMobile ? 24 : 28,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
Text(
|
|||
|
|
achievement.title,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 12 : 14,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: achievement.isUnlocked
|
|||
|
|
? Colors.grey[800]
|
|||
|
|
: Colors.grey[500],
|
|||
|
|
),
|
|||
|
|
textAlign: TextAlign.center,
|
|||
|
|
maxLines: 2,
|
|||
|
|
overflow: TextOverflow.ellipsis,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text(
|
|||
|
|
achievement.description,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 10,
|
|||
|
|
color: achievement.isUnlocked
|
|||
|
|
? Colors.grey[600]
|
|||
|
|
: Colors.grey[400],
|
|||
|
|
),
|
|||
|
|
textAlign: TextAlign.center,
|
|||
|
|
maxLines: 2,
|
|||
|
|
overflow: TextOverflow.ellipsis,
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildNoPlanState(BuildContext context, bool isMobile) {
|
|||
|
|
return Center(
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 32.0 : 48.0),
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
Icons.assignment,
|
|||
|
|
size: isMobile ? 80 : 100,
|
|||
|
|
color: Colors.grey[400],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
Text(
|
|||
|
|
'暂无学习计划',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 20 : 24,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 12),
|
|||
|
|
Text(
|
|||
|
|
'创建您的第一个学习计划,开始高效学习之旅',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 16 : 18,
|
|||
|
|
color: Colors.grey[500],
|
|||
|
|
),
|
|||
|
|
textAlign: TextAlign.center,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 32),
|
|||
|
|
ElevatedButton.icon(
|
|||
|
|
onPressed: _showCreatePlanDialog,
|
|||
|
|
icon: const Icon(Icons.add),
|
|||
|
|
label: const Text('创建计划'),
|
|||
|
|
style: ElevatedButton.styleFrom(
|
|||
|
|
backgroundColor: Colors.blue,
|
|||
|
|
foregroundColor: Colors.white,
|
|||
|
|
padding: EdgeInsets.symmetric(
|
|||
|
|
horizontal: isMobile ? 24 : 32,
|
|||
|
|
vertical: isMobile ? 12 : 16,
|
|||
|
|
),
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(25),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildEmptyPlansState(BuildContext context, bool isMobile) {
|
|||
|
|
return Center(
|
|||
|
|
child: Padding(
|
|||
|
|
padding: EdgeInsets.all(isMobile ? 32.0 : 48.0),
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
Icon(
|
|||
|
|
Icons.list_alt,
|
|||
|
|
size: isMobile ? 80 : 100,
|
|||
|
|
color: Colors.grey[400],
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 24),
|
|||
|
|
Text(
|
|||
|
|
'暂无学习计划',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 20 : 24,
|
|||
|
|
fontWeight: FontWeight.bold,
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 12),
|
|||
|
|
Text(
|
|||
|
|
'创建学习计划,制定个性化的学习目标',
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: isMobile ? 16 : 18,
|
|||
|
|
color: Colors.grey[500],
|
|||
|
|
),
|
|||
|
|
textAlign: TextAlign.center,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 32),
|
|||
|
|
Row(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
ElevatedButton.icon(
|
|||
|
|
onPressed: _showCreatePlanDialog,
|
|||
|
|
icon: const Icon(Icons.add),
|
|||
|
|
label: const Text('创建计划'),
|
|||
|
|
style: ElevatedButton.styleFrom(
|
|||
|
|
backgroundColor: Colors.blue,
|
|||
|
|
foregroundColor: Colors.white,
|
|||
|
|
padding: EdgeInsets.symmetric(
|
|||
|
|
horizontal: isMobile ? 20 : 24,
|
|||
|
|
vertical: isMobile ? 12 : 16,
|
|||
|
|
),
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(25),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const SizedBox(width: 16),
|
|||
|
|
OutlinedButton.icon(
|
|||
|
|
onPressed: _showPlanTemplates,
|
|||
|
|
icon: const Icon(Icons.library_books),
|
|||
|
|
label: const Text('使用模板'),
|
|||
|
|
style: OutlinedButton.styleFrom(
|
|||
|
|
padding: EdgeInsets.symmetric(
|
|||
|
|
horizontal: isMobile ? 20 : 24,
|
|||
|
|
vertical: isMobile ? 12 : 16,
|
|||
|
|
),
|
|||
|
|
shape: RoundedRectangleBorder(
|
|||
|
|
borderRadius: BorderRadius.circular(25),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _loadStudyPlans() async {
|
|||
|
|
setState(() {
|
|||
|
|
_isLoading = true;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 模拟加载数据
|
|||
|
|
await Future.delayed(const Duration(milliseconds: 800));
|
|||
|
|
|
|||
|
|
setState(() {
|
|||
|
|
_studyPlans = _generateMockPlans();
|
|||
|
|
_currentPlan = _studyPlans.isNotEmpty ? _studyPlans.first : null;
|
|||
|
|
_isLoading = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
List<StudyPlan> _generateMockPlans() {
|
|||
|
|
final now = DateTime.now();
|
|||
|
|
return [
|
|||
|
|
StudyPlan(
|
|||
|
|
id: '1',
|
|||
|
|
title: '30天词汇突破计划',
|
|||
|
|
description: '每天学习20个新单词,复习已学单词',
|
|||
|
|
type: PlanType.vocabulary,
|
|||
|
|
startDate: now.subtract(const Duration(days: 7)),
|
|||
|
|
endDate: now.add(const Duration(days: 23)),
|
|||
|
|
tasks: _generateMockTasks(),
|
|||
|
|
totalTasks: 30,
|
|||
|
|
completedTasks: 7,
|
|||
|
|
isActive: true,
|
|||
|
|
),
|
|||
|
|
StudyPlan(
|
|||
|
|
id: '2',
|
|||
|
|
title: '雅思备考计划',
|
|||
|
|
description: '全面提升听说读写能力,目标7分',
|
|||
|
|
type: PlanType.exam,
|
|||
|
|
startDate: now.add(const Duration(days: 1)),
|
|||
|
|
endDate: now.add(const Duration(days: 90)),
|
|||
|
|
tasks: [],
|
|||
|
|
totalTasks: 90,
|
|||
|
|
completedTasks: 0,
|
|||
|
|
isActive: false,
|
|||
|
|
),
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
List<StudyTask> _generateMockTasks() {
|
|||
|
|
final now = DateTime.now();
|
|||
|
|
return [
|
|||
|
|
StudyTask(
|
|||
|
|
id: '1',
|
|||
|
|
title: '学习商务词汇',
|
|||
|
|
description: '学习20个商务相关单词',
|
|||
|
|
type: TaskType.vocabulary,
|
|||
|
|
dueDate: now,
|
|||
|
|
estimatedMinutes: 30,
|
|||
|
|
isCompleted: false,
|
|||
|
|
),
|
|||
|
|
StudyTask(
|
|||
|
|
id: '2',
|
|||
|
|
title: '复习昨日单词',
|
|||
|
|
description: '复习昨天学习的单词',
|
|||
|
|
type: TaskType.review,
|
|||
|
|
dueDate: now,
|
|||
|
|
estimatedMinutes: 15,
|
|||
|
|
isCompleted: true,
|
|||
|
|
),
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _showCreatePlanDialog() {
|
|||
|
|
showDialog(
|
|||
|
|
context: context,
|
|||
|
|
builder: (context) {
|
|||
|
|
return AlertDialog(
|
|||
|
|
title: const Text('创建学习计划'),
|
|||
|
|
content: const Text('计划创建功能开发中...'),
|
|||
|
|
actions: [
|
|||
|
|
TextButton(
|
|||
|
|
onPressed: () {
|
|||
|
|
Navigator.of(context).pop();
|
|||
|
|
},
|
|||
|
|
child: const Text('关闭'),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _showPlanTemplates() {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('计划模板功能开发中')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _handleMenuAction(String action) {
|
|||
|
|
switch (action) {
|
|||
|
|
case 'templates':
|
|||
|
|
_showPlanTemplates();
|
|||
|
|
break;
|
|||
|
|
case 'statistics':
|
|||
|
|
_showStatistics();
|
|||
|
|
break;
|
|||
|
|
case 'settings':
|
|||
|
|
_showSettings();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _handlePlanAction(String action, StudyPlan plan) {
|
|||
|
|
switch (action) {
|
|||
|
|
case 'activate':
|
|||
|
|
_selectPlan(plan);
|
|||
|
|
break;
|
|||
|
|
case 'edit':
|
|||
|
|
_editPlan(plan);
|
|||
|
|
break;
|
|||
|
|
case 'duplicate':
|
|||
|
|
_duplicatePlan(plan);
|
|||
|
|
break;
|
|||
|
|
case 'delete':
|
|||
|
|
_deletePlan(plan);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _selectPlan(StudyPlan plan) {
|
|||
|
|
setState(() {
|
|||
|
|
_currentPlan = plan;
|
|||
|
|
});
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text('已切换到计划:${plan.title}')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _editPlan(StudyPlan plan) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('编辑计划功能开发中')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _duplicatePlan(StudyPlan plan) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('复制计划功能开发中')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _deletePlan(StudyPlan plan) {
|
|||
|
|
showDialog(
|
|||
|
|
context: context,
|
|||
|
|
builder: (context) {
|
|||
|
|
return AlertDialog(
|
|||
|
|
title: const Text('确认删除'),
|
|||
|
|
content: Text('确定要删除计划 "${plan.title}" 吗?'),
|
|||
|
|
actions: [
|
|||
|
|
TextButton(
|
|||
|
|
onPressed: () {
|
|||
|
|
Navigator.of(context).pop();
|
|||
|
|
},
|
|||
|
|
child: const Text('取消'),
|
|||
|
|
),
|
|||
|
|
TextButton(
|
|||
|
|
onPressed: () {
|
|||
|
|
Navigator.of(context).pop();
|
|||
|
|
setState(() {
|
|||
|
|
_studyPlans.removeWhere((p) => p.id == plan.id);
|
|||
|
|
if (_currentPlan?.id == plan.id) {
|
|||
|
|
_currentPlan = _studyPlans.isNotEmpty ? _studyPlans.first : null;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('计划已删除')),
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _toggleTaskCompletion(StudyTask task) {
|
|||
|
|
setState(() {
|
|||
|
|
task.isCompleted = !task.isCompleted;
|
|||
|
|
if (_currentPlan != null) {
|
|||
|
|
if (task.isCompleted) {
|
|||
|
|
_currentPlan!.completedTasks++;
|
|||
|
|
} else {
|
|||
|
|
_currentPlan!.completedTasks--;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _startTask(StudyTask task) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text('开始任务:${task.title}')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _startStudySession() {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('开始学习会话')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _startReview() {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('开始复习')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _showStatistics() {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('统计功能开发中')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _showSettings() {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('设置功能开发中')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _editCurrentPlan() {
|
|||
|
|
if (_currentPlan != null) {
|
|||
|
|
_editPlan(_currentPlan!);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
IconData _getPlanTypeIcon(PlanType type) {
|
|||
|
|
switch (type) {
|
|||
|
|
case PlanType.vocabulary:
|
|||
|
|
return Icons.book;
|
|||
|
|
case PlanType.grammar:
|
|||
|
|
return Icons.school;
|
|||
|
|
case PlanType.speaking:
|
|||
|
|
return Icons.mic;
|
|||
|
|
case PlanType.listening:
|
|||
|
|
return Icons.headphones;
|
|||
|
|
case PlanType.reading:
|
|||
|
|
return Icons.article;
|
|||
|
|
case PlanType.writing:
|
|||
|
|
return Icons.edit;
|
|||
|
|
case PlanType.exam:
|
|||
|
|
return Icons.quiz;
|
|||
|
|
case PlanType.custom:
|
|||
|
|
return Icons.settings;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Color _getPlanTypeColor(PlanType type) {
|
|||
|
|
switch (type) {
|
|||
|
|
case PlanType.vocabulary:
|
|||
|
|
return Colors.blue;
|
|||
|
|
case PlanType.grammar:
|
|||
|
|
return Colors.green;
|
|||
|
|
case PlanType.speaking:
|
|||
|
|
return Colors.orange;
|
|||
|
|
case PlanType.listening:
|
|||
|
|
return Colors.purple;
|
|||
|
|
case PlanType.reading:
|
|||
|
|
return Colors.teal;
|
|||
|
|
case PlanType.writing:
|
|||
|
|
return Colors.red;
|
|||
|
|
case PlanType.exam:
|
|||
|
|
return Colors.indigo;
|
|||
|
|
case PlanType.custom:
|
|||
|
|
return Colors.grey;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum PlanType {
|
|||
|
|
vocabulary,
|
|||
|
|
grammar,
|
|||
|
|
speaking,
|
|||
|
|
listening,
|
|||
|
|
reading,
|
|||
|
|
writing,
|
|||
|
|
exam,
|
|||
|
|
custom,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum TaskType {
|
|||
|
|
vocabulary,
|
|||
|
|
reading,
|
|||
|
|
listening,
|
|||
|
|
speaking,
|
|||
|
|
writing,
|
|||
|
|
review,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class StudyPlan {
|
|||
|
|
final String id;
|
|||
|
|
final String title;
|
|||
|
|
final String description;
|
|||
|
|
final PlanType type;
|
|||
|
|
final DateTime startDate;
|
|||
|
|
final DateTime endDate;
|
|||
|
|
final List<StudyTask> tasks;
|
|||
|
|
final int totalTasks;
|
|||
|
|
int completedTasks;
|
|||
|
|
final bool isActive;
|
|||
|
|
|
|||
|
|
StudyPlan({
|
|||
|
|
required this.id,
|
|||
|
|
required this.title,
|
|||
|
|
required this.description,
|
|||
|
|
required this.type,
|
|||
|
|
required this.startDate,
|
|||
|
|
required this.endDate,
|
|||
|
|
required this.tasks,
|
|||
|
|
required this.totalTasks,
|
|||
|
|
required this.completedTasks,
|
|||
|
|
required this.isActive,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class StudyTask {
|
|||
|
|
final String id;
|
|||
|
|
final String title;
|
|||
|
|
final String description;
|
|||
|
|
final TaskType type;
|
|||
|
|
final DateTime dueDate;
|
|||
|
|
final int estimatedMinutes;
|
|||
|
|
bool isCompleted;
|
|||
|
|
|
|||
|
|
StudyTask({
|
|||
|
|
required this.id,
|
|||
|
|
required this.title,
|
|||
|
|
required this.description,
|
|||
|
|
required this.type,
|
|||
|
|
required this.dueDate,
|
|||
|
|
required this.estimatedMinutes,
|
|||
|
|
required this.isCompleted,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class Achievement {
|
|||
|
|
final String title;
|
|||
|
|
final String description;
|
|||
|
|
final IconData icon;
|
|||
|
|
final Color color;
|
|||
|
|
final bool isUnlocked;
|
|||
|
|
|
|||
|
|
const Achievement({
|
|||
|
|
required this.title,
|
|||
|
|
required this.description,
|
|||
|
|
required this.icon,
|
|||
|
|
required this.color,
|
|||
|
|
required this.isUnlocked,
|
|||
|
|
});
|
|||
|
|
}
|