280 lines
9.1 KiB
Dart
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;
|
|
}
|
|
}
|
|
} |