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.
155 lines
4.9 KiB
155 lines
4.9 KiB
/// A question in a practice deck.
|
|
class Question {
|
|
/// Unique identifier for this question.
|
|
final String id;
|
|
|
|
/// The question prompt.
|
|
final String prompt;
|
|
|
|
/// Optional explanation shown when viewing question details (e.g. after an attempt).
|
|
final String? explanation;
|
|
|
|
/// List of possible answers.
|
|
final List<String> answers;
|
|
|
|
/// Indices of correct answers in [answers].
|
|
/// For backward compatibility, if empty, falls back to [correctAnswerIndex] (deprecated).
|
|
final List<int> correctAnswerIndices;
|
|
|
|
/// Deprecated: Use [correctAnswerIndices] instead.
|
|
/// Kept for backward compatibility with existing data.
|
|
@Deprecated('Use correctAnswerIndices instead')
|
|
final int? correctAnswerIndex;
|
|
|
|
/// Number of consecutive correct answers.
|
|
final int consecutiveCorrect;
|
|
|
|
/// Whether this question is considered "known".
|
|
final bool isKnown;
|
|
|
|
/// Whether the user has flagged this question for review.
|
|
final bool isFlagged;
|
|
|
|
/// Priority points (higher = more likely to be selected).
|
|
final int priorityPoints;
|
|
|
|
/// The last attempt index where this question was seen.
|
|
final int lastAttemptIndex;
|
|
|
|
/// Total number of correct attempts.
|
|
final int totalCorrectAttempts;
|
|
|
|
/// Total number of attempts.
|
|
final int totalAttempts;
|
|
|
|
Question({
|
|
required this.id,
|
|
required this.prompt,
|
|
this.explanation,
|
|
required this.answers,
|
|
List<int>? correctAnswerIndices,
|
|
@Deprecated('Use correctAnswerIndices instead') int? correctAnswerIndex,
|
|
this.consecutiveCorrect = 0,
|
|
this.isKnown = false,
|
|
this.isFlagged = false,
|
|
this.priorityPoints = 0,
|
|
this.lastAttemptIndex = -1,
|
|
this.totalCorrectAttempts = 0,
|
|
this.totalAttempts = 0,
|
|
}) : correctAnswerIndices = correctAnswerIndices ??
|
|
(correctAnswerIndex != null ? [correctAnswerIndex] : const []),
|
|
correctAnswerIndex = correctAnswerIndex;
|
|
|
|
/// Creates a copy of this question with the given fields replaced.
|
|
Question copyWith({
|
|
String? id,
|
|
String? prompt,
|
|
String? explanation,
|
|
List<String>? answers,
|
|
List<int>? correctAnswerIndices,
|
|
@Deprecated('Use correctAnswerIndices instead') int? correctAnswerIndex,
|
|
int? consecutiveCorrect,
|
|
bool? isKnown,
|
|
bool? isFlagged,
|
|
int? priorityPoints,
|
|
int? lastAttemptIndex,
|
|
int? totalCorrectAttempts,
|
|
int? totalAttempts,
|
|
}) {
|
|
return Question(
|
|
id: id ?? this.id,
|
|
prompt: prompt ?? this.prompt,
|
|
explanation: explanation ?? this.explanation,
|
|
answers: answers ?? this.answers,
|
|
correctAnswerIndices: correctAnswerIndices ?? this.correctAnswerIndices,
|
|
correctAnswerIndex: correctAnswerIndex ?? this.correctAnswerIndex,
|
|
consecutiveCorrect: consecutiveCorrect ?? this.consecutiveCorrect,
|
|
isKnown: isKnown ?? this.isKnown,
|
|
isFlagged: isFlagged ?? this.isFlagged,
|
|
priorityPoints: priorityPoints ?? this.priorityPoints,
|
|
lastAttemptIndex: lastAttemptIndex ?? this.lastAttemptIndex,
|
|
totalCorrectAttempts: totalCorrectAttempts ?? this.totalCorrectAttempts,
|
|
totalAttempts: totalAttempts ?? this.totalAttempts,
|
|
);
|
|
}
|
|
|
|
/// Gets the correct answer indices, with backward compatibility.
|
|
List<int> get correctIndices {
|
|
if (correctAnswerIndices.isNotEmpty) {
|
|
return correctAnswerIndices;
|
|
}
|
|
// Backward compatibility
|
|
if (correctAnswerIndex != null) {
|
|
return [correctAnswerIndex!];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/// Checks if an answer index is correct.
|
|
bool isCorrectAnswer(int index) {
|
|
return correctIndices.contains(index);
|
|
}
|
|
|
|
/// Whether this question has multiple correct answers.
|
|
bool get hasMultipleCorrectAnswers => correctIndices.length > 1;
|
|
|
|
/// Validates that priorityPoints is non-negative.
|
|
Question withPriorityPoints(int points) {
|
|
return copyWith(priorityPoints: points < 0 ? 0 : points);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is Question &&
|
|
runtimeType == other.runtimeType &&
|
|
id == other.id &&
|
|
prompt == other.prompt &&
|
|
explanation == other.explanation &&
|
|
answers.toString() == other.answers.toString() &&
|
|
correctAnswerIndices.toString() == other.correctAnswerIndices.toString() &&
|
|
consecutiveCorrect == other.consecutiveCorrect &&
|
|
isKnown == other.isKnown &&
|
|
isFlagged == other.isFlagged &&
|
|
priorityPoints == other.priorityPoints &&
|
|
lastAttemptIndex == other.lastAttemptIndex &&
|
|
totalCorrectAttempts == other.totalCorrectAttempts &&
|
|
totalAttempts == other.totalAttempts;
|
|
|
|
@override
|
|
int get hashCode =>
|
|
id.hashCode ^
|
|
prompt.hashCode ^
|
|
(explanation?.hashCode ?? 0) ^
|
|
answers.hashCode ^
|
|
correctAnswerIndices.hashCode ^
|
|
consecutiveCorrect.hashCode ^
|
|
isKnown.hashCode ^
|
|
isFlagged.hashCode ^
|
|
priorityPoints.hashCode ^
|
|
lastAttemptIndex.hashCode ^
|
|
totalCorrectAttempts.hashCode ^
|
|
totalAttempts.hashCode;
|
|
}
|
|
|