Files
ai_english/client/lib/features/vocabulary/providers/vocabulary_provider.dart
2025-11-17 14:09:17 +08:00

485 lines
15 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/word_model.dart';
import '../models/vocabulary_book_model.dart';
import '../models/study_session_model.dart';
import '../models/daily_stats_model.dart';
import '../services/vocabulary_service.dart';
import '../../../core/network/api_client.dart';
import '../../../core/services/storage_service.dart';
import '../services/learning_stats_service.dart';
import '../models/learning_stats_model.dart';
/// 词汇状态
class VocabularyState {
final bool isLoading;
final String? error;
final List<VocabularyBook> systemBooks;
final List<VocabularyBook> userBooks;
final List<Map<String, dynamic>> categories;
final List<Word> todayWords;
final List<Word> reviewWords;
final StudyStatistics? todayStatistics;
final StudySession? currentSession;
final DailyStats? dailyStats;
final Map<String, dynamic>? overallStats;
final int weeklyWordsStudied;
const VocabularyState({
this.isLoading = false,
this.error,
this.systemBooks = const [],
this.userBooks = const [],
this.categories = const [],
this.todayWords = const [],
this.reviewWords = const [],
this.todayStatistics,
this.currentSession,
this.dailyStats,
this.overallStats,
this.weeklyWordsStudied = 0,
});
VocabularyState copyWith({
bool? isLoading,
String? error,
List<VocabularyBook>? systemBooks,
List<VocabularyBook>? userBooks,
List<Map<String, dynamic>>? categories,
List<Word>? todayWords,
List<Word>? reviewWords,
StudyStatistics? todayStatistics,
StudySession? currentSession,
DailyStats? dailyStats,
Map<String, dynamic>? overallStats,
int? weeklyWordsStudied,
}) {
return VocabularyState(
isLoading: isLoading ?? this.isLoading,
error: error,
systemBooks: systemBooks ?? this.systemBooks,
userBooks: userBooks ?? this.userBooks,
categories: categories ?? this.categories,
todayWords: todayWords ?? this.todayWords,
reviewWords: reviewWords ?? this.reviewWords,
todayStatistics: todayStatistics ?? this.todayStatistics,
currentSession: currentSession ?? this.currentSession,
dailyStats: dailyStats ?? this.dailyStats,
overallStats: overallStats ?? this.overallStats,
weeklyWordsStudied: weeklyWordsStudied ?? this.weeklyWordsStudied,
);
}
}
/// 词汇状态管理
class VocabularyNotifier extends StateNotifier<VocabularyState> {
final VocabularyService _vocabularyService;
VocabularyNotifier(this._vocabularyService) : super(const VocabularyState());
/// 加载系统词汇书
Future<void> loadSystemVocabularyBooks({
VocabularyBookDifficulty? difficulty,
String? category,
}) async {
try {
state = state.copyWith(isLoading: true, error: null);
final books = await _vocabularyService.getSystemVocabularyBooks(
difficulty: difficulty,
category: category,
limit: 100, // 加载所有词汇书
);
state = state.copyWith(
isLoading: false,
systemBooks: books,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: e.toString(),
);
}
}
/// 加载词汇书分类
Future<void> loadVocabularyBookCategories() async {
try {
state = state.copyWith(isLoading: true, error: null);
final categories = await _vocabularyService.getVocabularyBookCategories();
state = state.copyWith(
isLoading: false,
categories: categories,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: e.toString(),
);
}
}
/// 加载用户词汇书
Future<void> loadUserVocabularyBooks() async {
try {
state = state.copyWith(isLoading: true, error: null);
final books = await _vocabularyService.getUserVocabularyBooks();
state = state.copyWith(
isLoading: false,
userBooks: books,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: e.toString(),
);
}
}
/// 加载今日学习单词
Future<void> loadTodayStudyWords({String? userId}) async {
try {
state = state.copyWith(isLoading: true, error: null);
final words = await _vocabularyService.getTodayStudyWords();
print('今日单词加载完成,数量: ${words.length}');
StudyStatistics statistics;
try {
statistics = await _vocabularyService.getStudyStatistics(DateTime.now());
print('学习统计加载成功: wordsStudied=${statistics.wordsStudied}, newWords=${statistics.newWordsLearned}');
} catch (e) {
print('学习统计加载失败: $e,使用默认值');
// 接口不可用时,构造默认的今日统计
final now = DateTime.now();
final correct = (words.length * 0.8).round();
final wrong = words.length - correct;
statistics = StudyStatistics(
id: 'local_${now.toIso8601String().split('T').first}',
userId: 'current_user',
date: now,
sessionCount: 1,
wordsStudied: words.length,
newWordsLearned: words.length,
wordsReviewed: 0,
wordsMastered: 0,
totalStudyTimeSeconds: words.length * 30,
correctAnswers: correct,
wrongAnswers: wrong,
averageAccuracy: words.isEmpty ? 0.0 : correct / (correct + wrong),
experienceGained: words.length * 5,
pointsGained: words.length * 2,
streakDays: 1,
);
}
// 额外加载每日词汇统计wordsLearned、studyTimeMinutes
DailyStats? dailyStats;
if (userId != null && userId.isNotEmpty) {
try {
dailyStats = await _vocabularyService.getDailyVocabularyStats(userId: userId);
print('每日词汇统计加载成功: wordsLearned=${dailyStats.wordsLearned}');
} catch (e) {
print('每日词汇统计加载失败: $e');
// ignore, 使用StudyStatistics中的wordsStudied作为兜底
dailyStats = DailyStats(
wordsLearned: statistics.wordsStudied,
studyTimeMinutes: (statistics.totalStudyTimeSeconds / 60).round(),
);
}
}
state = state.copyWith(
isLoading: false,
todayWords: words,
todayStatistics: statistics,
dailyStats: dailyStats,
);
print('状态更新完成: todayWords=${words.length}, statistics.wordsStudied=${statistics.wordsStudied}');
} catch (e) {
print('loadTodayStudyWords失败: $e');
state = state.copyWith(
isLoading: false,
error: e.toString(),
);
}
}
/// 加载用户词汇整体统计(总学习词汇数、准确率等)
Future<void> loadUserVocabularyOverallStats() async {
try {
state = state.copyWith(isLoading: true, error: null);
final stats = await _vocabularyService.getUserVocabularyStats();
print('整体统计加载成功: $stats');
state = state.copyWith(isLoading: false, overallStats: stats);
} catch (e) {
print('整体统计加载失败: $e');
state = state.copyWith(isLoading: false, error: e.toString());
}
}
/// 加载本周学习统计(累计学习单词数)
Future<void> loadWeeklyStudyStats() async {
try {
final now = DateTime.now();
final startOfWeek = now.subtract(Duration(days: now.weekday - 1)); // 周一
final endOfWeek = startOfWeek.add(const Duration(days: 6)); // 周日
print('加载本周统计: $startOfWeek$endOfWeek');
final list = await _vocabularyService.getStudyStatisticsHistory(
startDate: DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day),
endDate: DateTime(endOfWeek.year, endOfWeek.month, endOfWeek.day),
);
print('本周统计数据: ${list.length}');
final total = list.fold<int>(0, (sum, s) => sum + (s.wordsStudied));
print('本周累计学习: $total 个单词');
state = state.copyWith(weeklyWordsStudied: total);
} catch (e) {
print('本周统计加载失败: $e');
// 出错时保持为0
}
}
/// 加载复习单词
Future<void> loadReviewWords() async {
try {
state = state.copyWith(isLoading: true, error: null);
final words = await _vocabularyService.getReviewWords();
state = state.copyWith(
isLoading: false,
reviewWords: words,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: e.toString(),
);
}
}
/// 添加词汇书到用户
Future<void> addVocabularyBookToUser(String bookId) async {
try {
await _vocabularyService.addVocabularyBookToUser(bookId);
// 重新加载用户词汇书
await loadUserVocabularyBooks();
} catch (e) {
state = state.copyWith(error: e.toString());
}
}
/// 开始学习会话
Future<void> startStudySession({
required StudyMode mode,
String? vocabularyBookId,
required int targetWordCount,
}) async {
try {
state = state.copyWith(isLoading: true, error: null);
final session = await _vocabularyService.startStudySession(
mode: mode,
vocabularyBookId: vocabularyBookId,
targetWordCount: targetWordCount,
);
state = state.copyWith(
isLoading: false,
currentSession: session,
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: e.toString(),
);
}
}
/// 结束学习会话
Future<void> endStudySession({
required String sessionId,
required int durationSeconds,
required List<WordExerciseRecord> exercises,
}) async {
try {
await _vocabularyService.endStudySession(
sessionId,
durationSeconds: durationSeconds,
exercises: exercises,
);
state = state.copyWith(currentSession: null);
// 重新加载今日数据
await loadTodayStudyWords();
} catch (e) {
state = state.copyWith(error: e.toString());
}
}
/// 更新单词学习进度
Future<void> updateWordProgress({
required String wordId,
required LearningStatus status,
required bool isCorrect,
int responseTime = 0,
}) async {
try {
await _vocabularyService.updateUserWordProgress(
wordId: wordId,
status: status,
isCorrect: isCorrect,
responseTime: responseTime,
);
} catch (e) {
state = state.copyWith(error: e.toString());
}
}
/// 清除错误
void clearError() {
state = state.copyWith(error: null);
}
}
/// 词汇服务提供者
final vocabularyServiceProvider = FutureProvider<VocabularyService>((ref) async {
final apiClient = ApiClient.instance;
final storageService = await StorageService.getInstance();
return VocabularyService(
apiClient: apiClient,
storageService: storageService,
);
});
/// 词汇状态提供者(使用 StateNotifierProvider 以支持状态更新)
final vocabularyProvider = StateNotifierProvider.autoDispose<VocabularyNotifier, VocabularyState>((ref) {
// 同步创建 Notifier延迟加载数据
final vocabularyService = ref.watch(vocabularyServiceProvider).value;
if (vocabularyService == null) {
// 如果服务未就绪,返回空状态的 Notifier
throw Exception('词汇服务未就绪');
}
final notifier = VocabularyNotifier(vocabularyService);
// 在后台异步加载系统词汇书和分类
Future.wait([
notifier.loadSystemVocabularyBooks(),
notifier.loadVocabularyBookCategories(),
]);
return notifier;
});
/// 当前学习会话提供者
final currentStudySessionProvider = Provider<StudySession?>((ref) {
final state = ref.watch(vocabularyProvider);
return state.currentSession;
});
/// 今日学习统计提供者
final todayStatisticsProvider = Provider<StudyStatistics?>((ref) {
final state = ref.watch(vocabularyProvider);
return state.todayStatistics;
});
/// 每日词汇统计提供者
final dailyVocabularyStatsProvider = Provider<DailyStats?>((ref) {
final state = ref.watch(vocabularyProvider);
return state.dailyStats;
});
/// 用户词汇整体统计提供者
final overallVocabularyStatsProvider = Provider<Map<String, dynamic>?>(
(ref) {
final state = ref.watch(vocabularyProvider);
return state.overallStats;
},
);
/// 本周累计学习单词数提供者
final weeklyWordsStudiedProvider = Provider<int>((ref) {
final state = ref.watch(vocabularyProvider);
return state.weeklyWordsStudied;
});
/// 用户词汇书提供者
final userVocabularyBooksProvider = Provider<List<VocabularyBook>>((ref) {
final state = ref.watch(vocabularyProvider);
return state.userBooks;
});
/// 系统词汇书提供者
final systemVocabularyBooksProvider = Provider<List<VocabularyBook>>((ref) {
final state = ref.watch(vocabularyProvider);
return state.systemBooks;
});
/// 今日单词提供者
final todayWordsProvider = Provider<List<Word>>((ref) {
final state = ref.watch(vocabularyProvider);
return state.todayWords;
});
/// 复习单词提供者
final reviewWordsProvider = Provider<List<Word>>((ref) {
final state = ref.watch(vocabularyProvider);
return state.reviewWords;
});
/// 词汇书学习统计
class BookStudyStats {
final String bookId;
final int studyDays;
final double averageAccuracy;
const BookStudyStats({
required this.bookId,
required this.studyDays,
required this.averageAccuracy,
});
}
/// 学习统计服务提供者
final learningStatsServiceProvider = FutureProvider<LearningStatsService>((ref) async {
final apiClient = ApiClient.instance;
final storageService = await StorageService.getInstance();
return LearningStatsService(apiClient: apiClient, storageService: storageService);
});
/// 用户学习统计提供者
final learningStatsProvider = FutureProvider<LearningStats>((ref) async {
final service = await ref.watch(learningStatsServiceProvider.future);
return service.getUserStats();
});
/// 指定词汇书的学习统计(学习天数、平均准确率)
final bookStudyStatsProvider = FutureProvider.autoDispose.family<BookStudyStats, String>((ref, bookId) async {
final service = await ref.watch(learningStatsServiceProvider.future);
final records = await service.getDailyRecords();
final filtered = records.where((r) => r.vocabularyBookIds.contains(bookId)).toList();
final studyDays = filtered.length;
double avgAccuracy = 0.0;
if (filtered.isNotEmpty) {
final totalWeight = filtered.fold<int>(0, (sum, r) => sum + (r.wordsLearned + r.wordsReviewed));
if (totalWeight > 0) {
final weightedSum = filtered.fold<double>(0.0, (sum, r) => sum + r.accuracyRate * (r.wordsLearned + r.wordsReviewed));
avgAccuracy = weightedSum / totalWeight;
} else {
avgAccuracy = filtered.fold<double>(0.0, (sum, r) => sum + r.accuracyRate) / filtered.length;
}
}
return BookStudyStats(bookId: bookId, studyDays: studyDays, averageAccuracy: avgAccuracy);
});
/// 词汇书学习进度(百分比、已学/已掌握数量)
final vocabularyBookProgressProvider = FutureProvider.autoDispose.family<UserVocabularyBookProgress, String>((ref, bookId) async {
final service = await ref.watch(vocabularyServiceProvider.future);
return service.getVocabularyBookProgress(bookId);
});