From bbc791b314ccbef7653bd1639eb9afd61d4f6b49 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Sat, 11 Jan 2025 13:25:07 -0500 Subject: [PATCH] dev/feat: refactor of instructions, tweaking of morph/lemma edits --- assets/l10n/intl_en.arb | 37 +++- lib/pages/chat/chat_event_list.dart | 13 +- lib/pangea/choreographer/widgets/it_bar.dart | 16 +- .../choreographer/widgets/it_bar_buttons.dart | 12 +- lib/pangea/controllers/pangea_controller.dart | 12 +- .../instructions/instruction_settings.dart | 53 ++++++ .../instructions_enum.dart | 47 ++--- .../instructions_inline_tooltip.dart} | 25 +-- .../instructions/instructions_show_popup.dart | 71 +++++++ .../instructions/instructions_toggle.dart | 42 +++++ lib/pangea/models/pangea_token_model.dart | 18 +- .../practice_activity_model.dart | 51 +++++- lib/pangea/models/user_model.dart | 142 +------------- .../word_meaning_activity_generator.dart | 9 +- lib/pangea/utils/instructions.dart | 173 ------------------ .../chat/message_speech_to_text_card.dart | 19 +- .../chat/message_translation_card.dart | 15 +- lib/pangea/widgets/chat/tts_controller.dart | 17 +- lib/pangea/widgets/igc/pangea_rich_text.dart | 14 +- .../no_more_practice_card.dart | 9 +- .../practice_activity_card.dart | 12 +- .../word_zoom/lemma_meaning_widget.dart | 12 +- .../morphs/morphological_center_widget.dart | 97 ++++++---- 23 files changed, 434 insertions(+), 482 deletions(-) create mode 100644 lib/pangea/instructions/instruction_settings.dart rename lib/pangea/{enum => instructions}/instructions_enum.dart (67%) rename lib/pangea/{utils/inline_tooltip.dart => instructions/instructions_inline_tooltip.dart} (85%) create mode 100644 lib/pangea/instructions/instructions_show_popup.dart create mode 100644 lib/pangea/instructions/instructions_toggle.dart delete mode 100644 lib/pangea/utils/instructions.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 2ebb4c1ee..4b74b6c89 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4660,7 +4660,6 @@ "publicProfileTitle": "Allow my profile to be found in search", "publicProfileDesc": "By enabling this option, I confirm that I am of legal age in my country of residence", "clickWordsInstructions": "Click on individual words for more activities.", - "chooseBestDefinition": "What does this word mean?", "chooseBaseForm": "Choose the base form", "notTheCodeError": "Sorry, that's not the code!", "totalXP": "Total XP", @@ -4700,11 +4699,43 @@ "downloading": "Downloading...", "failedFetchUserAnalytics": "Failed to download user analytics", "downloadComplete": "Download complete!", - "editMorphologicalLabel": "Pangea Bot makes mistakes too! What should this label be?", + "whatIsTheMorphTag": "What is the {morphologicalFeature} of '{wordForm}'?", + "@whatIsTheMorphTag": { + "type": "text", + "placeholders": { + "morphologicalFeature": {}, + "wordForm": {} + } + }, "dataAvailable": "Data availability", "lemmasNeverUsedCorrectly": "Number of lemmas used correctly 0 times", "available": "Available", "unavailable": "Unavailable", "accessingMemberAnalytics": "Accessing member analytics...", - "editLemmaMeaning": "Pangea Bot makes mistakes too! What should be the definition of this lemma?" + "pangeaBotIsFallible": "Pangea Bot makes mistakes too!", + "whatIsMeaning": "What does '{lemma}' mean?", + "@whatIsMeaning": { + "type": "text", + "placeholders": { + "lemma": {}, + "partOfSpeech": {} + } + }, + "pickAnEmoji": "What's your favorite emoji for '{lemma}'?", + "@pickAnEmoji": { + "type": "text", + "placeholders": { + "lemma": {}, + "partOfSpeech": {} + } + }, + "lemmaMeaningInstructionsBody": "Above is the meaning of the lemma. Double-click to edit.", + "doubleClickToEdit": "Double-click to edit.", + "removeFeature": "Remove {feature}", + "@removeFeature": { + "type": "text", + "placeholders": { + "feature": {} + } + } } \ No newline at end of file diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 1fbf7ba05..8af90762c 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -1,8 +1,3 @@ -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -10,11 +5,15 @@ import 'package:fluffychat/pages/chat/events/message.dart'; import 'package:fluffychat/pages/chat/seen_by_row.dart'; import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; +import 'package:scroll_to_index/scroll_to_index.dart'; class ChatEventList extends StatelessWidget { final ChatController controller; @@ -59,7 +58,7 @@ class ChatEventList extends StatelessWidget { ) .toList(); if (msgEvents.isEmpty) return; - controller.pangeaController.instructions.showInstructionsPopup( + instructionsShowPopup( context, InstructionsEnum.clickMessage, msgEvents[0].eventId, diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index ad50d159c..927411aa8 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -1,9 +1,6 @@ import 'dart:async'; import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/it_controller.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart'; @@ -12,11 +9,14 @@ import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flo import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + import '../../controllers/it_feedback_controller.dart'; import '../../models/it_response_model.dart'; import '../../utils/overlay.dart'; @@ -67,7 +67,7 @@ class ITBarState extends State with SingleTickerProviderStateMixin { } bool get showITInstructionsTooltip { - final toggledOff = InstructionsEnum.clickBestOption.toggledOff(); + final toggledOff = InstructionsEnum.clickBestOption.isToggledOff; if (!toggledOff) { setState(() => showedClickInstruction = true); } @@ -205,11 +205,11 @@ class ITBarState extends State with SingleTickerProviderStateMixin { ), const SizedBox(height: 8.0), if (showITInstructionsTooltip) - const InlineTooltip( + const InstructionsInlineTooltip( instructionsEnum: InstructionsEnum.clickBestOption, ), if (showTranslationsChoicesTooltip) - const InlineTooltip( + const InstructionsInlineTooltip( instructionsEnum: InstructionsEnum.translationChoices, ), Container( diff --git a/lib/pangea/choreographer/widgets/it_bar_buttons.dart b/lib/pangea/choreographer/widgets/it_bar_buttons.dart index 8dab602bd..c54518fc4 100644 --- a/lib/pangea/choreographer/widgets/it_bar_buttons.dart +++ b/lib/pangea/choreographer/widgets/it_bar_buttons.dart @@ -1,8 +1,9 @@ +import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import '../../widgets/common/bot_face_svg.dart'; import '../controllers/choreographer.dart'; import '../controllers/it_controller.dart'; @@ -37,7 +38,7 @@ class ITBotButton extends StatelessWidget { @override Widget build(BuildContext context) { - choreographer.pangeaController.instructions.showInstructionsPopup( + instructionsShowPopup( context, InstructionsEnum.itInstructions, choreographer.itBotTransformTargetKey, @@ -45,8 +46,7 @@ class ITBotButton extends StatelessWidget { return IconButton( icon: const BotFace(width: 40.0, expression: BotExpression.idle), - onPressed: () => - choreographer.pangeaController.instructions.showInstructionsPopup( + onPressed: () => instructionsShowPopup( context, InstructionsEnum.itInstructions, choreographer.itBotTransformTargetKey, diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index ddb08c924..a1a6135b4 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -2,11 +2,6 @@ import 'dart:async'; import 'dart:developer'; import 'dart:math'; -import 'package:flutter/foundation.dart'; - -import 'package:matrix/matrix.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; - import 'package:fluffychat/pangea/constants/bot_mode.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; @@ -30,8 +25,11 @@ import 'package:fluffychat/pangea/guard/p_vguard.dart'; import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/utils/bot_name.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/utils/instructions.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:matrix/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + import '../../config/app_config.dart'; import '../utils/firebase_analytics.dart'; import '../utils/p_store.dart'; @@ -51,7 +49,6 @@ class PangeaController { // TODO: make these static so we can remove from here late ContextualDefinitionController definitions; late ITFeedbackController itFeedback; - late InstructionsController instructions; late SubscriptionController subscriptionController; late TextToSpeechController textToSpeech; late SpeechToTextController speechToText; @@ -106,7 +103,6 @@ class PangeaController { messageData = MessageDataController(this); wordNet = WordController(this); definitions = ContextualDefinitionController(this); - instructions = InstructionsController(this); subscriptionController = SubscriptionController(this); itFeedback = ITFeedbackController(this); textToSpeech = TextToSpeechController(this); diff --git a/lib/pangea/instructions/instruction_settings.dart b/lib/pangea/instructions/instruction_settings.dart new file mode 100644 index 000000000..420c70b18 --- /dev/null +++ b/lib/pangea/instructions/instruction_settings.dart @@ -0,0 +1,53 @@ +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +/// The user's settings for whether or not to show instuction messages. +class InstructionSettings { + Map _instructions = {}; + + InstructionSettings([Map? instructions]) { + if (instructions != null) { + _instructions = instructions; + } else { + for (final key in InstructionsEnum.values) { + _instructions[key.toString()] = false; + } + } + } + + factory InstructionSettings.fromJson(Map json) { + final Map instructions = {}; + for (final key in InstructionsEnum.values) { + instructions[key.toString()] = json[key.toString()] ?? false; + } + return InstructionSettings(instructions); + } + + Map toJson() { + final Map data = {}; + for (final key in InstructionsEnum.values) { + data[key.toString()] = _instructions[key.toString()]; + } + return data; + } + + factory InstructionSettings.migrateFromAccountData() { + final accountData = + MatrixState.pangeaController.matrixState.client.accountData; + final Map instructions = {}; + for (final key in InstructionsEnum.values) { + instructions[key.toString()] = + (accountData[key.toString()]?.content[key.toString()] as bool?) ?? + false; + } + return InstructionSettings(instructions); + } + + bool getStatus(InstructionsEnum instruction) { + return _instructions[instruction.toString()] ?? false; + } + + void setStatus(InstructionsEnum instruction, bool status) { + _instructions[instruction.toString()] = status; + } +} diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/instructions/instructions_enum.dart similarity index 67% rename from lib/pangea/enum/instructions_enum.dart rename to lib/pangea/instructions/instructions_enum.dart index 2185137b4..2465c33db 100644 --- a/lib/pangea/enum/instructions_enum.dart +++ b/lib/pangea/instructions/instructions_enum.dart @@ -1,12 +1,10 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; enum InstructionsEnum { itInstructions, @@ -20,6 +18,7 @@ enum InstructionsEnum { missingVoice, clickBestOption, unlockedLanguageTools, + lemmaMeaning, } extension InstructionsEnumExtension on InstructionsEnum { @@ -41,6 +40,7 @@ extension InstructionsEnumExtension on InstructionsEnum { case InstructionsEnum.translationChoices: case InstructionsEnum.clickBestOption: case InstructionsEnum.unlockedLanguageTools: + case InstructionsEnum.lemmaMeaning: ErrorHandler.logError( e: Exception("No title for this instruction"), m: 'InstructionsEnumExtension.title', @@ -79,35 +79,18 @@ extension InstructionsEnumExtension on InstructionsEnum { return l10n.clickBestOption; case InstructionsEnum.unlockedLanguageTools: return l10n.unlockedLanguageTools; + case InstructionsEnum.lemmaMeaning: + return l10n.lemmaMeaningInstructionsBody; } } - bool toggledOff() { - final instructionSettings = - MatrixState.pangeaController.userController.profile.instructionSettings; - switch (this) { - case InstructionsEnum.itInstructions: - return instructionSettings.showedItInstructions; - case InstructionsEnum.clickMessage: - return instructionSettings.showedClickMessage; - case InstructionsEnum.blurMeansTranslate: - return instructionSettings.showedBlurMeansTranslate; - case InstructionsEnum.tooltipInstructions: - return instructionSettings.showedTooltipInstructions; - case InstructionsEnum.speechToText: - return instructionSettings.showedSpeechToTextTooltip; - case InstructionsEnum.l1Translation: - return instructionSettings.showedL1TranslationTooltip; - case InstructionsEnum.translationChoices: - return instructionSettings.showedTranslationChoicesTooltip; - case InstructionsEnum.clickAgainToDeselect: - return instructionSettings.showedClickAgainToDeselect; - case InstructionsEnum.missingVoice: - return instructionSettings.showedMissingVoice; - case InstructionsEnum.clickBestOption: - return instructionSettings.showedClickBestOption; - case InstructionsEnum.unlockedLanguageTools: - return instructionSettings.showedUnlockedLanguageTools; - } - } + bool get isToggledOff => + MatrixState.pangeaController.userController.profile.instructionSettings + .getStatus(this); + + void setToggledOff(bool value) => + MatrixState.pangeaController.userController.updateProfile((profile) { + profile.instructionSettings.setStatus(this, value); + return profile; + }); } diff --git a/lib/pangea/utils/inline_tooltip.dart b/lib/pangea/instructions/instructions_inline_tooltip.dart similarity index 85% rename from lib/pangea/utils/inline_tooltip.dart rename to lib/pangea/instructions/instructions_inline_tooltip.dart index a399cca45..6135624fb 100644 --- a/lib/pangea/utils/inline_tooltip.dart +++ b/lib/pangea/instructions/instructions_inline_tooltip.dart @@ -1,25 +1,23 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; -import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; -class InlineTooltip extends StatefulWidget { +class InstructionsInlineTooltip extends StatefulWidget { final InstructionsEnum instructionsEnum; - const InlineTooltip({ + const InstructionsInlineTooltip({ super.key, required this.instructionsEnum, }); @override - InlineTooltipState createState() => InlineTooltipState(); + InstructionsInlineTooltipState createState() => + InstructionsInlineTooltipState(); } -class InlineTooltipState extends State +class InstructionsInlineTooltipState extends State with SingleTickerProviderStateMixin { bool _isToggledOff = true; late AnimationController _controller; @@ -28,7 +26,7 @@ class InlineTooltipState extends State @override void initState() { super.initState(); - _isToggledOff = widget.instructionsEnum.toggledOff(); + _isToggledOff = widget.instructionsEnum.isToggledOff; // Initialize AnimationController and Animation _controller = AnimationController( @@ -52,10 +50,7 @@ class InlineTooltipState extends State } void _closeTooltip() { - MatrixState.pangeaController.instructions.setToggledOff( - widget.instructionsEnum, - true, - ); + widget.instructionsEnum.setToggledOff(true); setState(() { _isToggledOff = true; _controller.reverse(); diff --git a/lib/pangea/instructions/instructions_show_popup.dart b/lib/pangea/instructions/instructions_show_popup.dart new file mode 100644 index 000000000..baffc3863 --- /dev/null +++ b/lib/pangea/instructions/instructions_show_popup.dart @@ -0,0 +1,71 @@ +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_toggle.dart'; +import 'package:fluffychat/pangea/utils/bot_style.dart'; +import 'package:fluffychat/pangea/utils/overlay.dart'; +import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart'; +import 'package:fluffychat/pangea/widgets/igc/card_header.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +/// Instruction Card gives users tips on +/// how to use Pangea Chat's features +Future instructionsShowPopup( + BuildContext context, + InstructionsEnum key, + String transformTargetKey, { + bool showToggle = true, + Widget? customContent, + bool forceShow = false, +}) async { + final bool userLangsSet = + await MatrixState.pangeaController.userController.areUserLanguagesSet; + if (!userLangsSet) { + return; + } + + // if ((_instructionsShown[key.toString()] ?? false) && !forceShow) { + // return; + // } + // _instructionsShown[key.toString()] = true; + + if (key.isToggledOff && !forceShow) { + return; + } + + final botStyle = BotStyle.text(context); + Future.delayed( + const Duration(seconds: 1), + () { + if (!context.mounted) return; + OverlayUtil.showPositionedCard( + context: context, + backDropToDismiss: false, + cardToShow: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CardHeader( + text: key.title(L10n.of(context)), + botExpression: BotExpression.idle, + // onClose: () => {_instructionsClosed[key.toString()] = true}, + ), + const SizedBox(height: 10.0), + Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + key.body(L10n.of(context)), + style: botStyle, + ), + ), + if (customContent != null) customContent, + if (showToggle) InstructionsToggle(instructionsKey: key), + ], + ), + maxHeight: 300, + maxWidth: 300, + transformTargetId: transformTargetKey, + closePrevOverlay: false, + ); + }, + ); +} diff --git a/lib/pangea/instructions/instructions_toggle.dart b/lib/pangea/instructions/instructions_toggle.dart new file mode 100644 index 000000000..5c20233e0 --- /dev/null +++ b/lib/pangea/instructions/instructions_toggle.dart @@ -0,0 +1,42 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +/// User can toggle on to prevent Instruction Card +/// from appearing in future sessions +class InstructionsToggle extends StatefulWidget { + const InstructionsToggle({ + super.key, + required this.instructionsKey, + }); + + final InstructionsEnum instructionsKey; + + @override + InstructionsToggleState createState() => InstructionsToggleState(); +} + +class InstructionsToggleState extends State { + PangeaController pangeaController = MatrixState.pangeaController; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SwitchListTile.adaptive( + activeColor: AppConfig.activeToggleColor, + title: Text(L10n.of(context).doNotShowAgain), + value: widget.instructionsKey.isToggledOff, + onChanged: ((value) async { + widget.instructionsKey.setToggledOff(value); + setState(() {}); + }), + ); + } +} diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index 327ecc7bf..b68b236da 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -1,11 +1,7 @@ import 'dart:developer'; import 'dart:math'; -import 'package:flutter/foundation.dart'; - import 'package:collection/collection.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; @@ -20,6 +16,9 @@ import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart'; import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:matrix/matrix.dart'; + import '../constants/model_keys.dart'; import 'lemma.dart'; @@ -313,8 +312,15 @@ class PangeaToken { case ActivityTypeEnum.hiddenWordListening: return daysSinceLastUseByType(a) > 7; case ActivityTypeEnum.lemmaId: - return _didActivitySuccessfully(ActivityTypeEnum.wordMeaning) && - daysSinceLastUseByType(a) > 7; + return false; + // disabling lemma activities for now + // It has 2 purposes:• learning value• triangulating our determination of the lemma with + // AI plus user verification.However, displaying the lemma during the meaning activity helps + // disambiguate what the meaning activity is about. This is probably more valuable than the + // lemma activity itself. The piping for the lemma activity will stay there if we want to turn + //it back on, maybe in select instances. + // return _didActivitySuccessfully(ActivityTypeEnum.wordMeaning) && + // daysSinceLastUseByType(a) > 7; case ActivityTypeEnum.emoji: return true; case ActivityTypeEnum.morphId: diff --git a/lib/pangea/models/practice_activities.dart/practice_activity_model.dart b/lib/pangea/models/practice_activities.dart/practice_activity_model.dart index eb02a8cc7..d9aae9512 100644 --- a/lib/pangea/models/practice_activities.dart/practice_activity_model.dart +++ b/lib/pangea/models/practice_activities.dart/practice_activity_model.dart @@ -1,16 +1,17 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; - import 'package:collection/collection.dart'; -import 'package:sentry_flutter/sentry_flutter.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/analytics/morph_categories_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/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; class ConstructIdentifier { final String lemma; @@ -228,7 +229,47 @@ class PracticeActivityModel { required this.content, }); - String get question => content.question; + String get targetLemma => + targetTokens?.first.lemma.text ?? + tgtConstructs + .firstWhereOrNull( + (element) => element.type == ConstructTypeEnum.vocab, + ) + ?.lemma ?? + "___"; + + String get partOfSpeech => + targetTokens?.first.pos ?? + tgtConstructs + .firstWhereOrNull( + (element) => element.type == ConstructTypeEnum.vocab, + ) + ?.category ?? + "___"; + + String get targetWordForm => targetTokens?.first.text.content ?? "___"; + + /// we were setting the question copy on creation of the activity + /// but, in order to localize the question using the same system + /// as other copy, we should do it with context, when it is built + /// some types are doing this now, others should be migrated + String question(BuildContext context, String? morphFeature) { + switch (activityType) { + case ActivityTypeEnum.hiddenWordListening: + case ActivityTypeEnum.wordFocusListening: + case ActivityTypeEnum.lemmaId: + return content.question; + case ActivityTypeEnum.emoji: + return L10n.of(context).pickAnEmoji(targetLemma, partOfSpeech); + case ActivityTypeEnum.wordMeaning: + return L10n.of(context).whatIsMeaning(targetLemma, partOfSpeech); + case ActivityTypeEnum.morphId: + return L10n.of(context).whatIsTheMorphTag( + getMorphologicalCategoryCopy(morphFeature!, context) ?? morphFeature, + targetWordForm, + ); + } + } factory PracticeActivityModel.fromJson(Map json) { // moving from multiple_choice to content as the key diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 662c2d77e..d5dafffcc 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -1,10 +1,10 @@ -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instruction_settings.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:matrix/matrix.dart'; + import 'language_model.dart'; /// The user's settings learning settings. @@ -187,142 +187,20 @@ class UserToolSettings { } } -/// The user's settings for whether or not to show instuction messages. -class UserInstructions { - bool showedItInstructions; - bool showedClickMessage; - bool showedBlurMeansTranslate; - bool showedTooltipInstructions; - bool showedMissingVoice; - bool showedClickBestOption; - bool showedUnlockedLanguageTools; - - bool showedSpeechToTextTooltip; - bool showedL1TranslationTooltip; - bool showedTranslationChoicesTooltip; - bool showedClickAgainToDeselect; - - UserInstructions({ - this.showedItInstructions = false, - this.showedClickMessage = false, - this.showedBlurMeansTranslate = false, - this.showedTooltipInstructions = false, - this.showedSpeechToTextTooltip = false, - this.showedL1TranslationTooltip = false, - this.showedTranslationChoicesTooltip = false, - this.showedClickAgainToDeselect = false, - this.showedMissingVoice = false, - this.showedClickBestOption = false, - this.showedUnlockedLanguageTools = false, - }); - - factory UserInstructions.fromJson(Map json) => - UserInstructions( - showedItInstructions: json[InstructionsEnum.itInstructions.toString()], - showedClickMessage: - json[InstructionsEnum.clickMessage.toString()] ?? false, - showedBlurMeansTranslate: - json[InstructionsEnum.blurMeansTranslate.toString()] ?? false, - showedTooltipInstructions: - json[InstructionsEnum.tooltipInstructions.toString()] ?? false, - showedL1TranslationTooltip: - json[InstructionsEnum.l1Translation.toString()] ?? false, - showedTranslationChoicesTooltip: - json[InstructionsEnum.translationChoices.toString()] ?? false, - showedSpeechToTextTooltip: - json[InstructionsEnum.speechToText.toString()] ?? false, - showedClickAgainToDeselect: - json[InstructionsEnum.clickAgainToDeselect.toString()] ?? false, - showedMissingVoice: - json[InstructionsEnum.missingVoice.toString()] ?? false, - showedClickBestOption: - json[InstructionsEnum.clickBestOption.toString()] ?? false, - showedUnlockedLanguageTools: - json[InstructionsEnum.unlockedLanguageTools.toString()] ?? false, - ); - - Map toJson() { - final Map data = {}; - data[InstructionsEnum.itInstructions.toString()] = showedItInstructions; - data[InstructionsEnum.clickMessage.toString()] = showedClickMessage; - data[InstructionsEnum.blurMeansTranslate.toString()] = - showedBlurMeansTranslate; - data[InstructionsEnum.tooltipInstructions.toString()] = - showedTooltipInstructions; - data[InstructionsEnum.l1Translation.toString()] = - showedL1TranslationTooltip; - data[InstructionsEnum.translationChoices.toString()] = - showedTranslationChoicesTooltip; - data[InstructionsEnum.speechToText.toString()] = showedSpeechToTextTooltip; - data[InstructionsEnum.clickAgainToDeselect.toString()] = - showedClickAgainToDeselect; - data[InstructionsEnum.missingVoice.toString()] = showedMissingVoice; - data[InstructionsEnum.clickBestOption.toString()] = showedClickBestOption; - data[InstructionsEnum.unlockedLanguageTools.toString()] = - showedUnlockedLanguageTools; - return data; - } - - factory UserInstructions.migrateFromAccountData() { - final accountData = - MatrixState.pangeaController.matrixState.client.accountData; - return UserInstructions( - showedItInstructions: - (accountData[InstructionsEnum.itInstructions.toString()] - ?.content[InstructionsEnum.itInstructions.toString()] - as bool?) ?? - false, - showedClickMessage: (accountData[InstructionsEnum.clickMessage.toString()] - ?.content[InstructionsEnum.clickMessage.toString()] as bool?) ?? - false, - showedBlurMeansTranslate: - (accountData[InstructionsEnum.blurMeansTranslate.toString()] - ?.content[InstructionsEnum.blurMeansTranslate.toString()] - as bool?) ?? - false, - showedTooltipInstructions: - (accountData[InstructionsEnum.tooltipInstructions.toString()] - ?.content[InstructionsEnum.tooltipInstructions.toString()] - as bool?) ?? - false, - showedL1TranslationTooltip: - (accountData[InstructionsEnum.l1Translation.toString()] - ?.content[InstructionsEnum.l1Translation.toString()] - as bool?) ?? - false, - showedTranslationChoicesTooltip: - (accountData[InstructionsEnum.translationChoices.toString()] - ?.content[InstructionsEnum.translationChoices.toString()] - as bool?) ?? - false, - showedSpeechToTextTooltip: - (accountData[InstructionsEnum.speechToText.toString()] - ?.content[InstructionsEnum.speechToText.toString()] - as bool?) ?? - false, - showedClickAgainToDeselect: (accountData[ - InstructionsEnum.clickAgainToDeselect.toString()] - ?.content[InstructionsEnum.clickAgainToDeselect.toString()] - as bool?) ?? - false, - ); - } -} - /// A wrapper around the matrix account data for the user profile. /// Enables easy access to the profile data and saving new data. class Profile { late UserSettings userSettings; late UserToolSettings toolSettings; - late UserInstructions instructionSettings; + late InstructionSettings instructionSettings; Profile({ required this.userSettings, UserToolSettings? toolSettings, - UserInstructions? instructionSettings, + InstructionSettings? instructionSettings, }) { this.toolSettings = toolSettings ?? UserToolSettings(); - this.instructionSettings = instructionSettings ?? UserInstructions(); + this.instructionSettings = instructionSettings ?? InstructionSettings(); } /// Load an instance of profile from the client's account data. @@ -347,10 +225,10 @@ class Profile { ) : UserToolSettings(), instructionSettings: instructionSettingsContent != null - ? UserInstructions.fromJson( + ? InstructionSettings.fromJson( instructionSettingsContent as Map, ) - : UserInstructions(), + : InstructionSettings(), ); } @@ -370,7 +248,7 @@ class Profile { if (userSettings == null) return null; final toolSettings = UserToolSettings.migrateFromAccountData(); - final instructionSettings = UserInstructions.migrateFromAccountData(); + final instructionSettings = InstructionSettings.migrateFromAccountData(); return Profile( userSettings: userSettings, toolSettings: toolSettings, @@ -417,7 +295,7 @@ class Profile { return Profile( userSettings: UserSettings(), toolSettings: UserToolSettings(), - instructionSettings: UserInstructions(), + instructionSettings: InstructionSettings(), ); } } diff --git a/lib/pangea/repo/practice/word_meaning_activity_generator.dart b/lib/pangea/repo/practice/word_meaning_activity_generator.dart index e591f4dba..be0bec197 100644 --- a/lib/pangea/repo/practice/word_meaning_activity_generator.dart +++ b/lib/pangea/repo/practice/word_meaning_activity_generator.dart @@ -1,7 +1,3 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/message_activity_request.dart'; @@ -9,6 +5,8 @@ import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choic import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart'; import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; class WordMeaningActivityGenerator { Future get( @@ -48,7 +46,8 @@ class WordMeaningActivityGenerator { langCode: req.userL2, activityType: ActivityTypeEnum.wordMeaning, content: ActivityContent( - question: L10n.of(context).chooseBestDefinition, + question: + L10n.of(context).whatIsMeaning(lemmaId.lemma, lemmaId.category), choices: choices, answers: [res.meaning], spanDisplayDetails: null, diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart deleted file mode 100644 index 36d12a9eb..000000000 --- a/lib/pangea/utils/instructions.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; -import '../../config/app_config.dart'; -import '../../widgets/matrix.dart'; -import '../controllers/pangea_controller.dart'; -import '../widgets/common/bot_face_svg.dart'; -import '../widgets/igc/card_header.dart'; -import 'bot_style.dart'; -import 'overlay.dart'; - -class InstructionsController { - late PangeaController _pangeaController; - - // We have these three methods to make sure that the instructions are not shown too much - - /// Instruction popup was closed by the user - final Map _instructionsClosed = {}; - - /// Instruction popup has already been shown this session - final Map _instructionsShown = {}; - - InstructionsController(PangeaController pangeaController) { - _pangeaController = pangeaController; - } - - void setToggledOff( - InstructionsEnum key, - bool value, - ) { - _pangeaController.userController.updateProfile((profile) { - switch (key) { - case InstructionsEnum.speechToText: - profile.instructionSettings.showedSpeechToTextTooltip = value; - break; - case InstructionsEnum.l1Translation: - profile.instructionSettings.showedL1TranslationTooltip = value; - break; - case InstructionsEnum.translationChoices: - profile.instructionSettings.showedTranslationChoicesTooltip = value; - break; - case InstructionsEnum.tooltipInstructions: - profile.instructionSettings.showedTooltipInstructions = value; - break; - case InstructionsEnum.itInstructions: - profile.instructionSettings.showedItInstructions = value; - break; - case InstructionsEnum.clickMessage: - profile.instructionSettings.showedClickMessage = value; - break; - case InstructionsEnum.blurMeansTranslate: - profile.instructionSettings.showedBlurMeansTranslate = value; - break; - case InstructionsEnum.clickAgainToDeselect: - profile.instructionSettings.showedClickAgainToDeselect = value; - break; - case InstructionsEnum.missingVoice: - profile.instructionSettings.showedMissingVoice = value; - break; - case InstructionsEnum.clickBestOption: - profile.instructionSettings.showedClickBestOption = value; - break; - case InstructionsEnum.unlockedLanguageTools: - profile.instructionSettings.showedUnlockedLanguageTools = value; - break; - } - return profile; - }); - } - - /// Instruction Card gives users tips on - /// how to use Pangea Chat's features - Future showInstructionsPopup( - BuildContext context, - InstructionsEnum key, - String transformTargetKey, { - bool showToggle = true, - Widget? customContent, - bool forceShow = false, - }) async { - final bool userLangsSet = - await _pangeaController.userController.areUserLanguagesSet; - if (!userLangsSet) { - return; - } - - if ((_instructionsShown[key.toString()] ?? false) && !forceShow) { - return; - } - _instructionsShown[key.toString()] = true; - - if (key.toggledOff() && !forceShow) { - return; - } - - final botStyle = BotStyle.text(context); - Future.delayed( - const Duration(seconds: 1), - () { - if (!context.mounted) return; - OverlayUtil.showPositionedCard( - context: context, - backDropToDismiss: false, - cardToShow: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CardHeader( - text: key.title(L10n.of(context)), - botExpression: BotExpression.idle, - onClose: () => {_instructionsClosed[key.toString()] = true}, - ), - const SizedBox(height: 10.0), - Padding( - padding: const EdgeInsets.all(6.0), - child: Text( - key.body(L10n.of(context)), - style: botStyle, - ), - ), - if (customContent != null) customContent, - if (showToggle) InstructionsToggle(instructionsKey: key), - ], - ), - maxHeight: 300, - maxWidth: 300, - transformTargetId: transformTargetKey, - closePrevOverlay: false, - ); - }, - ); - } -} - -/// User can toggle on to prevent Instruction Card -/// from appearing in future sessions -class InstructionsToggle extends StatefulWidget { - const InstructionsToggle({ - super.key, - required this.instructionsKey, - }); - - final InstructionsEnum instructionsKey; - - @override - InstructionsToggleState createState() => InstructionsToggleState(); -} - -class InstructionsToggleState extends State { - PangeaController pangeaController = MatrixState.pangeaController; - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return SwitchListTile.adaptive( - activeColor: AppConfig.activeToggleColor, - title: Text(L10n.of(context).doNotShowAgain), - value: widget.instructionsKey.toggledOff(), - onChanged: ((value) async { - pangeaController.instructions.setToggledOff( - widget.instructionsKey, - value, - ); - setState(() {}); - }), - ); - } -} diff --git a/lib/pangea/widgets/chat/message_speech_to_text_card.dart b/lib/pangea/widgets/chat/message_speech_to_text_card.dart index 00fb29a6f..dd2085cfe 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -1,22 +1,21 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:material_symbols_icons/symbols.dart'; - import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/speech_to_text_models.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/widgets/common/icon_number_widget.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:material_symbols_icons/symbols.dart'; + import '../../utils/bot_style.dart'; class MessageSpeechToTextCard extends StatefulWidget { @@ -214,7 +213,7 @@ class MessageSpeechToTextCardState extends State { mainAxisSize: MainAxisSize.min, children: [ Expanded( - child: InlineTooltip( + child: InstructionsInlineTooltip( instructionsEnum: InstructionsEnum.speechToText, ), ), diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index 5d5a62e85..adb012531 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -1,16 +1,15 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; class MessageTranslationCard extends StatefulWidget { final PangeaMessageEvent messageEvent; @@ -172,24 +171,24 @@ class MessageTranslationCardState extends State { ), if (notGoingToTranslate && widget.selection == null && - !InstructionsEnum.l1Translation.toggledOff()) + !InstructionsEnum.l1Translation.isToggledOff) const Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( - child: InlineTooltip( + child: InstructionsInlineTooltip( instructionsEnum: InstructionsEnum.l1Translation, ), ), ], ), if (widget.selection != null && - !InstructionsEnum.clickAgainToDeselect.toggledOff()) + !InstructionsEnum.clickAgainToDeselect.isToggledOff) const Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( - child: InlineTooltip( + child: InstructionsInlineTooltip( instructionsEnum: InstructionsEnum.clickAgainToDeselect, ), ), diff --git a/lib/pangea/widgets/chat/tts_controller.dart b/lib/pangea/widgets/chat/tts_controller.dart index e89fbdf51..1a6701b6c 100644 --- a/lib/pangea/widgets/chat/tts_controller.dart +++ b/lib/pangea/widgets/chat/tts_controller.dart @@ -1,19 +1,18 @@ import 'dart:async'; import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_tts/flutter_tts.dart' as flutter_tts; -import 'package:matrix/matrix_api_lite/utils/logs.dart'; -import 'package:text_to_speech/text_to_speech.dart'; - import 'package:fluffychat/pangea/controllers/user_controller.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_show_popup.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'; +import 'package:flutter_tts/flutter_tts.dart' as flutter_tts; +import 'package:matrix/matrix_api_lite/utils/logs.dart'; +import 'package:text_to_speech/text_to_speech.dart'; class TtsController { String? get targetLanguage => @@ -163,7 +162,7 @@ class TtsController { BuildContext context, String eventID, ) async { - await MatrixState.pangeaController.instructions.showInstructionsPopup( + await instructionsShowPopup( context, InstructionsEnum.missingVoice, eventID, diff --git a/lib/pangea/widgets/igc/pangea_rich_text.dart b/lib/pangea/widgets/igc/pangea_rich_text.dart index fc2ae24f6..4b7c5e9b2 100644 --- a/lib/pangea/widgets/igc/pangea_rich_text.dart +++ b/lib/pangea/widgets/igc/pangea_rich_text.dart @@ -1,20 +1,20 @@ import 'dart:developer'; import 'dart:ui'; -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/pages/chat/chat.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar_selection_area.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; + import '../../models/pangea_match_model.dart'; class PangeaRichText extends StatefulWidget { @@ -131,7 +131,7 @@ class PangeaRichTextState extends State { @override Widget build(BuildContext context) { if (blur > 0) { - pangeaController.instructions.showInstructionsPopup( + instructionsShowPopup( context, InstructionsEnum.blurMeansTranslate, widget.pangeaMessageEvent.eventId, diff --git a/lib/pangea/widgets/practice_activity/no_more_practice_card.dart b/lib/pangea/widgets/practice_activity/no_more_practice_card.dart index dda5443a1..b124c6914 100644 --- a/lib/pangea/widgets/practice_activity/no_more_practice_card.dart +++ b/lib/pangea/widgets/practice_activity/no_more_practice_card.dart @@ -1,8 +1,7 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/enum/instructions_enum.dart'; -import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; +import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; +import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; +import 'package:flutter/material.dart'; class StarAnimationWidget extends StatefulWidget { const StarAnimationWidget({super.key}); @@ -79,7 +78,7 @@ class GamifiedTextWidget extends StatelessWidget { child: Column( children: [ StarAnimationWidget(), - InlineTooltip( + InstructionsInlineTooltip( instructionsEnum: InstructionsEnum.unlockedLanguageTools, ), ], diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index f2d9ec9d3..fe4818b52 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -1,11 +1,7 @@ import 'dart:async'; import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; @@ -26,6 +22,8 @@ import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:fluffychat/pangea/widgets/practice_activity/multiple_choice_activity.dart'; import 'package:fluffychat/pangea/widgets/word_zoom/word_zoom_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// The wrapper for practice activity content. /// Handles the activities associated with a message, @@ -70,6 +68,8 @@ class PracticeActivityCardState extends State { PangeaController get pangeaController => MatrixState.pangeaController; String? _error; + String? activityQuestion; + @override void initState() { super.initState(); @@ -122,7 +122,9 @@ class PracticeActivityCardState extends State { } currentCompletionRecord = PracticeActivityRecordModel( - question: activity.question, + question: mounted + ? currentActivity?.question(context, widget.morphFeature) + : currentActivity?.content.question, ); } catch (e, s) { ErrorHandler.logError( diff --git a/lib/pangea/widgets/word_zoom/lemma_meaning_widget.dart b/lib/pangea/widgets/word_zoom/lemma_meaning_widget.dart index 2e3a0eff0..f07d9abb1 100644 --- a/lib/pangea/widgets/word_zoom/lemma_meaning_widget.dart +++ b/lib/pangea/widgets/word_zoom/lemma_meaning_widget.dart @@ -96,7 +96,7 @@ class LemmaMeaningWidgetState extends State { child: Column( children: [ Text( - L10n.of(context).editLemmaMeaning, + "${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(widget.lemma, widget.pos)}", textAlign: TextAlign.center, style: const TextStyle(fontStyle: FontStyle.italic), ), @@ -138,9 +138,13 @@ class LemmaMeaningWidgetState extends State { return GestureDetector( onLongPress: () => _toggleEditMode(true), onDoubleTap: () => _toggleEditMode(true), - child: Text( - snapshot.data!.meaning, - textAlign: TextAlign.center, + child: Tooltip( + message: L10n.of(context).doubleClickToEdit, + waitDuration: const Duration(milliseconds: 2000), + child: Text( + snapshot.data!.meaning, + textAlign: TextAlign.center, + ), ), ); }, diff --git a/lib/pangea/widgets/word_zoom/morphs/morphological_center_widget.dart b/lib/pangea/widgets/word_zoom/morphs/morphological_center_widget.dart index 86ed562ce..f1268989c 100644 --- a/lib/pangea/widgets/word_zoom/morphs/morphological_center_widget.dart +++ b/lib/pangea/widgets/word_zoom/morphs/morphological_center_widget.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/constants/morph_categories_and_labels.dart'; +import 'package:fluffychat/pangea/enum/analytics/morph_categories_enum.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; @@ -67,7 +68,12 @@ class MorphologicalCenterWidgetState extends State { PangeaMessageEvent get pm => widget.pangeaMessageEvent; - Future confirmChanges() async { + /// confirm the changes made by the user + /// this will send a new message to the server + /// with the new morphological tag + Future saveChanges( + PangeaToken Function(PangeaToken token) changeCallback, + ) async { try { // NOTE: it is not clear how this would work if the user was not editing the originalSent tokens // this case would only happen in immersion mode which is disabled until further notice @@ -85,7 +91,7 @@ class MorphologicalCenterWidgetState extends State { if (tokenIndex == -1) { throw Exception("Token not found in message"); } - existingTokens[tokenIndex].morph[widget.morphFeature] = selectedMorphTag; + existingTokens[tokenIndex] = changeCallback(existingTokens[tokenIndex]); // send a new message as an edit to original message to the server // including the new tokens @@ -123,22 +129,27 @@ class MorphologicalCenterWidgetState extends State { } } - List get allMorphTagsForEdit { - final List tags = getLabelsForMorphCategory(widget.morphFeature) - .where( - (tag) => !["punct", "space", "sym", "x", "other"] - .contains(tag.toLowerCase()), - ) - .toList(); + /// all morphological tags for the selected morphological category + /// that are eligible for setting as the morphological tag + List get allMorphTagsForEdit => + getLabelsForMorphCategory(widget.morphFeature) + .where( + (tag) => !["punct", "space", "sym", "x", "other"] + .contains(tag.toLowerCase()), + ) + .toList(); - // as long as the feature is not POS, add a nan tag - // this will allow the user to remove the feature from the tags - if (widget.morphFeature.toLowerCase() != "pos") { - tags.add(L10n.of(context).constructUseNanDesc); - } + String get morphCopy => + getMorphologicalCategoryCopy(widget.morphFeature, context) ?? + widget.morphFeature; - return tags; - } + String get tagCopy => + getGrammarCopy( + category: widget.morphFeature, + lemma: selectedMorphTag, + context: context, + ) ?? + selectedMorphTag; @override Widget build(BuildContext context) { @@ -146,14 +157,13 @@ class MorphologicalCenterWidgetState extends State { return GestureDetector( onLongPress: enterEditMode, onDoubleTap: enterEditMode, - child: Text( - getGrammarCopy( - category: widget.morphFeature, - lemma: widget.token.morph[widget.morphFeature], - context: context, - ) ?? - widget.token.morph[widget.morphFeature]!, - textAlign: TextAlign.center, + child: Tooltip( + message: L10n.of(context).doubleClickToEdit, + waitDuration: const Duration(milliseconds: 2000), + child: Text( + "$morphCopy: $tagCopy", + textAlign: TextAlign.center, + ), ), ); } @@ -161,7 +171,10 @@ class MorphologicalCenterWidgetState extends State { return Column( children: [ Text( - L10n.of(context).editMorphologicalLabel, + "${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsTheMorphTag( + morphCopy, + widget.token.text.content, + )}", textAlign: TextAlign.center, style: const TextStyle(fontStyle: FontStyle.italic), ), @@ -228,8 +241,20 @@ class MorphologicalCenterWidgetState extends State { ), const SizedBox(height: 10), Row( + spacing: 10, children: [ - //cancel button + // this would allow the user to totally remove the morphological feature from the token + // disabled and let's see if someone asks for it + // if (widget.morphFeature.toLowerCase() != "pos") + // TextButton( + // onPressed: () => saveChanges( + // (token) { + // token.morph.remove(widget.morphFeature); + // return token; + // }, + // ), + // child: Text(L10n.of(context).removeFeature(morphCopy)), + // ), ElevatedButton( onPressed: () { setState(() { @@ -238,15 +263,19 @@ class MorphologicalCenterWidgetState extends State { }, child: Text(L10n.of(context).cancel), ), - const SizedBox(width: 10), ElevatedButton( - onPressed: - selectedMorphTag == widget.token.morph[widget.morphFeature] - ? null - : () => showFutureLoadingDialog( - context: context, - future: confirmChanges, - ), + onPressed: selectedMorphTag == + widget.token.morph[widget.morphFeature] + ? null + : () => showFutureLoadingDialog( + context: context, + future: () => saveChanges( + (token) { + token.morph[widget.morphFeature] = selectedMorphTag; + return token; + }, + ), + ), child: Text(L10n.of(context).saveChanges), ), ],