From b6e27d739ae85c72f2f455b06e965212f59acaf9 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:44:18 -0400 Subject: [PATCH] chore(reading_assistance): persistent distractor selection and fixes to analytics saving --- .../morph_analytics_list_view.dart | 9 +- .../vocab_analytics_list_view.dart | 93 ++----- .../analytics_misc/constructs_model.dart | 18 +- lib/pangea/choreographer/widgets/it_bar.dart | 256 +++++++++--------- lib/pangea/constructs/construct_form.dart | 29 +- .../events/models/pangea_token_model.dart | 4 + .../instructions_inline_tooltip.dart | 55 ++-- .../message_token_button.dart | 6 +- .../emoji_activity_generator.dart | 14 +- .../lemma_meaning_activity_generator.dart | 7 +- .../practice_activity_model.dart | 64 ++--- .../practice_activities/practice_choice.dart | 26 ++ .../practice_activities/practice_match.dart | 38 +-- .../word_focus_listening_generator.dart | 11 +- .../message_morph_choice.dart | 15 +- .../practice_match_card.dart | 10 +- .../practice_match_item.dart | 16 +- .../reading_assistance_input_bar.dart | 124 ++++----- .../widgets/message_selection_overlay.dart | 7 +- pubspec.lock | 94 +++---- 20 files changed, 473 insertions(+), 423 deletions(-) create mode 100644 lib/pangea/practice_activities/practice_choice.dart diff --git a/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart b/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart index c0115a0fd..e0b646d7b 100644 --- a/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart +++ b/lib/pangea/analytics_details_popup/morph_analytics_list_view.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; @@ -15,6 +12,7 @@ import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; import 'package:fluffychat/pangea/user/client_extension.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; class MorphAnalyticsListView extends StatelessWidget { final void Function(ConstructIdentifier) onConstructZoom; @@ -29,10 +27,10 @@ class MorphAnalyticsListView extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - // spacing: 16.0, + spacing: 16.0, children: [ // Add your text widget here const InstructionsInlineTooltip( @@ -84,7 +82,6 @@ class MorphFeatureBox extends StatelessWidget { return Container( padding: const EdgeInsets.all(16.0), - margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppConfig.borderRadius), border: Border.all( diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart index c80ece10c..e62ffab58 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_list_tile.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -10,6 +7,7 @@ import 'package:fluffychat/pangea/constructs/construct_level_enum.dart'; import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; /// Displays vocab analytics, sorted into categories /// (flowers, greens, and seeds) by points @@ -113,14 +111,14 @@ class VocabAnalyticsListViewState extends State { ), ); - return Column( - children: [ - const InstructionsInlineTooltip( - instructionsEnum: InstructionsEnum.analyticsVocabList, - ), - Padding( - padding: const EdgeInsets.all(32.0), - child: AnimatedContainer( + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const InstructionsInlineTooltip( + instructionsEnum: InstructionsEnum.analyticsVocabList, + ), + AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, padding: @@ -175,62 +173,29 @@ class VocabAnalyticsListViewState extends State { ), ), ), - ), - // Padding( - // padding: const EdgeInsets.all(32.0), - // child: Row( - // spacing: _isSearching ? 8.0 : 24.0, - // mainAxisAlignment: MainAxisAlignment.center, - // children: _isSearching - // ? [ - // ConstrainedBox( - // constraints: const BoxConstraints(maxWidth: 200), - // child: TextField( - // autofocus: true, - // controller: _searchController, - // decoration: const InputDecoration( - // contentPadding: EdgeInsets.symmetric( - // vertical: 6.0, - // horizontal: 12.0, - // ), - // isDense: true, - // border: OutlineInputBorder(), - // ), - // onChanged: (value) { - // if (mounted) setState(() {}); - // }, - // ), - // ), - // IconButton( - // icon: const Icon(Icons.close), - // onPressed: _toggleSearching, - // ), - // ] - // : filters, - // ), - // ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: GridView.builder( - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 100.0, - mainAxisExtent: 100.0, - crossAxisSpacing: 8.0, - mainAxisSpacing: 8.0, + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 100.0, + mainAxisExtent: 100.0, + crossAxisSpacing: 8.0, + mainAxisSpacing: 8.0, + ), + itemCount: _filteredVocab.length, + itemBuilder: (context, index) { + final vocabItem = _filteredVocab[index]; + return VocabAnalyticsListTile( + onTap: () => widget.onConstructZoom(vocabItem.id), + constructUse: vocabItem, + ); + }, ), - itemCount: _filteredVocab.length, - itemBuilder: (context, index) { - final vocabItem = _filteredVocab[index]; - return VocabAnalyticsListTile( - onTap: () => widget.onConstructZoom(vocabItem.id), - constructUse: vocabItem, - ); - }, ), ), - ), - ], + ], + ), ); } } diff --git a/lib/pangea/analytics_misc/constructs_model.dart b/lib/pangea/analytics_misc/constructs_model.dart index 756da3ad0..ab8b21f37 100644 --- a/lib/pangea/analytics_misc/constructs_model.dart +++ b/lib/pangea/analytics_misc/constructs_model.dart @@ -1,14 +1,14 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; - -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/morphs/default_morph_mapping.dart'; +import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_models.dart'; +import 'package:flutter/foundation.dart'; +import 'package:matrix/matrix.dart'; + import 'construct_type_enum.dart'; class ConstructAnalyticsModel { @@ -76,7 +76,7 @@ class OneConstructUse { /// For vocab constructs, this is the POS. For morph /// constructs, this is the morphological category. - String _category; + late String _category; ConstructTypeEnum constructType; ConstructUseTypeEnum useType; @@ -95,7 +95,13 @@ class OneConstructUse { required category, required this.form, this.id, - }) : _category = category ?? "other"; + }) { + if (category is MorphFeaturesEnum) { + _category = category.name; + } else { + _category = category ?? "other"; + } + } String? get chatId => metadata.roomId; String get msgId => metadata.eventId!; diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index cde32a074..686326b79 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/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; @@ -15,6 +12,9 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + import '../../common/utils/overlay.dart'; import '../controllers/it_feedback_controller.dart'; import '../models/it_response_model.dart'; @@ -101,139 +101,145 @@ class ITBarState extends State with SingleTickerProviderStateMixin { axisAlignment: -1.0, child: CompositedTransformTarget( link: widget.choreographer.itBarLinkAndKey.link, - child: Container( - key: widget.choreographer.itBarLinkAndKey.key, - decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.light - ? Colors.white - : Colors.black, - ), - padding: const EdgeInsets.fromLTRB(0, 3, 3, 3), - child: SingleChildScrollView( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, + child: Column( + spacing: 8.0, + children: [ + if (showITInstructionsTooltip) + const InstructionsInlineTooltip( + instructionsEnum: InstructionsEnum.clickBestOption, + animate: false, + ), + if (showTranslationsChoicesTooltip) + const InstructionsInlineTooltip( + instructionsEnum: InstructionsEnum.translationChoices, + animate: false, + ), + Container( + key: widget.choreographer.itBarLinkAndKey.key, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + ), + padding: const EdgeInsets.fromLTRB(0, 3, 3, 3), + child: SingleChildScrollView( + child: Column( children: [ - if (itController.isEditingSourceText) - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 20, - right: 10, - top: 10, - ), - child: TextField( - controller: TextEditingController( - text: itController.sourceText, - ), - autofocus: true, - enableSuggestions: false, - maxLines: null, - textInputAction: TextInputAction.send, - onSubmitted: itController.onEditSourceTextSubmit, - obscureText: false, - decoration: const InputDecoration( - border: OutlineInputBorder(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (itController.isEditingSourceText) + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 20, + right: 10, + top: 10, + ), + child: TextField( + controller: TextEditingController( + text: itController.sourceText, + ), + autofocus: true, + enableSuggestions: false, + maxLines: null, + textInputAction: TextInputAction.send, + onSubmitted: + itController.onEditSourceTextSubmit, + obscureText: false, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), ), ), + if (!itController.isEditingSourceText && + itController.sourceText != null) + SizedBox( + width: iconDimension, + height: iconDimension, + child: IconButton( + iconSize: iconSize, + color: Theme.of(context).colorScheme.primary, + onPressed: () { + if (itController.nextITStep != null) { + itController.setIsEditingSourceText(true); + } + }, + icon: const Icon(Icons.edit_outlined), + // iconSize: 20, + ), + ), + if (!itController.isEditingSourceText) + SizedBox( + width: iconDimension, + height: iconDimension, + child: IconButton( + iconSize: iconSize, + color: Theme.of(context).colorScheme.primary, + icon: const Icon(Icons.settings_outlined), + onPressed: () => showDialog( + context: context, + builder: (c) => const SettingsLearning(), + barrierDismissible: false, + ), + ), + ), + SizedBox( + width: iconDimension, + height: iconDimension, + child: IconButton( + iconSize: iconSize, + color: Theme.of(context).colorScheme.primary, + icon: const Icon(Icons.close_outlined), + onPressed: () { + itController.isEditingSourceText + ? itController.setIsEditingSourceText(false) + : itController.closeIT(); + }, + ), ), - ), - if (!itController.isEditingSourceText && - itController.sourceText != null) - SizedBox( - width: iconDimension, - height: iconDimension, - child: IconButton( - iconSize: iconSize, - color: Theme.of(context).colorScheme.primary, - onPressed: () { - if (itController.nextITStep != null) { - itController.setIsEditingSourceText(true); - } - }, - icon: const Icon(Icons.edit_outlined), - // iconSize: 20, - ), - ), + ], + ), if (!itController.isEditingSourceText) - SizedBox( - width: iconDimension, - height: iconDimension, - child: IconButton( - iconSize: iconSize, - color: Theme.of(context).colorScheme.primary, - icon: const Icon(Icons.settings_outlined), - onPressed: () => showDialog( - context: context, - builder: (c) => const SettingsLearning(), - barrierDismissible: false, - ), - ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: itController.sourceText != null + ? Text( + itController.sourceText!, + textAlign: TextAlign.center, + ) + : const LinearProgressIndicator(), ), - SizedBox( - width: iconDimension, - height: iconDimension, - child: IconButton( - iconSize: iconSize, - color: Theme.of(context).colorScheme.primary, - icon: const Icon(Icons.close_outlined), - onPressed: () { - itController.isEditingSourceText - ? itController.setIsEditingSourceText(false) - : itController.closeIT(); - }, + const SizedBox(height: 8.0), + Container( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + constraints: const BoxConstraints(minHeight: 80), + child: AnimatedSize( + duration: itController.animationSpeed, + child: Center( + child: itController.choreographer.errorService.isError + ? ITError( + error: itController + .choreographer.errorService.error!, + controller: itController, + ) + : itController.showChoiceFeedback + ? ChoiceFeedbackText( + controller: itController, + ) + : itController.isTranslationDone + ? TranslationFeedback( + controller: itController, + ) + : ITChoices(controller: itController), + ), ), ), ], ), - if (!itController.isEditingSourceText) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: itController.sourceText != null - ? Text( - itController.sourceText!, - textAlign: TextAlign.center, - ) - : const LinearProgressIndicator(), - ), - const SizedBox(height: 8.0), - if (showITInstructionsTooltip) - const InstructionsInlineTooltip( - instructionsEnum: InstructionsEnum.clickBestOption, - ), - if (showTranslationsChoicesTooltip) - const InstructionsInlineTooltip( - instructionsEnum: InstructionsEnum.translationChoices, - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - constraints: const BoxConstraints(minHeight: 80), - child: AnimatedSize( - duration: itController.animationSpeed, - child: Center( - child: itController.choreographer.errorService.isError - ? ITError( - error: itController - .choreographer.errorService.error!, - controller: itController, - ) - : itController.showChoiceFeedback - ? ChoiceFeedbackText( - controller: itController, - ) - : itController.isTranslationDone - ? TranslationFeedback( - controller: itController, - ) - : ITChoices(controller: itController), - ), - ), - ), - ], + ), ), - ), + ], ), ), ); diff --git a/lib/pangea/constructs/construct_form.dart b/lib/pangea/constructs/construct_form.dart index 949657155..50f62d47d 100644 --- a/lib/pangea/constructs/construct_form.dart +++ b/lib/pangea/constructs/construct_form.dart @@ -1,13 +1,16 @@ import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; class ConstructForm { - String form; - ConstructIdentifier cId; + /// Form of the construct + final String form; - ConstructForm( - this.form, - this.cId, - ); + /// The constructIdenfifier + final ConstructIdentifier cId; + + ConstructForm({ + required this.form, + required this.cId, + }); @override bool operator ==(Object other) { @@ -18,4 +21,18 @@ class ConstructForm { @override int get hashCode => form.hashCode ^ cId.hashCode; + + factory ConstructForm.fromJson(Map json) { + return ConstructForm( + form: json['form'], + cId: ConstructIdentifier.fromJson(json['cId']), + ); + } + + Map toJson() { + return { + 'form': form, + 'cId': cId.toJson(), + }; + } } diff --git a/lib/pangea/events/models/pangea_token_model.dart b/lib/pangea/events/models/pangea_token_model.dart index 5a0478795..c51e00814 100644 --- a/lib/pangea/events/models/pangea_token_model.dart +++ b/lib/pangea/events/models/pangea_token_model.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/constructs/construct_level_enum.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; @@ -493,6 +494,9 @@ class PangeaToken { category: pos, ); + ConstructForm get vocabForm => + ConstructForm(form: text.content, cId: vocabConstructID); + /// [setEmoji] sets the emoji for the lemma /// NOTE: assumes that the language of the lemma is the same as the user's current l2 Future setEmoji(List emojis) => diff --git a/lib/pangea/instructions/instructions_inline_tooltip.dart b/lib/pangea/instructions/instructions_inline_tooltip.dart index fc44d43d6..6d4a07b6a 100644 --- a/lib/pangea/instructions/instructions_inline_tooltip.dart +++ b/lib/pangea/instructions/instructions_inline_tooltip.dart @@ -9,11 +9,15 @@ import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; class InstructionsInlineTooltip extends StatefulWidget { final InstructionsEnum instructionsEnum; final bool bold; + final bool animate; + final double padding; const InstructionsInlineTooltip({ super.key, required this.instructionsEnum, this.bold = false, + this.animate = true, + this.padding = 0.0, }); @override @@ -24,8 +28,8 @@ class InstructionsInlineTooltip extends StatefulWidget { class InstructionsInlineTooltipState extends State with TickerProviderStateMixin { bool _isToggledOff = true; - late AnimationController _controller; - late Animation _animation; + AnimationController? _controller; + Animation? _animation; @override void didUpdateWidget(covariant InstructionsInlineTooltip oldWidget) { @@ -44,26 +48,28 @@ class InstructionsInlineTooltipState extends State void setToggled() { _isToggledOff = widget.instructionsEnum.isToggledOff; - // Initialize AnimationController and Animation - _controller = AnimationController( - duration: FluffyThemes.animationDuration, - vsync: this, - ); + if (widget.animate) { + // Initialize AnimationController and Animation only if animate is true + _controller = AnimationController( + duration: FluffyThemes.animationDuration, + vsync: this, + ); - _animation = CurvedAnimation( - parent: _controller, - curve: Curves.easeInOut, - ); + _animation = CurvedAnimation( + parent: _controller!, + curve: Curves.easeInOut, + ); - // Start in correct state - if (!_isToggledOff) _controller.forward(); + // Start in correct state + if (!_isToggledOff) _controller!.forward(); + } setState(() {}); } @override void dispose() { - _controller.dispose(); + _controller?.dispose(); super.dispose(); } @@ -71,15 +77,28 @@ class InstructionsInlineTooltipState extends State widget.instructionsEnum.setToggledOff(true); setState(() { _isToggledOff = true; - _controller.reverse(); + if (widget.animate) { + _controller?.reverse(); + } }); } @override Widget build(BuildContext context) { - return SizeTransition( - sizeFactor: _animation, - axisAlignment: -1.0, + return widget.animate + ? SizeTransition( + sizeFactor: _animation!, + axisAlignment: -1.0, + child: _buildTooltipContent(context), + ) + : (_isToggledOff + ? const SizedBox.shrink() + : _buildTooltipContent(context)); + } + + Widget _buildTooltipContent(BuildContext context) { + return Padding( + padding: EdgeInsets.all(widget.padding), child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppConfig.borderRadius), diff --git a/lib/pangea/message_token_text/message_token_button.dart b/lib/pangea/message_token_text/message_token_button.dart index 31fde812f..dcb13e46f 100644 --- a/lib/pangea/message_token_text/message_token_button.dart +++ b/lib/pangea/message_token_text/message_token_button.dart @@ -9,10 +9,10 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/message_token_text/dotted_border_painter.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; +import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; import 'package:fluffychat/pangea/practice_activities/practice_target.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart'; @@ -129,7 +129,7 @@ class MessageTokenButtonState extends State PracticeTarget? get activity => widget.practiceTarget; - onMatch(ConstructForm form) { + onMatch(PracticeChoice form) { if (widget.overlayController?.activity == null) { debugger(when: kDebugMode); ErrorHandler.logError( @@ -256,7 +256,7 @@ class MessageTokenButtonState extends State ); } - return DragTarget( + return DragTarget( builder: (BuildContext context, accepted, rejected) { final double colorAlpha = 0.3 + (widget.overlayController?.selectedChoice != null ? 0.4 : 0.0) + diff --git a/lib/pangea/practice_activities/emoji_activity_generator.dart b/lib/pangea/practice_activities/emoji_activity_generator.dart index a5ac9cc12..6b2607abd 100644 --- a/lib/pangea/practice_activities/emoji_activity_generator.dart +++ b/lib/pangea/practice_activities/emoji_activity_generator.dart @@ -1,8 +1,4 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; +import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; @@ -10,6 +6,8 @@ import 'package:fluffychat/pangea/practice_activities/message_activity_request.d import 'package:fluffychat/pangea/practice_activities/multiple_choice_activity_model.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; import 'package:fluffychat/pangea/practice_activities/practice_match.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; class EmojiActivityGenerator { Future get( @@ -57,8 +55,8 @@ class EmojiActivityGenerator { final List lemmaInfos = await Future.wait(lemmaInfoFutures); - final Map> matchInfo = Map.fromIterables( - req.targetTokens.map((token) => token.vocabConstructID), + final Map> matchInfo = Map.fromIterables( + req.targetTokens.map((token) => token.vocabForm), lemmaInfos.map((e) => e.emoji), ); @@ -67,7 +65,7 @@ class EmojiActivityGenerator { activityType: ActivityTypeEnum.emoji, targetTokens: req.targetTokens, langCode: req.userL2, - matchContent: PracticeMatch( + matchContent: PracticeMatchActivity( matchInfo: matchInfo, ), ), diff --git a/lib/pangea/practice_activities/lemma_meaning_activity_generator.dart b/lib/pangea/practice_activities/lemma_meaning_activity_generator.dart index 59a8969a0..333d769c2 100644 --- a/lib/pangea/practice_activities/lemma_meaning_activity_generator.dart +++ b/lib/pangea/practice_activities/lemma_meaning_activity_generator.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart'; @@ -80,8 +81,8 @@ class LemmaMeaningActivityGenerator { final List lemmaInfos = await Future.wait(lemmaInfoFutures); - final Map> matchInfo = Map.fromIterables( - req.targetTokens.map((token) => token.vocabConstructID), + final Map> matchInfo = Map.fromIterables( + req.targetTokens.map((token) => token.vocabForm), lemmaInfos.map((lemmaInfo) => [lemmaInfo.meaning]), ); @@ -90,7 +91,7 @@ class LemmaMeaningActivityGenerator { activityType: ActivityTypeEnum.wordMeaning, targetTokens: req.targetTokens, langCode: req.userL2, - matchContent: PracticeMatch( + matchContent: PracticeMatchActivity( matchInfo: matchInfo, ), ), diff --git a/lib/pangea/practice_activities/practice_activity_model.dart b/lib/pangea/practice_activities/practice_activity_model.dart index 4b857f796..a9a97231d 100644 --- a/lib/pangea/practice_activities/practice_activity_model.dart +++ b/lib/pangea/practice_activities/practice_activity_model.dart @@ -11,13 +11,13 @@ import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/constructs/construct_form.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/lemmas/user_set_lemma_info.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/multiple_choice_activity_model.dart'; +import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; import 'package:fluffychat/pangea/practice_activities/practice_match.dart'; import 'package:fluffychat/pangea/practice_activities/practice_record.dart'; import 'package:fluffychat/pangea/practice_activities/practice_target.dart'; @@ -32,7 +32,7 @@ class PracticeActivityModel { final String langCode; final MultipleChoiceActivity? multipleChoiceContent; - final PracticeMatch? matchContent; + final PracticeMatchActivity? matchContent; PracticeActivityModel({ required this.targetTokens, @@ -60,7 +60,7 @@ class PracticeActivityModel { void onMultipleChoiceSelect( PangeaToken token, - ConstructForm choice, + PracticeChoice choice, PangeaMessageEvent? event, void Function() callback, ) { @@ -74,23 +74,24 @@ class PracticeActivityModel { return; } - if (practiceTarget.record.hasTextResponse(choice.form) || isComplete) { + if (practiceTarget.record.hasTextResponse(choice.choiceContent) || + isComplete) { // the user has already selected this choice // so we don't want to record it again return; } final bool isCorrect = multipleChoiceContent!.answers.any( - (answer) => answer.toLowerCase() == choice.form.toLowerCase(), + (answer) => answer.toLowerCase() == choice.choiceContent.toLowerCase(), ); // NOTE: the response is associated with the contructId of the choice, not the selected token // example: the user selects the word "cat" to match with the emoji 🐶 // the response is associated with correct word "dog", not the word "cat" practiceTarget.record.addResponse( - cId: choice.cId, + cId: choice.form.cId, target: practiceTarget, - text: choice.form, + text: choice.choiceContent, score: isCorrect ? 1 : 0, ); @@ -105,15 +106,15 @@ class PracticeActivityModel { constructs: [ OneConstructUse( useType: responseUseType(choice)!, - lemma: choice.cId.lemma, - constructType: choice.cId.type, + lemma: choice.form.cId.lemma, + constructType: choice.form.cId.type, metadata: ConstructUseMetaData( roomId: event?.room.id, timeStamp: DateTime.now(), eventId: event?.eventId, ), - category: choice.cId.category, - form: choice.form, + category: choice.form.cId.category, + form: choice.form.form, ), ], targetID: targetTokens.first.text.uniqueKey, @@ -126,24 +127,25 @@ class PracticeActivityModel { /// only set up for vocab constructs atm void onMatch( PangeaToken token, - ConstructForm choice, + PracticeChoice choice, PangeaMessageEvent? event, void Function() callback, ) { // the user has already selected this choice // so we don't want to record it again - if (practiceTarget.record.hasTextResponse(choice.form) || isComplete) { + if (practiceTarget.record.hasTextResponse(choice.choiceContent) || + isComplete) { return; } bool isCorrect = false; if (multipleChoiceContent != null) { isCorrect = multipleChoiceContent!.answers.any( - (answer) => answer.toLowerCase() == choice.form.toLowerCase(), + (answer) => answer.toLowerCase() == choice.choiceContent.toLowerCase(), ); } else if (matchContent != null) { - isCorrect = matchContent!.matchInfo[token.vocabConstructID]! - .contains(choice.form); + isCorrect = matchContent!.matchInfo[token.vocabForm]! + .contains(choice.choiceContent); } else { debugger(when: kDebugMode); ErrorHandler.logError( @@ -158,8 +160,8 @@ class PracticeActivityModel { // example: the user selects the word "cat" to match with the emoji 🐶 // the response is associated with correct word "dog", not the word "cat" practiceTarget.record.addResponse( - cId: choice.cId, - text: choice.form, + cId: choice.form.cId, + text: choice.choiceContent, target: practiceTarget, score: isCorrect ? 1 : 0, ); @@ -173,14 +175,14 @@ class PracticeActivityModel { constructs: [ OneConstructUse( useType: responseUseType(choice)!, - lemma: choice.cId.lemma, - constructType: choice.cId.type, + lemma: choice.form.cId.lemma, + constructType: choice.form.cId.type, metadata: ConstructUseMetaData( roomId: event?.room.id, timeStamp: DateTime.now(), eventId: event?.eventId, ), - category: choice.cId.category, + category: choice.form.cId.category, // in the case of a wrong answer, the cId doesn't match the token form: token.text.content, ), @@ -193,16 +195,16 @@ class PracticeActivityModel { if (activityType == ActivityTypeEnum.emoji) { // final allEmojis = ; - choice.cId - .setUserLemmaInfo(UserSetLemmaInfo(emojis: [choice.form])) + choice.form.cId + .setUserLemmaInfo(UserSetLemmaInfo(emojis: [choice.choiceContent])) .then((value) { callback(); }); } if (activityType == ActivityTypeEnum.wordMeaning) { - choice.cId - .setUserLemmaInfo(UserSetLemmaInfo(meaning: choice.form)) + choice.form.cId + .setUserLemmaInfo(UserSetLemmaInfo(meaning: choice.choiceContent)) .then((value) { callback(); }); @@ -222,23 +224,23 @@ class PracticeActivityModel { } /// if null, it means the user has not yet responded with that choice - bool? wasCorrectMatch(ConstructForm choice) { + bool? wasCorrectMatch(PracticeChoice choice) { for (final response in practiceTarget.record.responses) { - if (response.cId == choice.cId && response.isCorrect) { + if (response.cId == choice.form.cId && response.isCorrect) { return true; } } for (final response in practiceTarget.record.responses) { - if (response.cId == choice.cId) { + if (response.cId == choice.form.cId) { return false; } } return null; } - ConstructUseTypeEnum? responseUseType(ConstructForm choice) { + ConstructUseTypeEnum? responseUseType(PracticeChoice choice) { for (final response in practiceTarget.record.responses) { - if (response.cId == choice.cId) { + if (response.cId == choice.form.cId) { return response.useType(activityType); } } @@ -324,7 +326,7 @@ class PracticeActivityModel { .map((e) => PangeaToken.fromJson(e as Map)) .toList(), matchContent: json['match_content'] != null - ? PracticeMatch.fromJson(contentMap) + ? PracticeMatchActivity.fromJson(contentMap) : null, morphFeature: json['morph_feature'] != null ? MorphFeaturesEnumExtension.fromString( diff --git a/lib/pangea/practice_activities/practice_choice.dart b/lib/pangea/practice_activities/practice_choice.dart new file mode 100644 index 000000000..b5b9a4c9d --- /dev/null +++ b/lib/pangea/practice_activities/practice_choice.dart @@ -0,0 +1,26 @@ +import 'package:fluffychat/pangea/constructs/construct_form.dart'; + +class PracticeChoice { + /// choiceContent + final String choiceContent; + + /// Form of the associated token + final ConstructForm form; + + PracticeChoice({ + required this.choiceContent, + required this.form, + }); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PracticeChoice && + other.form == form && + other.choiceContent == choiceContent; + } + + @override + int get hashCode => form.hashCode ^ choiceContent.hashCode; +} diff --git a/lib/pangea/practice_activities/practice_match.dart b/lib/pangea/practice_activities/practice_match.dart index a7f1ce256..99984e468 100644 --- a/lib/pangea/practice_activities/practice_match.dart +++ b/lib/pangea/practice_activities/practice_match.dart @@ -1,16 +1,16 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constructs/construct_form.dart'; -import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; +import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; -class PracticeMatch { +class PracticeMatchActivity { /// The constructIdenfifiers involved in the activity /// and the forms that are acceptable answers - final Map> matchInfo; + final Map> matchInfo; - List displayForms = List.empty(growable: true); + List choices = List.empty(growable: true); - PracticeMatch({ + PracticeMatchActivity({ required this.matchInfo, }) { // if there are multiple forms for a construct, pick one to display @@ -21,38 +21,40 @@ class PracticeMatch { // either from that construct's options, or returning to the previous construct // and picking a different form from there for (final ith in matchInfo.entries) { - for (final form in ith.value) { - if (!displayForms.any((element) => element.form == form)) { - displayForms.add(ConstructForm(form, ith.key)); + for (final acceptableAnswer in ith.value) { + if (!choices + .any((element) => element.choiceContent == acceptableAnswer)) { + choices.add( + PracticeChoice(choiceContent: acceptableAnswer, form: ith.key), + ); break; } // TODO: if none found, we can probably pick a different form for the other one } } - // remove any items from matchInfo that don't have an item in displayForms + // remove any items from matchInfo that don't have an item in choices for (final ith in matchInfo.keys) { - if (!displayForms.any((element) => element.cId == ith)) { + if (!choices.any((choice) => choice.form == ith)) { matchInfo.remove(ith); } } } - bool isCorrect(ConstructIdentifier cId, String value) { - return matchInfo[cId]!.contains(value); + bool isCorrect(ConstructForm form, String value) { + return matchInfo[form]!.contains(value); } - factory PracticeMatch.fromJson(Map json) { - final Map> matchInfo = {}; + factory PracticeMatchActivity.fromJson(Map json) { + final Map> matchInfo = {}; for (final constructJson in json['match_info']) { - final ConstructIdentifier cId = - ConstructIdentifier.fromJson(constructJson['cId']); + final ConstructForm cId = ConstructForm.fromJson(constructJson['cId']); final List surfaceForms = List.from(constructJson['forms']); matchInfo[cId] = surfaceForms; } - return PracticeMatch( + return PracticeMatchActivity( matchInfo: matchInfo, ); } @@ -75,7 +77,7 @@ class PracticeMatch { bool operator ==(Object other) { if (identical(this, other)) return true; - return other is PracticeMatch && + return other is PracticeMatchActivity && const MapEquality().equals(other.matchInfo, matchInfo); } diff --git a/lib/pangea/practice_activities/word_focus_listening_generator.dart b/lib/pangea/practice_activities/word_focus_listening_generator.dart index 9d8fdd4a8..cb6664300 100644 --- a/lib/pangea/practice_activities/word_focus_listening_generator.dart +++ b/lib/pangea/practice_activities/word_focus_listening_generator.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart'; @@ -55,10 +56,16 @@ class WordFocusListeningGenerator { activityType: ActivityTypeEnum.wordFocusListening, targetTokens: req.targetTokens, langCode: req.userL2, - matchContent: PracticeMatch( + matchContent: PracticeMatchActivity( matchInfo: Map.fromEntries( req.targetTokens.map( - (token) => MapEntry(token.vocabConstructID, [token.text.content]), + (token) => MapEntry( + ConstructForm( + cId: token.vocabConstructID, + form: token.text.content, + ), + [token.text.content], + ), ), ), ), diff --git a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart index d62127410..3561a7397 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; +import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; @@ -126,11 +127,15 @@ class MessageMorphInputBarContentState widget.activity.onMultipleChoiceSelect( token, - ConstructForm( - choice, - widget.activity.targetTokens.first.morphIdByFeature( - widget.activity.morphFeature!, - )!, + PracticeChoice( + choiceContent: choice, + form: ConstructForm( + cId: widget.activity.targetTokens.first + .morphIdByFeature( + widget.activity.morphFeature!, + )!, + form: token.text.content, + ), ), widget.pangeaMessageEvent, () => overlay.setState(() {}), diff --git a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart index 1c59c922b..22467a7cd 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart @@ -5,9 +5,9 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/choreographer/widgets/choice_animation.dart'; -import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; +import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart'; @@ -81,8 +81,8 @@ class MatchActivityCard extends StatelessWidget { alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, - children: activity.matchContent!.displayForms.map( - (ConstructForm cf) { + children: activity.matchContent!.choices.map( + (PracticeChoice cf) { return ChoiceAnimationWidget( isSelected: overlayController.selectedChoice == cf, isCorrect: currentActivity.wasCorrectMatch(cf) ?? false, @@ -90,10 +90,10 @@ class MatchActivityCard extends StatelessWidget { isSelected: overlayController.selectedChoice == cf, isCorrect: currentActivity.wasCorrectMatch(cf), constructForm: cf, - content: choiceDisplayContent(cf.form, fontSize), + content: choiceDisplayContent(cf.choiceContent, fontSize), audioContent: activityType == ActivityTypeEnum.wordFocusListening - ? cf.form + ? cf.choiceContent : null, overlayController: overlayController, fixedSize: activityType == ActivityTypeEnum.wordMeaning diff --git a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart index 96a9c06db..7cd1f0651 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/constructs/construct_form.dart'; +import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; @@ -22,7 +22,7 @@ class PracticeMatchItem extends StatefulWidget { }); final Widget content; - final ConstructForm constructForm; + final PracticeChoice constructForm; final String? audioContent; final MessageOverlayController overlayController; final double? fixedSize; @@ -129,9 +129,14 @@ class PracticeMatchItemState extends State { ); } + void onTap() { + play(); + widget.overlayController.onChoiceSelect(widget.constructForm); + } + @override Widget build(BuildContext context) { - return LongPressDraggable( + return LongPressDraggable( data: widget.constructForm, feedback: Material( type: MaterialType.transparency, @@ -144,10 +149,7 @@ class PracticeMatchItemState extends State { child: InkWell( onHover: (isHovered) => setState(() => _isHovered = isHovered), borderRadius: BorderRadius.circular(AppConfig.borderRadius), - onTap: () { - play(); - widget.overlayController.onChoiceSelect(widget.constructForm); - }, + onTap: onTap, child: content(context), ), ); diff --git a/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart b/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart index 16c1e7b0b..4b6c24b98 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_mode_locked_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/message_speech_to_text_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_translation_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart'; @@ -35,63 +36,65 @@ class ReadingAssistanceInputBar extends StatelessWidget { ) : null; - switch (overlayController.toolbarMode) { - case MessageMode.messageSpeechToText: - case MessageMode.practiceActivity: - case MessageMode.wordZoom: - case MessageMode.noneSelected: - case MessageMode.messageMeaning: - //TODO: show all emojis for the lemmas and allow sending normal reactions - break; + if (overlayController.pangeaMessageEvent?.isAudioMessage == true) { + return MessageSpeechToTextCard( + messageEvent: overlayController.pangeaMessageEvent!, + ); + } else { + switch (overlayController.toolbarMode) { + case MessageMode.messageSpeechToText: + case MessageMode.practiceActivity: + case MessageMode.wordZoom: + case MessageMode.noneSelected: + case MessageMode.messageMeaning: + //TODO: show all emojis for the lemmas and allow sending normal reactions + break; - case MessageMode.messageTranslation: - if (overlayController.isTranslationUnlocked) { - content = MessageTranslationCard( - messageEvent: overlayController.pangeaMessageEvent!, - ); - } else { - content = MessageModeLockedCard(controller: overlayController); - } + case MessageMode.messageTranslation: + if (overlayController.isTranslationUnlocked) { + content = MessageTranslationCard( + messageEvent: overlayController.pangeaMessageEvent!, + ); + } else { + content = MessageModeLockedCard(controller: overlayController); + } - case MessageMode.wordEmoji: - case MessageMode.wordMeaning: - case MessageMode.listening: - debugPrint( - "reading_assistance_input_bar: wordEmoji or wordMeaning with target: $target", - ); - - if (target != null) { - content = PracticeActivityCard( - pangeaMessageEvent: overlayController.pangeaMessageEvent!, - targetTokensAndActivityType: target, - overlayController: overlayController, - ); - } else { - content = const Text("All done!"); - } - case MessageMode.wordMorph: - if (target != null) { - content = PracticeActivityCard( - pangeaMessageEvent: overlayController.pangeaMessageEvent!, - targetTokensAndActivityType: target, - overlayController: overlayController, - ); - } else if (overlayController.selectedMorph != null) { - content = MorphFocusWidget( - token: overlayController.selectedMorph!.token, - morphFeature: overlayController.selectedMorph!.morph, - pangeaMessageEvent: overlayController.pangeaMessageEvent!, - overlayController: overlayController, - onEditDone: () => overlayController.setState(() {}), - ); - } else { - content = Center( - child: Text( - L10n.of(context).selectForGrammar, - style: Theme.of(context).textTheme.bodyLarge, - ), - ); - } + case MessageMode.wordEmoji: + case MessageMode.wordMeaning: + case MessageMode.listening: + if (target != null) { + content = PracticeActivityCard( + pangeaMessageEvent: overlayController.pangeaMessageEvent!, + targetTokensAndActivityType: target, + overlayController: overlayController, + ); + } else { + content = const Text("All done!"); + } + case MessageMode.wordMorph: + if (target != null) { + content = PracticeActivityCard( + pangeaMessageEvent: overlayController.pangeaMessageEvent!, + targetTokensAndActivityType: target, + overlayController: overlayController, + ); + } else if (overlayController.selectedMorph != null) { + content = MorphFocusWidget( + token: overlayController.selectedMorph!.token, + morphFeature: overlayController.selectedMorph!.morph, + pangeaMessageEvent: overlayController.pangeaMessageEvent!, + overlayController: overlayController, + onEditDone: () => overlayController.setState(() {}), + ); + } else { + content = Center( + child: Text( + L10n.of(context).selectForGrammar, + style: Theme.of(context).textTheme.bodyLarge, + ), + ); + } + } } if (content == null) { @@ -108,17 +111,6 @@ class ReadingAssistanceInputBar extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.showEmojiPicker) return const SizedBox.shrink(); - - final display = controller.editEvent == null && - controller.replyEvent == null && - controller.room.canSendDefaultMessages && - controller.selectedEvents.isNotEmpty; - - if (!display) { - return const SizedBox.shrink(); - } - return Expanded( child: Container( width: overlayController.maxWidth, diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 6871e3c5a..957563658 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -12,7 +12,6 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/utils/overlay.dart'; -import 'package:fluffychat/pangea/constructs/construct_form.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/event_wrappers/pangea_representation_event.dart'; @@ -23,6 +22,7 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; +import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; import 'package:fluffychat/pangea/practice_activities/practice_selection.dart'; import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart'; import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; @@ -77,7 +77,7 @@ class MessageOverlayController extends State MorphSelection? selectedMorph; /// tracks selected choice - ConstructForm? selectedChoice; + PracticeChoice? selectedChoice; PangeaTokenText? _selectedSpan; @@ -336,7 +336,7 @@ class MessageOverlayController extends State } } - void onChoiceSelect(ConstructForm choice, [bool force = false]) { + void onChoiceSelect(PracticeChoice choice, [bool force = false]) { if (selectedChoice == choice && !force) { selectedChoice = null; } else { @@ -399,6 +399,7 @@ class MessageOverlayController extends State /// you have to complete one of the mode mini-games to unlock translation bool get isTranslationUnlocked => + pangeaMessageEvent?.ownMessage == true || !messageInUserL2 || (messageLemmaInfos?.isEmpty ?? false) || isEmojiDone || diff --git a/pubspec.lock b/pubspec.lock index b50135df6..d4dfca5d9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -114,10 +114,10 @@ packages: dependency: "direct main" description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" audio_session: dependency: transitive description: @@ -218,10 +218,10 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" cached_network_image: dependency: "direct main" description: @@ -266,10 +266,10 @@ packages: dependency: "direct main" description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" charcode: dependency: transitive description: @@ -306,18 +306,18 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: "direct main" description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" colorize: dependency: transitive description: @@ -506,10 +506,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" fcm_shared_isolate: dependency: "direct main" description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_picker: dependency: "direct main" description: @@ -1389,18 +1389,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -1477,10 +1477,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -1510,10 +1510,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mgrs_dart: dependency: transitive description: @@ -1694,10 +1694,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -1822,10 +1822,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" platform_detect: dependency: transitive description: @@ -1902,10 +1902,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" proj4dart: dependency: transitive description: @@ -2283,10 +2283,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -2363,26 +2363,26 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" string_validator: dependency: transitive description: @@ -2435,34 +2435,34 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test: dependency: transitive description: name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.25.8" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.8" text_to_speech: dependency: "direct main" description: @@ -2755,10 +2755,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" wakelock_plus: dependency: "direct main" description: @@ -2888,5 +2888,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.27.0"