Files
ai_english/client/lib/features/reading/models/reading_stats.dart
2025-11-17 14:09:17 +08:00

280 lines
9.1 KiB
Dart

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;
}
}
}