From acd5bad957620ce3c6c138e4219a3cc7bfe376b3 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 4 Nov 2024 14:26:59 -0500 Subject: [PATCH 1/5] display error to user if launching tts settings fails --- lib/pangea/widgets/chat/missing_voice_button.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/pangea/widgets/chat/missing_voice_button.dart b/lib/pangea/widgets/chat/missing_voice_button.dart index 1765a9d20..5ea13164d 100644 --- a/lib/pangea/widgets/chat/missing_voice_button.dart +++ b/lib/pangea/widgets/chat/missing_voice_button.dart @@ -14,14 +14,14 @@ class MissingVoiceButton extends StatelessWidget { super.key, }); - void launchTTSSettings(BuildContext context) { + Future launchTTSSettings(BuildContext context) async { if (Platform.isAndroid) { const intent = AndroidIntent( action: 'com.android.settings.TTS_SETTINGS', package: 'com.talktolearn.chat', ); - showFutureLoadingDialog( + await showFutureLoadingDialog( context: context, future: intent.launch, ); @@ -31,6 +31,7 @@ class MissingVoiceButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( + constraints: const BoxConstraints(maxWidth: AppConfig.toolbarMinWidth), decoration: BoxDecoration( color: Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.1), @@ -51,9 +52,7 @@ class MissingVoiceButton extends StatelessWidget { textAlign: TextAlign.center, ), TextButton( - onPressed: () => launchTTSSettings, - // commenting out as suspecting this is causing an issue - // #freeze-activity + onPressed: () => launchTTSSettings(context), style: const ButtonStyle( tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), From fe41800e0528cbae0c60a82628f5378efefe4c00 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 4 Nov 2024 14:55:40 -0500 Subject: [PATCH 2/5] Exclude the word_focus_listening activity from the list of client compatible activities --- .../message_activity_request.dart | 29 +++++++++++++++++-- .../practice_activity_card.dart | 5 ++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/pangea/models/practice_activities.dart/message_activity_request.dart b/lib/pangea/models/practice_activities.dart/message_activity_request.dart index 9101a78ce..edb702cda 100644 --- a/lib/pangea/models/practice_activities.dart/message_activity_request.dart +++ b/lib/pangea/models/practice_activities.dart/message_activity_request.dart @@ -195,6 +195,8 @@ class MessageActivityRequest { final String messageId; + final List clientCompatibleActivities; + MessageActivityRequest({ required this.userL1, required this.userL2, @@ -203,9 +205,28 @@ class MessageActivityRequest { required this.messageId, required this.existingActivities, required this.activityQualityFeedback, - }); + clientCompatibleActivities, + }) : clientCompatibleActivities = + clientCompatibleActivities ?? ActivityTypeEnum.values; factory MessageActivityRequest.fromJson(Map json) { + final clientCompatibleActivitiesEntry = + json['client_version_compatible_activity_types']; + List? clientCompatibleActivities; + if (clientCompatibleActivitiesEntry != null && + clientCompatibleActivitiesEntry is List) { + clientCompatibleActivities = clientCompatibleActivitiesEntry + .map( + (e) => ActivityTypeEnum.values.firstWhereOrNull( + (element) => + element.string == e as String || + element.string.split('.').last == e, + ), + ) + .where((entry) => entry != null) + .cast() + .toList(); + } return MessageActivityRequest( userL1: json['user_l1'] as String, userL2: json['user_l2'] as String, @@ -224,6 +245,10 @@ class MessageActivityRequest { json['activity_quality_feedback'] as Map, ) : null, + clientCompatibleActivities: clientCompatibleActivities != null && + clientCompatibleActivities.isNotEmpty + ? clientCompatibleActivities + : ActivityTypeEnum.values, ); } @@ -241,7 +266,7 @@ class MessageActivityRequest { // the server will only return activities of these types // this for backwards compatibility with old clients 'client_version_compatible_activity_types': - ActivityTypeEnum.values.map((e) => e.string).toList(), + clientCompatibleActivities.map((e) => e.string).toList(), }; } diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index ee451d1d9..570cb0576 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -148,6 +148,11 @@ class PracticeActivityCardState extends State { .map((activity) => activity.activityRequestMetaData) .toList(), activityQualityFeedback: activityFeedback, + clientCompatibleActivities: widget.tts.isLanguageFullySupported + ? ActivityTypeEnum.values + : ActivityTypeEnum.values + .where((type) => type != ActivityTypeEnum.wordFocusListening) + .toList(), ), widget.pangeaMessageEvent, ); From ea1ad9bc615fbeb0cb472afb532d44f4df12cdf6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 4 Nov 2024 16:02:39 -0500 Subject: [PATCH 3/5] make missing voice warning into an instructions popup --- assets/l10n/intl_en.arb | 5 +- lib/pages/chat/chat_event_list.dart | 1 - .../choreographer/widgets/it_bar_buttons.dart | 3 +- lib/pangea/enum/instructions_enum.dart | 7 ++ lib/pangea/models/user_model.dart | 5 ++ lib/pangea/utils/instructions.dart | 9 ++- .../widgets/chat/message_audio_card.dart | 26 ++++--- .../widgets/chat/missing_voice_button.dart | 49 ++++--------- lib/pangea/widgets/chat/tts_controller.dart | 40 ++++++++--- .../multiple_choice_activity.dart | 3 + .../practice_activity_card.dart | 2 + .../practice_activity/word_audio_button.dart | 69 ++++++++++--------- 12 files changed, 121 insertions(+), 98 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 1731cadf2..fdc574009 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4215,8 +4215,9 @@ "l2SupportAlpha": "Alpha", "l2SupportBeta": "Beta", "l2SupportFull": "Full", - "voiceNotAvailable": "It looks like you don't have a voice installed for this language.", - "openVoiceSettings": "Click here to open voice settings", + "missingVoiceTitle": "Missing voice", + "voiceNotAvailable": "You don't have a voice installed for this language.", + "openVoiceSettings": "Open voice settings", "playAudio": "Play", "stop": "Stop", "grammarCopySCONJ": "Subordinating Conjunction", diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 5e82651ce..b091be925 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -56,7 +56,6 @@ class ChatEventList extends StatelessWidget { context, InstructionsEnum.clickMessage, msgEvents[0].eventId, - true, ); }); // Pangea# diff --git a/lib/pangea/choreographer/widgets/it_bar_buttons.dart b/lib/pangea/choreographer/widgets/it_bar_buttons.dart index 9fcaa927e..d6d7caa3c 100644 --- a/lib/pangea/choreographer/widgets/it_bar_buttons.dart +++ b/lib/pangea/choreographer/widgets/it_bar_buttons.dart @@ -41,7 +41,6 @@ class ITBotButton extends StatelessWidget { context, InstructionsEnum.itInstructions, choreographer.itBotTransformTargetKey, - true, ); return IconButton( @@ -51,7 +50,7 @@ class ITBotButton extends StatelessWidget { context, InstructionsEnum.itInstructions, choreographer.itBotTransformTargetKey, - false, + showToggle: false, ), ); } diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/enum/instructions_enum.dart index a42a01643..64bad0fb3 100644 --- a/lib/pangea/enum/instructions_enum.dart +++ b/lib/pangea/enum/instructions_enum.dart @@ -15,6 +15,7 @@ enum InstructionsEnum { l1Translation, translationChoices, clickAgainToDeselect, + missingVoice, } extension InstructionsEnumExtension on InstructionsEnum { @@ -28,6 +29,8 @@ extension InstructionsEnumExtension on InstructionsEnum { return l10n.blurMeansTranslateTitle; case InstructionsEnum.tooltipInstructions: return l10n.tooltipInstructionsTitle; + case InstructionsEnum.missingVoice: + return l10n.missingVoiceTitle; case InstructionsEnum.clickAgainToDeselect: case InstructionsEnum.speechToText: case InstructionsEnum.l1Translation: @@ -64,6 +67,8 @@ extension InstructionsEnumExtension on InstructionsEnum { return PlatformInfos.isMobile ? l10n.tooltipInstructionsMobileBody : l10n.tooltipInstructionsBrowserBody; + case InstructionsEnum.missingVoice: + return l10n.voiceNotAvailable; } } @@ -87,6 +92,8 @@ extension InstructionsEnumExtension on InstructionsEnum { return instructionSettings.showedTranslationChoicesTooltip; case InstructionsEnum.clickAgainToDeselect: return instructionSettings.showedClickAgainToDeselect; + case InstructionsEnum.missingVoice: + return instructionSettings.showedMissingVoice; } } } diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 1fdebef3a..ba55330f3 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -185,6 +185,7 @@ class UserInstructions { bool showedClickMessage; bool showedBlurMeansTranslate; bool showedTooltipInstructions; + bool showedMissingVoice; bool showedSpeechToTextTooltip; bool showedL1TranslationTooltip; @@ -200,6 +201,7 @@ class UserInstructions { this.showedL1TranslationTooltip = false, this.showedTranslationChoicesTooltip = false, this.showedClickAgainToDeselect = false, + this.showedMissingVoice = false, }); factory UserInstructions.fromJson(Map json) => @@ -219,6 +221,8 @@ class UserInstructions { json[InstructionsEnum.speechToText.toString()] ?? false, showedClickAgainToDeselect: json[InstructionsEnum.clickAgainToDeselect.toString()] ?? false, + showedMissingVoice: + json[InstructionsEnum.missingVoice.toString()] ?? false, ); Map toJson() { @@ -236,6 +240,7 @@ class UserInstructions { data[InstructionsEnum.speechToText.toString()] = showedSpeechToTextTooltip; data[InstructionsEnum.clickAgainToDeselect.toString()] = showedClickAgainToDeselect; + data[InstructionsEnum.missingVoice.toString()] = showedMissingVoice; return data; } diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index 681c0de08..a4e8ef151 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -56,6 +56,9 @@ class InstructionsController { case InstructionsEnum.clickAgainToDeselect: profile.instructionSettings.showedClickAgainToDeselect = value; break; + case InstructionsEnum.missingVoice: + profile.instructionSettings.showedMissingVoice = value; + break; } return profile; }); @@ -66,9 +69,10 @@ class InstructionsController { Future showInstructionsPopup( BuildContext context, InstructionsEnum key, - String transformTargetKey, [ + String transformTargetKey, { bool showToggle = true, - ]) async { + Widget? customContent, + }) async { final bool userLangsSet = await _pangeaController.userController.areUserLanguagesSet; if (!userLangsSet) { @@ -115,6 +119,7 @@ class InstructionsController { style: botStyle, ), ), + if (customContent != null) customContent, if (showToggle) InstructionsToggle(instructionsKey: key), ], ), diff --git a/lib/pangea/widgets/chat/message_audio_card.dart b/lib/pangea/widgets/chat/message_audio_card.dart index 302dcdef9..cc41605c9 100644 --- a/lib/pangea/widgets/chat/message_audio_card.dart +++ b/lib/pangea/widgets/chat/message_audio_card.dart @@ -71,7 +71,11 @@ class MessageAudioCardState extends State { final PangeaTokenText selection = widget.selection!; final tokenText = selection.content; - await widget.tts.speak(tokenText); + await widget.tts.tryToSpeak( + tokenText, + context, + widget.messageEvent.eventId, + ); } void setSectionStartAndEnd(int? start, int? end) => mounted @@ -196,19 +200,13 @@ class MessageAudioCardState extends State { child: _isLoading ? const ToolbarContentLoadingIndicator() : audioFile != null - ? Column( - children: [ - AudioPlayerWidget( - null, - matrixFile: audioFile, - sectionStartMS: sectionStartMS, - sectionEndMS: sectionEndMS, - color: - Theme.of(context).colorScheme.onPrimaryContainer, - setIsPlayingAudio: widget.setIsPlayingAudio, - ), - widget.tts.missingVoiceButton, - ], + ? AudioPlayerWidget( + null, + matrixFile: audioFile, + sectionStartMS: sectionStartMS, + sectionEndMS: sectionEndMS, + color: Theme.of(context).colorScheme.onPrimaryContainer, + setIsPlayingAudio: widget.setIsPlayingAudio, ) : const CardErrorWidget( error: "Null audio file in message_audio_card", diff --git a/lib/pangea/widgets/chat/missing_voice_button.dart b/lib/pangea/widgets/chat/missing_voice_button.dart index 5ea13164d..67c94494b 100644 --- a/lib/pangea/widgets/chat/missing_voice_button.dart +++ b/lib/pangea/widgets/chat/missing_voice_button.dart @@ -2,20 +2,17 @@ import 'dart:io'; import 'package:android_intent_plus/android_intent.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; class MissingVoiceButton extends StatelessWidget { - final String targetLangCode; - - const MissingVoiceButton({ - required this.targetLangCode, - super.key, - }); + const MissingVoiceButton({super.key}); Future launchTTSSettings(BuildContext context) async { - if (Platform.isAndroid) { + if (!kIsWeb && Platform.isAndroid) { const intent = AndroidIntent( action: 'com.android.settings.TTS_SETTINGS', package: 'com.talktolearn.chat', @@ -30,36 +27,18 @@ class MissingVoiceButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - constraints: const BoxConstraints(maxWidth: AppConfig.toolbarMinWidth), - decoration: BoxDecoration( - color: - Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.1), - borderRadius: const BorderRadius.all( - Radius.circular(AppConfig.borderRadius), + return TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + AppConfig.primaryColor.withOpacity(0.1), ), ), - padding: const EdgeInsets.all(8), - margin: const EdgeInsets.only(top: 8), - child: SizedBox( - width: AppConfig.toolbarMinWidth, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - L10n.of(context)!.voiceNotAvailable, - textAlign: TextAlign.center, - ), - TextButton( - onPressed: () => launchTTSSettings(context), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: Text(L10n.of(context)!.openVoiceSettings), - ), - ], - ), + onPressed: () async { + MatrixState.pAnyState.closeOverlay(); + await launchTTSSettings(context); + }, + child: Center( + child: Text(L10n.of(context)!.openVoiceSettings), ), ); } diff --git a/lib/pangea/widgets/chat/tts_controller.dart b/lib/pangea/widgets/chat/tts_controller.dart index baffb7b9b..4c178d440 100644 --- a/lib/pangea/widgets/chat/tts_controller.dart +++ b/lib/pangea/widgets/chat/tts_controller.dart @@ -1,8 +1,8 @@ import 'dart:developer'; +import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/chat/missing_voice_button.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -85,6 +85,37 @@ class TtsController { } } + Future showMissingVoicePopup( + BuildContext context, + String eventID, + ) async { + await MatrixState.pangeaController.instructions.showInstructionsPopup( + context, + InstructionsEnum.missingVoice, + eventID, + showToggle: false, + customContent: const Padding( + padding: EdgeInsets.only(top: 12), + child: MissingVoiceButton(), + ), + ); + return; + } + + /// A safer version of speak, that handles the case of + /// the language not being supported by the TTS engine + Future tryToSpeak( + String text, + BuildContext context, + String eventID, + ) async { + if (isLanguageFullySupported) { + await speak(text); + } else { + await showMissingVoicePopup(context, eventID); + } + } + Future speak(String text) async { try { stop(); @@ -112,11 +143,4 @@ class TtsController { bool get isLanguageFullySupported => availableLangCodes.contains(targetLanguage); - - Widget get missingVoiceButton => targetLanguage != null && - (kIsWeb || isLanguageFullySupported || !PlatformInfos.isAndroid) - ? const SizedBox.shrink() - : MissingVoiceButton( - targetLangCode: targetLanguage!, - ); } diff --git a/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart b/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart index dda975c33..a477efa9b 100644 --- a/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart +++ b/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart @@ -18,12 +18,14 @@ class MultipleChoiceActivity extends StatefulWidget { final PracticeActivityCardState practiceCardController; final PracticeActivityModel currentActivity; final TtsController tts; + final String eventID; const MultipleChoiceActivity({ super.key, required this.practiceCardController, required this.currentActivity, required this.tts, + required this.eventID, }); @override @@ -117,6 +119,7 @@ class MultipleChoiceActivityState extends State { WordAudioButton( text: practiceActivity.content.answer, ttsController: widget.tts, + eventID: widget.eventID, ), ChoicesArray( isLoading: false, diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index 570cb0576..1e97f2fe2 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -302,6 +302,7 @@ class PracticeActivityCardState extends State { practiceCardController: this, currentActivity: currentActivity!, tts: widget.tts, + eventID: widget.pangeaMessageEvent.eventId, ); case ActivityTypeEnum.wordFocusListening: // return WordFocusListeningActivity( @@ -310,6 +311,7 @@ class PracticeActivityCardState extends State { practiceCardController: this, currentActivity: currentActivity!, tts: widget.tts, + eventID: widget.pangeaMessageEvent.eventId, ); // default: // ErrorHandler.logError( diff --git a/lib/pangea/widgets/practice_activity/word_audio_button.dart b/lib/pangea/widgets/practice_activity/word_audio_button.dart index 591228e18..03147ef0d 100644 --- a/lib/pangea/widgets/practice_activity/word_audio_button.dart +++ b/lib/pangea/widgets/practice_activity/word_audio_button.dart @@ -5,11 +5,13 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; class WordAudioButton extends StatefulWidget { final String text; final TtsController ttsController; + final String eventID; const WordAudioButton({ super.key, required this.text, required this.ttsController, + required this.eventID, }); @override @@ -22,41 +24,40 @@ class WordAudioButtonState extends State { @override Widget build(BuildContext context) { debugPrint('build WordAudioButton'); - return Column( - children: [ - IconButton( - icon: const Icon(Icons.play_arrow_outlined), - isSelected: _isPlaying, - selectedIcon: const Icon(Icons.pause_outlined), - color: _isPlaying ? Colors.white : null, - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - _isPlaying - ? Theme.of(context).colorScheme.secondary - : Theme.of(context).colorScheme.primaryContainer, - ), - ), - tooltip: - _isPlaying ? L10n.of(context)!.stop : L10n.of(context)!.playAudio, - onPressed: () async { - if (_isPlaying) { - await widget.ttsController.tts.stop(); - if (mounted) { - setState(() => _isPlaying = false); - } - } else { - if (mounted) { - setState(() => _isPlaying = true); - } - await widget.ttsController.speak(widget.text); - if (mounted) { - setState(() => _isPlaying = false); - } - } - }, // Disable button if language isn't supported + return IconButton( + icon: const Icon(Icons.play_arrow_outlined), + isSelected: _isPlaying, + selectedIcon: const Icon(Icons.pause_outlined), + color: _isPlaying ? Colors.white : null, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + _isPlaying + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.primaryContainer, ), - widget.ttsController.missingVoiceButton, - ], + ), + tooltip: + _isPlaying ? L10n.of(context)!.stop : L10n.of(context)!.playAudio, + onPressed: () async { + if (_isPlaying) { + await widget.ttsController.tts.stop(); + if (mounted) { + setState(() => _isPlaying = false); + } + } else { + if (mounted) { + setState(() => _isPlaying = true); + } + await widget.ttsController.tryToSpeak( + widget.text, + context, + widget.eventID, + ); + if (mounted) { + setState(() => _isPlaying = false); + } + } + }, // Disable button if language isn't supported ); } } From 26e850af278b39eab50720e44d550f0b595f950c Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 4 Nov 2024 16:14:22 -0500 Subject: [PATCH 4/5] enable other toolbar buttons in message not in l2 --- lib/pangea/widgets/chat/message_toolbar.dart | 15 +++++++-------- .../widgets/chat/message_toolbar_buttons.dart | 11 +++++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index bc6ed54bc..ffca4f32c 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -47,14 +47,6 @@ class MessageToolbar extends StatelessWidget { final bool messageInUserL2 = pangeaMessageEvent.messageDisplayLangCode == MatrixState.pangeaController.languageController.userL2?.langCode; - // If not in the target language show specific messsage - if (!messageInUserL2) { - return MessageDisplayCard( - displayText: - L10n.of(context)!.messageNotInTargetLang, // Pass the display text, - ); - } - switch (overLayController.toolbarMode) { case MessageMode.translation: return MessageTranslationCard( @@ -104,6 +96,13 @@ class MessageToolbar extends StatelessWidget { } } case MessageMode.practiceActivity: + // If not in the target language show specific messsage + if (!messageInUserL2) { + return MessageDisplayCard( + displayText: L10n.of(context)! + .messageNotInTargetLang, // Pass the display text, + ); + } return PracticeActivityCard( pangeaMessageEvent: pangeaMessageEvent, overlayController: overLayController, diff --git a/lib/pangea/widgets/chat/message_toolbar_buttons.dart b/lib/pangea/widgets/chat/message_toolbar_buttons.dart index 190a0fdff..7cead7b5a 100644 --- a/lib/pangea/widgets/chat/message_toolbar_buttons.dart +++ b/lib/pangea/widgets/chat/message_toolbar_buttons.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class ToolbarButtons extends StatelessWidget { @@ -25,10 +26,16 @@ class ToolbarButtons extends StatelessWidget { .where((mode) => mode.shouldShowAsToolbarButton(pangeaMessageEvent.event)) .toList(); + bool get messageInUserL2 => + pangeaMessageEvent.messageDisplayLangCode == + MatrixState.pangeaController.languageController.userL2?.langCode; + static const double iconWidth = 36.0; @override Widget build(BuildContext context) { + final totallyDone = + overlayController.isPracticeComplete || !messageInUserL2; final double barWidth = width - iconWidth; if (overlayController.pangeaMessageEvent.isAudioMessage) { @@ -85,14 +92,14 @@ class ToolbarButtons extends StatelessWidget { index, overlayController.toolbarMode, pangeaMessageEvent.numberOfActivitiesCompleted, - overlayController.isPracticeComplete, + totallyDone, ), ), ), onPressed: mode.isUnlocked( index, pangeaMessageEvent.numberOfActivitiesCompleted, - overlayController.isPracticeComplete, + totallyDone, ) ? () => overlayController.updateToolbarMode(mode) : null, From 425779e8687e422220ced3efe97403af9524af4b Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 5 Nov 2024 09:43:17 -0500 Subject: [PATCH 5/5] redact the current activity event when submitting feedback --- ...actice_activity_generation_controller.dart | 47 +++++++++++++++---- .../practice_activity_card.dart | 31 +++++++++--- pubspec.yaml | 2 +- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/lib/pangea/controllers/practice_activity_generation_controller.dart b/lib/pangea/controllers/practice_activity_generation_controller.dart index 410f8eeaa..c7238c9cc 100644 --- a/lib/pangea/controllers/practice_activity_generation_controller.dart +++ b/lib/pangea/controllers/practice_activity_generation_controller.dart @@ -19,7 +19,7 @@ import 'package:matrix/matrix.dart'; /// Represents an item in the completion cache. class _RequestCacheItem { MessageActivityRequest req; - PracticeActivityModel? practiceActivity; + PracticeActivityModelResponse? practiceActivity; _RequestCacheItem({ required this.req, @@ -99,7 +99,7 @@ class PracticeGenerationController { //TODO - allow return of activity content before sending the event // this requires some downstream changes to the way the event is handled - Future getPracticeActivity( + Future getPracticeActivity( MessageActivityRequest req, PangeaMessageEvent event, ) async { @@ -119,6 +119,8 @@ class PracticeGenerationController { return null; } + final eventCompleter = Completer(); + // if the server points to an existing event, return that event if (res.existingActivityEventId != null) { final Event? existingEvent = @@ -127,11 +129,19 @@ class PracticeGenerationController { debugPrint( 'Existing activity event found: ${existingEvent?.content}', ); - if (existingEvent != null) { - return PracticeActivityEvent( + debugPrint( + "eventID: ${existingEvent?.eventId}, event is redacted: ${existingEvent?.redacted}", + ); + if (existingEvent != null && !existingEvent.redacted) { + final activityEvent = PracticeActivityEvent( event: existingEvent, timeline: event.timeline, - ).practiceActivity; + ); + eventCompleter.complete(activityEvent); + return PracticeActivityModelResponse( + activity: activityEvent.practiceActivity, + eventCompleter: eventCompleter, + ); } } @@ -141,11 +151,30 @@ class PracticeGenerationController { } debugPrint('Activity generated: ${res.activity!.toJson()}'); + _sendAndPackageEvent(res.activity!, event).then((event) { + eventCompleter.complete(event); + }); - _sendAndPackageEvent(res.activity!, event); - _cache[cacheKey] = - _RequestCacheItem(req: req, practiceActivity: res.activity!); + final responseModel = PracticeActivityModelResponse( + activity: res.activity!, + eventCompleter: eventCompleter, + ); - return _cache[cacheKey]!.practiceActivity; + _cache[cacheKey] = _RequestCacheItem( + req: req, + practiceActivity: responseModel, + ); + + return responseModel; } } + +class PracticeActivityModelResponse { + final PracticeActivityModel? activity; + final Completer eventCompleter; + + PracticeActivityModelResponse({ + required this.activity, + required this.eventCompleter, + }); +} diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index 1e97f2fe2..00b11f658 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/controllers/practice_activity_generation_controller.dart'; import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; @@ -45,6 +46,8 @@ class PracticeActivityCard extends StatefulWidget { class PracticeActivityCardState extends State { PracticeActivityModel? currentActivity; + Completer? currentActivityCompleter; + PracticeActivityRecordModel? currentCompletionRecord; bool fetchingActivity = false; @@ -133,9 +136,9 @@ class PracticeActivityCardState extends State { return null; } - final PracticeActivityModel? ourNewActivity = await pangeaController - .practiceGenerationController - .getPracticeActivity( + final PracticeActivityModelResponse? activityResponse = + await pangeaController.practiceGenerationController + .getPracticeActivity( MessageActivityRequest( userL1: pangeaController.languageController.userL1!.langCode, userL2: pangeaController.languageController.userL2!.langCode, @@ -157,9 +160,10 @@ class PracticeActivityCardState extends State { widget.pangeaMessageEvent, ); + currentActivityCompleter = activityResponse?.eventCompleter; _updateFetchingActivity(false); - return ourNewActivity; + return activityResponse?.activity; } catch (e, s) { debugger(when: kDebugMode); ErrorHandler.logError( @@ -255,12 +259,27 @@ class PracticeActivityCardState extends State { /// clear the current activity, record, and selection /// fetch a new activity, including the offending activity in the request - void submitFeedback(String feedback) { - if (currentActivity == null) { + Future submitFeedback(String feedback) async { + if (currentActivity == null || currentCompletionRecord == null) { debugger(when: kDebugMode); return; } + if (currentActivityCompleter != null) { + final activityEvent = await currentActivityCompleter!.future; + await activityEvent?.event.redactEvent(reason: feedback); + } else { + debugger(when: kDebugMode); + ErrorHandler.logError( + e: Exception('No completer found for current activity'), + data: { + 'activity': currentActivity, + 'record': currentCompletionRecord, + 'feedback': feedback, + }, + ); + } + _fetchNewActivity( ActivityQualityFeedback( feedbackText: feedback, diff --git a/pubspec.yaml b/pubspec.yaml index 25c564cef..ca105194e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ description: Learn a language while texting your friends. # Pangea# publish_to: none # On version bump also increase the build number for F-Droid -version: 1.23.3+3562 +version: 1.23.4+3563 environment: sdk: ">=3.0.0 <4.0.0"