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.
decky/packages/practice_engine/lib/models/incomplete_attempt.dart

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

Powered by TurnKey Linux.