From 9790d2e56d87e22fd6dde7e19a7629dcbf8c7473 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:13:39 -0400 Subject: [PATCH] feat: allow users to give token feedback in word card --- lib/l10n/intl_en.arb | 3 + lib/pangea/common/network/urls.dart | 2 + lib/pangea/lemmas/lemma_info_request.dart | 6 +- .../phonetic_transcription_repo.dart | 6 +- .../token_info_feedback_button.dart | 73 +++++ .../token_info_feedback_dialog.dart | 279 ++++++++++++++++++ .../token_info_feedback_repo.dart | 40 +++ .../token_info_feedback_request.dart | 87 ++++++ .../token_info_feedback_response.dart | 70 +++++ .../widgets/reading_assistance_content.dart | 26 ++ .../widgets/word_zoom/word_zoom_widget.dart | 68 +++-- 11 files changed, 630 insertions(+), 30 deletions(-) create mode 100644 lib/pangea/token_info_feedback/token_info_feedback_button.dart create mode 100644 lib/pangea/token_info_feedback/token_info_feedback_dialog.dart create mode 100644 lib/pangea/token_info_feedback/token_info_feedback_repo.dart create mode 100644 lib/pangea/token_info_feedback/token_info_feedback_request.dart create mode 100644 lib/pangea/token_info_feedback/token_info_feedback_response.dart diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e9bcb7ec3..f7af97299 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5304,6 +5304,9 @@ "activityAnalyticsListBody": "These are your completed activities! After finishing activities, you can view them here.", "languageMismatchTitle": "Language mismatch", "languageMismatchDesc": "Your target language doesn't match the language of this activity. Update your target language?", + "reportWordIssueTooltip": "Report word information issue", + "tokenInfoFeedbackDialogTitle": "Word Information Feedback", + "tokenInfoFeedbackDialogDesc": "AI makes mistakes. Please describe any issues you found with the information above.", "noPublicCoursesFound": "No public courses found. Would you like to create one?", "noCourseTemplatesFound": "We couldn't find any courses for your target language. You can chat with Pangea Bot in the meantime, and check back later for more courses." } diff --git a/lib/pangea/common/network/urls.dart b/lib/pangea/common/network/urls.dart index 43bb0e147..c9a3c90d4 100644 --- a/lib/pangea/common/network/urls.dart +++ b/lib/pangea/common/network/urls.dart @@ -69,6 +69,8 @@ class PApiUrls { static String activityFeedback = "${PApiUrls._choreoEndpoint}/activity_plan/feedback"; + static String tokenFeedback = "${PApiUrls._choreoEndpoint}/token/feedback"; + static String morphFeaturesAndTags = "${PApiUrls._choreoEndpoint}/morphs"; static String constructSummary = "${PApiUrls._choreoEndpoint}/construct_summary"; diff --git a/lib/pangea/lemmas/lemma_info_request.dart b/lib/pangea/lemmas/lemma_info_request.dart index b82ee077b..f934a02af 100644 --- a/lib/pangea/lemmas/lemma_info_request.dart +++ b/lib/pangea/lemmas/lemma_info_request.dart @@ -9,14 +9,14 @@ class LemmaInfoRequest { final String lemmaLang; final String userL1; - ContentFeedback? feedback; + List> feedback; LemmaInfoRequest({ required String partOfSpeech, required String lemmaLang, required this.userL1, required this.lemma, - this.feedback, + this.feedback = const [], }) : partOfSpeech = partOfSpeech.toLowerCase(), lemmaLang = lemmaLang.toLowerCase(); @@ -26,7 +26,7 @@ class LemmaInfoRequest { 'part_of_speech': partOfSpeech, 'lemma_lang': lemmaLang, 'user_l1': userL1, - 'feedback': feedback?.toJson(), + 'feedback': feedback.map((e) => e.toJson()).toList(), }; } diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart index 124389eed..7fe6885fe 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart @@ -18,12 +18,12 @@ class PhoneticTranscriptionRepo { static final GetStorage _storage = GetStorage('phonetic_transcription_storage'); - static void set( + static Future set( PhoneticTranscriptionRequest request, PhoneticTranscriptionResponse response, - ) { + ) async { response.expireAt ??= DateTime.now().add(const Duration(days: 100)); - _storage.write(request.storageKey, response.toJson()); + await _storage.write(request.storageKey, response.toJson()); } static Future _fetch( diff --git a/lib/pangea/token_info_feedback/token_info_feedback_button.dart b/lib/pangea/token_info_feedback/token_info_feedback_button.dart new file mode 100644 index 000000000..e0fc5a736 --- /dev/null +++ b/lib/pangea/token_info_feedback/token_info_feedback_button.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart'; +import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_dialog.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart'; + +class TokenInfoFeedbackButton extends StatelessWidget { + final TokenInfoFeedbackRequestData requestData; + final String langCode; + final PangeaMessageEvent event; + final VoidCallback onUpdate; + + const TokenInfoFeedbackButton({ + super.key, + required this.requestData, + required this.langCode, + required this.event, + required this.onUpdate, + }); + + Future _submitFeedback(BuildContext context) async { + final resp = await showDialog( + context: context, + builder: (context) => TokenInfoFeedbackDialog( + requestData: requestData, + langCode: langCode, + event: event, + onUpdate: onUpdate, + ), + ); + + if (resp != null && resp is String) { + _showSuccessSnackBar(resp, context); + } + } + + void _showSuccessSnackBar(String message, BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const BotFace( + width: 30, + expression: BotExpression.idle, + ), + const SizedBox(width: 12), + Expanded( + child: Text(message), + ), + ], + ), + duration: const Duration(seconds: 30), + action: SnackBarAction( + label: L10n.of(context).close, + onPressed: () { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + }, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return IconButton( + icon: const Icon(Icons.flag_outlined), + onPressed: () => _submitFeedback(context), + tooltip: L10n.of(context).reportWordIssueTooltip, + ); + } +} diff --git a/lib/pangea/token_info_feedback/token_info_feedback_dialog.dart b/lib/pangea/token_info_feedback/token_info_feedback_dialog.dart new file mode 100644 index 000000000..65d8df8e5 --- /dev/null +++ b/lib/pangea/token_info_feedback/token_info_feedback_dialog.dart @@ -0,0 +1,279 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import 'package:fluffychat/l10n/l10n.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/events/models/tokens_event_content_model.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; +import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; +import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; +import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_repo.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_response.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class TokenInfoFeedbackDialog extends StatefulWidget { + final TokenInfoFeedbackRequestData requestData; + final String langCode; + final PangeaMessageEvent event; + final VoidCallback onUpdate; + + const TokenInfoFeedbackDialog({ + super.key, + required this.requestData, + required this.langCode, + required this.event, + required this.onUpdate, + }); + + @override + State createState() => + _TokenInfoFeedbackDialogState(); +} + +class _TokenInfoFeedbackDialogState extends State { + final TextEditingController _feedbackController = TextEditingController(); + + @override + void dispose() { + _feedbackController.dispose(); + super.dispose(); + } + + Future _submitFeedback() async { + final request = TokenInfoFeedbackRequest( + userFeedback: _feedbackController.text, + data: widget.requestData, + ); + + final TokenInfoFeedbackResponse response = + await TokenInfoFeedbackRepo.submitFeedback(request); + + final originalToken = + widget.requestData.tokens[widget.requestData.selectedToken]; + final token = response.updatedToken ?? originalToken; + + // first, update lemma info if changed + if (response.updatedLemmaInfo != null) { + await _updateLemmaInfo( + token, + response.updatedLemmaInfo!, + ); + } + + // second, update the phonetic info if changed + if (response.updatedPhonetics != null) { + await _updatePhoneticTranscription( + response.updatedPhonetics!, + ); + } + + final originalSent = widget.event.originalSent; + + // if no other changes, just return the message + final hasTokenUpdate = response.updatedToken != null; + final hasLangUpdate = originalSent != null && + response.updatedLanguage != null && + response.updatedLanguage != originalSent.langCode; + + if (!hasTokenUpdate && !hasLangUpdate) { + widget.onUpdate(); + return response.userFriendlyMessage; + } + + // update the tokens to be sent in the message edit + final tokens = List.from(widget.requestData.tokens); + if (hasTokenUpdate) { + tokens[widget.requestData.selectedToken] = response.updatedToken!; + } + + if (hasLangUpdate) { + originalSent.content.langCode = response.updatedLanguage!; + } + + await widget.event.room.pangeaSendTextEvent( + widget.requestData.fullText, + editEventId: widget.event.eventId, + originalSent: originalSent?.content, + originalWritten: widget.event.originalWritten?.content, + tokensSent: PangeaMessageTokens( + tokens: tokens, + ), + tokensWritten: widget.event.originalWritten?.tokens != null + ? PangeaMessageTokens( + tokens: widget.event.originalWritten!.tokens!, + detections: widget.event.originalWritten?.detections, + ) + : null, + choreo: originalSent?.choreo, + ); + + widget.onUpdate(); + return response.userFriendlyMessage; + } + + Future _updateLemmaInfo( + PangeaToken token, + LemmaInfoResponse response, + ) async { + final construct = token.vocabConstructID; + + final currentLemmaInfo = construct.userLemmaInfo; + final updatedLemmaInfo = UserSetLemmaInfo( + meaning: response.meaning, + emojis: response.emoji, + ); + + if (currentLemmaInfo != updatedLemmaInfo) { + await construct.setUserLemmaInfo(updatedLemmaInfo); + } + } + + Future _updatePhoneticTranscription( + PhoneticTranscriptionResponse response, + ) async { + final req = PhoneticTranscriptionRequest( + arc: LanguageArc( + l1: PLanguageStore.byLangCode(widget.requestData.wordCardL1) ?? + MatrixState.pangeaController.languageController.userL1!, + l2: PLanguageStore.byLangCode(widget.langCode) ?? + MatrixState.pangeaController.languageController.userL2!, + ), + content: response.content, + ); + await PhoneticTranscriptionRepo.set(req, response); + } + + @override + Widget build(BuildContext context) { + final selectedToken = + widget.requestData.tokens[widget.requestData.selectedToken]; + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2.5, sigmaY: 2.5), + child: Dialog( + backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + child: SizedBox( + width: 325.0, + child: Column( + spacing: 20.0, + mainAxisSize: MainAxisSize.min, + children: [ + // Header with title and close button + Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + Expanded( + child: Text( + L10n.of(context).tokenInfoFeedbackDialogTitle, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox( + width: 40.0, + height: 40.0, + child: Center( + child: Icon(Icons.flag_outlined), + ), + ), + ], + ), + ), + // Content + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + ), + child: Column( + spacing: 20.0, + mainAxisSize: MainAxisSize.min, + children: [ + // Placeholder for word card + Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.outline, + ), + borderRadius: BorderRadius.circular(8.0), + ), + child: Center( + child: WordZoomWidget( + token: selectedToken.text, + construct: selectedToken.vocabConstructID, + langCode: widget.langCode, + ), + ), + ), + // Description text + Text( + L10n.of(context).tokenInfoFeedbackDialogDesc, + textAlign: TextAlign.center, + ), + // Feedback text field + TextField( + controller: _feedbackController, + decoration: InputDecoration( + hintText: L10n.of(context).feedbackHint, + ), + keyboardType: TextInputType.multiline, + minLines: 1, + maxLines: 5, + ), + // Submit button + ValueListenableBuilder( + valueListenable: _feedbackController, + builder: (context, value, _) { + final isNotEmpty = value.text.isNotEmpty; + return ElevatedButton( + onPressed: isNotEmpty + ? () async { + final resp = await showFutureLoadingDialog( + context: context, + future: () => _submitFeedback(), + ); + + if (!resp.isError) { + Navigator.of(context).pop(resp.result!); + } + } + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(L10n.of(context).feedbackButton), + ], + ), + ); + }, + ), + const SizedBox.shrink(), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pangea/token_info_feedback/token_info_feedback_repo.dart b/lib/pangea/token_info_feedback/token_info_feedback_repo.dart new file mode 100644 index 000000000..f062685d4 --- /dev/null +++ b/lib/pangea/token_info_feedback/token_info_feedback_repo.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; + +import 'package:fluffychat/pangea/common/config/environment.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; +import 'package:fluffychat/pangea/common/network/urls.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_response.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class TokenInfoFeedbackRepo { + /// Submit token info feedback for processing + /// + /// This method sends user feedback about token information to the server + /// for evaluation and potential updates. The feedback is processed + /// and may result in updated token data, lemma information, or phonetics. + static Future submitFeedback( + TokenInfoFeedbackRequest request, + ) async { + final Requests req = Requests( + choreoApiKey: Environment.choreoApiKey, + accessToken: MatrixState.pangeaController.userController.accessToken, + ); + + final Response res = await req.post( + url: PApiUrls.tokenFeedback, + body: request.toJson(), + ); + + if (res.statusCode != 200) { + throw Exception( + 'Failed to submit token info feedback: ${res.statusCode} ${res.body}', + ); + } + + final decodedBody = jsonDecode(utf8.decode(res.bodyBytes)); + return TokenInfoFeedbackResponse.fromJson(decodedBody); + } +} diff --git a/lib/pangea/token_info_feedback/token_info_feedback_request.dart b/lib/pangea/token_info_feedback/token_info_feedback_request.dart new file mode 100644 index 000000000..9caf6c4ea --- /dev/null +++ b/lib/pangea/token_info_feedback/token_info_feedback_request.dart @@ -0,0 +1,87 @@ +import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; + +class TokenInfoFeedbackRequestData { + final String userId; + final String roomId; + final String fullText; + final String detectedLanguage; + final List tokens; + final int selectedToken; + final LemmaInfoResponse? lemmaInfo; + final String? phonetics; + final String wordCardL1; + + TokenInfoFeedbackRequestData({ + required this.userId, + required this.roomId, + required this.fullText, + required this.detectedLanguage, + required this.tokens, + required this.selectedToken, + this.lemmaInfo, + this.phonetics, + required this.wordCardL1, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TokenInfoFeedbackRequestData && + runtimeType == other.runtimeType && + userId == other.userId && + roomId == other.roomId && + fullText == other.fullText && + detectedLanguage == other.detectedLanguage && + selectedToken == other.selectedToken && + lemmaInfo == other.lemmaInfo && + phonetics == other.phonetics && + wordCardL1 == other.wordCardL1; + + @override + int get hashCode => + userId.hashCode ^ + roomId.hashCode ^ + fullText.hashCode ^ + detectedLanguage.hashCode ^ + selectedToken.hashCode ^ + lemmaInfo.hashCode ^ + phonetics.hashCode ^ + wordCardL1.hashCode; +} + +class TokenInfoFeedbackRequest { + final TokenInfoFeedbackRequestData data; + final String userFeedback; + + TokenInfoFeedbackRequest({ + required this.data, + required this.userFeedback, + }); + + Map toJson() { + return { + 'user_id': data.userId, + 'room_id': data.roomId, + 'full_text': data.fullText, + 'detected_language': data.detectedLanguage, + 'tokens': data.tokens.map((token) => token.toJson()).toList(), + 'selected_token': data.selectedToken, + 'lemma_info': data.lemmaInfo?.toJson(), + 'phonetics': data.phonetics, + 'user_feedback': userFeedback, + 'word_card_l1': data.wordCardL1, + }; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TokenInfoFeedbackRequest && + runtimeType == other.runtimeType && + data == other.data && + userFeedback == other.userFeedback; + + @override + int get hashCode => data.hashCode ^ userFeedback.hashCode; +} diff --git a/lib/pangea/token_info_feedback/token_info_feedback_response.dart b/lib/pangea/token_info_feedback/token_info_feedback_response.dart new file mode 100644 index 000000000..0b484289d --- /dev/null +++ b/lib/pangea/token_info_feedback/token_info_feedback_response.dart @@ -0,0 +1,70 @@ +import 'package:fluffychat/pangea/events/models/content_feedback.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart'; + +class TokenInfoFeedbackResponse implements JsonSerializable { + final String userFriendlyMessage; + final PangeaToken? updatedToken; + final LemmaInfoResponse? updatedLemmaInfo; + final PhoneticTranscriptionResponse? updatedPhonetics; + final String? updatedLanguage; + + TokenInfoFeedbackResponse({ + required this.userFriendlyMessage, + this.updatedToken, + this.updatedLemmaInfo, + this.updatedPhonetics, + this.updatedLanguage, + }); + + factory TokenInfoFeedbackResponse.fromJson(Map json) { + return TokenInfoFeedbackResponse( + userFriendlyMessage: json['user_friendly_message'] as String, + updatedToken: json['updated_token'] != null + ? PangeaToken.fromJson(json['updated_token'] as Map) + : null, + updatedLemmaInfo: json['updated_lemma_info'] != null + ? LemmaInfoResponse.fromJson( + json['updated_lemma_info'] as Map, + ) + : null, + updatedPhonetics: json['updated_phonetics'] != null + ? PhoneticTranscriptionResponse.fromJson( + json['updated_phonetics'] as Map, + ) + : null, + updatedLanguage: json['updated_language'] as String?, + ); + } + + @override + Map toJson() { + return { + 'user_friendly_message': userFriendlyMessage, + 'updated_token': updatedToken?.toJson(), + 'updated_lemma_info': updatedLemmaInfo?.toJson(), + 'updated_phonetics': updatedPhonetics?.toJson(), + 'updated_language': updatedLanguage, + }; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TokenInfoFeedbackResponse && + runtimeType == other.runtimeType && + userFriendlyMessage == other.userFriendlyMessage && + updatedToken == other.updatedToken && + updatedLemmaInfo == other.updatedLemmaInfo && + updatedPhonetics == other.updatedPhonetics && + updatedLanguage == other.updatedLanguage; + + @override + int get hashCode => + userFriendlyMessage.hashCode ^ + updatedToken.hashCode ^ + updatedLemmaInfo.hashCode ^ + updatedPhonetics.hashCode ^ + updatedLanguage.hashCode; +} diff --git a/lib/pangea/toolbar/widgets/reading_assistance_content.dart b/lib/pangea/toolbar/widgets/reading_assistance_content.dart index 004824b2b..539ee968b 100644 --- a/lib/pangea/toolbar/widgets/reading_assistance_content.dart +++ b/lib/pangea/toolbar/widgets/reading_assistance_content.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_unsubscribed_card.dart'; @@ -71,6 +72,16 @@ class ReadingAssistanceContentState extends State { // return MessageModeLockedCard(controller: widget.overlayController); // } + final tokens = + widget.overlayController.pangeaMessageEvent.originalSent?.tokens; + final selectedToken = widget.overlayController.selectedToken; + final selectedTokenIndex = selectedToken != null + ? tokens?.indexWhere( + (t) => t.text == selectedToken.text, + ) ?? + -1 + : -1; + switch (widget.overlayController.toolbarMode) { case MessageMode.messageTranslation: // return MessageTranslationCard( @@ -125,6 +136,21 @@ class ReadingAssistanceContentState extends State { .overlayController.pangeaMessageEvent.messageDisplayLangCode, onDismissNewWordOverlay: () => widget.overlayController.setState(() {}), + requestData: selectedTokenIndex >= 0 + ? TokenInfoFeedbackRequestData( + userId: Matrix.of(context).client.userID!, + roomId: widget.overlayController.event.room.id, + fullText: widget + .overlayController.pangeaMessageEvent.messageDisplayText, + detectedLanguage: widget.overlayController.pangeaMessageEvent + .messageDisplayLangCode, + tokens: tokens ?? [], + selectedToken: selectedTokenIndex, + wordCardL1: MatrixState.pangeaController.languageController + .activeL1Code()!, + ) + : null, + pangeaMessageEvent: widget.overlayController.pangeaMessageEvent, ); } } diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index ef00d7edf..5ba2af874 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; +import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; -import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart'; import 'package:fluffychat/pangea/lemmas/lemma_reaction_picker.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_button.dart'; +import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/new_word_overlay.dart'; @@ -24,21 +24,26 @@ class WordZoomWidget extends StatelessWidget { final ConstructIdentifier construct; final String langCode; - final VoidCallback onClose; + final VoidCallback? onClose; final bool wordIsNew; final VoidCallback? onDismissNewWordOverlay; final Event? event; + final TokenInfoFeedbackRequestData? requestData; + final PangeaMessageEvent? pangeaMessageEvent; + const WordZoomWidget({ super.key, required this.token, required this.construct, required this.langCode, - required this.onClose, + this.onClose, this.wordIsNew = false, this.onDismissNewWordOverlay, this.event, + this.requestData, + this.pangeaMessageEvent, }); String get transformTargetId => "word-zoom-card-${token.uniqueKey}"; @@ -69,20 +74,25 @@ class WordZoomWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox( - width: 24.0, - height: 24.0, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onClose, - child: const Icon( - Icons.close, - size: 16.0, + onClose != null + ? SizedBox( + width: 24.0, + height: 24.0, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: onClose, + child: const Icon( + Icons.close, + size: 16.0, + ), + ), + ), + ) + : const SizedBox( + width: 24.0, + height: 24.0, ), - ), - ), - ), Flexible( child: Text( token.content, @@ -98,12 +108,22 @@ class WordZoomWidget extends StatelessWidget { ), ), ), - ConstructXpWidget( - id: construct, - onTap: () => context.go( - "/rooms/analytics/${ConstructTypeEnum.vocab.string}/${construct.string}", - ), - ), + requestData != null && pangeaMessageEvent != null + ? TokenInfoFeedbackButton( + requestData: requestData!, + langCode: langCode, + event: pangeaMessageEvent!, + onUpdate: () { + // close the zoom when updating + if (onClose != null) { + onClose!(); + } + }, + ) + : const SizedBox( + width: 24.0, + height: 24.0, + ), ], ), LemmaMeaningBuilder(