word focus turned off and tts on Choice click (#1118)

* word focus turned off and tts on Choice click

* play audio on word selection
This commit is contained in:
wcjord 2024-11-29 17:47:00 -05:00 committed by GitHub
parent 25ab5e54bc
commit 78cb3afe0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 198 additions and 94 deletions

View file

@ -140,6 +140,7 @@ class ChatController extends State<ChatPageWithRoom>
Timer? typingTimeout; Timer? typingTimeout;
bool currentlyTyping = false; bool currentlyTyping = false;
// #Pangea // #Pangea
// bool dragging = false; // bool dragging = false;
// void onDragEntered(_) => setState(() => dragging = true); // void onDragEntered(_) => setState(() => dragging = true);

View file

@ -17,6 +17,7 @@ import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/utils/overlay.dart';
import 'package:fluffychat/pangea/widgets/chat/tts_controller.dart';
import 'package:fluffychat/pangea/widgets/igc/paywall_card.dart'; import 'package:fluffychat/pangea/widgets/igc/paywall_card.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -40,6 +41,7 @@ class Choreographer {
late IgcController igc; late IgcController igc;
late AlternativeTranslator altTranslator; late AlternativeTranslator altTranslator;
late ErrorService errorService; late ErrorService errorService;
final tts = TtsController();
bool isFetching = false; bool isFetching = false;
int _timesClicked = 0; int _timesClicked = 0;
@ -66,6 +68,8 @@ class Choreographer {
.subscriptionController.trialActivationStream.stream .subscriptionController.trialActivationStream.stream
.listen((_) => _onChangeListener); .listen((_) => _onChangeListener);
tts.setupTTS();
clear(); clear();
} }
@ -454,6 +458,7 @@ class Choreographer {
choreoRecord = ChoreoRecord.newRecord; choreoRecord = ChoreoRecord.newRecord;
itController.clear(); itController.clear();
igc.clear(); igc.clear();
//@ggurdin - why is this commented out?
// errorService.clear(); // errorService.clear();
_resetDebounceTimer(); _resetDebounceTimer();
} }
@ -477,6 +482,7 @@ class Choreographer {
dispose() { dispose() {
_textController.dispose(); _textController.dispose();
trialStream?.cancel(); trialStream?.cancel();
tts.dispose();
} }
LanguageModel? get l2Lang { LanguageModel? get l2Lang {

View file

@ -2,6 +2,7 @@ import 'dart:developer';
import 'dart:math'; import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/widgets/chat/tts_controller.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -20,6 +21,10 @@ class ChoicesArray extends StatefulWidget {
final String originalSpan; final String originalSpan;
final String Function(int) uniqueKeyForLayerLink; final String Function(int) uniqueKeyForLayerLink;
/// If null then should not be used
/// We don't want tts in the case of L1 options
final TtsController? tts;
/// Used to unqiuely identify the keys for choices, in cases where multiple /// Used to unqiuely identify the keys for choices, in cases where multiple
/// choices could have identical text, like in back-to-back practice activities /// choices could have identical text, like in back-to-back practice activities
final String? id; final String? id;
@ -35,6 +40,7 @@ class ChoicesArray extends StatefulWidget {
required this.originalSpan, required this.originalSpan,
required this.uniqueKeyForLayerLink, required this.uniqueKeyForLayerLink,
required this.selectedChoiceIndex, required this.selectedChoiceIndex,
required this.tts,
this.isActive = true, this.isActive = true,
this.onLongPress, this.onLongPress,
this.id, this.id,
@ -73,7 +79,11 @@ class ChoicesArrayState extends State<ChoicesArray> {
theme: theme, theme: theme,
onLongPress: widget.isActive ? widget.onLongPress : null, onLongPress: widget.isActive ? widget.onLongPress : null,
onPressed: widget.isActive onPressed: widget.isActive
? widget.onPressed ? (String value, int index) {
widget.onPressed(value, index);
// TODO - what to pass here as eventID?
widget.tts?.tryToSpeak(value, context, null);
}
: (String value, int index) { : (String value, int index) {
debugger(when: kDebugMode); debugger(when: kDebugMode);
}, },

View file

@ -418,6 +418,7 @@ class ITChoices extends StatelessWidget {
onLongPress: (value, index) => showCard(context, index), onLongPress: (value, index) => showCard(context, index),
uniqueKeyForLayerLink: (int index) => "itChoices$index", uniqueKeyForLayerLink: (int index) => "itChoices$index",
selectedChoiceIndex: null, selectedChoiceIndex: null,
tts: controller.choreographer.tts,
); );
} catch (e) { } catch (e) {
debugger(when: kDebugMode); debugger(when: kDebugMode);

View file

@ -67,7 +67,7 @@ class AlternativeTranslations extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChoicesArray( return ChoicesArray(
originalSpan: controller.choreographer.itController.sourceText ?? "dummy", originalSpan: controller.sourceText ?? "dummy",
isLoading: isLoading:
controller.choreographer.altTranslator.loadingAlternativeTranslations, controller.choreographer.altTranslator.loadingAlternativeTranslations,
// choices: controller.choreographer.altTranslator.similarityResponse.scores // choices: controller.choreographer.altTranslator.similarityResponse.scores
@ -82,6 +82,7 @@ class AlternativeTranslations extends StatelessWidget {
}, },
uniqueKeyForLayerLink: (int index) => "altTranslation$index", uniqueKeyForLayerLink: (int index) => "altTranslation$index",
selectedChoiceIndex: null, selectedChoiceIndex: null,
tts: controller.choreographer.tts,
); );
} }
} }

