You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
4.2 KiB
135 lines
4.2 KiB
import 'attempt.dart';
|
|
import 'question.dart';
|
|
|
|
/// Represents an incomplete attempt that can be resumed later.
|
|
class IncompleteAttempt {
|
|
/// The attempt ID.
|
|
final String attemptId;
|
|
|
|
/// List of question IDs in the attempt (in order).
|
|
final List<String> questionIds;
|
|
|
|
/// Timestamp when the attempt was started.
|
|
final int startTime;
|
|
|
|
/// Current question index (0-based).
|
|
final int currentQuestionIndex;
|
|
|
|
/// Map of questionId -> answer (int for single, List<int> for multiple).
|
|
final Map<String, dynamic> answers;
|
|
|
|
/// Map of questionId -> manual override (needs practice).
|
|
final Map<String, bool> manualOverrides;
|
|
|
|
/// Timestamp when the attempt was paused.
|
|
final int pausedAt;
|
|
|
|
/// Remaining time in seconds (if time limit was set).
|
|
final int? remainingSeconds;
|
|
|
|
const IncompleteAttempt({
|
|
required this.attemptId,
|
|
required this.questionIds,
|
|
required this.startTime,
|
|
required this.currentQuestionIndex,
|
|
required this.answers,
|
|
required this.manualOverrides,
|
|
required this.pausedAt,
|
|
this.remainingSeconds,
|
|
});
|
|
|
|
/// Creates an Attempt from this incomplete attempt using questions from the deck.
|
|
Attempt toAttempt(List<Question> deckQuestions) {
|
|
final questionMap = {for (var q in deckQuestions) q.id: q};
|
|
final questions = questionIds
|
|
.map((id) => questionMap[id])
|
|
.whereType<Question>()
|
|
.toList();
|
|
|
|
return Attempt(
|
|
id: attemptId,
|
|
questions: questions,
|
|
startTime: startTime,
|
|
);
|
|
}
|
|
|
|
/// Creates an incomplete attempt from JSON.
|
|
factory IncompleteAttempt.fromJson(Map<String, dynamic> json) {
|
|
return IncompleteAttempt(
|
|
attemptId: json['attemptId'] as String,
|
|
questionIds: List<String>.from(json['questionIds'] as List),
|
|
startTime: json['startTime'] as int,
|
|
currentQuestionIndex: json['currentQuestionIndex'] as int,
|
|
answers: Map<String, dynamic>.from(json['answers'] as Map),
|
|
manualOverrides: Map<String, bool>.from(
|
|
(json['manualOverrides'] as Map?)?.map((k, v) => MapEntry(k.toString(), v as bool)) ?? {},
|
|
),
|
|
pausedAt: json['pausedAt'] as int,
|
|
remainingSeconds: json['remainingSeconds'] as int?,
|
|
);
|
|
}
|
|
|
|
/// Converts to JSON for storage.
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'attemptId': attemptId,
|
|
'questionIds': questionIds,
|
|
'startTime': startTime,
|
|
'currentQuestionIndex': currentQuestionIndex,
|
|
'answers': answers,
|
|
'manualOverrides': manualOverrides,
|
|
'pausedAt': pausedAt,
|
|
if (remainingSeconds != null) 'remainingSeconds': remainingSeconds,
|
|
};
|
|
}
|
|
|
|
/// Creates a copy with updated fields.
|
|
IncompleteAttempt copyWith({
|
|
String? attemptId,
|
|
List<String>? questionIds,
|
|
int? startTime,
|
|
int? currentQuestionIndex,
|
|
Map<String, dynamic>? answers,
|
|
Map<String, bool>? manualOverrides,
|
|
int? pausedAt,
|
|
int? remainingSeconds,
|
|
}) {
|
|
return IncompleteAttempt(
|
|
attemptId: attemptId ?? this.attemptId,
|
|
questionIds: questionIds ?? this.questionIds,
|
|
startTime: startTime ?? this.startTime,
|
|
currentQuestionIndex: currentQuestionIndex ?? this.currentQuestionIndex,
|
|
answers: answers ?? this.answers,
|
|
manualOverrides: manualOverrides ?? this.manualOverrides,
|
|
pausedAt: pausedAt ?? this.pausedAt,
|
|
remainingSeconds: remainingSeconds ?? this.remainingSeconds,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is IncompleteAttempt &&
|
|
runtimeType == other.runtimeType &&
|
|
attemptId == other.attemptId &&
|
|
questionIds.toString() == other.questionIds.toString() &&
|
|
startTime == other.startTime &&
|
|
currentQuestionIndex == other.currentQuestionIndex &&
|
|
answers.toString() == other.answers.toString() &&
|
|
manualOverrides.toString() == other.manualOverrides.toString() &&
|
|
pausedAt == other.pausedAt &&
|
|
remainingSeconds == other.remainingSeconds;
|
|
|
|
@override
|
|
int get hashCode =>
|
|
attemptId.hashCode ^
|
|
questionIds.hashCode ^
|
|
startTime.hashCode ^
|
|
currentQuestionIndex.hashCode ^
|
|
answers.hashCode ^
|
|
manualOverrides.hashCode ^
|
|
pausedAt.hashCode ^
|
|
(remainingSeconds?.hashCode ?? 0);
|
|
}
|
|
|