398 lines
9.8 KiB
Dart
398 lines
9.8 KiB
Dart
|
|
import 'package:json_annotation/json_annotation.dart';
|
||
|
|
import 'word_model.dart';
|
||
|
|
|
||
|
|
part 'study_session_model.g.dart';
|
||
|
|
|
||
|
|
/// 学习模式
|
||
|
|
enum StudyMode {
|
||
|
|
@JsonValue('new_words')
|
||
|
|
newWords, // 学习新单词
|
||
|
|
@JsonValue('review')
|
||
|
|
review, // 复习单词
|
||
|
|
@JsonValue('mixed')
|
||
|
|
mixed, // 混合模式
|
||
|
|
@JsonValue('test')
|
||
|
|
test, // 测试模式
|
||
|
|
@JsonValue('quick_review')
|
||
|
|
quickReview, // 快速复习
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 练习类型
|
||
|
|
enum ExerciseType {
|
||
|
|
@JsonValue('word_meaning')
|
||
|
|
wordMeaning, // 单词释义
|
||
|
|
@JsonValue('meaning_word')
|
||
|
|
meaningWord, // 释义选单词
|
||
|
|
@JsonValue('spelling')
|
||
|
|
spelling, // 拼写练习
|
||
|
|
@JsonValue('listening')
|
||
|
|
listening, // 听力练习
|
||
|
|
@JsonValue('sentence_completion')
|
||
|
|
sentenceCompletion, // 句子填空
|
||
|
|
@JsonValue('synonym_antonym')
|
||
|
|
synonymAntonym, // 同义词反义词
|
||
|
|
@JsonValue('image_word')
|
||
|
|
imageWord, // 图片识词
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 答题结果
|
||
|
|
enum AnswerResult {
|
||
|
|
@JsonValue('correct')
|
||
|
|
correct,
|
||
|
|
@JsonValue('wrong')
|
||
|
|
wrong,
|
||
|
|
@JsonValue('skipped')
|
||
|
|
skipped,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 学习会话
|
||
|
|
@JsonSerializable()
|
||
|
|
class StudySession {
|
||
|
|
/// 会话ID
|
||
|
|
final String id;
|
||
|
|
|
||
|
|
/// 用户ID
|
||
|
|
final String userId;
|
||
|
|
|
||
|
|
/// 词汇书ID
|
||
|
|
final String? vocabularyBookId;
|
||
|
|
|
||
|
|
/// 学习模式
|
||
|
|
final StudyMode mode;
|
||
|
|
|
||
|
|
/// 目标单词数
|
||
|
|
final int targetWordCount;
|
||
|
|
|
||
|
|
/// 实际学习单词数
|
||
|
|
final int actualWordCount;
|
||
|
|
|
||
|
|
/// 正确答题数
|
||
|
|
final int correctAnswers;
|
||
|
|
|
||
|
|
/// 错误答题数
|
||
|
|
final int wrongAnswers;
|
||
|
|
|
||
|
|
/// 跳过答题数
|
||
|
|
final int skippedAnswers;
|
||
|
|
|
||
|
|
/// 学习时长(秒)
|
||
|
|
final int durationSeconds;
|
||
|
|
|
||
|
|
/// 准确率
|
||
|
|
final double accuracy;
|
||
|
|
|
||
|
|
/// 获得经验值
|
||
|
|
final int experienceGained;
|
||
|
|
|
||
|
|
/// 获得积分
|
||
|
|
final int pointsGained;
|
||
|
|
|
||
|
|
/// 是否完成
|
||
|
|
final bool isCompleted;
|
||
|
|
|
||
|
|
/// 开始时间
|
||
|
|
final DateTime startedAt;
|
||
|
|
|
||
|
|
/// 结束时间
|
||
|
|
final DateTime? endedAt;
|
||
|
|
|
||
|
|
/// 创建时间
|
||
|
|
final DateTime createdAt;
|
||
|
|
|
||
|
|
const StudySession({
|
||
|
|
required this.id,
|
||
|
|
required this.userId,
|
||
|
|
this.vocabularyBookId,
|
||
|
|
required this.mode,
|
||
|
|
required this.targetWordCount,
|
||
|
|
this.actualWordCount = 0,
|
||
|
|
this.correctAnswers = 0,
|
||
|
|
this.wrongAnswers = 0,
|
||
|
|
this.skippedAnswers = 0,
|
||
|
|
this.durationSeconds = 0,
|
||
|
|
this.accuracy = 0.0,
|
||
|
|
this.experienceGained = 0,
|
||
|
|
this.pointsGained = 0,
|
||
|
|
this.isCompleted = false,
|
||
|
|
required this.startedAt,
|
||
|
|
this.endedAt,
|
||
|
|
required this.createdAt,
|
||
|
|
});
|
||
|
|
|
||
|
|
factory StudySession.fromJson(Map<String, dynamic> json) => _$StudySessionFromJson(json);
|
||
|
|
Map<String, dynamic> toJson() => _$StudySessionToJson(this);
|
||
|
|
|
||
|
|
StudySession copyWith({
|
||
|
|
String? id,
|
||
|
|
String? userId,
|
||
|
|
String? vocabularyBookId,
|
||
|
|
StudyMode? mode,
|
||
|
|
int? targetWordCount,
|
||
|
|
int? actualWordCount,
|
||
|
|
int? correctAnswers,
|
||
|
|
int? wrongAnswers,
|
||
|
|
int? skippedAnswers,
|
||
|
|
int? durationSeconds,
|
||
|
|
double? accuracy,
|
||
|
|
int? experienceGained,
|
||
|
|
int? pointsGained,
|
||
|
|
bool? isCompleted,
|
||
|
|
DateTime? startedAt,
|
||
|
|
DateTime? endedAt,
|
||
|
|
DateTime? createdAt,
|
||
|
|
}) {
|
||
|
|
return StudySession(
|
||
|
|
id: id ?? this.id,
|
||
|
|
userId: userId ?? this.userId,
|
||
|
|
vocabularyBookId: vocabularyBookId ?? this.vocabularyBookId,
|
||
|
|
mode: mode ?? this.mode,
|
||
|
|
targetWordCount: targetWordCount ?? this.targetWordCount,
|
||
|
|
actualWordCount: actualWordCount ?? this.actualWordCount,
|
||
|
|
correctAnswers: correctAnswers ?? this.correctAnswers,
|
||
|
|
wrongAnswers: wrongAnswers ?? this.wrongAnswers,
|
||
|
|
skippedAnswers: skippedAnswers ?? this.skippedAnswers,
|
||
|
|
durationSeconds: durationSeconds ?? this.durationSeconds,
|
||
|
|
accuracy: accuracy ?? this.accuracy,
|
||
|
|
experienceGained: experienceGained ?? this.experienceGained,
|
||
|
|
pointsGained: pointsGained ?? this.pointsGained,
|
||
|
|
isCompleted: isCompleted ?? this.isCompleted,
|
||
|
|
startedAt: startedAt ?? this.startedAt,
|
||
|
|
endedAt: endedAt ?? this.endedAt,
|
||
|
|
createdAt: createdAt ?? this.createdAt,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 总答题数
|
||
|
|
int get totalAnswers => correctAnswers + wrongAnswers + skippedAnswers;
|
||
|
|
|
||
|
|
/// 计算准确率
|
||
|
|
double calculateAccuracy() {
|
||
|
|
if (totalAnswers == 0) return 0.0;
|
||
|
|
return correctAnswers / totalAnswers;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 学习时长(分钟)
|
||
|
|
double get durationMinutes => durationSeconds / 60.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 单词练习记录
|
||
|
|
@JsonSerializable()
|
||
|
|
class WordExerciseRecord {
|
||
|
|
/// 记录ID
|
||
|
|
final String id;
|
||
|
|
|
||
|
|
/// 学习会话ID
|
||
|
|
final String sessionId;
|
||
|
|
|
||
|
|
/// 单词ID
|
||
|
|
final String wordId;
|
||
|
|
|
||
|
|
/// 练习类型
|
||
|
|
final ExerciseType exerciseType;
|
||
|
|
|
||
|
|
/// 用户答案
|
||
|
|
final String userAnswer;
|
||
|
|
|
||
|
|
/// 正确答案
|
||
|
|
final String correctAnswer;
|
||
|
|
|
||
|
|
/// 答题结果
|
||
|
|
final AnswerResult result;
|
||
|
|
|
||
|
|
/// 答题时间(秒)
|
||
|
|
final int responseTimeSeconds;
|
||
|
|
|
||
|
|
/// 提示次数
|
||
|
|
final int hintCount;
|
||
|
|
|
||
|
|
/// 单词信息
|
||
|
|
final Word? word;
|
||
|
|
|
||
|
|
/// 答题时间
|
||
|
|
final DateTime answeredAt;
|
||
|
|
|
||
|
|
const WordExerciseRecord({
|
||
|
|
required this.id,
|
||
|
|
required this.sessionId,
|
||
|
|
required this.wordId,
|
||
|
|
required this.exerciseType,
|
||
|
|
required this.userAnswer,
|
||
|
|
required this.correctAnswer,
|
||
|
|
required this.result,
|
||
|
|
required this.responseTimeSeconds,
|
||
|
|
this.hintCount = 0,
|
||
|
|
this.word,
|
||
|
|
required this.answeredAt,
|
||
|
|
});
|
||
|
|
|
||
|
|
factory WordExerciseRecord.fromJson(Map<String, dynamic> json) => _$WordExerciseRecordFromJson(json);
|
||
|
|
Map<String, dynamic> toJson() => _$WordExerciseRecordToJson(this);
|
||
|
|
|
||
|
|
WordExerciseRecord copyWith({
|
||
|
|
String? id,
|
||
|
|
String? sessionId,
|
||
|
|
String? wordId,
|
||
|
|
ExerciseType? exerciseType,
|
||
|
|
String? userAnswer,
|
||
|
|
String? correctAnswer,
|
||
|
|
AnswerResult? result,
|
||
|
|
int? responseTimeSeconds,
|
||
|
|
int? hintCount,
|
||
|
|
Word? word,
|
||
|
|
DateTime? answeredAt,
|
||
|
|
}) {
|
||
|
|
return WordExerciseRecord(
|
||
|
|
id: id ?? this.id,
|
||
|
|
sessionId: sessionId ?? this.sessionId,
|
||
|
|
wordId: wordId ?? this.wordId,
|
||
|
|
exerciseType: exerciseType ?? this.exerciseType,
|
||
|
|
userAnswer: userAnswer ?? this.userAnswer,
|
||
|
|
correctAnswer: correctAnswer ?? this.correctAnswer,
|
||
|
|
result: result ?? this.result,
|
||
|
|
responseTimeSeconds: responseTimeSeconds ?? this.responseTimeSeconds,
|
||
|
|
hintCount: hintCount ?? this.hintCount,
|
||
|
|
word: word ?? this.word,
|
||
|
|
answeredAt: answeredAt ?? this.answeredAt,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 是否正确
|
||
|
|
bool get isCorrect => result == AnswerResult.correct;
|
||
|
|
|
||
|
|
/// 是否错误
|
||
|
|
bool get isWrong => result == AnswerResult.wrong;
|
||
|
|
|
||
|
|
/// 是否跳过
|
||
|
|
bool get isSkipped => result == AnswerResult.skipped;
|
||
|
|
|
||
|
|
/// 答题时间(分钟)
|
||
|
|
double get responseTimeMinutes => responseTimeSeconds / 60.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 学习统计
|
||
|
|
@JsonSerializable()
|
||
|
|
class StudyStatistics {
|
||
|
|
/// 统计ID
|
||
|
|
final String id;
|
||
|
|
|
||
|
|
/// 用户ID
|
||
|
|
@JsonKey(name: 'user_id')
|
||
|
|
final String userId;
|
||
|
|
|
||
|
|
/// 统计日期
|
||
|
|
final DateTime date;
|
||
|
|
|
||
|
|
/// 学习会话数
|
||
|
|
@JsonKey(name: 'session_count')
|
||
|
|
final int sessionCount;
|
||
|
|
|
||
|
|
/// 学习单词数
|
||
|
|
@JsonKey(name: 'words_studied')
|
||
|
|
final int wordsStudied;
|
||
|
|
|
||
|
|
/// 新学单词数
|
||
|
|
@JsonKey(name: 'new_words_learned')
|
||
|
|
final int newWordsLearned;
|
||
|
|
|
||
|
|
/// 复习单词数
|
||
|
|
@JsonKey(name: 'words_reviewed')
|
||
|
|
final int wordsReviewed;
|
||
|
|
|
||
|
|
/// 掌握单词数
|
||
|
|
@JsonKey(name: 'words_mastered')
|
||
|
|
final int wordsMastered;
|
||
|
|
|
||
|
|
/// 学习时长(秒)
|
||
|
|
@JsonKey(name: 'total_study_time_seconds')
|
||
|
|
final int totalStudyTimeSeconds;
|
||
|
|
|
||
|
|
/// 正确答题数
|
||
|
|
@JsonKey(name: 'correct_answers')
|
||
|
|
final int correctAnswers;
|
||
|
|
|
||
|
|
/// 错误答题数
|
||
|
|
@JsonKey(name: 'wrong_answers')
|
||
|
|
final int wrongAnswers;
|
||
|
|
|
||
|
|
/// 平均准确率
|
||
|
|
@JsonKey(name: 'average_accuracy')
|
||
|
|
final double averageAccuracy;
|
||
|
|
|
||
|
|
/// 获得经验值
|
||
|
|
@JsonKey(name: 'experience_gained')
|
||
|
|
final int experienceGained;
|
||
|
|
|
||
|
|
/// 获得积分
|
||
|
|
@JsonKey(name: 'points_gained')
|
||
|
|
final int pointsGained;
|
||
|
|
|
||
|
|
/// 连续学习天数
|
||
|
|
@JsonKey(name: 'streak_days')
|
||
|
|
final int streakDays;
|
||
|
|
|
||
|
|
const StudyStatistics({
|
||
|
|
required this.id,
|
||
|
|
required this.userId,
|
||
|
|
required this.date,
|
||
|
|
this.sessionCount = 0,
|
||
|
|
this.wordsStudied = 0,
|
||
|
|
this.newWordsLearned = 0,
|
||
|
|
this.wordsReviewed = 0,
|
||
|
|
this.wordsMastered = 0,
|
||
|
|
this.totalStudyTimeSeconds = 0,
|
||
|
|
this.correctAnswers = 0,
|
||
|
|
this.wrongAnswers = 0,
|
||
|
|
this.averageAccuracy = 0.0,
|
||
|
|
this.experienceGained = 0,
|
||
|
|
this.pointsGained = 0,
|
||
|
|
this.streakDays = 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
factory StudyStatistics.fromJson(Map<String, dynamic> json) => _$StudyStatisticsFromJson(json);
|
||
|
|
Map<String, dynamic> toJson() => _$StudyStatisticsToJson(this);
|
||
|
|
|
||
|
|
StudyStatistics copyWith({
|
||
|
|
String? id,
|
||
|
|
String? userId,
|
||
|
|
DateTime? date,
|
||
|
|
int? sessionCount,
|
||
|
|
int? wordsStudied,
|
||
|
|
int? newWordsLearned,
|
||
|
|
int? wordsReviewed,
|
||
|
|
int? wordsMastered,
|
||
|
|
int? totalStudyTimeSeconds,
|
||
|
|
int? correctAnswers,
|
||
|
|
int? wrongAnswers,
|
||
|
|
double? averageAccuracy,
|
||
|
|
int? experienceGained,
|
||
|
|
int? pointsGained,
|
||
|
|
int? streakDays,
|
||
|
|
}) {
|
||
|
|
return StudyStatistics(
|
||
|
|
id: id ?? this.id,
|
||
|
|
userId: userId ?? this.userId,
|
||
|
|
date: date ?? this.date,
|
||
|
|
sessionCount: sessionCount ?? this.sessionCount,
|
||
|
|
wordsStudied: wordsStudied ?? this.wordsStudied,
|
||
|
|
newWordsLearned: newWordsLearned ?? this.newWordsLearned,
|
||
|
|
wordsReviewed: wordsReviewed ?? this.wordsReviewed,
|
||
|
|
wordsMastered: wordsMastered ?? this.wordsMastered,
|
||
|
|
totalStudyTimeSeconds: totalStudyTimeSeconds ?? this.totalStudyTimeSeconds,
|
||
|
|
correctAnswers: correctAnswers ?? this.correctAnswers,
|
||
|
|
wrongAnswers: wrongAnswers ?? this.wrongAnswers,
|
||
|
|
averageAccuracy: averageAccuracy ?? this.averageAccuracy,
|
||
|
|
experienceGained: experienceGained ?? this.experienceGained,
|
||
|
|
pointsGained: pointsGained ?? this.pointsGained,
|
||
|
|
streakDays: streakDays ?? this.streakDays,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 总答题数
|
||
|
|
int get totalAnswers => correctAnswers + wrongAnswers;
|
||
|
|
|
||
|
|
/// 学习时长(分钟)
|
||
|
|
double get totalStudyTimeMinutes => totalStudyTimeSeconds / 60.0;
|
||
|
|
|
||
|
|
/// 学习时长(小时)
|
||
|
|
double get totalStudyTimeHours => totalStudyTimeSeconds / 3600.0;
|
||
|
|
}
|