View file

@ -26,6 +26,16 @@ extension ActivityTypeExtension on ActivityTypeEnum {
} }
} }
bool get includeTTSOnClick {
switch (this) {
case ActivityTypeEnum.wordMeaning:
return false;
case ActivityTypeEnum.wordFocusListening:
case ActivityTypeEnum.hiddenWordListening:
return true;
}
}
ActivityTypeEnum fromString(String value) { ActivityTypeEnum fromString(String value) {
final split = value.split('.').last; final split = value.split('.').last;
switch (split) { switch (split) {

View file

@ -197,6 +197,7 @@ class PangeaToken {
case ActivityTypeEnum.wordMeaning: case ActivityTypeEnum.wordMeaning:
return canBeDefined; return canBeDefined;
case ActivityTypeEnum.wordFocusListening: case ActivityTypeEnum.wordFocusListening:
return false;
case ActivityTypeEnum.hiddenWordListening: case ActivityTypeEnum.hiddenWordListening:
return canBeHeard; return canBeHeard;
} }

View file

@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/enum/activity_display_instructions_enum.dart'; import 'package:fluffychat/pangea/enum/activity_display_instructions_enum.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -200,13 +201,20 @@ class PracticeActivityRequest {
} }
class PracticeActivityModel { class PracticeActivityModel {
// deprecated in favor of targetTokens
final List<ConstructIdentifier> tgtConstructs; final List<ConstructIdentifier> tgtConstructs;
// being added after creation from request info
// TODO - replace tgtConstructs with targetTokens in server return
List<PangeaToken>? targetTokens;
final String langCode; final String langCode;
final ActivityTypeEnum activityType; final ActivityTypeEnum activityType;
final ActivityContent content; final ActivityContent content;
PracticeActivityModel({ PracticeActivityModel({
required this.tgtConstructs, required this.tgtConstructs,
required this.targetTokens,
required this.langCode, required this.langCode,
required this.activityType, required this.activityType,
required this.content, required this.content,
@ -244,6 +252,11 @@ class PracticeActivityModel {
activityType: activityType:
ActivityTypeEnum.wordMeaning.fromString(json['activity_type']), ActivityTypeEnum.wordMeaning.fromString(json['activity_type']),
content: ActivityContent.fromJson(contentMap), content: ActivityContent.fromJson(contentMap),
targetTokens: json['target_tokens'] is List
? (json['target_tokens'] as List)
.map((e) => PangeaToken.fromJson(e as Map<String, dynamic>))
.toList()
: null,
); );
} }
@ -256,6 +269,7 @@ class PracticeActivityModel {
'lang_code': langCode, 'lang_code': langCode,
'activity_type': activityType.string, 'activity_type': activityType.string,
'content': content.toJson(), 'content': content.toJson(),
'target_tokens': targetTokens?.map((e) => e.toJson()).toList(),
}; };
} }

View file

@ -59,11 +59,11 @@ class MessageAudioCardState extends State<MessageAudioCard> {
@override @override
void didUpdateWidget(covariant oldWidget) { void didUpdateWidget(covariant oldWidget) {
if (oldWidget.selection != widget.selection && widget.selection != null) { // if (oldWidget.selection != widget.selection && widget.selection != null) {
debugPrint('selection changed'); // debugPrint('selection changed');
setSectionStartAndEndFromSelection(); // setSectionStartAndEndFromSelection();
playSelectionAudio(); // playSelectionAudio();
} // }
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }

View file

@ -18,7 +18,6 @@ import 'package:fluffychat/pangea/widgets/chat/message_toolbar_buttons.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart';
import 'package:fluffychat/pangea/widgets/chat/tts_controller.dart';
import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -67,7 +66,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
PangeaMessageEvent? get pangeaMessageEvent => widget._pangeaMessageEvent; PangeaMessageEvent? get pangeaMessageEvent => widget._pangeaMessageEvent;
final TtsController tts = TtsController();
bool _isPlayingAudio = false; bool _isPlayingAudio = false;
bool get showToolbarButtons => bool get showToolbarButtons =>
@ -139,8 +137,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
); );
}, },
).listen((_) => setState(() {})); ).listen((_) => setState(() {}));
tts.setupTTS();
} }
MessageAnalyticsEntry? get messageAnalyticsEntry => MessageAnalyticsEntry? get messageAnalyticsEntry =>
@ -297,6 +293,14 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
} }
} }
if (_selectedSpan != null) {
widget.chatController.choreographer.tts.tryToSpeak(
token.text.content,
context,
pangeaMessageEvent!.eventId,
);
}
setState(() {}); setState(() {});
} }
@ -450,7 +454,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
void dispose() { void dispose() {
_animationController.dispose(); _animationController.dispose();
_reactionSubscription?.cancel(); _reactionSubscription?.cancel();
tts.dispose();
super.dispose(); super.dispose();
} }
@ -562,7 +566,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
MessageToolbar( MessageToolbar(
pangeaMessageEvent: pangeaMessageEvent!, pangeaMessageEvent: pangeaMessageEvent!,
overLayController: this, overLayController: this,
ttsController: tts,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
SizedBox( SizedBox(

View file

@ -27,15 +27,16 @@ const double minCardHeight = 70;
class MessageToolbar extends StatelessWidget { class MessageToolbar extends StatelessWidget {
final PangeaMessageEvent pangeaMessageEvent; final PangeaMessageEvent pangeaMessageEvent;
final MessageOverlayController overLayController; final MessageOverlayController overLayController;
final TtsController ttsController;
const MessageToolbar({ const MessageToolbar({
super.key, super.key,
required this.pangeaMessageEvent, required this.pangeaMessageEvent,
required this.overLayController, required this.overLayController,
required this.ttsController,
}); });
TtsController get ttsController =>
overLayController.widget.chatController.choreographer.tts;
Widget toolbarContent(BuildContext context) { Widget toolbarContent(BuildContext context) {
final bool subscribed = final bool subscribed =
MatrixState.pangeaController.subscriptionController.isSubscribed; MatrixState.pangeaController.subscriptionController.isSubscribed;
@ -135,7 +136,6 @@ class MessageToolbar extends StatelessWidget {
return PracticeActivityCard( return PracticeActivityCard(
pangeaMessageEvent: pangeaMessageEvent, pangeaMessageEvent: pangeaMessageEvent,
overlayController: overLayController, overlayController: overLayController,
ttsController: ttsController,
); );
default: default:
debugger(when: kDebugMode); debugger(when: kDebugMode);

View file

@ -145,7 +145,8 @@ class TtsController {
Future<void> tryToSpeak( Future<void> tryToSpeak(
String text, String text,
BuildContext context, BuildContext context,
String eventID, // TODO - make non-nullable again
String? eventID,
) async { ) async {
if (_isLanguageFullySupported) { if (_isLanguageFullySupported) {
await _speak(text); await _speak(text);
@ -157,7 +158,9 @@ class TtsController {
'availableLangCodes': _availableLangCodes, 'availableLangCodes': _availableLangCodes,
}, },
); );
await _showMissingVoicePopup(context, eventID); if (eventID != null) {
await _showMissingVoicePopup(context, eventID);
}
} }
} }

View file

@ -280,6 +280,7 @@ class WordMatchContent extends StatelessWidget {
uniqueKeyForLayerLink: (int index) => uniqueKeyForLayerLink: (int index) =>
"wordMatch$index", "wordMatch$index",
selectedChoiceIndex: controller.selectedChoiceIndex, selectedChoiceIndex: controller.selectedChoiceIndex,
tts: controller.widget.scm.choreographer.tts,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
PromptAndFeedback(controller: controller), PromptAndFeedback(controller: controller),

View file

@ -5,6 +5,7 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart'; import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart';
import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart';
@ -21,7 +22,6 @@ import 'package:matrix/matrix.dart';
class MultipleChoiceActivity extends StatefulWidget { class MultipleChoiceActivity extends StatefulWidget {
final PracticeActivityCardState practiceCardController; final PracticeActivityCardState practiceCardController;
final PracticeActivityModel currentActivity; final PracticeActivityModel currentActivity;
final TtsController tts;
final Event event; final Event event;
final VoidCallback? onError; final VoidCallback? onError;
@ -29,7 +29,6 @@ class MultipleChoiceActivity extends StatefulWidget {
super.key, super.key,
required this.practiceCardController, required this.practiceCardController,
required this.currentActivity, required this.currentActivity,
required this.tts,
required this.event, required this.event,
this.onError, this.onError,
}); });
@ -46,6 +45,8 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
@override @override
void initState() { void initState() {
speakTargetTokens();
super.initState(); super.initState();
} }
@ -55,18 +56,59 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
if (widget.practiceCardController.currentCompletionRecord?.responses if (widget.practiceCardController.currentCompletionRecord?.responses
.isEmpty ?? .isEmpty ??
false) { false) {
speakTargetTokens();
setState(() => selectedChoiceIndex = null); setState(() => selectedChoiceIndex = null);
} }
} }
void speakTargetTokens() {
if (widget.practiceCardController.currentActivity?.targetTokens != null) {
widget.practiceCardController.tts.tryToSpeak(
PangeaToken.reconstructText(
widget.practiceCardController.currentActivity!.targetTokens!,
),
context,
null,
);
}
}
TtsController get tts => widget.practiceCardController.tts;
void updateChoice(String value, int index) { void updateChoice(String value, int index) {
final bool isCorrect =
widget.currentActivity.content.isCorrect(value, index);
// If the activity is not set to include TTS on click, and the choice is correct, speak the target tokens
// We have to check if tokens
if (!widget.currentActivity.activityType.includeTTSOnClick &&
isCorrect &&
mounted) {
// should be set by now but just in case we make a mistake
if (widget.practiceCardController.currentActivity?.targetTokens == null) {
debugger(when: kDebugMode);
ErrorHandler.logError(
e: "Missing target tokens in multiple choice activity",
data: {
"currentActivity": widget.practiceCardController.currentActivity,
},
);
} else {
tts.tryToSpeak(
PangeaToken.reconstructText(
widget.practiceCardController.currentActivity!.targetTokens!,
),
context,
null,
);
}
}
if (currentRecordModel?.hasTextResponse(value) ?? false) { if (currentRecordModel?.hasTextResponse(value) ?? false) {
return; return;
} }
final bool isCorrect =
widget.currentActivity.content.isCorrect(value, index);
currentRecordModel?.addResponse( currentRecordModel?.addResponse(
text: value, text: value,
score: isCorrect ? 1 : 0, score: isCorrect ? 1 : 0,
@ -136,7 +178,7 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
ActivityTypeEnum.wordFocusListening) ActivityTypeEnum.wordFocusListening)
WordAudioButton( WordAudioButton(
text: practiceActivity.content.answer, text: practiceActivity.content.answer,
ttsController: widget.tts, ttsController: tts,
eventID: widget.event.eventId, eventID: widget.event.eventId,
), ),
if (practiceActivity.activityType == if (practiceActivity.activityType ==
@ -146,7 +188,7 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
widget.practiceCardController.widget.pangeaMessageEvent, widget.practiceCardController.widget.pangeaMessageEvent,
overlayController: overlayController:
widget.practiceCardController.widget.overlayController, widget.practiceCardController.widget.overlayController,
tts: widget.practiceCardController.widget.overlayController.tts, tts: tts,
setIsPlayingAudio: widget.practiceCardController.widget setIsPlayingAudio: widget.practiceCardController.widget
.overlayController.setIsPlayingAudio, .overlayController.setIsPlayingAudio,
onError: widget.onError, onError: widget.onError,
@ -170,6 +212,7 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
.toList(), .toList(),
isActive: true, isActive: true,
id: currentRecordModel?.hashCode.toString(), id: currentRecordModel?.hashCode.toString(),
tts: practiceActivity.activityType.includeTTSOnClick ? tts : null,
), ),
], ],
); );

View file

@ -30,13 +30,11 @@ import 'package:flutter/material.dart';
class PracticeActivityCard extends StatefulWidget { class PracticeActivityCard extends StatefulWidget {
final PangeaMessageEvent pangeaMessageEvent; final PangeaMessageEvent pangeaMessageEvent;
final MessageOverlayController overlayController; final MessageOverlayController overlayController;
final TtsController ttsController;
const PracticeActivityCard({ const PracticeActivityCard({
super.key, super.key,
required this.pangeaMessageEvent, required this.pangeaMessageEvent,
required this.overlayController, required this.overlayController,
required this.ttsController,
}); });
@override @override
@ -59,6 +57,9 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
Duration appropriateTimeForJoy = const Duration(milliseconds: 1500); Duration appropriateTimeForJoy = const Duration(milliseconds: 1500);
bool savoringTheJoy = false; bool savoringTheJoy = false;
TtsController get tts =>
widget.overlayController.widget.chatController.choreographer.tts;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -100,76 +101,86 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
Future<PracticeActivityModel?> _fetchActivity({ Future<PracticeActivityModel?> _fetchActivity({
ActivityQualityFeedback? activityFeedback, ActivityQualityFeedback? activityFeedback,
}) async { }) async {
// try { try {
debugPrint('Fetching activity'); debugPrint('Fetching activity');
_updateFetchingActivity(true); _updateFetchingActivity(true);
// target tokens can be empty if activities have been completed for each // target tokens can be empty if activities have been completed for each
// it's set on initialization and then removed when each activity is completed // it's set on initialization and then removed when each activity is completed
if (!mounted || if (!mounted ||
!pangeaController.languageController.languagesSet || !pangeaController.languageController.languagesSet ||
widget.overlayController.messageAnalyticsEntry == null) { widget.overlayController.messageAnalyticsEntry == null) {
debugger(when: kDebugMode);
_updateFetchingActivity(false);
return null;
}
final nextActivitySpecs =
widget.overlayController.messageAnalyticsEntry?.nextActivity;
// the client is going to be choosing the next activity now
// if nothing is set then it must be done with practice
if (nextActivitySpecs == null) {
debugPrint("No next activity set, exiting practice flow");
_updateFetchingActivity(false);
return null;
}
// check if we already have an activity matching the specs
final existingActivity = practiceActivities.firstWhereOrNull(
(activity) =>
nextActivitySpecs.matchesActivity(activity.practiceActivity),
);
if (existingActivity != null) {
debugPrint('found existing activity');
_updateFetchingActivity(false);
existingActivity.practiceActivity.targetTokens =
nextActivitySpecs.tokens;
return existingActivity.practiceActivity;
}
debugPrint(
"client requesting ${nextActivitySpecs.activityType.string} for: ${nextActivitySpecs.tokens.map((t) => "word: ${t.text.content} xp: ${t.xp}").join(' ')}",
);
final PracticeActivityModelResponse? activityResponse =
await pangeaController.practiceGenerationController
.getPracticeActivity(
MessageActivityRequest(
userL1: pangeaController.languageController.userL1!.langCode,
userL2: pangeaController.languageController.userL2!.langCode,
messageText: widget.pangeaMessageEvent.messageDisplayText,
messageTokens: widget.overlayController.tokens!,
activityQualityFeedback: activityFeedback,
targetTokens: nextActivitySpecs.tokens,
targetType: nextActivitySpecs.activityType,
),
widget.pangeaMessageEvent,
);
currentActivityCompleter = activityResponse?.eventCompleter;
_updateFetchingActivity(false);
if (activityResponse == null || activityResponse.activity == null) {
debugPrint('No activity found');
return null;
}
activityResponse.activity!.targetTokens = nextActivitySpecs.tokens;
return activityResponse.activity;
} catch (e, s) {
debugger(when: kDebugMode); debugger(when: kDebugMode);
_updateFetchingActivity(false); ErrorHandler.logError(
e: e,
s: s,
m: 'Failed to get new activity',
data: {
'activity': currentActivity,
'record': currentCompletionRecord,
},
);
return null; return null;
} }
final nextActivitySpecs =
widget.overlayController.messageAnalyticsEntry?.nextActivity;
// the client is going to be choosing the next activity now
// if nothing is set then it must be done with practice
if (nextActivitySpecs == null) {
debugPrint("No next activity set, exiting practice flow");
_updateFetchingActivity(false);
return null;
}
// check if we already have an activity matching the specs
final existingActivity = practiceActivities.firstWhereOrNull(
(activity) =>
nextActivitySpecs.matchesActivity(activity.practiceActivity),
);
if (existingActivity != null) {
debugPrint('found existing activity');
_updateFetchingActivity(false);
return existingActivity.practiceActivity;
}
debugPrint(
"client requesting ${nextActivitySpecs.activityType.string} for: ${nextActivitySpecs.tokens.map((t) => "word: ${t.text.content} xp: ${t.xp}").join(' ')}",
);
final PracticeActivityModelResponse? activityResponse =
await pangeaController.practiceGenerationController.getPracticeActivity(
MessageActivityRequest(
userL1: pangeaController.languageController.userL1!.langCode,
userL2: pangeaController.languageController.userL2!.langCode,
messageText: widget.pangeaMessageEvent.messageDisplayText,
messageTokens: widget.overlayController.tokens!,
activityQualityFeedback: activityFeedback,
targetTokens: nextActivitySpecs.tokens,
targetType: nextActivitySpecs.activityType,
),
widget.pangeaMessageEvent,
);
currentActivityCompleter = activityResponse?.eventCompleter;
_updateFetchingActivity(false);
return activityResponse?.activity;
// } catch (e, s) {
// debugger(when: kDebugMode);
// ErrorHandler.logError(
// e: e,
// s: s,
// m: 'Failed to get new activity',
// data: {
// 'activity': currentActivity,
// 'record': currentCompletionRecord,
// },
// );
// return null;
// }
} }
ConstructUseMetaData get metadata => ConstructUseMetaData( ConstructUseMetaData get metadata => ConstructUseMetaData(
@ -313,7 +324,6 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
return MultipleChoiceActivity( return MultipleChoiceActivity(
practiceCardController: this, practiceCardController: this,
currentActivity: currentActivity!, currentActivity: currentActivity!,
tts: widget.ttsController,
event: widget.pangeaMessageEvent.event, event: widget.pangeaMessageEvent.event,
onError: _onError, onError: _onError,
); );