1761 lines
58 KiB
Dart
1761 lines
58 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import '../../../core/utils/responsive_utils.dart';
|
||
import '../../../core/routes/app_routes.dart';
|
||
import '../models/vocabulary_book_model.dart';
|
||
import '../models/vocabulary_book_category.dart';
|
||
import '../models/word_model.dart';
|
||
import '../data/vocabulary_book_factory.dart';
|
||
import '../providers/vocabulary_provider.dart';
|
||
import '../../../core/routes/app_routes.dart';
|
||
import '../../auth/providers/auth_provider.dart';
|
||
|
||
/// 词汇学习主页面
|
||
class VocabularyHomeScreen extends ConsumerStatefulWidget {
|
||
const VocabularyHomeScreen({super.key});
|
||
|
||
@override
|
||
ConsumerState<VocabularyHomeScreen> createState() => _VocabularyHomeScreenState();
|
||
}
|
||
|
||
class _VocabularyHomeScreenState extends ConsumerState<VocabularyHomeScreen> {
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 初始化时加载统计数据
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
_loadStats();
|
||
});
|
||
}
|
||
|
||
// 移除 _loadVocabularyBooks 方法,因为 provider 已经自动加载}
|
||
|
||
Future<void> _loadStats() async {
|
||
try {
|
||
final vocabularyNotifier = ref.read(vocabularyProvider.notifier);
|
||
|
||
// 获取当前用户ID以加载每日统计
|
||
final user = ref.read(currentUserProvider);
|
||
final userId = user?.id;
|
||
|
||
await vocabularyNotifier.loadUserVocabularyOverallStats();
|
||
await vocabularyNotifier.loadWeeklyStudyStats();
|
||
if (userId != null && userId.isNotEmpty) {
|
||
await vocabularyNotifier.loadTodayStudyWords(userId: userId);
|
||
} else {
|
||
// 无用户时仍加载今日单词与默认统计
|
||
await vocabularyNotifier.loadTodayStudyWords();
|
||
}
|
||
} catch (_) {
|
||
// 忽略错误,在UI中以默认值显示
|
||
}
|
||
}
|
||
|
||
// 获取推荐词汇书数据
|
||
static List<VocabularyBook> get _recommendedBooks => VocabularyBookFactory.getRecommendedBooks(limit: 8);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final vocabularyAsync = ref.watch(vocabularyProvider);
|
||
|
||
return Scaffold(
|
||
backgroundColor: const Color(0xFFF5F5F5),
|
||
appBar: AppBar(
|
||
backgroundColor: Colors.transparent,
|
||
elevation: 0,
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back, color: Colors.black87),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
),
|
||
title: const Text(
|
||
'词汇学习',
|
||
style: TextStyle(
|
||
color: Colors.black87,
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
centerTitle: true,
|
||
),
|
||
body: SafeArea(
|
||
child: SingleChildScrollView(
|
||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||
child: Column(
|
||
children: [
|
||
const SizedBox(height: 12),
|
||
_buildTitle(),
|
||
const SizedBox(height: 12),
|
||
_buildCategoryNavigation(),
|
||
const SizedBox(height: 12),
|
||
_buildTodayWords(),
|
||
const SizedBox(height: 12),
|
||
// _buildAIRecommendation(),
|
||
// const SizedBox(height: 12),
|
||
_buildLearningStats(),
|
||
const SizedBox(height: 80), // 底部导航栏空间
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
|
||
|
||
Widget _buildTitle() {
|
||
return const Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||
child: Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
'选择词书开始学习',
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCategoryNavigation() {
|
||
// 从 API 加载分类数据
|
||
final state = ref.watch(vocabularyProvider);
|
||
final categories = state.categories;
|
||
|
||
print('📂 分类数据: ${categories.length} 个分类');
|
||
if (categories.isNotEmpty) {
|
||
print('📂 第一个分类: ${categories.first}');
|
||
}
|
||
|
||
// 如果API没有返回分类数据,使用本地分类
|
||
final displayCategories = categories.isEmpty
|
||
? _getLocalCategories()
|
||
: categories;
|
||
|
||
return ResponsiveBuilder(
|
||
builder: (context, isMobile, isTablet, isDesktop) {
|
||
return Container(
|
||
margin: ResponsiveUtils.getResponsivePadding(context),
|
||
child: GridView.builder(
|
||
shrinkWrap: true,
|
||
physics: const NeverScrollableScrollPhysics(),
|
||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: 2,
|
||
crossAxisSpacing: 12,
|
||
mainAxisSpacing: 12,
|
||
mainAxisExtent: isMobile ? 110 : 130,
|
||
),
|
||
itemCount: displayCategories.length,
|
||
itemBuilder: (context, index) {
|
||
final category = displayCategories[index];
|
||
final categoryName = category['name'] as String;
|
||
final count = category['count'] as int;
|
||
|
||
// 从中文名称映射到枚举(用于显示图标和颜色)
|
||
final mainCategory = VocabularyBookCategoryHelper.getMainCategoryFromName(categoryName);
|
||
if (mainCategory == null) return const SizedBox.shrink();
|
||
|
||
// 根据分类获取图标和颜色
|
||
final iconData = _getCategoryIcon(mainCategory);
|
||
final color = _getCategoryColor(mainCategory);
|
||
|
||
return _buildCategoryCard(
|
||
mainCategory,
|
||
iconData,
|
||
color,
|
||
count: count,
|
||
);
|
||
},
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
// 获取本地分类数据作为后备方案
|
||
List<Map<String, dynamic>> _getLocalCategories() {
|
||
return [
|
||
{'name': '学段基础词汇', 'count': 4},
|
||
{'name': '国内应试类词汇', 'count': 4},
|
||
{'name': '出国考试类词汇', 'count': 3},
|
||
{'name': '职业与专业类词汇', 'count': 3},
|
||
{'name': '功能型词库', 'count': 3},
|
||
];
|
||
}
|
||
|
||
IconData _getCategoryIcon(VocabularyBookMainCategory category) {
|
||
switch (category) {
|
||
case VocabularyBookMainCategory.academicStage:
|
||
return Icons.school;
|
||
case VocabularyBookMainCategory.domesticTest:
|
||
return Icons.quiz;
|
||
case VocabularyBookMainCategory.internationalTest:
|
||
return Icons.public;
|
||
case VocabularyBookMainCategory.professional:
|
||
return Icons.work;
|
||
case VocabularyBookMainCategory.functional:
|
||
return Icons.functions;
|
||
}
|
||
}
|
||
|
||
Color _getCategoryColor(VocabularyBookMainCategory category) {
|
||
switch (category) {
|
||
case VocabularyBookMainCategory.academicStage:
|
||
return Colors.blue;
|
||
case VocabularyBookMainCategory.domesticTest:
|
||
return Colors.green;
|
||
case VocabularyBookMainCategory.internationalTest:
|
||
return Colors.orange;
|
||
case VocabularyBookMainCategory.professional:
|
||
return Colors.purple;
|
||
case VocabularyBookMainCategory.functional:
|
||
return Colors.teal;
|
||
}
|
||
}
|
||
|
||
Widget _buildCategoryCard(VocabularyBookMainCategory category, IconData icon, Color color, {int? count}) {
|
||
final categoryName = VocabularyBookCategoryHelper.getMainCategoryName(category);
|
||
|
||
return GestureDetector(
|
||
onTap: () => _navigateToCategory(category),
|
||
child: Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withOpacity(0.05),
|
||
blurRadius: 8,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Container(
|
||
width: 40,
|
||
height: 40,
|
||
decoration: BoxDecoration(
|
||
color: color.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Icon(
|
||
icon,
|
||
color: color,
|
||
size: 22,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
categoryName,
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
maxLines: 2,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
if (count != null) ...[
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
'$count 本',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.grey[600],
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildVocabularyBooks() {
|
||
final systemBooks = ref.watch(systemVocabularyBooksProvider);
|
||
final userBooks = ref.watch(userVocabularyBooksProvider);
|
||
|
||
// 合并系统词汇书和用户词汇书
|
||
final allBooks = [...systemBooks, ...userBooks];
|
||
|
||
// 如果没有数据,显示推荐词书
|
||
final booksToShow = allBooks.isNotEmpty ? allBooks : _recommendedBooks;
|
||
|
||
return ResponsiveBuilder(
|
||
builder: (context, isMobile, isTablet, isDesktop) {
|
||
final crossAxisCount = ResponsiveUtils.getGridColumns(
|
||
context,
|
||
mobileColumns: 3,
|
||
tabletColumns: 4,
|
||
desktopColumns: 5,
|
||
);
|
||
|
||
final childAspectRatio = ResponsiveUtils.getValueForScreenSize(
|
||
context,
|
||
mobile: 1.3,
|
||
tablet: 1.4,
|
||
desktop: 1.5,
|
||
);
|
||
|
||
return Container(
|
||
margin: ResponsiveUtils.getResponsivePadding(context),
|
||
child: GridView.builder(
|
||
shrinkWrap: true,
|
||
physics: const NeverScrollableScrollPhysics(),
|
||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: crossAxisCount,
|
||
crossAxisSpacing: 8,
|
||
mainAxisSpacing: 8,
|
||
childAspectRatio: childAspectRatio,
|
||
),
|
||
itemCount: booksToShow.length,
|
||
itemBuilder: (context, index) {
|
||
final book = booksToShow[index];
|
||
// 使用后端 Provider 获取词书学习进度
|
||
final progressAsync = ref.watch(vocabularyBookProgressProvider(book.id));
|
||
final progress = progressAsync.when(
|
||
data: (p) => (p.progressPercentage / 100.0).clamp(0.0, 1.0),
|
||
loading: () => null,
|
||
error: (e, st) => 0.0,
|
||
);
|
||
return _buildBookCard(
|
||
book,
|
||
progress,
|
||
);
|
||
},
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildBookCard(VocabularyBook book, double? progress) {
|
||
return ResponsiveBuilder(
|
||
builder: (context, isMobile, isTablet, isDesktop) {
|
||
final iconSize = ResponsiveUtils.getValueForScreenSize(
|
||
context,
|
||
mobile: 64.0,
|
||
tablet: 72.0,
|
||
desktop: 80.0,
|
||
);
|
||
|
||
final titleFontSize = ResponsiveUtils.getResponsiveFontSize(
|
||
context,
|
||
mobileSize: 14,
|
||
tabletSize: 16,
|
||
desktopSize: 18,
|
||
);
|
||
|
||
final subtitleFontSize = ResponsiveUtils.getResponsiveFontSize(
|
||
context,
|
||
mobileSize: 12,
|
||
tabletSize: 14,
|
||
desktopSize: 16,
|
||
);
|
||
|
||
final progressFontSize = ResponsiveUtils.getResponsiveFontSize(
|
||
context,
|
||
mobileSize: 10,
|
||
tabletSize: 12,
|
||
desktopSize: 14,
|
||
);
|
||
|
||
final cardPadding = ResponsiveUtils.getValueForScreenSize(
|
||
context,
|
||
mobile: 8.0,
|
||
tablet: 10.0,
|
||
desktop: 12.0,
|
||
);
|
||
|
||
return GestureDetector(
|
||
onTap: () => _navigateToVocabularyBook(book),
|
||
child: Container(
|
||
padding: EdgeInsets.all(cardPadding),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withOpacity(0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Container(
|
||
width: iconSize,
|
||
height: iconSize,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(8),
|
||
image: book.coverImageUrl != null
|
||
? DecorationImage(
|
||
image: NetworkImage(book.coverImageUrl!),
|
||
fit: BoxFit.cover,
|
||
)
|
||
: null,
|
||
color: book.coverImageUrl == null ? Colors.grey[300] : null,
|
||
),
|
||
child: book.coverImageUrl == null
|
||
? Icon(
|
||
Icons.book,
|
||
color: Colors.grey,
|
||
size: iconSize * 0.4,
|
||
)
|
||
: null,
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
book.name,
|
||
style: TextStyle(
|
||
fontSize: titleFontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
const SizedBox(height: 1),
|
||
Text(
|
||
'${book.totalWords}词',
|
||
style: TextStyle(
|
||
fontSize: subtitleFontSize,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
const SizedBox(height: 3),
|
||
LinearProgressIndicator(
|
||
value: progress,
|
||
backgroundColor: Colors.grey[200],
|
||
valueColor: const AlwaysStoppedAnimation<Color>(Color(0xFF2196F3)),
|
||
minHeight: 4,
|
||
),
|
||
const SizedBox(height: 1),
|
||
Text(
|
||
progress == null
|
||
? '加载中...'
|
||
: '${(progress * 100).toInt()}%',
|
||
style: TextStyle(
|
||
fontSize: ResponsiveUtils.getValueForScreenSize(
|
||
context,
|
||
mobile: 9.0,
|
||
tablet: 10.0,
|
||
desktop: 12.0,
|
||
),
|
||
color: Colors.grey[600],
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
void _navigateToVocabularyBook(VocabularyBook book) {
|
||
Navigator.of(context).pushNamed(
|
||
Routes.vocabularyList,
|
||
arguments: {'vocabularyBook': book},
|
||
);
|
||
}
|
||
|
||
void _startTodayWordLearning() {
|
||
// 导航到今日单词详情页面
|
||
Navigator.of(context).pushNamed(Routes.dailyWords);
|
||
}
|
||
|
||
void _showWordDetailFromModel(Word word) {
|
||
final meaning = word.definitions.isNotEmpty
|
||
? word.definitions.map((d) => d.translation).join('; ')
|
||
: '暂无释义';
|
||
final phonetic = word.phonetic ?? '';
|
||
|
||
_showWordDetail(word.word, meaning, phonetic, word);
|
||
}
|
||
|
||
void _showWordDetail(String wordText, String meaning, String phonetic, [Word? wordModel]) {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
backgroundColor: Colors.transparent,
|
||
builder: (context) => Container(
|
||
height: MediaQuery.of(context).size.height * 0.7,
|
||
decoration: const BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Container(
|
||
width: 40,
|
||
height: 4,
|
||
margin: const EdgeInsets.only(top: 12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey[300],
|
||
borderRadius: BorderRadius.circular(2),
|
||
),
|
||
),
|
||
Expanded(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(24),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
wordText,
|
||
style: const TextStyle(
|
||
fontSize: 28,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
phonetic,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
color: Color(0xFF2196F3),
|
||
fontStyle: FontStyle.italic,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
IconButton(
|
||
onPressed: () => _playWordPronunciation(wordText),
|
||
icon: const Icon(
|
||
Icons.volume_up,
|
||
color: Color(0xFF2196F3),
|
||
size: 32,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 24),
|
||
const Text(
|
||
'释义',
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
meaning,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
color: Colors.black87,
|
||
),
|
||
),
|
||
const SizedBox(height: 24),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: ElevatedButton(
|
||
onPressed: () {
|
||
Navigator.pop(context);
|
||
if (wordModel != null) {
|
||
_startWordLearningWithModel(wordModel);
|
||
} else {
|
||
_startWordLearning(wordText);
|
||
}
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF2196F3),
|
||
foregroundColor: Colors.white,
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
child: const Text('开始学习'),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: OutlinedButton(
|
||
onPressed: () {
|
||
if (wordModel != null) {
|
||
_addWordToFavorites(wordModel);
|
||
} else {
|
||
_addToFavorites(wordText);
|
||
}
|
||
},
|
||
style: OutlinedButton.styleFrom(
|
||
foregroundColor: const Color(0xFF2196F3),
|
||
side: const BorderSide(color: Color(0xFF2196F3)),
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
child: const Text('收藏'),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _startWordLearningWithModel(Word word) {
|
||
Navigator.of(context).pushNamed(
|
||
Routes.wordLearning,
|
||
arguments: {
|
||
'words': [word],
|
||
'mode': LearningMode.normal,
|
||
'dailyTarget': 1,
|
||
},
|
||
);
|
||
}
|
||
|
||
void _startWordLearning(String word) {
|
||
// 为单个单词创建学习会话
|
||
final wordToLearn = Word(
|
||
id: '1',
|
||
word: word,
|
||
phonetic: '/example/',
|
||
difficulty: WordDifficulty.intermediate,
|
||
frequency: 1000,
|
||
definitions: [
|
||
WordDefinition(
|
||
type: WordType.noun,
|
||
definition: word,
|
||
translation: '示例释义',
|
||
),
|
||
],
|
||
examples: [
|
||
WordExample(
|
||
sentence: 'This is an example sentence.',
|
||
translation: '这是一个示例句子。',
|
||
),
|
||
],
|
||
createdAt: DateTime.now(),
|
||
updatedAt: DateTime.now(),
|
||
);
|
||
|
||
Navigator.of(context).pushNamed(
|
||
Routes.wordLearning,
|
||
arguments: {
|
||
'words': [wordToLearn],
|
||
'mode': LearningMode.normal,
|
||
'dailyTarget': 1,
|
||
},
|
||
);
|
||
}
|
||
|
||
void _playWordAudio(Word word) async {
|
||
if (word.audioUrl != null && word.audioUrl!.isNotEmpty) {
|
||
// TODO: 集成音频服务播放
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('正在播放 "${word.word}" 的发音'),
|
||
duration: const Duration(seconds: 1),
|
||
),
|
||
);
|
||
} else {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('"${word.word}" 暂无音频'),
|
||
duration: const Duration(seconds: 1),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
void _playWordPronunciation(String word) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('播放 "$word" 的发音'),
|
||
duration: const Duration(seconds: 1),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _addWordToFavorites(Word word) async {
|
||
// TODO: 集成生词本服务
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('已将 "${word.word}" 添加到生词本'),
|
||
duration: const Duration(seconds: 2),
|
||
backgroundColor: Colors.green,
|
||
),
|
||
);
|
||
}
|
||
|
||
void _addToFavorites(String word) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('已将 "$word" 添加到收藏夹'),
|
||
duration: const Duration(seconds: 2),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTodayWords() {
|
||
final vocabularyAsync = ref.watch(vocabularyProvider);
|
||
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withOpacity(0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
const Text(
|
||
'今日单词',
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
TextButton(
|
||
onPressed: _startTodayWordLearning,
|
||
child: const Text(
|
||
'开始学习',
|
||
style: TextStyle(
|
||
color: Color(0xFF2196F3),
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Builder(
|
||
builder: (context) {
|
||
final state = ref.watch(vocabularyProvider);
|
||
final todayWords = state.todayWords;
|
||
if (todayWords.isEmpty) {
|
||
return const Center(
|
||
child: Padding(
|
||
padding: EdgeInsets.all(16.0),
|
||
child: Text(
|
||
'今日暂无学习单词',
|
||
style: TextStyle(
|
||
color: Colors.grey,
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// 显示前3个单词
|
||
final displayWords = todayWords.take(3).toList();
|
||
return Column(
|
||
children: displayWords.asMap().entries.map((entry) {
|
||
final index = entry.key;
|
||
final word = entry.value;
|
||
return Column(
|
||
children: [
|
||
if (index > 0) const SizedBox(height: 12),
|
||
_buildWordItemFromModel(word),
|
||
],
|
||
);
|
||
}).toList(),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildWordItemFromModel(Word word) {
|
||
final meaning = word.definitions.isNotEmpty
|
||
? word.definitions.first.translation
|
||
: '暂无释义';
|
||
final phonetic = word.phonetic ?? '';
|
||
|
||
return GestureDetector(
|
||
onTap: () => _showWordDetailFromModel(word),
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
word.word,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
if (phonetic.isNotEmpty) ...[
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
phonetic,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
color: Color(0xFF2196F3),
|
||
fontStyle: FontStyle.italic,
|
||
),
|
||
),
|
||
],
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
meaning,
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey,
|
||
),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
if (word.audioUrl != null && word.audioUrl!.isNotEmpty)
|
||
IconButton(
|
||
onPressed: () => _playWordAudio(word),
|
||
icon: const Icon(
|
||
Icons.volume_up,
|
||
color: Color(0xFF2196F3),
|
||
size: 20,
|
||
),
|
||
),
|
||
// IconButton(
|
||
// onPressed: () => _addWordToFavorites(word),
|
||
// icon: const Icon(
|
||
// Icons.favorite_border,
|
||
// color: Colors.grey,
|
||
// size: 20,
|
||
// ),
|
||
// ),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildAIRecommendation() {
|
||
final state = ref.watch(vocabularyProvider);
|
||
final reviewWords = state.reviewWords;
|
||
final todayWords = state.todayWords;
|
||
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Padding(
|
||
padding: EdgeInsets.only(left: 4, bottom: 12),
|
||
child: Text(
|
||
'推荐功能',
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
Row(
|
||
children: [
|
||
// 智能复习卡片
|
||
Expanded(
|
||
child: _buildFeatureCard(
|
||
title: '智能复习',
|
||
subtitle: reviewWords.isEmpty
|
||
? '暂无复习内容'
|
||
: '${reviewWords.length} 个单词待复习',
|
||
icon: Icons.psychology_outlined,
|
||
color: const Color(0xFF9C27B0),
|
||
onTap: () => _startSmartReview(),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
// 词汇测试卡片
|
||
Expanded(
|
||
child: _buildFeatureCard(
|
||
title: '词汇测试',
|
||
subtitle: '测试你的词汇量',
|
||
icon: Icons.quiz_outlined,
|
||
color: const Color(0xFFFF9800),
|
||
onTap: () => _startVocabularyTest(),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildFeatureCard({
|
||
required String title,
|
||
required String subtitle,
|
||
required IconData icon,
|
||
required Color color,
|
||
required VoidCallback onTap,
|
||
}) {
|
||
return GestureDetector(
|
||
onTap: onTap,
|
||
child: Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withOpacity(0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Container(
|
||
width: 48,
|
||
height: 48,
|
||
decoration: BoxDecoration(
|
||
color: color.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Icon(
|
||
icon,
|
||
color: color,
|
||
size: 28,
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
title,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF2C3E50),
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
subtitle,
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
color: Colors.grey[600],
|
||
),
|
||
maxLines: 2,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// 启动智能复习
|
||
void _startSmartReview() async {
|
||
final state = ref.read(vocabularyProvider);
|
||
final reviewWords = state.reviewWords;
|
||
|
||
if (reviewWords.isEmpty) {
|
||
// 如果没有复习词汇,尝试加载
|
||
await ref.read(vocabularyProvider.notifier).loadReviewWords();
|
||
final updatedWords = ref.read(vocabularyProvider).reviewWords;
|
||
|
||
if (updatedWords.isEmpty) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text('暂无需要复习的单词,赶紧去学习新单词吧!'),
|
||
duration: Duration(seconds: 2),
|
||
),
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 导航到智能复习页面
|
||
Navigator.of(context).pushNamed(
|
||
Routes.smartReview,
|
||
arguments: {
|
||
'reviewMode': 'adaptive',
|
||
'dailyTarget': 20,
|
||
},
|
||
);
|
||
}
|
||
|
||
// 启动词汇测试
|
||
void _startVocabularyTest() {
|
||
// 显示测试选项对话框
|
||
showModalBottomSheet(
|
||
context: context,
|
||
backgroundColor: Colors.transparent,
|
||
builder: (context) => Container(
|
||
decoration: const BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||
),
|
||
padding: const EdgeInsets.all(24),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'选择测试类型',
|
||
style: TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 20),
|
||
_buildTestOption(
|
||
title: '快速测试',
|
||
subtitle: '10道题,约 3 分钟',
|
||
icon: Icons.speed,
|
||
color: const Color(0xFF4CAF50),
|
||
onTap: () {
|
||
Navigator.pop(context);
|
||
_navigateToTest(10);
|
||
},
|
||
),
|
||
const SizedBox(height: 12),
|
||
_buildTestOption(
|
||
title: '标准测试',
|
||
subtitle: '20道题,约 6 分钟',
|
||
icon: Icons.assessment,
|
||
color: const Color(0xFF2196F3),
|
||
onTap: () {
|
||
Navigator.pop(context);
|
||
_navigateToTest(20);
|
||
},
|
||
),
|
||
const SizedBox(height: 12),
|
||
_buildTestOption(
|
||
title: '完整测试',
|
||
subtitle: '50道题,约 15 分钟',
|
||
icon: Icons.assignment,
|
||
color: const Color(0xFFFF9800),
|
||
onTap: () {
|
||
Navigator.pop(context);
|
||
_navigateToTest(50);
|
||
},
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
child: const Center(
|
||
child: Text('取消'),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTestOption({
|
||
required String title,
|
||
required String subtitle,
|
||
required IconData icon,
|
||
required Color color,
|
||
required VoidCallback onTap,
|
||
}) {
|
||
return InkWell(
|
||
onTap: onTap,
|
||
borderRadius: BorderRadius.circular(12),
|
||
child: Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
border: Border.all(color: Colors.grey[200]!),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 48,
|
||
height: 48,
|
||
decoration: BoxDecoration(
|
||
color: color.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Icon(
|
||
icon,
|
||
color: color,
|
||
size: 24,
|
||
),
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
subtitle,
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
color: Colors.grey[600],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Icon(
|
||
Icons.arrow_forward_ios,
|
||
size: 16,
|
||
color: Colors.grey[400],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _navigateToTest(int questionCount) {
|
||
Navigator.of(context).pushNamed(
|
||
Routes.vocabularyTest,
|
||
arguments: {
|
||
'testType': 'vocabularyLevel',
|
||
'questionCount': questionCount,
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildLearningStats() {
|
||
final dailyStats = ref.watch(dailyVocabularyStatsProvider);
|
||
final todayStats = ref.watch(todayStatisticsProvider);
|
||
final overallStats = ref.watch(overallVocabularyStatsProvider);
|
||
final weeklyTotal = ref.watch(weeklyWordsStudiedProvider);
|
||
final currentUser = ref.watch(currentUserProvider);
|
||
|
||
final todayLearned = dailyStats?.wordsLearned ?? todayStats?.wordsStudied ?? 0;
|
||
final totalStudied = (overallStats != null && overallStats['total_studied'] != null)
|
||
? (overallStats['total_studied'] as num).toInt()
|
||
: 0;
|
||
final dailyWordGoal = currentUser?.settings?.dailyWordGoal ?? 20;
|
||
final weeklyTarget = (dailyWordGoal * 7).clamp(1, 100000);
|
||
final weeklyProgress = weeklyTarget > 0 ? (weeklyTotal / weeklyTarget) : 0.0;
|
||
final weeklyPercent = ((weeklyProgress * 100).clamp(0, 100)).round();
|
||
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withOpacity(0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
const Text(
|
||
'学习统计',
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
TextButton(
|
||
onPressed: _showDetailedStats,
|
||
child: const Text(
|
||
'查看详情',
|
||
style: TextStyle(
|
||
color: Color(0xFF2196F3),
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: GestureDetector(
|
||
onTap: _showTodayProgress,
|
||
child: _buildStatItem('今日学习', '$todayLearned', '个单词', Icons.today),
|
||
),
|
||
),
|
||
Expanded(
|
||
child: GestureDetector(
|
||
onTap: _showWeeklyProgress,
|
||
child: _buildStatItem('本周学习', '$weeklyTotal', '个单词', Icons.calendar_view_week),
|
||
),
|
||
),
|
||
Expanded(
|
||
child: GestureDetector(
|
||
onTap: _showVocabularyTest,
|
||
child: _buildStatItem('总词汇量', '$totalStudied', '个单词', Icons.library_books),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF2196F3).withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
const Icon(
|
||
Icons.trending_up,
|
||
color: Color(0xFF2196F3),
|
||
size: 20,
|
||
),
|
||
const SizedBox(width: 8),
|
||
const Text(
|
||
'本周学习进度:',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: LinearProgressIndicator(
|
||
value: weeklyProgress.clamp(0.0, 1.0),
|
||
backgroundColor: Colors.grey[300],
|
||
valueColor: const AlwaysStoppedAnimation<Color>(Color(0xFF2196F3)),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
'$weeklyPercent%',
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
color: Color(0xFF2196F3),
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildStatItem(String label, String value, String unit, IconData icon) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey[50],
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Icon(
|
||
icon,
|
||
color: const Color(0xFF2196F3),
|
||
size: 24,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
value,
|
||
style: const TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF2196F3),
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
unit,
|
||
style: const TextStyle(
|
||
fontSize: 10,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
label,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.black87,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showDetailedStats() {
|
||
Navigator.of(context).pushNamed(Routes.learningStatsDetail);
|
||
}
|
||
|
||
void _showTodayProgress() {
|
||
// 显示今日学习进度详情
|
||
final dailyStats = ref.read(dailyVocabularyStatsProvider);
|
||
final todayStats = ref.read(todayStatisticsProvider);
|
||
final todayLearned = dailyStats?.wordsLearned ?? todayStats?.wordsStudied ?? 0;
|
||
final currentUser = ref.read(currentUserProvider);
|
||
final dailyWordGoal = currentUser?.settings?.dailyWordGoal ?? 20;
|
||
final progress = dailyWordGoal > 0 ? (todayLearned / dailyWordGoal) : 0.0;
|
||
|
||
showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
backgroundColor: Colors.transparent,
|
||
builder: (context) => Container(
|
||
height: MediaQuery.of(context).size.height * 0.6,
|
||
decoration: const BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
// 拖拽条
|
||
Container(
|
||
width: 40,
|
||
height: 4,
|
||
margin: const EdgeInsets.only(top: 12, bottom: 20),
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey[300],
|
||
borderRadius: BorderRadius.circular(2),
|
||
),
|
||
),
|
||
// 内容
|
||
Expanded(
|
||
child: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(24),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Container(
|
||
width: 48,
|
||
height: 48,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF2196F3).withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: const Icon(
|
||
Icons.today,
|
||
color: Color(0xFF2196F3),
|
||
size: 28,
|
||
),
|
||
),
|
||
const SizedBox(width: 16),
|
||
const Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'今日学习进度',
|
||
style: TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
SizedBox(height: 4),
|
||
Text(
|
||
'保持每日学习,让进步成为习惯',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 32),
|
||
// 进度环
|
||
Center(
|
||
child: SizedBox(
|
||
width: 160,
|
||
height: 160,
|
||
child: Stack(
|
||
alignment: Alignment.center,
|
||
children: [
|
||
SizedBox(
|
||
width: 160,
|
||
height: 160,
|
||
child: CircularProgressIndicator(
|
||
value: progress.clamp(0.0, 1.0),
|
||
strokeWidth: 12,
|
||
backgroundColor: Colors.grey[200],
|
||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||
Color(0xFF2196F3),
|
||
),
|
||
),
|
||
),
|
||
Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
'$todayLearned',
|
||
style: const TextStyle(
|
||
fontSize: 48,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF2196F3),
|
||
),
|
||
),
|
||
Text(
|
||
'/ $dailyWordGoal 个单词',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey[600],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 32),
|
||
// 统计信息
|
||
_buildProgressStatRow('学习进度', '${(progress * 100).clamp(0, 100).toInt()}%', Icons.trending_up),
|
||
const SizedBox(height: 16),
|
||
_buildProgressStatRow('已学单词', '$todayLearned 个', Icons.check_circle),
|
||
const SizedBox(height: 16),
|
||
_buildProgressStatRow('剩余目标', '${(dailyWordGoal - todayLearned).clamp(0, dailyWordGoal)} 个', Icons.flag),
|
||
const SizedBox(height: 32),
|
||
// 鼓励文字
|
||
Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF2196F3).withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
const Icon(
|
||
Icons.lightbulb_outline,
|
||
color: Color(0xFF2196F3),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Text(
|
||
progress >= 1.0
|
||
? '太棒了!今天的目标已经完成!'
|
||
: progress >= 0.5
|
||
? '再加把劲,马上就能完成今天的目标!'
|
||
: '加油!坚持学习,每天进步一点点!',
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
color: Color(0xFF2196F3),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildProgressStatRow(String label, String value, IconData icon) {
|
||
return Row(
|
||
children: [
|
||
Icon(
|
||
icon,
|
||
size: 20,
|
||
color: Colors.grey[600],
|
||
),
|
||
const SizedBox(width: 12),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
color: Colors.grey[700],
|
||
),
|
||
),
|
||
const Spacer(),
|
||
Text(
|
||
value,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF2196F3),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
void _showWeeklyProgress() {
|
||
// 显示本周学习进度详情
|
||
final weeklyTotal = ref.read(weeklyWordsStudiedProvider);
|
||
final currentUser = ref.read(currentUserProvider);
|
||
final dailyWordGoal = currentUser?.settings?.dailyWordGoal ?? 20;
|
||
final weeklyTarget = (dailyWordGoal * 7).clamp(1, 100000);
|
||
final weeklyProgress = weeklyTarget > 0 ? (weeklyTotal / weeklyTarget) : 0.0;
|
||
|
||
showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
backgroundColor: Colors.transparent,
|
||
builder: (context) => Container(
|
||
height: MediaQuery.of(context).size.height * 0.6,
|
||
decoration: const BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
// 拖拽条
|
||
Container(
|
||
width: 40,
|
||
height: 4,
|
||
margin: const EdgeInsets.only(top: 12, bottom: 20),
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey[300],
|
||
borderRadius: BorderRadius.circular(2),
|
||
),
|
||
),
|
||
// 内容
|
||
Expanded(
|
||
child: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(24),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Container(
|
||
width: 48,
|
||
height: 48,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF4CAF50).withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: const Icon(
|
||
Icons.calendar_view_week,
|
||
color: Color(0xFF4CAF50),
|
||
size: 28,
|
||
),
|
||
),
|
||
const SizedBox(width: 16),
|
||
const Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'本周学习进度',
|
||
style: TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
SizedBox(height: 4),
|
||
Text(
|
||
'坑持七天学习,养成好习惯',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 32),
|
||
// 进度环
|
||
Center(
|
||
child: SizedBox(
|
||
width: 160,
|
||
height: 160,
|
||
child: Stack(
|
||
alignment: Alignment.center,
|
||
children: [
|
||
SizedBox(
|
||
width: 160,
|
||
height: 160,
|
||
child: CircularProgressIndicator(
|
||
value: weeklyProgress.clamp(0.0, 1.0),
|
||
strokeWidth: 12,
|
||
backgroundColor: Colors.grey[200],
|
||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||
Color(0xFF4CAF50),
|
||
),
|
||
),
|
||
),
|
||
Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
'$weeklyTotal',
|
||
style: const TextStyle(
|
||
fontSize: 48,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF4CAF50),
|
||
),
|
||
),
|
||
Text(
|
||
'/ $weeklyTarget 个单词',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey[600],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 32),
|
||
// 统计信息
|
||
_buildProgressStatRow('周学习进度', '${(weeklyProgress * 100).clamp(0, 100).toInt()}%', Icons.trending_up),
|
||
const SizedBox(height: 16),
|
||
_buildProgressStatRow('已学单词', '$weeklyTotal 个', Icons.check_circle),
|
||
const SizedBox(height: 16),
|
||
_buildProgressStatRow('周目标', '$weeklyTarget 个', Icons.flag),
|
||
const SizedBox(height: 16),
|
||
_buildProgressStatRow('剩余目标', '${(weeklyTarget - weeklyTotal).clamp(0, weeklyTarget)} 个', Icons.timer),
|
||
const SizedBox(height: 32),
|
||
// 鼓励文字
|
||
Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF4CAF50).withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
const Icon(
|
||
Icons.emoji_events,
|
||
color: Color(0xFF4CAF50),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Text(
|
||
weeklyProgress >= 1.0
|
||
? '完美!本周目标已经达成!'
|
||
: weeklyProgress >= 0.7
|
||
? '太棒了!本周已经完成大部分目标!'
|
||
: '加油!距离本周目标还差一点点!',
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
color: Color(0xFF4CAF50),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
// 查看详情按钮
|
||
SizedBox(
|
||
width: double.infinity,
|
||
child: ElevatedButton(
|
||
onPressed: () {
|
||
Navigator.pop(context);
|
||
_showDetailedStats();
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF4CAF50),
|
||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
),
|
||
child: const Text(
|
||
'查看详细统计',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showVocabularyTest() {
|
||
// TODO: 导航到词汇量测试页面
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text('开始词汇量测试'),
|
||
duration: Duration(seconds: 1),
|
||
),
|
||
);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
void _navigateToCategory(VocabularyBookMainCategory category) {
|
||
// 将枚举转换为中文名称传递
|
||
final categoryName = VocabularyBookCategoryHelper.getMainCategoryName(category);
|
||
Navigator.pushNamed(
|
||
context,
|
||
Routes.vocabularyCategory,
|
||
arguments: {
|
||
'category': categoryName,
|
||
},
|
||
);
|
||
}
|
||
} |