104 lines
3.1 KiB
Dart
104 lines
3.1 KiB
Dart
/// Question pushed from the API over the SignalR questions hub.
|
|
class IncomingQuestion {
|
|
const IncomingQuestion({
|
|
required this.id,
|
|
required this.text,
|
|
required this.sentAt,
|
|
this.unansweredCount = 1,
|
|
this.sourceId = defaultSourceId,
|
|
this.presentation = defaultPresentation,
|
|
this.spokenWord,
|
|
this.audioTitle,
|
|
this.needsPrepare = false,
|
|
});
|
|
|
|
static const String defaultSourceId = 'market_history';
|
|
static const String defaultPresentation = 'market_glyph_slider';
|
|
|
|
final String id;
|
|
final String text;
|
|
final DateTime sentAt;
|
|
|
|
/// Unanswered questions in the database for this user (from API).
|
|
final int unansweredCount;
|
|
|
|
/// Question source (`market_history`, `spelling`, …).
|
|
final String sourceId;
|
|
|
|
/// UI presentation key (`market_glyph_slider`, `spelling_word`, …).
|
|
final String presentation;
|
|
|
|
/// Word read aloud after server-side selection at tile load.
|
|
final String? spokenWord;
|
|
|
|
/// Question id referenced as the audio utterance title.
|
|
final String? audioTitle;
|
|
|
|
/// True when the server still needs tile-load word selection.
|
|
final bool needsPrepare;
|
|
|
|
/// Spelling pipeline source (word quiz and follow-up prompts).
|
|
bool get isSpelling => sourceId == 'spelling';
|
|
|
|
/// Typed spelling tile (hear word, type answer).
|
|
bool get usesSpellingWordTile {
|
|
if (presentation == 'spelling_word' || needsPrepare) {
|
|
return true;
|
|
}
|
|
if (!isSpelling) {
|
|
return false;
|
|
}
|
|
if (spokenWord != null && spokenWord!.isNotEmpty) {
|
|
return true;
|
|
}
|
|
return !isSpellingFollowUp;
|
|
}
|
|
|
|
/// Spelling branch prompt to continue (+1) or pause (-1); uses swipe UI.
|
|
bool get isSpellingFollowUp {
|
|
if (!isSpelling || presentation == 'spelling_word') {
|
|
return false;
|
|
}
|
|
final String lower = text.toLowerCase();
|
|
return lower.contains('enter +1') ||
|
|
lower.contains('another word') ||
|
|
lower.contains('to pause');
|
|
}
|
|
|
|
bool get isMarketHistory => sourceId == 'market_history';
|
|
|
|
factory IncomingQuestion.fromJson(Map<String, dynamic> json) {
|
|
return IncomingQuestion(
|
|
id: json['id'] as String,
|
|
text: json['text'] as String,
|
|
sentAt: DateTime.parse(json['sentAt'] as String).toUtc(),
|
|
unansweredCount: (json['unansweredCount'] as num?)?.toInt() ?? 1,
|
|
sourceId: json['sourceId'] as String? ?? defaultSourceId,
|
|
presentation: json['presentation'] as String? ?? defaultPresentation,
|
|
spokenWord: json['spokenWord'] as String?,
|
|
audioTitle: json['audioTitle'] as String?,
|
|
needsPrepare: json['needsPrepare'] as bool? ?? false,
|
|
);
|
|
}
|
|
|
|
IncomingQuestion copyWith({
|
|
String? text,
|
|
String? spokenWord,
|
|
String? audioTitle,
|
|
int? unansweredCount,
|
|
bool? needsPrepare,
|
|
}) {
|
|
return IncomingQuestion(
|
|
id: id,
|
|
text: text ?? this.text,
|
|
sentAt: sentAt,
|
|
unansweredCount: unansweredCount ?? this.unansweredCount,
|
|
sourceId: sourceId,
|
|
presentation: presentation,
|
|
spokenWord: spokenWord ?? this.spokenWord,
|
|
audioTitle: audioTitle ?? this.audioTitle,
|
|
needsPrepare: needsPrepare ?? this.needsPrepare,
|
|
);
|
|
}
|
|
}
|