You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fluffychat/lib/pangea/toolbar/widgets/practice_activity/multiple_choice_activity.dart

271 lines
9.4 KiB
Dart

import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_record.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
import 'package:fluffychat/widgets/matrix.dart';
/// The multiple choice activity view
class MultipleChoiceActivity extends StatefulWidget {
final PracticeActivityCardState practiceCardController;
final PracticeActivityModel currentActivity;
final Event event;
final VoidCallback? onError;
final MessageOverlayController overlayController;
final String? initialSelectedChoice;
final bool clearResponsesOnUpdate;
const MultipleChoiceActivity({
super.key,
required this.practiceCardController,
required this.currentActivity,
required this.event,
required this.overlayController,
this.initialSelectedChoice,
this.clearResponsesOnUpdate = false,
this.onError,
});
@override
MultipleChoiceActivityState createState() => MultipleChoiceActivityState();
}
class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
int? selectedChoiceIndex;
PracticeRecord? get currentRecordModel =>
widget.practiceCardController.currentCompletionRecord;
@override
void initState() {
super.initState();
if (widget.currentActivity.multipleChoiceContent == null) {
throw Exception(
"MultipleChoiceActivityState: currentActivity.multipleChoiceContent is null",
);
}
if (widget.initialSelectedChoice != null) {
currentRecordModel?.addResponse(
target: widget.currentActivity.practiceTarget,
cId: widget.currentActivity.morphFeature == null
? widget.currentActivity.targetTokens.first.vocabConstructID
: widget.currentActivity.targetTokens.first
.morphIdByFeature(widget.currentActivity.morphFeature!)!,
text: widget.initialSelectedChoice,
score: 1,
);
}
}
@override
void didUpdateWidget(covariant MultipleChoiceActivity oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.currentActivity.hashCode != oldWidget.currentActivity.hashCode) {
setState(() => selectedChoiceIndex = null);
}
}
void updateChoice(String value, int index) {
final bool isCorrect =
widget.currentActivity.multipleChoiceContent!.isCorrect(value, index);
if (currentRecordModel?.hasTextResponse(value) ?? false) {
return;
}
if (widget.clearResponsesOnUpdate) {
currentRecordModel?.clearResponses();
}
currentRecordModel?.addResponse(
target: widget.currentActivity.practiceTarget,
cId: widget.currentActivity.morphFeature == null
? widget.currentActivity.targetTokens.first.vocabConstructID
: widget.currentActivity.targetTokens.first
.morphIdByFeature(widget.currentActivity.morphFeature!)!,
text: value,
score: isCorrect ? 1 : 0,
);
if (currentRecordModel == null ||
currentRecordModel?.latestResponse == null ||
widget.practiceCardController.currentActivity == null) {
ErrorHandler.logError(
e: "Missing necessary information to send analytics in multiple choice activity",
data: {
"currentRecordModel": currentRecordModel,
"latestResponse": currentRecordModel?.latestResponse,
"currentActivity": widget.practiceCardController.currentActivity,
},
);
debugger(when: kDebugMode);
return;
}
MatrixState.pangeaController.putAnalytics.setState(
AnalyticsStream(
// note - this maybe should be the activity event id
eventId:
widget.practiceCardController.widget.pangeaMessageEvent.eventId,
roomId: widget.practiceCardController.widget.pangeaMessageEvent.room.id,
constructs: currentRecordModel!.latestResponse!.toUses(
widget.practiceCardController.currentActivity!,
widget.practiceCardController.metadata,
),
),
);
// If the selected choice is correct, send the record
if (widget.currentActivity.multipleChoiceContent!.isCorrect(value, index)) {
// If the activity is an emoji activity, set the emoji value
// TODO: this widget is deprecated for use with emoji activities
// if (widget.currentActivity.activityType == ActivityTypeEnum.emoji) {
// if (widget.currentActivity.targetTokens?.length != 1) {
// debugger(when: kDebugMode);
// } else {
// widget.currentActivity.targetTokens!.first.setEmoji(value);
// }
// }
// The next entry in the analytics stream should be from the above putAnalytics.setState.
// So we can wait for the stream to update before calling onActivityFinish.
final streamFuture = MatrixState
.pangeaController.getAnalytics.analyticsStream.stream.first;
streamFuture.then((_) {
widget.practiceCardController.onActivityFinish();
});
}
if (mounted) {
setState(
() => selectedChoiceIndex = index,
);
}
}
List<Choice> choices(BuildContext context) {
final activity = widget.currentActivity.multipleChoiceContent;
final List<Choice> choices = [];
for (int i = 0; i < activity!.choices.length; i++) {
final String value = activity.choices[i];
final color = currentRecordModel?.hasTextResponse(value) ?? false
? activity.choiceColor(i)
: null;
final isGold = activity.isCorrect(value, i);
choices.add(
Choice(
text: value,
color: color,
isGold: isGold,
),
);
}
return choices;
}
String _getDisplayCopy(String value) {
if (widget.currentActivity.activityType != ActivityTypeEnum.morphId) {
return value;
}
final morphFeature = widget
.practiceCardController.widget.targetTokensAndActivityType.morphFeature;
if (morphFeature == null) return value;
return getGrammarCopy(
category: morphFeature.name,
lemma: value,
context: context,
) ??
value;
}
@override
Widget build(BuildContext context) {
final PracticeActivityModel practiceActivity = widget.currentActivity;
final question = practiceActivity.multipleChoiceContent!.question;
// if (ActivityTypeEnum.emoji == practiceActivity.activityType) {
// return WordEmojiChoiceRow(
// activity: practiceActivity,
// selectedChoiceIndex: selectedChoiceIndex,
// onTap: updateChoice,
// );
// }
final content = Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (question.isNotEmpty)
Text(
question,
textAlign: TextAlign.center,
style: AppConfig.messageTextStyle(
widget.event,
Theme.of(context).colorScheme.primary,
).merge(const TextStyle(fontStyle: FontStyle.italic)),
),
if (question.isNotEmpty) const SizedBox(height: 8.0),
const SizedBox(height: 8),
if (practiceActivity.activityType ==
ActivityTypeEnum.wordFocusListening)
WordAudioButton(
text: practiceActivity.multipleChoiceContent!.answers.first,
uniqueID: "audio-activity-${widget.event.eventId}",
langCode: widget
.overlayController.pangeaMessageEvent!.messageDisplayLangCode,
),
if (practiceActivity.activityType ==
ActivityTypeEnum.hiddenWordListening)
MessageAudioCard(
messageEvent:
widget.practiceCardController.widget.pangeaMessageEvent,
overlayController: widget.overlayController,
onError: widget.onError,
),
ChoicesArray(
isLoading: false,
originalSpan: "placeholder",
onPressed: updateChoice,
selectedChoiceIndex: selectedChoiceIndex,
choices: choices(context),
isActive: true,
id: currentRecordModel?.hashCode.toString(),
enableAudio: practiceActivity.activityType.includeTTSOnClick,
langCode:
MatrixState.pangeaController.languageController.activeL2Code(),
getDisplayCopy: _getDisplayCopy,
enableMultiSelect:
widget.currentActivity.activityType == ActivityTypeEnum.emoji,
),
],
);
return ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: AppConfig.toolbarMinWidth,
maxHeight: AppConfig.toolbarMaxHeight,
),
child: Padding(
padding: const EdgeInsets.all(8),
child: content,
),
);
}
}