270 lines
9 KiB
Dart
270 lines
9 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:async/async.dart';
|
|
import 'package:collection/collection.dart';
|
|
|
|
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
|
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
|
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
|
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
|
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
|
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/practice_choice.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/practice_generation_repo.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/practice_selection.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart';
|
|
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
|
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
|
import 'package:fluffychat/pangea/toolbar/message_practice/message_practice_mode_enum.dart';
|
|
import 'package:fluffychat/pangea/toolbar/message_practice/morph_selection.dart';
|
|
import 'package:fluffychat/pangea/toolbar/message_practice/practice_record_controller.dart';
|
|
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
|
import 'package:fluffychat/widgets/matrix.dart';
|
|
|
|
class PracticeController with ChangeNotifier {
|
|
final PangeaMessageEvent pangeaMessageEvent;
|
|
|
|
PracticeController(this.pangeaMessageEvent) {
|
|
_fetchPracticeSelection();
|
|
}
|
|
|
|
PracticeActivityModel? _activity;
|
|
|
|
MessagePracticeMode _practiceMode = MessagePracticeMode.noneSelected;
|
|
|
|
MorphSelection? _selectedMorph;
|
|
PracticeChoice? _selectedChoice;
|
|
|
|
PracticeSelection? practiceSelection;
|
|
|
|
MessagePracticeMode get practiceMode => _practiceMode;
|
|
MorphSelection? get selectedMorph => _selectedMorph;
|
|
PracticeChoice? get selectedChoice => _selectedChoice;
|
|
|
|
PracticeTarget? get currentTarget {
|
|
final activityType = _practiceMode.associatedActivityType;
|
|
if (activityType == null) return null;
|
|
if (activityType == ActivityTypeEnum.morphId) {
|
|
if (_selectedMorph == null) return null;
|
|
return practiceSelection?.getMorphTarget(
|
|
_selectedMorph!.token,
|
|
_selectedMorph!.morph,
|
|
);
|
|
}
|
|
return practiceSelection?.getTarget(activityType);
|
|
}
|
|
|
|
bool get showChoiceShimmer {
|
|
if (_activity == null) return false;
|
|
if (_activity is MorphMatchPracticeActivityModel) {
|
|
return _selectedMorph != null &&
|
|
!PracticeRecordController.hasResponse(_activity!.practiceTarget);
|
|
}
|
|
|
|
return _selectedChoice == null &&
|
|
!PracticeRecordController.hasAnyCorrectChoices(
|
|
_activity!.practiceTarget,
|
|
);
|
|
}
|
|
|
|
bool get isTotallyDone =>
|
|
isPracticeSessionDone(ActivityTypeEnum.emoji) &&
|
|
isPracticeSessionDone(ActivityTypeEnum.wordMeaning) &&
|
|
isPracticeSessionDone(ActivityTypeEnum.wordFocusListening) &&
|
|
isPracticeSessionDone(ActivityTypeEnum.morphId);
|
|
|
|
bool get isCurrentPracticeSessionDone {
|
|
final activityType = _practiceMode.associatedActivityType;
|
|
if (activityType == null) return false;
|
|
return isPracticeSessionDone(activityType);
|
|
}
|
|
|
|
bool? wasCorrectMatch(PracticeChoice choice) {
|
|
if (_activity == null) return false;
|
|
return PracticeRecordController.wasCorrectMatch(
|
|
_activity!.practiceTarget,
|
|
choice,
|
|
);
|
|
}
|
|
|
|
bool? wasCorrectChoice(String choice) {
|
|
if (_activity == null) return false;
|
|
return PracticeRecordController.wasCorrectChoice(
|
|
_activity!.practiceTarget,
|
|
choice,
|
|
);
|
|
}
|
|
|
|
bool isPracticeSessionDone(ActivityTypeEnum activityType) =>
|
|
practiceSelection
|
|
?.activities(activityType)
|
|
.every((a) => PracticeRecordController.isCompleteByTarget(a)) ==
|
|
true;
|
|
|
|
bool isPracticeButtonEmpty(PangeaToken token) {
|
|
final target = practiceTargetForToken(token);
|
|
switch (_practiceMode) {
|
|
// Keep open when completed if emoji assigned
|
|
case MessagePracticeMode.wordEmoji:
|
|
if (token.vocabConstructID.userSetEmoji != null) return false;
|
|
return target == null;
|
|
// Keep open when completed to show morph icon
|
|
case MessagePracticeMode.wordMorph:
|
|
return target == null;
|
|
default:
|
|
return target == null ||
|
|
PracticeRecordController.isCompleteByToken(target, token);
|
|
}
|
|
}
|
|
|
|
PracticeTarget? practiceTargetForToken(PangeaToken token) {
|
|
if (_practiceMode.associatedActivityType == null) return null;
|
|
return practiceSelection
|
|
?.activities(_practiceMode.associatedActivityType!)
|
|
.firstWhereOrNull((a) => a.tokens.contains(token));
|
|
}
|
|
|
|
void updateToolbarMode(MessagePracticeMode mode) {
|
|
_selectedChoice = null;
|
|
_practiceMode = mode;
|
|
if (_practiceMode != MessagePracticeMode.wordMorph) {
|
|
_selectedMorph = null;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
void updatePracticeMorph(MorphSelection newMorph) {
|
|
_practiceMode = MessagePracticeMode.wordMorph;
|
|
_selectedMorph = newMorph;
|
|
notifyListeners();
|
|
}
|
|
|
|
void onChoiceSelect(PracticeChoice? choice) {
|
|
if (_activity == null) return;
|
|
if (_selectedChoice == choice) {
|
|
_selectedChoice = null;
|
|
} else {
|
|
_selectedChoice = choice;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
void onMatch(PangeaToken token, PracticeChoice choice) {
|
|
if (_activity == null) return;
|
|
final isCorrect = PracticeRecordController.onSelectChoice(
|
|
choice.choiceContent,
|
|
token,
|
|
_activity!,
|
|
);
|
|
|
|
final targetId =
|
|
"message-token-${token.text.uniqueKey}-${pangeaMessageEvent.eventId}";
|
|
|
|
final updateService = MatrixState
|
|
.pangeaController
|
|
.matrixState
|
|
.analyticsDataService
|
|
.updateService;
|
|
|
|
// we don't take off points for incorrect emoji matches
|
|
if (_activity is! EmojiPracticeActivityModel || isCorrect) {
|
|
final l2 =
|
|
MatrixState.pangeaController.userController.userL2?.langCodeShort;
|
|
if (l2 == null) {
|
|
ErrorHandler.logError(
|
|
e: "User L2 is null when trying to log construct use for token ${token.text.content} in practice activity",
|
|
data: {
|
|
"eventId": pangeaMessageEvent.eventId,
|
|
"token": token.text.content,
|
|
"activityType": _activity!.activityType.toString(),
|
|
},
|
|
);
|
|
return;
|
|
}
|
|
|
|
final constructUseType = PracticeRecordController.lastResponse(
|
|
_activity!.practiceTarget,
|
|
)!.useType(_activity!.activityType);
|
|
|
|
final constructs = [
|
|
OneConstructUse(
|
|
useType: constructUseType,
|
|
lemma: token.lemma.text,
|
|
constructType: ConstructTypeEnum.vocab,
|
|
metadata: ConstructUseMetaData(
|
|
roomId: pangeaMessageEvent.room.id,
|
|
timeStamp: DateTime.now(),
|
|
eventId: pangeaMessageEvent.eventId,
|
|
),
|
|
category: token.pos,
|
|
// in the case of a wrong answer, the cId doesn't match the token
|
|
form: token.text.content,
|
|
xp: constructUseType.pointValue,
|
|
),
|
|
];
|
|
|
|
updateService.addAnalytics(targetId, constructs, l2);
|
|
}
|
|
|
|
if (isCorrect) {
|
|
if (_activity is EmojiPracticeActivityModel) {
|
|
updateService.setLemmaInfo(
|
|
choice.form.cId,
|
|
emoji: choice.choiceContent,
|
|
);
|
|
}
|
|
|
|
if (_activity is LemmaMeaningPracticeActivityModel) {
|
|
updateService.setLemmaInfo(
|
|
choice.form.cId,
|
|
meaning: choice.choiceContent,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (_activity is LemmaMeaningPracticeActivityModel ||
|
|
_activity is EmojiPracticeActivityModel) {
|
|
TtsController.tryToSpeak(
|
|
token.text.content,
|
|
langCode: MatrixState.pangeaController.userController.userL2!.langCode,
|
|
pos: token.pos,
|
|
morph: token.morph.map((k, v) => MapEntry(k.name, v)),
|
|
);
|
|
}
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> _fetchPracticeSelection() async {
|
|
if (pangeaMessageEvent.messageDisplayRepresentation?.tokens == null) return;
|
|
practiceSelection = await PracticeSelectionRepo.get(
|
|
pangeaMessageEvent.eventId,
|
|
pangeaMessageEvent.messageDisplayLangCode,
|
|
pangeaMessageEvent.messageDisplayRepresentation!.tokens!,
|
|
);
|
|
}
|
|
|
|
Future<Result<PracticeActivityModel>> fetchActivityModel(
|
|
PracticeTarget target,
|
|
) async {
|
|
final req = MessageActivityRequest(
|
|
userL1: MatrixState.pangeaController.userController.userL1!.langCode,
|
|
userL2: MatrixState.pangeaController.userController.userL2!.langCode,
|
|
activityQualityFeedback: null,
|
|
target: target,
|
|
);
|
|
|
|
final result = await PracticeRepo.getPracticeActivity(
|
|
req,
|
|
messageInfo: pangeaMessageEvent.event.content,
|
|
);
|
|
if (result.isValue) {
|
|
_activity = result.result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|