485 lines
15 KiB
Dart
485 lines
15 KiB
Dart
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);
|
||
}); |