init
This commit is contained in:
138
client/lib/features/reading/models/reading_article.dart
Normal file
138
client/lib/features/reading/models/reading_article.dart
Normal file
@@ -0,0 +1,138 @@
|
||||
class ReadingArticle {
|
||||
final String id;
|
||||
final String title;
|
||||
final String content;
|
||||
final String category;
|
||||
final String difficulty;
|
||||
final int wordCount;
|
||||
final int estimatedReadingTime; // in minutes
|
||||
final List<String> tags;
|
||||
final String source;
|
||||
final DateTime publishDate;
|
||||
final bool isCompleted;
|
||||
final double? comprehensionScore;
|
||||
final int? readingTime; // actual reading time in seconds
|
||||
|
||||
const ReadingArticle({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.content,
|
||||
required this.category,
|
||||
required this.difficulty,
|
||||
required this.wordCount,
|
||||
required this.estimatedReadingTime,
|
||||
required this.tags,
|
||||
required this.source,
|
||||
required this.publishDate,
|
||||
this.isCompleted = false,
|
||||
this.comprehensionScore,
|
||||
this.readingTime,
|
||||
});
|
||||
|
||||
factory ReadingArticle.fromJson(Map<String, dynamic> json) {
|
||||
return ReadingArticle(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
content: json['content'] as String,
|
||||
category: json['category'] as String,
|
||||
difficulty: json['difficulty'] as String,
|
||||
wordCount: json['wordCount'] as int,
|
||||
estimatedReadingTime: json['estimatedReadingTime'] as int,
|
||||
tags: List<String>.from(json['tags'] as List),
|
||||
source: json['source'] as String,
|
||||
publishDate: DateTime.parse(json['publishDate'] as String),
|
||||
isCompleted: json['isCompleted'] as bool? ?? false,
|
||||
comprehensionScore: json['comprehensionScore'] as double?,
|
||||
readingTime: json['readingTime'] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'content': content,
|
||||
'category': category,
|
||||
'difficulty': difficulty,
|
||||
'wordCount': wordCount,
|
||||
'estimatedReadingTime': estimatedReadingTime,
|
||||
'tags': tags,
|
||||
'source': source,
|
||||
'publishDate': publishDate.toIso8601String(),
|
||||
'isCompleted': isCompleted,
|
||||
'comprehensionScore': comprehensionScore,
|
||||
'readingTime': readingTime,
|
||||
};
|
||||
}
|
||||
|
||||
ReadingArticle copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? content,
|
||||
String? category,
|
||||
String? difficulty,
|
||||
int? wordCount,
|
||||
int? estimatedReadingTime,
|
||||
List<String>? tags,
|
||||
String? source,
|
||||
DateTime? publishDate,
|
||||
bool? isCompleted,
|
||||
double? comprehensionScore,
|
||||
int? readingTime,
|
||||
}) {
|
||||
return ReadingArticle(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
content: content ?? this.content,
|
||||
category: category ?? this.category,
|
||||
difficulty: difficulty ?? this.difficulty,
|
||||
wordCount: wordCount ?? this.wordCount,
|
||||
estimatedReadingTime: estimatedReadingTime ?? this.estimatedReadingTime,
|
||||
tags: tags ?? this.tags,
|
||||
source: source ?? this.source,
|
||||
publishDate: publishDate ?? this.publishDate,
|
||||
isCompleted: isCompleted ?? this.isCompleted,
|
||||
comprehensionScore: comprehensionScore ?? this.comprehensionScore,
|
||||
readingTime: readingTime ?? this.readingTime,
|
||||
);
|
||||
}
|
||||
|
||||
String get difficultyLabel {
|
||||
switch (difficulty.toLowerCase()) {
|
||||
case 'a1':
|
||||
case 'a2':
|
||||
return '初级';
|
||||
case 'b1':
|
||||
case 'b2':
|
||||
return '中级';
|
||||
case 'c1':
|
||||
case 'c2':
|
||||
return '高级';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
}
|
||||
|
||||
String get categoryLabel {
|
||||
switch (category.toLowerCase()) {
|
||||
case 'cet4':
|
||||
return '四级阅读';
|
||||
case 'cet6':
|
||||
return '六级阅读';
|
||||
case 'toefl':
|
||||
return '托福阅读';
|
||||
case 'ielts':
|
||||
return '雅思阅读';
|
||||
case 'daily':
|
||||
return '日常阅读';
|
||||
case 'business':
|
||||
return '商务阅读';
|
||||
case 'academic':
|
||||
return '学术阅读';
|
||||
case 'news':
|
||||
return '新闻阅读';
|
||||
default:
|
||||
return category;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
client/lib/features/reading/models/reading_exercise_model.dart
Normal file
125
client/lib/features/reading/models/reading_exercise_model.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
/// 阅读练习类型枚举
|
||||
enum ReadingExerciseType {
|
||||
news, // 新闻
|
||||
story, // 故事
|
||||
science, // 科学
|
||||
business, // 商务
|
||||
culture, // 文化
|
||||
technology, // 科技
|
||||
health, // 健康
|
||||
travel, // 旅游
|
||||
}
|
||||
|
||||
/// 阅读难度枚举
|
||||
enum ReadingDifficulty {
|
||||
elementary, // 初级 A1-A2
|
||||
intermediate, // 中级 B1
|
||||
upperIntermediate, // 中高级 B2
|
||||
advanced, // 高级 C1
|
||||
proficient, // 精通 C2
|
||||
}
|
||||
|
||||
/// 阅读练习分类
|
||||
class ReadingCategory {
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final String icon;
|
||||
final int articleCount;
|
||||
final ReadingExerciseType type;
|
||||
|
||||
const ReadingCategory({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.icon,
|
||||
required this.articleCount,
|
||||
required this.type,
|
||||
});
|
||||
}
|
||||
|
||||
/// 阅读练习
|
||||
class ReadingExercise {
|
||||
final String id;
|
||||
final String title;
|
||||
final String content;
|
||||
final String summary;
|
||||
final ReadingExerciseType type;
|
||||
final ReadingDifficulty difficulty;
|
||||
final int wordCount;
|
||||
final int estimatedTime; // 预估阅读时间(分钟)
|
||||
final List<ReadingQuestion> questions;
|
||||
final List<String> tags;
|
||||
final String source;
|
||||
final DateTime publishDate;
|
||||
final String imageUrl;
|
||||
|
||||
const ReadingExercise({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.content,
|
||||
required this.summary,
|
||||
required this.type,
|
||||
required this.difficulty,
|
||||
required this.wordCount,
|
||||
required this.estimatedTime,
|
||||
required this.questions,
|
||||
required this.tags,
|
||||
required this.source,
|
||||
required this.publishDate,
|
||||
required this.imageUrl,
|
||||
});
|
||||
}
|
||||
|
||||
/// 阅读问题
|
||||
class ReadingQuestion {
|
||||
final String id;
|
||||
final String question;
|
||||
final List<String> options;
|
||||
final int correctAnswer; // 正确答案的索引
|
||||
final String explanation;
|
||||
final String type; // multiple_choice, true_false, fill_blank
|
||||
|
||||
const ReadingQuestion({
|
||||
required this.id,
|
||||
required this.question,
|
||||
required this.options,
|
||||
required this.correctAnswer,
|
||||
required this.explanation,
|
||||
required this.type,
|
||||
});
|
||||
}
|
||||
|
||||
/// 阅读结果
|
||||
class ReadingResult {
|
||||
final String exerciseId;
|
||||
final int correctAnswers;
|
||||
final int totalQuestions;
|
||||
final int readingTime; // 实际阅读时间(秒)
|
||||
final DateTime completedAt;
|
||||
final List<UserAnswer> userAnswers;
|
||||
|
||||
const ReadingResult({
|
||||
required this.exerciseId,
|
||||
required this.correctAnswers,
|
||||
required this.totalQuestions,
|
||||
required this.readingTime,
|
||||
required this.completedAt,
|
||||
required this.userAnswers,
|
||||
});
|
||||
|
||||
double get accuracy => correctAnswers / totalQuestions;
|
||||
}
|
||||
|
||||
/// 用户答案
|
||||
class UserAnswer {
|
||||
final String questionId;
|
||||
final int selectedAnswer;
|
||||
final bool isCorrect;
|
||||
|
||||
const UserAnswer({
|
||||
required this.questionId,
|
||||
required this.selectedAnswer,
|
||||
required this.isCorrect,
|
||||
});
|
||||
}
|
||||
241
client/lib/features/reading/models/reading_question.dart
Normal file
241
client/lib/features/reading/models/reading_question.dart
Normal file
@@ -0,0 +1,241 @@
|
||||
enum QuestionType {
|
||||
multipleChoice,
|
||||
trueFalse,
|
||||
fillInBlank,
|
||||
shortAnswer,
|
||||
}
|
||||
|
||||
class ReadingQuestion {
|
||||
final String id;
|
||||
final String articleId;
|
||||
final QuestionType type;
|
||||
final String question;
|
||||
final List<String> options; // For multiple choice questions
|
||||
final String correctAnswer;
|
||||
final String explanation;
|
||||
final int order;
|
||||
final String? userAnswer;
|
||||
final bool? isCorrect;
|
||||
|
||||
const ReadingQuestion({
|
||||
required this.id,
|
||||
required this.articleId,
|
||||
required this.type,
|
||||
required this.question,
|
||||
required this.options,
|
||||
required this.correctAnswer,
|
||||
required this.explanation,
|
||||
required this.order,
|
||||
this.userAnswer,
|
||||
this.isCorrect,
|
||||
});
|
||||
|
||||
factory ReadingQuestion.fromJson(Map<String, dynamic> json) {
|
||||
return ReadingQuestion(
|
||||
id: json['id'] as String,
|
||||
articleId: json['articleId'] as String,
|
||||
type: QuestionType.values.firstWhere(
|
||||
(e) => e.toString().split('.').last == json['type'],
|
||||
orElse: () => QuestionType.multipleChoice,
|
||||
),
|
||||
question: json['question'] as String,
|
||||
options: List<String>.from(json['options'] as List? ?? []),
|
||||
correctAnswer: json['correctAnswer'] as String,
|
||||
explanation: json['explanation'] as String,
|
||||
order: json['order'] as int,
|
||||
userAnswer: json['userAnswer'] as String?,
|
||||
isCorrect: json['isCorrect'] as bool?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'articleId': articleId,
|
||||
'type': type.toString().split('.').last,
|
||||
'question': question,
|
||||
'options': options,
|
||||
'correctAnswer': correctAnswer,
|
||||
'explanation': explanation,
|
||||
'order': order,
|
||||
'userAnswer': userAnswer,
|
||||
'isCorrect': isCorrect,
|
||||
};
|
||||
}
|
||||
|
||||
ReadingQuestion copyWith({
|
||||
String? id,
|
||||
String? articleId,
|
||||
QuestionType? type,
|
||||
String? question,
|
||||
List<String>? options,
|
||||
String? correctAnswer,
|
||||
String? explanation,
|
||||
int? order,
|
||||
String? userAnswer,
|
||||
bool? isCorrect,
|
||||
}) {
|
||||
return ReadingQuestion(
|
||||
id: id ?? this.id,
|
||||
articleId: articleId ?? this.articleId,
|
||||
type: type ?? this.type,
|
||||
question: question ?? this.question,
|
||||
options: options ?? this.options,
|
||||
correctAnswer: correctAnswer ?? this.correctAnswer,
|
||||
explanation: explanation ?? this.explanation,
|
||||
order: order ?? this.order,
|
||||
userAnswer: userAnswer ?? this.userAnswer,
|
||||
isCorrect: isCorrect ?? this.isCorrect,
|
||||
);
|
||||
}
|
||||
|
||||
String get typeLabel {
|
||||
switch (type) {
|
||||
case QuestionType.multipleChoice:
|
||||
return '选择题';
|
||||
case QuestionType.trueFalse:
|
||||
return '判断题';
|
||||
case QuestionType.fillInBlank:
|
||||
return '填空题';
|
||||
case QuestionType.shortAnswer:
|
||||
return '简答题';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReadingExercise {
|
||||
final String id;
|
||||
final String articleId;
|
||||
final List<ReadingQuestion> questions;
|
||||
final DateTime? startTime;
|
||||
final DateTime? endTime;
|
||||
final double? score;
|
||||
final bool isCompleted;
|
||||
|
||||
const ReadingExercise({
|
||||
required this.id,
|
||||
required this.articleId,
|
||||
required this.questions,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.score,
|
||||
this.isCompleted = false,
|
||||
});
|
||||
|
||||
factory ReadingExercise.fromJson(Map<String, dynamic> json) {
|
||||
return ReadingExercise(
|
||||
id: json['id'] as String,
|
||||
articleId: json['articleId'] as String,
|
||||
questions: (json['questions'] as List)
|
||||
.map((q) => ReadingQuestion.fromJson(q as Map<String, dynamic>))
|
||||
.toList(),
|
||||
startTime: json['startTime'] != null
|
||||
? DateTime.parse(json['startTime'] as String)
|
||||
: null,
|
||||
endTime: json['endTime'] != null
|
||||
? DateTime.parse(json['endTime'] as String)
|
||||
: null,
|
||||
score: json['score'] as double?,
|
||||
isCompleted: json['isCompleted'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'articleId': articleId,
|
||||
'questions': questions.map((q) => q.toJson()).toList(),
|
||||
'startTime': startTime?.toIso8601String(),
|
||||
'endTime': endTime?.toIso8601String(),
|
||||
'score': score,
|
||||
'isCompleted': isCompleted,
|
||||
};
|
||||
}
|
||||
|
||||
ReadingExercise copyWith({
|
||||
String? id,
|
||||
String? articleId,
|
||||
List<ReadingQuestion>? questions,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime,
|
||||
double? score,
|
||||
bool? isCompleted,
|
||||
}) {
|
||||
return ReadingExercise(
|
||||
id: id ?? this.id,
|
||||
articleId: articleId ?? this.articleId,
|
||||
questions: questions ?? this.questions,
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
score: score ?? this.score,
|
||||
isCompleted: isCompleted ?? this.isCompleted,
|
||||
);
|
||||
}
|
||||
|
||||
int get totalQuestions => questions.length;
|
||||
|
||||
int get answeredQuestions {
|
||||
return questions.where((q) => q.userAnswer != null).length;
|
||||
}
|
||||
|
||||
int get correctAnswers {
|
||||
return questions.where((q) => q.isCorrect == true).length;
|
||||
}
|
||||
|
||||
double get progressPercentage {
|
||||
if (totalQuestions == 0) return 0.0;
|
||||
return (answeredQuestions / totalQuestions) * 100;
|
||||
}
|
||||
|
||||
Duration? get duration {
|
||||
if (startTime != null && endTime != null) {
|
||||
return endTime!.difference(startTime!);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 阅读练习结果
|
||||
class ReadingExerciseResult {
|
||||
final double score;
|
||||
final int correctCount;
|
||||
final int totalCount;
|
||||
final Duration timeSpent; // 用时
|
||||
final double accuracy;
|
||||
|
||||
const ReadingExerciseResult({
|
||||
required this.score,
|
||||
required this.correctCount,
|
||||
required this.totalCount,
|
||||
required this.timeSpent,
|
||||
required this.accuracy,
|
||||
});
|
||||
|
||||
/// 错误题数
|
||||
int get wrongCount => totalCount - correctCount;
|
||||
|
||||
/// 总题数(别名)
|
||||
int get totalQuestions => totalCount;
|
||||
|
||||
factory ReadingExerciseResult.fromJson(Map<String, dynamic> json) {
|
||||
return ReadingExerciseResult(
|
||||
score: (json['score'] as num).toDouble(),
|
||||
correctCount: json['correctCount'] as int,
|
||||
totalCount: json['totalCount'] as int,
|
||||
timeSpent: Duration(seconds: json['timeSpent'] as int),
|
||||
accuracy: (json['accuracy'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'score': score,
|
||||
'correctCount': correctCount,
|
||||
'totalCount': totalCount,
|
||||
'timeSpent': timeSpent.inSeconds,
|
||||
'accuracy': accuracy,
|
||||
};
|
||||
}
|
||||
|
||||
bool get isPassed => score >= 60.0;
|
||||
}
|
||||
280
client/lib/features/reading/models/reading_stats.dart
Normal file
280
client/lib/features/reading/models/reading_stats.dart
Normal file
@@ -0,0 +1,280 @@
|
||||
class ReadingStats {
|
||||
final int totalArticlesRead;
|
||||
final int practicesDone;
|
||||
final double averageScore;
|
||||
final int totalReadingTime; // in minutes
|
||||
final double averageReadingSpeed; // words per minute
|
||||
final double comprehensionAccuracy; // percentage
|
||||
final int vocabularyMastered;
|
||||
final int consecutiveDays;
|
||||
final Map<String, int> categoryStats; // category -> articles read
|
||||
final Map<String, double> difficultyStats; // difficulty -> average score
|
||||
final List<DailyReadingRecord> dailyRecords;
|
||||
|
||||
const ReadingStats({
|
||||
required this.totalArticlesRead,
|
||||
required this.practicesDone,
|
||||
required this.averageScore,
|
||||
required this.totalReadingTime,
|
||||
required this.averageReadingSpeed,
|
||||
required this.comprehensionAccuracy,
|
||||
required this.vocabularyMastered,
|
||||
required this.consecutiveDays,
|
||||
required this.categoryStats,
|
||||
required this.difficultyStats,
|
||||
required this.dailyRecords,
|
||||
});
|
||||
|
||||
factory ReadingStats.fromJson(Map<String, dynamic> json) {
|
||||
final totalArticlesRead = json['totalArticlesRead'];
|
||||
final practicesDone = json['practicesDone'];
|
||||
final averageScore = json['averageScore'];
|
||||
final totalReadingTime = json['totalReadingTime'];
|
||||
final averageReadingSpeed = json['averageReadingSpeed'];
|
||||
final comprehensionAccuracy = json['comprehensionAccuracy'];
|
||||
final vocabularyMastered = json['vocabularyMastered'];
|
||||
final consecutiveDays = json['consecutiveDays'];
|
||||
|
||||
final rawCategoryStats = json['categoryStats'];
|
||||
final Map<String, int> categoryStats = {};
|
||||
if (rawCategoryStats is Map) {
|
||||
rawCategoryStats.forEach((key, value) {
|
||||
final k = key is String ? key : key.toString();
|
||||
final v = value is num ? value.toInt() : 0;
|
||||
categoryStats[k] = v;
|
||||
});
|
||||
}
|
||||
|
||||
final rawDifficultyStats = json['difficultyStats'];
|
||||
final Map<String, double> difficultyStats = {};
|
||||
if (rawDifficultyStats is Map) {
|
||||
rawDifficultyStats.forEach((key, value) {
|
||||
final k = key is String ? key : key.toString();
|
||||
final v = value is num ? value.toDouble() : 0.0;
|
||||
difficultyStats[k] = v;
|
||||
});
|
||||
}
|
||||
|
||||
final rawDailyRecords = json['dailyRecords'];
|
||||
final List<DailyReadingRecord> dailyRecords = [];
|
||||
if (rawDailyRecords is List) {
|
||||
for (final record in rawDailyRecords) {
|
||||
if (record is Map<String, dynamic>) {
|
||||
dailyRecords.add(DailyReadingRecord.fromJson(record));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ReadingStats(
|
||||
totalArticlesRead: totalArticlesRead is num ? totalArticlesRead.toInt() : 0,
|
||||
practicesDone: practicesDone is num ? practicesDone.toInt() : 0,
|
||||
averageScore: averageScore is num ? averageScore.toDouble() : 0.0,
|
||||
totalReadingTime: totalReadingTime is num ? totalReadingTime.toInt() : 0,
|
||||
averageReadingSpeed: averageReadingSpeed is num ? averageReadingSpeed.toDouble() : 0.0,
|
||||
comprehensionAccuracy: comprehensionAccuracy is num ? comprehensionAccuracy.toDouble() : 0.0,
|
||||
vocabularyMastered: vocabularyMastered is num ? vocabularyMastered.toInt() : 0,
|
||||
consecutiveDays: consecutiveDays is num ? consecutiveDays.toInt() : 0,
|
||||
categoryStats: categoryStats,
|
||||
difficultyStats: difficultyStats,
|
||||
dailyRecords: dailyRecords,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'totalArticlesRead': totalArticlesRead,
|
||||
'practicesDone': practicesDone,
|
||||
'averageScore': averageScore,
|
||||
'totalReadingTime': totalReadingTime,
|
||||
'averageReadingSpeed': averageReadingSpeed,
|
||||
'comprehensionAccuracy': comprehensionAccuracy,
|
||||
'vocabularyMastered': vocabularyMastered,
|
||||
'consecutiveDays': consecutiveDays,
|
||||
'categoryStats': categoryStats,
|
||||
'difficultyStats': difficultyStats,
|
||||
'dailyRecords': dailyRecords.map((record) => record.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
ReadingStats copyWith({
|
||||
int? totalArticlesRead,
|
||||
int? practicesDone,
|
||||
double? averageScore,
|
||||
int? totalReadingTime,
|
||||
double? averageReadingSpeed,
|
||||
double? comprehensionAccuracy,
|
||||
int? vocabularyMastered,
|
||||
int? consecutiveDays,
|
||||
Map<String, int>? categoryStats,
|
||||
Map<String, double>? difficultyStats,
|
||||
List<DailyReadingRecord>? dailyRecords,
|
||||
}) {
|
||||
return ReadingStats(
|
||||
totalArticlesRead: totalArticlesRead ?? this.totalArticlesRead,
|
||||
practicesDone: practicesDone ?? this.practicesDone,
|
||||
averageScore: averageScore ?? this.averageScore,
|
||||
totalReadingTime: totalReadingTime ?? this.totalReadingTime,
|
||||
averageReadingSpeed: averageReadingSpeed ?? this.averageReadingSpeed,
|
||||
comprehensionAccuracy: comprehensionAccuracy ?? this.comprehensionAccuracy,
|
||||
vocabularyMastered: vocabularyMastered ?? this.vocabularyMastered,
|
||||
consecutiveDays: consecutiveDays ?? this.consecutiveDays,
|
||||
categoryStats: categoryStats ?? this.categoryStats,
|
||||
difficultyStats: difficultyStats ?? this.difficultyStats,
|
||||
dailyRecords: dailyRecords ?? this.dailyRecords,
|
||||
);
|
||||
}
|
||||
|
||||
String get readingTimeFormatted {
|
||||
final hours = totalReadingTime ~/ 60;
|
||||
final minutes = totalReadingTime % 60;
|
||||
if (hours > 0) {
|
||||
return '${hours}小时${minutes}分钟';
|
||||
}
|
||||
return '${minutes}分钟';
|
||||
}
|
||||
|
||||
String get averageScoreFormatted {
|
||||
return '${averageScore.toStringAsFixed(1)}分';
|
||||
}
|
||||
|
||||
String get comprehensionAccuracyFormatted {
|
||||
return '${comprehensionAccuracy.toStringAsFixed(1)}%';
|
||||
}
|
||||
|
||||
String get averageReadingSpeedFormatted {
|
||||
return '${averageReadingSpeed.toStringAsFixed(0)}词/分钟';
|
||||
}
|
||||
}
|
||||
|
||||
class DailyReadingRecord {
|
||||
final DateTime date;
|
||||
final int articlesRead;
|
||||
final int practicesDone;
|
||||
final int readingTime; // in minutes
|
||||
final double averageScore;
|
||||
final int vocabularyLearned;
|
||||
|
||||
const DailyReadingRecord({
|
||||
required this.date,
|
||||
required this.articlesRead,
|
||||
required this.practicesDone,
|
||||
required this.readingTime,
|
||||
required this.averageScore,
|
||||
required this.vocabularyLearned,
|
||||
});
|
||||
|
||||
factory DailyReadingRecord.fromJson(Map<String, dynamic> json) {
|
||||
final dateRaw = json['date'];
|
||||
final articlesRead = json['articlesRead'];
|
||||
final practicesDone = json['practicesDone'];
|
||||
final readingTime = json['readingTime'];
|
||||
final averageScore = json['averageScore'];
|
||||
final vocabularyLearned = json['vocabularyLearned'];
|
||||
|
||||
return DailyReadingRecord(
|
||||
date: dateRaw is String ? DateTime.parse(dateRaw) : DateTime.fromMillisecondsSinceEpoch(0),
|
||||
articlesRead: articlesRead is num ? articlesRead.toInt() : 0,
|
||||
practicesDone: practicesDone is num ? practicesDone.toInt() : 0,
|
||||
readingTime: readingTime is num ? readingTime.toInt() : 0,
|
||||
averageScore: averageScore is num ? averageScore.toDouble() : 0.0,
|
||||
vocabularyLearned: vocabularyLearned is num ? vocabularyLearned.toInt() : 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'date': date.toIso8601String(),
|
||||
'articlesRead': articlesRead,
|
||||
'practicesDone': practicesDone,
|
||||
'readingTime': readingTime,
|
||||
'averageScore': averageScore,
|
||||
'vocabularyLearned': vocabularyLearned,
|
||||
};
|
||||
}
|
||||
|
||||
DailyReadingRecord copyWith({
|
||||
DateTime? date,
|
||||
int? articlesRead,
|
||||
int? practicesDone,
|
||||
int? readingTime,
|
||||
double? averageScore,
|
||||
int? vocabularyLearned,
|
||||
}) {
|
||||
return DailyReadingRecord(
|
||||
date: date ?? this.date,
|
||||
articlesRead: articlesRead ?? this.articlesRead,
|
||||
practicesDone: practicesDone ?? this.practicesDone,
|
||||
readingTime: readingTime ?? this.readingTime,
|
||||
averageScore: averageScore ?? this.averageScore,
|
||||
vocabularyLearned: vocabularyLearned ?? this.vocabularyLearned,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReadingProgress {
|
||||
final String category;
|
||||
final int totalArticles;
|
||||
final int completedArticles;
|
||||
final double averageScore;
|
||||
final String difficulty;
|
||||
|
||||
const ReadingProgress({
|
||||
required this.category,
|
||||
required this.totalArticles,
|
||||
required this.completedArticles,
|
||||
required this.averageScore,
|
||||
required this.difficulty,
|
||||
});
|
||||
|
||||
factory ReadingProgress.fromJson(Map<String, dynamic> json) {
|
||||
return ReadingProgress(
|
||||
category: json['category'] as String,
|
||||
totalArticles: json['totalArticles'] as int,
|
||||
completedArticles: json['completedArticles'] as int,
|
||||
averageScore: (json['averageScore'] as num).toDouble(),
|
||||
difficulty: json['difficulty'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'category': category,
|
||||
'totalArticles': totalArticles,
|
||||
'completedArticles': completedArticles,
|
||||
'averageScore': averageScore,
|
||||
'difficulty': difficulty,
|
||||
};
|
||||
}
|
||||
|
||||
double get progressPercentage {
|
||||
if (totalArticles == 0) return 0.0;
|
||||
return (completedArticles / totalArticles) * 100;
|
||||
}
|
||||
|
||||
String get progressText {
|
||||
return '$completedArticles/$totalArticles';
|
||||
}
|
||||
|
||||
String get categoryLabel {
|
||||
switch (category.toLowerCase()) {
|
||||
case 'cet4':
|
||||
return '四级阅读';
|
||||
case 'cet6':
|
||||
return '六级阅读';
|
||||
case 'toefl':
|
||||
return '托福阅读';
|
||||
case 'ielts':
|
||||
return '雅思阅读';
|
||||
case 'daily':
|
||||
return '日常阅读';
|
||||
case 'business':
|
||||
return '商务阅读';
|
||||
case 'academic':
|
||||
return '学术阅读';
|
||||
case 'news':
|
||||
return '新闻阅读';
|
||||
default:
|
||||
return category;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user