diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 82ac9ca96..448987a4c 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4814,7 +4814,7 @@ "pleaseEnterInt": "Please enter a number", "home": "Home", "join": "Join", - "readingAssistanceOverviewBody": "Click the buttons below for mini-games on visualizing vocab, practice listening, meaning, and grammar concepts. Click any word for details.", + "readingAssistanceOverviewBody": "Click the buttons below for mini-games on matching emojis, audios, word meanings, and grammar concepts. Or click on any word for details.", "learnByTexting": "Learn by texting", "levelSummaryTrigger": "View summary", "levelSummaryPopupTitle": "Level {level} Summary", diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 5e098c190..6cfb57ad1 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -1,8 +1,6 @@ -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/common/config/environment.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; abstract class AppConfig { // #Pangea @@ -28,7 +26,7 @@ abstract class AppConfig { static const double toolbarMinWidth = 350.0; static const double defaultHeaderHeight = 56.0; static const double readingAssistanceInputBarHeight = 170; - static const double toolbarButtonsHeight = 100.0; + static const double toolbarButtonsHeight = 50.0; static const double toolbarSpacing = 8.0; static const double toolbarIconSize = 24.0; diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 025de396d..24d35a4cc 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -4,22 +4,9 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:collection/collection.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:matrix/matrix.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:universal_html/html.dart' as html; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/themes.dart'; @@ -64,6 +51,18 @@ import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart' import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/share_scaffold_dialog.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:matrix/matrix.dart'; +import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:universal_html/html.dart' as html; + import '../../utils/account_bundles.dart'; import '../../utils/localized_exception_extension.dart'; import 'send_file_dialog.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 3d0f38ca6..82779c933 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; @@ -88,9 +86,7 @@ class MorphFeatureBox extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppConfig.borderRadius), border: Border.all( - color: Theme.of(context).brightness == Brightness.dark - ? AppConfig.primaryColorLight - : Theme.of(context).colorScheme.primary, + color: Theme.of(context).colorScheme.primary, width: 2, ), ), diff --git a/lib/pangea/analytics_details_popup/morph_meaning_widget.dart b/lib/pangea/analytics_details_popup/morph_meaning_widget.dart index 858d0bfaf..fb1c5697f 100644 --- a/lib/pangea/analytics_details_popup/morph_meaning_widget.dart +++ b/lib/pangea/analytics_details_popup/morph_meaning_widget.dart @@ -1,14 +1,12 @@ import 'dart:developer'; import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart'; import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; import 'package:fluffychat/pangea/morphs/morph_meaning/morph_info_repo.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; class MorphMeaningWidget extends StatefulWidget { final String feature; @@ -33,11 +31,24 @@ class MorphMeaningWidgetState extends State { late TextEditingController _controller; static const int maxCharacters = 140; String? _cachedResponse; + bool _isLoading = true; + String? _error; + + @override + void didUpdateWidget(covariant MorphMeaningWidget oldWidget) { + if (oldWidget.tag != widget.tag || oldWidget.feature != widget.feature) { + _cachedResponse = null; + _isLoading = true; + _loadMorphMeaning(); + } + super.didUpdateWidget(oldWidget); + } @override void initState() { super.initState(); _controller = TextEditingController(); + _loadMorphMeaning(); } @override @@ -46,6 +57,19 @@ class MorphMeaningWidgetState extends State { super.dispose(); } + Future _loadMorphMeaning() async { + try { + final response = await _morphMeaning(); + _setMeaningText(response); + } catch (e) { + _error = e.toString(); + } finally { + setState(() { + _isLoading = false; + }); + } + } + Future _morphMeaning() async { if (_cachedResponse != null) { return _cachedResponse!; @@ -87,62 +111,56 @@ class MorphMeaningWidgetState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( - future: _morphMeaning(), - builder: (context, snapshot) { - if (snapshot.hasData) { - _setMeaningText(snapshot.data!); - } + if (_isLoading) { + return const TextLoadingShimmer(); + } - if (_editMode) { - return MorphEditView( - morphFeature: widget.feature, - morphTag: widget.tag, - meaning: snapshot.data ?? "", - controller: _controller, - toggleEditMode: _toggleEditMode, - editMorphMeaning: editMorphMeaning, - ); - } + if (_error != null) { + debugger(when: kDebugMode); + return Text( + L10n.of(context).oopsSomethingWentWrong, + textAlign: TextAlign.center, + style: widget.style, + ); + } - if (snapshot.connectionState != ConnectionState.done) { - return const TextLoadingShimmer(); - } + if (_editMode) { + return MorphEditView( + morphFeature: widget.feature, + morphTag: widget.tag, + meaning: _cachedResponse ?? "", + controller: _controller, + toggleEditMode: _toggleEditMode, + editMorphMeaning: editMorphMeaning, + ); + } - if (snapshot.hasError || snapshot.data == null) { - debugger(when: kDebugMode); - return Text( - L10n.of(context).oopsSomethingWentWrong, - textAlign: TextAlign.center, - style: widget.style, - ); - } - - return Row( - children: [ - Flexible( - child: Tooltip( - triggerMode: TooltipTriggerMode.tap, - message: L10n.of(context).doubleClickToEdit, - child: GestureDetector( - onLongPress: () => _toggleEditMode(true), - onDoubleTap: () => _toggleEditMode(true), - child: RichText( - text: TextSpan( - style: widget.style, - children: [ - if (widget.leading != null) widget.leading!, - if (widget.leading != null) const TextSpan(text: ' '), - TextSpan(text: snapshot.data!), - ], - ), - ), + return Row( + mainAxisAlignment: widget.leading != null + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + Flexible( + child: Tooltip( + triggerMode: TooltipTriggerMode.tap, + message: L10n.of(context).doubleClickToEdit, + child: GestureDetector( + onLongPress: () => _toggleEditMode(true), + onDoubleTap: () => _toggleEditMode(true), + child: RichText( + text: TextSpan( + style: widget.style, + children: [ + if (widget.leading != null) widget.leading!, + if (widget.leading != null) const TextSpan(text: ' '), + TextSpan(text: _cachedResponse!), + ], ), ), ), - ], - ); - }, + ), + ), + ], ); } } diff --git a/lib/pangea/analytics_misc/get_analytics_controller.dart b/lib/pangea/analytics_misc/get_analytics_controller.dart index 2f1d125dc..398003ed5 100644 --- a/lib/pangea/analytics_misc/get_analytics_controller.dart +++ b/lib/pangea/analytics_misc/get_analytics_controller.dart @@ -1,12 +1,6 @@ import 'dart:async'; import 'dart:math'; -import 'package:flutter/material.dart'; - -import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; - import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_list_model.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; @@ -23,6 +17,10 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/practice_activities/message_analytics_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:matrix/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; /// A minimized version of AnalyticsController that get the logged in user's analytics class GetAnalyticsController extends BaseController { diff --git a/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart b/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart index 3cf53c557..1cc059a67 100644 --- a/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart +++ b/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart @@ -1,13 +1,12 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.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/practice_activities/message_analytics_controller.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_token_text.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + import '../../../utils/matrix_sdk_extensions/matrix_locals.dart'; class ChatListItemSubtitle extends StatelessWidget { @@ -91,7 +90,6 @@ class ChatListItemSubtitle extends StatelessWidget { final analyticsEntry = tokens != null ? MessageAnalyticsController.get( tokens, - pangeaMessageEvent, ) : null; diff --git a/lib/pangea/choreographer/widgets/igc/card_header.dart b/lib/pangea/choreographer/widgets/igc/card_header.dart index ab98e82db..ae7550951 100644 --- a/lib/pangea/choreographer/widgets/igc/card_header.dart +++ b/lib/pangea/choreographer/widgets/igc/card_header.dart @@ -1,8 +1,7 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/bot/utils/bot_style.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; + import '../../../bot/widgets/bot_face_svg.dart'; class CardHeader extends StatelessWidget { @@ -49,9 +48,6 @@ class CardHeader extends StatelessWidget { if (onClose != null) onClose!(); MatrixState.pAnyState.closeOverlay(); }, - color: Theme.of(context).brightness == Brightness.dark - ? AppConfig.primaryColorLight - : Theme.of(context).colorScheme.primary, ), ], ), diff --git a/lib/pangea/events/models/pangea_token_model.dart b/lib/pangea/events/models/pangea_token_model.dart index fd990b0f2..c1478f93a 100644 --- a/lib/pangea/events/models/pangea_token_model.dart +++ b/lib/pangea/events/models/pangea_token_model.dart @@ -1,9 +1,6 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -23,6 +20,8 @@ import 'package:fluffychat/pangea/morphs/parts_of_speech_enum.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; + import '../../common/constants/model_keys.dart'; import '../../lemmas/lemma.dart'; @@ -640,9 +639,9 @@ class PangeaToken { ); } - /// [0,infinity) - a lower number means higher priority + /// [0,infinity) - a higher number means higher priority int activityPriorityScore(ActivityTypeEnum a, String? morphFeature) { return daysSinceLastUseByType(a, morphFeature) * - (vocabConstructID.isContentWord ? 1 : 2); + (vocabConstructID.isContentWord ? 10 : 9); } } diff --git a/lib/pangea/events/utils/message_text_util.dart b/lib/pangea/events/utils/message_text_util.dart index 6e5297c46..1f5e8d491 100644 --- a/lib/pangea/events/utils/message_text_util.dart +++ b/lib/pangea/events/utils/message_text_util.dart @@ -1,9 +1,8 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/pangea/common/utils/error_handler.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/practice_activities/message_analytics_controller.dart'; +import 'package:fluffychat/pangea/practice_activities/message_analytics_entry.dart'; +import 'package:flutter/material.dart'; class TokenPosition { /// Start index of the full substring in the message diff --git a/lib/pangea/instructions/instructions_inline_tooltip.dart b/lib/pangea/instructions/instructions_inline_tooltip.dart index 0f664a225..2c410be34 100644 --- a/lib/pangea/instructions/instructions_inline_tooltip.dart +++ b/lib/pangea/instructions/instructions_inline_tooltip.dart @@ -1,10 +1,8 @@ -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/instructions/instructions_enum.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; class InstructionsInlineTooltip extends StatefulWidget { final InstructionsEnum instructionsEnum; @@ -29,7 +27,6 @@ class InstructionsInlineTooltipState extends State @override void didUpdateWidget(covariant InstructionsInlineTooltip oldWidget) { - debugPrint("InstructionsInlineTooltip didUpdateWidget"); if (oldWidget.instructionsEnum != widget.instructionsEnum) { setToggled(); } diff --git a/lib/pangea/lemmas/lemma_emoji_row.dart b/lib/pangea/lemmas/lemma_emoji_row.dart index c79849dd9..02428bf86 100644 --- a/lib/pangea/lemmas/lemma_emoji_row.dart +++ b/lib/pangea/lemmas/lemma_emoji_row.dart @@ -1,9 +1,6 @@ import 'dart:developer'; import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_emojis.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -13,6 +10,8 @@ import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; class LemmaEmojiRow extends StatefulWidget { final ConstructIdentifier cId; @@ -89,6 +88,7 @@ class LemmaEmojiRowState extends State { blurBackground: false, borderColor: Theme.of(context).colorScheme.primary, closePrevOverlay: false, + offset: const Offset(0, 60), ); } catch (e, s) { debugger(when: kDebugMode); @@ -109,6 +109,11 @@ class LemmaEmojiRowState extends State { .catchError((e, s) { debugger(when: kDebugMode); ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s); + }).then((_) { + if (mounted) { + widget.emojiSetCallback?.call(); + setState(() {}); + } }); MatrixState.pAnyState.closeOverlay(); @@ -124,46 +129,49 @@ class LemmaEmojiRowState extends State { @override Widget build(BuildContext context) { - return CompositedTransformTarget( - link: MatrixState.pAnyState - .layerLinkAndKey( - widget.cId.string, - ) - .link, - child: Container( - key: MatrixState.pAnyState + return Material( + child: CompositedTransformTarget( + link: MatrixState.pAnyState .layerLinkAndKey( widget.cId.string, ) - .key, - height: 50, - width: 50, - alignment: Alignment.center, - child: displayEmoji != null && widget.shouldShowEmojis - ? InkWell( - hoverColor: Theme.of(context).colorScheme.primary.withAlpha(50), - onTap: widget.onTapOverride ?? openEmojiSetOverlay, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - displayEmoji!, - style: Theme.of(context).textTheme.headlineSmall, - ), - ), + .link, + child: Container( + key: MatrixState.pAnyState + .layerLinkAndKey( + widget.cId.string, ) - : WordZoomActivityButton( - icon: Icon( - Icons.add_reaction_outlined, - color: widget.isSelected - ? Theme.of(context).colorScheme.primary - : null, + .key, + height: 50, + width: 50, + alignment: Alignment.center, + child: displayEmoji != null && widget.shouldShowEmojis + ? InkWell( + hoverColor: + Theme.of(context).colorScheme.primary.withAlpha(50), + onTap: widget.onTapOverride ?? openEmojiSetOverlay, + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + displayEmoji!, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + ) + : WordZoomActivityButton( + icon: Icon( + Icons.add_reaction_outlined, + color: widget.isSelected + ? Theme.of(context).colorScheme.primary + : null, + ), + isSelected: widget.isSelected, + onPressed: widget.onTapOverride ?? openEmojiSetOverlay, + opacity: widget.isSelected ? 1 : 0.4, + tooltip: MessageMode.wordEmoji.title(context), ), - isSelected: widget.isSelected, - onPressed: widget.onTapOverride ?? openEmojiSetOverlay, - opacity: widget.isSelected ? 1 : 0.4, - tooltip: MessageMode.wordEmoji.title(context), - ), + ), ), ); } @@ -184,6 +192,7 @@ class EmojiEditOverlay extends StatelessWidget { @override Widget build(BuildContext context) { return Material( + borderRadius: BorderRadius.circular(AppConfig.borderRadius), child: Container( padding: const EdgeInsets.all(8), height: 70, diff --git a/lib/pangea/message_token_text/message_token_button.dart b/lib/pangea/message_token_text/message_token_button.dart index 6d14dcc0e..0b5fc682a 100644 --- a/lib/pangea/message_token_text/message_token_button.dart +++ b/lib/pangea/message_token_text/message_token_button.dart @@ -1,20 +1,19 @@ import 'dart:developer'; import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; -import 'package:material_symbols_icons/symbols.dart'; - import 'package:fluffychat/config/app_config.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/target_tokens_and_activity_type.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; +import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart'; import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; const double tokenButtonHeight = 40.0; const double tokenButtonDefaultFontSize = 10; @@ -155,23 +154,26 @@ class MessageTokenButtonState extends State } return InkWell( onHover: (isHovered) => setState(() => _isHovered = isHovered), - onTap: () => widget.overlayController! - .onMorphActivitySelect(widget.token, activity!.morphFeature!), + onTap: () => widget.overlayController!.onMorphActivitySelect( + MorphSelection(widget.token, activity!.morphFeature!), + ), borderRadius: borderRadius, child: Container( height: height, width: min(widget.width, height), alignment: Alignment.center, child: Opacity( - opacity: (widget.overlayController?.selectedToken == widget.token && - widget.overlayController?.selectedMorph == + opacity: (widget.overlayController?.selectedMorph?.token == + widget.token && + widget.overlayController?.selectedMorph?.morph == activity?.morphFeature) || _isHovered ? 1.0 - : 0.5, + : 0.4, child: Icon( Symbols.toys_and_games, color: Theme.of(context).colorScheme.primary, + size: min(24, widget.width), ), // MorphIcon(morphFeature: activity!.morphFeature!, morphTag: null), ), diff --git a/lib/pangea/practice_activities/message_analytics_controller.dart b/lib/pangea/practice_activities/message_analytics_controller.dart index 8f4d12244..8545732c3 100644 --- a/lib/pangea/practice_activities/message_analytics_controller.dart +++ b/lib/pangea/practice_activities/message_analytics_controller.dart @@ -1,301 +1,76 @@ -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; - -import 'package:collection/collection.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/lemma_info_response.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/target_tokens_and_activity_type.dart'; +import 'package:fluffychat/pangea/practice_activities/message_analytics_entry.dart'; +import 'package:flutter/material.dart'; +import 'package:get_storage/get_storage.dart'; -class MessageAnalyticsEntry { - final DateTime createdAt = DateTime.now(); - - late final List _tokens; - - final Map> - _activityQueue = {}; - - final int _maxQueueLength = 5; - - MessageAnalyticsEntry({ - required List tokens, - required bool includeHiddenWordActivities, - required PangeaMessageEvent pangeaMessageEvent, - }) { - _tokens = tokens; - initialize(); - } - - void _pushQueue(TargetTokensAndActivityType entry) { - if (_activityQueue.containsKey(entry.activityType)) { - _activityQueue[entry.activityType]!.insert(0, entry); - } else { - _activityQueue[entry.activityType] = [entry]; - } - - // just in case we make a mistake and the queue gets too long - if (_activityQueue[entry.activityType]!.length > _maxQueueLength) { - debugger(when: kDebugMode); - _activityQueue[entry.activityType]!.removeRange( - _maxQueueLength, - _activityQueue.length, - ); - } - } - - TargetTokensAndActivityType? nextActivity(ActivityTypeEnum a) => - _activityQueue[a]?.firstOrNull; - - bool get hasHiddenWordActivity => - activities(ActivityTypeEnum.hiddenWordListening).isNotEmpty; - - bool get hasMessageMeaningActivity => - activities(ActivityTypeEnum.messageMeaning).isNotEmpty; - - int get numActivities => _activityQueue.length; - - List activities(ActivityTypeEnum a) => - _activityQueue[a] ?? []; - - // /// If there are more than 4 tokens that can be heard, we don't want to do word focus listening - // /// Otherwise, we don't have enough distractors - // bool get canDoWordFocusListening => - // _tokens.where((t) => t.canBeHeard).length > 4; - - /// On initialization, we pick which tokens to do activities on and what types of activities to do - void initialize() { - final eligibleTokens = _tokens.where((t) => t.lemma.saveVocab); - - // EMOJI - // sort the tokens by the preference of them for an emoji activity - // order from least to most recent - // words that have never been used are counted as 1000 days - // we preference content words over function words by multiplying the days since last use by 2 - // NOTE: for now, we put it at the end if it has no uses and basically just give them the answer - // later on, we may introduce an emoji activity that is easier than the current matching one - // i.e. we show them 3 good emojis and 1 bad one and ask them to pick the bad one - _activityQueue[ActivityTypeEnum.emoji] = eligibleTokens - .map( - (t) => TargetTokensAndActivityType( - tokens: [t], - activityType: ActivityTypeEnum.emoji, - ), - ) - .sorted( - (a, b) => a.tokens.first - .activityPriorityScore(ActivityTypeEnum.emoji, null) - .compareTo( - b.tokens.first - .activityPriorityScore(ActivityTypeEnum.emoji, null), - ), - ) - .take(_maxQueueLength) - .shuffled() - .toList(); - - // WORD MEANING - // make word meaning activities - // same as emojis for now - _activityQueue[ActivityTypeEnum.wordMeaning] = eligibleTokens - .map( - (t) => TargetTokensAndActivityType( - tokens: [t], - activityType: ActivityTypeEnum.wordMeaning, - ), - ) - .sorted( - (a, b) => a.tokens.first - .activityPriorityScore(ActivityTypeEnum.wordMeaning, null) - .compareTo( - b.tokens.first - .activityPriorityScore(ActivityTypeEnum.wordMeaning, null), - ), - ) - .take(_maxQueueLength) - .shuffled() - .toList(); - - // WORD FOCUS LISTENING - // make word focus listening activities - // same as emojis for now - _activityQueue[ActivityTypeEnum.wordFocusListening] = eligibleTokens - .map( - (t) => TargetTokensAndActivityType( - tokens: [t], - activityType: ActivityTypeEnum.wordFocusListening, - ), - ) - .sorted( - (a, b) => a.tokens.first - .activityPriorityScore(ActivityTypeEnum.wordFocusListening, null) - .compareTo( - b.tokens.first.activityPriorityScore( - ActivityTypeEnum.wordFocusListening, - null, - ), - ), - ) - .take(_maxQueueLength) - .shuffled() - .toList(); - - // GRAMMAR - // build a list of TargetTokensAndActivityType for all tokens and all features in the message - // limits to _maxQueueLength activities and only one per token - final List candidates = eligibleTokens.expand( - (t) { - return t.morphsBasicallyEligibleForPracticeByPriority.map( - (m) => TargetTokensAndActivityType( - tokens: [t], - activityType: ActivityTypeEnum.morphId, - morphFeature: MorphFeaturesEnumExtension.fromString(m.category), - ), - ); - }, - ).sorted( - (a, b) => a.tokens.first - .activityPriorityScore( - ActivityTypeEnum.morphId, - a.morphFeature!.name, - ) - .compareTo( - b.tokens.first.activityPriorityScore( - ActivityTypeEnum.morphId, - b.morphFeature!.name, - ), - ), - ); - //pick from the top 5, only including one per token - _activityQueue[ActivityTypeEnum.morphId] = []; - for (final candidate in candidates) { - if (_activityQueue[ActivityTypeEnum.morphId]!.length >= _maxQueueLength) { - break; - } - if (_activityQueue[ActivityTypeEnum.morphId]?.any( - (entry) => entry.tokens.contains(candidate.tokens.first), - ) == - false) { - _activityQueue[ActivityTypeEnum.morphId]?.add(candidate); - } - } - } - - bool hasActivity( - ActivityTypeEnum a, - PangeaToken t, [ - MorphFeaturesEnum? morph, - ]) => - _activityQueue[a]?.any( - (entry) => - entry.tokens.contains(t) && - (morph == null || entry.morphFeature == morph), - ) == - true; - - /// Add a message meaning activity to the front of the queue - /// And limits to _maxQueueLength activities - void addMessageMeaningActivity() { - final entry = TargetTokensAndActivityType( - tokens: _tokens, - activityType: ActivityTypeEnum.messageMeaning, - ); - _pushQueue(entry); - } - - void onActivityComplete(ActivityTypeEnum a, PangeaToken? token) { - _activityQueue[a] - ?.removeWhere((entry) => token == null || entry.tokens.contains(token)); - } - - void exitPracticeFlow() => _activityQueue.clear(); - - void revealAllTokens() => - _activityQueue[ActivityTypeEnum.hiddenWordListening]?.clear(); - - bool isTokenInHiddenWordActivity(PangeaToken token) => - _activityQueue[ActivityTypeEnum.hiddenWordListening]?.isNotEmpty ?? false; - - Future> getLemmaInfoForActivityTokens() async { - // make a list of unique tokens in emoji and wordMeaning activities - final List uniqueTokens = []; - for (final t in _activityQueue[ActivityTypeEnum.emoji] ?? []) { - if (!uniqueTokens.contains(t.tokens.first)) { - uniqueTokens.add(t.tokens.first); - } - } - for (final t in _activityQueue[ActivityTypeEnum.wordMeaning] ?? []) { - if (!uniqueTokens.contains(t.tokens.first)) { - uniqueTokens.add(t.tokens.first); - } - } - - // get the lemma info for each token - final List> lemmaInfoFutures = []; - for (final t in uniqueTokens) { - lemmaInfoFutures.add(t.vocabConstructID.getLemmaInfo()); - } - - return Future.wait(lemmaInfoFutures); - } -} - -/// computes TokenWithXP for given a pangeaMessageEvent and caches the result, according to the full text of the message -/// listens for analytics updates and updates the cache accordingly class MessageAnalyticsController { - static final Map _cache = {}; + static final GetStorage _storage = GetStorage('message_analytics_cache'); + static final Map _memoryCache = {}; + static const int _maxMemoryCacheSize = 50; void dispose() { - _cache.clear(); + _storage.erase(); + _memoryCache.clear(); + } + + static void save(MessageAnalyticsEntry entry) { + final key = _key(entry.tokens); + _storage.write(key, entry.toJson()); + _memoryCache[key] = entry; } - // if over 300, remove oldest 5 entries by createdAt static void clean() { - if (_cache.length > 300) { - final sortedEntries = _cache.entries.toList() + final Iterable keys = _storage.getKeys(); + if (keys.length > 300) { + final entries = keys + .map((key) { + final entry = MessageAnalyticsEntry.fromJson(_storage.read(key)); + return MapEntry(key, entry); + }) + .cast>() + .toList() ..sort((a, b) => a.value.createdAt.compareTo(b.value.createdAt)); for (var i = 0; i < 5; i++) { - _cache.remove(sortedEntries[i].key); + _storage.remove(entries[i].key); } } + if (_memoryCache.length > _maxMemoryCacheSize) { + _memoryCache.remove(_memoryCache.keys.first); + } } static String _key(List tokens) => - PangeaToken.reconstructText(tokens); + tokens.map((t) => t.text.content).join(' '); static MessageAnalyticsEntry? get( List tokens, - PangeaMessageEvent pangeaMessageEvent, ) { final String key = _key(tokens); - final entry = _cache[key]; - - // if cache is older than 1 day, then remove and recompute - if (entry != null && - DateTime.now().difference(entry.createdAt).inDays > 1) { - _cache.remove(key); + if (_memoryCache.containsKey(key)) { + return _memoryCache[key]; } - if (entry != null) { - return entry; + final entryJson = _storage.read(key); + if (entryJson != null) { + final entry = MessageAnalyticsEntry.fromJson(entryJson); + if (DateTime.now().difference(entry.createdAt).inDays > 1) { + debugPrint('removing old entry ${entry.createdAt}'); + _storage.remove(key); + } else { + _memoryCache[key] = entry; + return entry; + } } - final bool includeHiddenWordActivities = !pangeaMessageEvent.ownMessage && - pangeaMessageEvent.messageDisplayRepresentation?.tokens != null && - pangeaMessageEvent.messageDisplayLangIsL2 && - !pangeaMessageEvent.event.isRichMessage; - - _cache[key] = MessageAnalyticsEntry( + final newEntry = MessageAnalyticsEntry( tokens: tokens, - includeHiddenWordActivities: includeHiddenWordActivities, - pangeaMessageEvent: pangeaMessageEvent, ); + _storage.write(key, newEntry.toJson()); + _memoryCache[key] = newEntry; + clean(); - return _cache[key]; + return newEntry; } } diff --git a/lib/pangea/practice_activities/message_analytics_entry.dart b/lib/pangea/practice_activities/message_analytics_entry.dart new file mode 100644 index 000000000..a6913ea35 --- /dev/null +++ b/lib/pangea/practice_activities/message_analytics_entry.dart @@ -0,0 +1,284 @@ +import 'dart:developer'; + +import 'package:collection/collection.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/lemmas/lemma_info_response.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/message_analytics_controller.dart'; +import 'package:fluffychat/pangea/practice_activities/target_tokens_and_activity_type.dart'; +import 'package:flutter/foundation.dart'; + +class MessageAnalyticsEntry { + final DateTime createdAt = DateTime.now(); + + late final List _tokens; + + final Map> + _activityQueue = {}; + + final int _maxQueueLength = 5; + + MessageAnalyticsEntry({ + required List tokens, + }) { + _tokens = tokens; + initialize(); + } + + List get tokens => _tokens; + + Map toJson() => { + 'createdAt': createdAt.toIso8601String(), + 'tokens': _tokens.map((t) => t.toJson()).toList(), + 'activityQueue': _activityQueue.map( + (key, value) => MapEntry( + key.toString(), + value.map((e) => e.toJson()).toList(), + ), + ), + }; + + static MessageAnalyticsEntry fromJson(Map json) { + return MessageAnalyticsEntry( + tokens: + (json['tokens'] as List).map((t) => PangeaToken.fromJson(t)).toList(), + ).._activityQueue.addAll( + (json['activityQueue'] as Map).map( + (key, value) => MapEntry( + ActivityTypeEnum.values.firstWhere((e) => e.toString() == key), + (value as List) + .map((e) => TargetTokensAndActivityType.fromJson(e)) + .toList(), + ), + ), + ); + } + + void _pushQueue(TargetTokensAndActivityType entry) { + if (_activityQueue.containsKey(entry.activityType)) { + _activityQueue[entry.activityType]!.insert(0, entry); + } else { + _activityQueue[entry.activityType] = [entry]; + } + + // just in case we make a mistake and the queue gets too long + if (_activityQueue[entry.activityType]!.length > _maxQueueLength) { + debugger(when: kDebugMode); + _activityQueue[entry.activityType]!.removeRange( + _maxQueueLength, + _activityQueue.length, + ); + } + } + + TargetTokensAndActivityType? nextActivity(ActivityTypeEnum a) => + _activityQueue[a]?.firstOrNull; + + bool get hasHiddenWordActivity => + activities(ActivityTypeEnum.hiddenWordListening).isNotEmpty; + + bool get hasMessageMeaningActivity => + activities(ActivityTypeEnum.messageMeaning).isNotEmpty; + + int get numActivities => _activityQueue.length; + + List activities(ActivityTypeEnum a) => + _activityQueue[a] ?? []; + + // /// If there are more than 4 tokens that can be heard, we don't want to do word focus listening + // /// Otherwise, we don't have enough distractors + // bool get canDoWordFocusListening => + // _tokens.where((t) => t.canBeHeard).length > 4; + + /// On initialization, we pick which tokens to do activities on and what types of activities to do + void initialize() { + final eligibleTokens = _tokens.where((t) => t.lemma.saveVocab); + + // EMOJI + // sort the tokens by the preference of them for an emoji activity + // order from least to most recent + // words that have never been used are counted as 1000 days + // we preference content words over function words by multiplying the days since last use by 2 + // NOTE: for now, we put it at the end if it has no uses and basically just give them the answer + // later on, we may introduce an emoji activity that is easier than the current matching one + // i.e. we show them 3 good emojis and 1 bad one and ask them to pick the bad one + _activityQueue[ActivityTypeEnum.emoji] = eligibleTokens + .map( + (t) => TargetTokensAndActivityType( + tokens: [t], + activityType: ActivityTypeEnum.emoji, + ), + ) + .sorted( + (a, b) => b.tokens.first + .activityPriorityScore(ActivityTypeEnum.emoji, null) + .compareTo( + a.tokens.first + .activityPriorityScore(ActivityTypeEnum.emoji, null), + ), + ); + debugPrint( + 'emoji activity priority score: ${_activityQueue[ActivityTypeEnum.emoji]!.map( + (e) => e.tokens.first.activityPriorityScore(ActivityTypeEnum.emoji, null), + )}'); + + _activityQueue[ActivityTypeEnum.emoji] = + _activityQueue[ActivityTypeEnum.emoji]! + .take(_maxQueueLength) + .shuffled() + .toList(); + + // WORD MEANING + // make word meaning activities + // same as emojis for now + _activityQueue[ActivityTypeEnum.wordMeaning] = eligibleTokens + .map( + (t) => TargetTokensAndActivityType( + tokens: [t], + activityType: ActivityTypeEnum.wordMeaning, + ), + ) + .sorted( + (a, b) => b.tokens.first + .activityPriorityScore(ActivityTypeEnum.wordMeaning, null) + .compareTo( + a.tokens.first + .activityPriorityScore(ActivityTypeEnum.wordMeaning, null), + ), + ) + .take(_maxQueueLength) + .shuffled() + .toList(); + + // WORD FOCUS LISTENING + // make word focus listening activities + // same as emojis for now + _activityQueue[ActivityTypeEnum.wordFocusListening] = eligibleTokens + .map( + (t) => TargetTokensAndActivityType( + tokens: [t], + activityType: ActivityTypeEnum.wordFocusListening, + ), + ) + .sorted( + (a, b) => b.tokens.first + .activityPriorityScore(ActivityTypeEnum.wordFocusListening, null) + .compareTo( + a.tokens.first.activityPriorityScore( + ActivityTypeEnum.wordFocusListening, + null, + ), + ), + ) + .take(_maxQueueLength) + .shuffled() + .toList(); + + // GRAMMAR + // build a list of TargetTokensAndActivityType for all tokens and all features in the message + // limits to _maxQueueLength activities and only one per token + final List candidates = eligibleTokens.expand( + (t) { + return t.morphsBasicallyEligibleForPracticeByPriority.map( + (m) => TargetTokensAndActivityType( + tokens: [t], + activityType: ActivityTypeEnum.morphId, + morphFeature: MorphFeaturesEnumExtension.fromString(m.category), + ), + ); + }, + ).sorted( + (a, b) => b.tokens.first + .activityPriorityScore( + ActivityTypeEnum.morphId, + b.morphFeature!.name, + ) + .compareTo( + a.tokens.first.activityPriorityScore( + ActivityTypeEnum.morphId, + a.morphFeature!.name, + ), + ), + ); + //pick from the top 5, only including one per token + _activityQueue[ActivityTypeEnum.morphId] = []; + for (final candidate in candidates) { + if (_activityQueue[ActivityTypeEnum.morphId]!.length >= _maxQueueLength) { + break; + } + if (_activityQueue[ActivityTypeEnum.morphId]?.any( + (entry) => entry.tokens.contains(candidate.tokens.first), + ) == + false) { + _activityQueue[ActivityTypeEnum.morphId]?.add(candidate); + } + } + + MessageAnalyticsController.save(this); + } + + bool hasActivity( + ActivityTypeEnum a, + PangeaToken t, [ + MorphFeaturesEnum? morph, + ]) => + _activityQueue[a]?.any( + (entry) => + entry.tokens.contains(t) && + (morph == null || entry.morphFeature == morph), + ) == + true; + + /// Add a message meaning activity to the front of the queue + /// And limits to _maxQueueLength activities + void addMessageMeaningActivity() { + final entry = TargetTokensAndActivityType( + tokens: _tokens, + activityType: ActivityTypeEnum.messageMeaning, + ); + _pushQueue(entry); + } + + void onActivityComplete(ActivityTypeEnum a, PangeaToken? token) { + _activityQueue[a] + ?.removeWhere((entry) => token == null || entry.tokens.contains(token)); + MessageAnalyticsController.save(this); + } + + void exitPracticeFlow() { + _activityQueue.clear(); + MessageAnalyticsController.save(this); + } + + void revealAllTokens() { + _activityQueue[ActivityTypeEnum.hiddenWordListening]?.clear(); + MessageAnalyticsController.save(this); + } + + bool isTokenInHiddenWordActivity(PangeaToken token) => + _activityQueue[ActivityTypeEnum.hiddenWordListening]?.isNotEmpty ?? false; + + Future> getLemmaInfoForActivityTokens() async { + // make a list of unique tokens in emoji and wordMeaning activities + final List uniqueTokens = []; + for (final t in _activityQueue[ActivityTypeEnum.emoji] ?? []) { + if (!uniqueTokens.contains(t.tokens.first)) { + uniqueTokens.add(t.tokens.first); + } + } + for (final t in _activityQueue[ActivityTypeEnum.wordMeaning] ?? []) { + if (!uniqueTokens.contains(t.tokens.first)) { + uniqueTokens.add(t.tokens.first); + } + } + + // get the lemma info for each token + final List> lemmaInfoFutures = []; + for (final t in uniqueTokens) { + lemmaInfoFutures.add(t.vocabConstructID.getLemmaInfo()); + } + + return Future.wait(lemmaInfoFutures); + } +} diff --git a/lib/pangea/practice_activities/target_tokens_and_activity_type.dart b/lib/pangea/practice_activities/target_tokens_and_activity_type.dart index 65c621290..db1a9476d 100644 --- a/lib/pangea/practice_activities/target_tokens_and_activity_type.dart +++ b/lib/pangea/practice_activities/target_tokens_and_activity_type.dart @@ -1,8 +1,7 @@ -import 'package:flutter/foundation.dart'; - import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; +import 'package:flutter/foundation.dart'; /// Picks which tokens to do activities on and what types of activities to do /// Caches result so that we don't have to recompute it @@ -46,4 +45,23 @@ class TargetTokensAndActivityType { @override int get hashCode => tokens.hashCode ^ activityType.hashCode ^ morphFeature.hashCode; + + static TargetTokensAndActivityType fromJson(Map json) { + return TargetTokensAndActivityType( + tokens: + (json['tokens'] as List).map((e) => PangeaToken.fromJson(e)).toList(), + activityType: ActivityTypeEnum.values[json['activityType']], + morphFeature: json['morphFeature'] == null + ? null + : MorphFeaturesEnum.values[json['morphFeature']], + ); + } + + Map toJson() { + return { + 'tokens': tokens.map((e) => e.toJson()).toList(), + 'activityType': activityType.index, + 'morphFeature': morphFeature?.index, + }; + } } diff --git a/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity.dart b/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity.dart index b13ca1cc9..912eab68f 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity.dart @@ -1,8 +1,5 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/message_token_text/message_token_button.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; @@ -11,6 +8,8 @@ import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_match_activity_item.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; class MessageMatchActivity extends StatelessWidget { final MessageOverlayController overlayController; diff --git a/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity_item.dart b/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity_item.dart index 788d106e3..f81f04230 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity_item.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/message_match_activity_item.dart @@ -1,16 +1,14 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:collection/collection.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/toolbar/controllers/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/match_feedback_model.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; class MessageMatchActivityItem extends StatefulWidget { const MessageMatchActivityItem({ @@ -88,14 +86,14 @@ class MessageMatchActivityItemState extends State { } if (isSelected) { - return AppConfig.primaryColor; + return Theme.of(context).colorScheme.primaryContainer; } if (_isHovered) { - return Theme.of(context).colorScheme.primary; + return Theme.of(context).colorScheme.primaryContainer; } - return Colors.transparent; + return Theme.of(context).colorScheme.surface; } @override @@ -147,12 +145,6 @@ class MessageMatchActivityItemState extends State { onDragStarted: () { widget.overlayController.onChoiceSelect(widget.constructForm, true); }, - // onDragCompleted: () { - // debugger(when: kDebugMode); - // }, - // onDragEnd: (details) { - // // debugger(when: kDebugMode); - // }, child: InkWell( onHover: (isHovered) => setState(() => _isHovered = isHovered), borderRadius: BorderRadius.circular(AppConfig.borderRadius), 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 955d54be5..70ff2ecb2 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 @@ -1,11 +1,6 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; - import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; @@ -23,6 +18,9 @@ import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_m import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; // this widget will handle the content of the input bar when mode == MessageMode.wordMorph @@ -63,8 +61,8 @@ class MessageMorphInputBarContentState // } MessageOverlayController get overlay => widget.overlayController; - PangeaToken? get token => overlay.selectedToken; - MorphFeaturesEnum? get morph => overlay.selectedMorph; + PangeaToken? get token => overlay.selectedMorph?.token; + MorphFeaturesEnum? get morph => overlay.selectedMorph?.morph; // void init() async { // initialized = false; diff --git a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart index 41857a8bf..88f67a2dd 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart @@ -1,10 +1,9 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; +import 'package:flutter/material.dart'; class MessageMorphChoiceItem extends StatefulWidget { const MessageMorphChoiceItem({ @@ -47,10 +46,13 @@ class MessageMorphChoiceItemState extends State { : AppConfig.warning.withAlpha((0.4 * 255).toInt()); } if (widget.isSelected) { - return AppConfig.primaryColor.withAlpha((0.4 * 255).toInt()); + return Theme.of(context) + .colorScheme + .primary + .withAlpha((0.4 * 255).toInt()); } return _isHovered - ? AppConfig.primaryColor.withAlpha((0.2 * 255).toInt()) + ? Theme.of(context).colorScheme.primary.withAlpha((0.2 * 255).toInt()) : Colors.transparent; } diff --git a/lib/pangea/toolbar/reading_assistance_input_row/morph_selection.dart b/lib/pangea/toolbar/reading_assistance_input_row/morph_selection.dart new file mode 100644 index 000000000..612a1698c --- /dev/null +++ b/lib/pangea/toolbar/reading_assistance_input_row/morph_selection.dart @@ -0,0 +1,24 @@ +import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; + +class MorphSelection { + PangeaToken token; + MorphFeaturesEnum morph; + + MorphSelection( + this.token, + this.morph, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is MorphSelection && + other.token == token && + other.morph == morph; + } + + @override + int get hashCode => token.hashCode ^ morph.hashCode; +} 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 0f0190c64..3bdf00a89 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 @@ -1,8 +1,5 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; @@ -17,6 +14,10 @@ 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_translation_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +const double minContentHeight = 120; class ReadingAssistanceInputBar extends StatelessWidget { final ChatController controller; @@ -56,6 +57,7 @@ class ReadingAssistanceInputBar extends StatelessWidget { } Widget barContent(BuildContext context) { + Widget? content; switch (overlayController.toolbarMode) { // message meaning will not use the input bar (for now at least) // maybe we move some choices there later @@ -64,7 +66,7 @@ class ReadingAssistanceInputBar extends StatelessWidget { case MessageMode.wordZoom: case MessageMode.noneSelected: //TODO: show all emojis for the lemmas and allow sending normal reactions - return const SizedBox.shrink(); + break; // return MessageEmojiChoice( // controller: controller, // overlayController: overlayController, @@ -72,27 +74,38 @@ class ReadingAssistanceInputBar extends StatelessWidget { case MessageMode.messageTranslation: if (overlayController.isTranslationUnlocked) { - return MessageTranslationCard( + content = MessageTranslationCard( messageEvent: overlayController.pangeaMessageEvent!, ); } else { - return MessageModeLockedCard(controller: overlayController); + content = MessageModeLockedCard(controller: overlayController); } case MessageMode.wordEmoji: case MessageMode.messageMeaning: case MessageMode.wordMeaning: case MessageMode.listening: - return MessageMatchActivity( + content = MessageMatchActivity( overlayController: overlayController, ); case MessageMode.wordMorph: - return MessageMorphInputBarContent( + content = MessageMorphInputBarContent( overlayController: overlayController, pangeaMessageEvent: overlayController.pangeaMessageEvent!, ); } + + if (content == null) { + return const SizedBox(); + } + + return Container( + constraints: const BoxConstraints( + minHeight: minContentHeight, + ), + child: content, + ); } @override diff --git a/lib/pangea/toolbar/widgets/message_audio_card.dart b/lib/pangea/toolbar/widgets/message_audio_card.dart index 141a38a05..6aa05e686 100644 --- a/lib/pangea/toolbar/widgets/message_audio_card.dart +++ b/lib/pangea/toolbar/widgets/message_audio_card.dart @@ -1,19 +1,17 @@ import 'dart:developer'; -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/events/audio_player.dart'; +import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/card_error_widget.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/toolbar_content_loading_indicator.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; class MessageAudioCard extends StatefulWidget { final PangeaMessageEvent messageEvent; @@ -86,8 +84,9 @@ class MessageAudioCardState extends State { children: [ Container( alignment: Alignment.center, + height: 40, child: _isLoading - ? const ToolbarContentLoadingIndicator() + ? const TextLoadingShimmer(width: 200) : audioFile != null ? AudioPlayerWidget( null, diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 058cbdc15..5228e44f5 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -1,13 +1,7 @@ import 'dart:async'; import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - import 'package:collection/collection.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; @@ -25,15 +19,20 @@ import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.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/message_analytics_controller.dart'; +import 'package:fluffychat/pangea/practice_activities/message_analytics_entry.dart'; import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/match_feedback_model.dart'; +import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:matrix/matrix.dart'; /// Controls data at the top level of the toolbar (mainly token / toolbar mode selection) class MessageSelectionOverlay extends StatefulWidget { @@ -72,7 +71,7 @@ class MessageOverlayController extends State Map? messageLemmaInfos; - MorphFeaturesEnum? selectedMorph; + MorphSelection? selectedMorph; ConstructForm? selectedChoice; PangeaTokenText? _selectedSpan; @@ -108,7 +107,6 @@ class MessageOverlayController extends State Future initializeTokensAndMode() async { try { - debugPrint("what"); RepresentationEvent? repEvent = pangeaMessageEvent?.messageDisplayRepresentation; repEvent ??= await _fetchNewRepEvent(); @@ -323,12 +321,12 @@ class MessageOverlayController extends State setState(() {}); } - void onMorphActivitySelect(PangeaToken token, MorphFeaturesEnum morph) { + void onMorphActivitySelect(MorphSelection newMorph) { if (toolbarMode != MessageMode.wordMorph) { updateToolbarMode(MessageMode.wordMorph); } - selectedMorph = morph; - _updateSelectedSpan(token.text, true); + selectedMorph = newMorph; + setState(() {}); } // only used for word meaning, emoji, and word focus listening atm @@ -338,6 +336,7 @@ class MessageOverlayController extends State ) async { final ActivityTypeEnum activityType = toolbarMode.associatedActivityType!; + //TODO - account for some emojis being the same for multiple words final bool isCorrect = token.vocabConstructID == choice.cId; final ConstructUseTypeEnum? useType = @@ -457,7 +456,6 @@ class MessageOverlayController extends State pangeaMessageEvent?.messageDisplayRepresentation?.tokens != null ? MessageAnalyticsController.get( pangeaMessageEvent!.messageDisplayRepresentation!.tokens!, - pangeaMessageEvent!, ) : null; diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index 8a74a01fe..a0e44ea15 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -1,10 +1,6 @@ import 'dart:async'; import 'dart:math'; -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/themes.dart'; @@ -23,6 +19,8 @@ import 'package:fluffychat/pangea/toolbar/widgets/overlay_center_content.dart'; import 'package:fluffychat/pangea/toolbar/widgets/overlay_header.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; /// Controls positioning of the message overlay. class MessageSelectionPositioner extends StatefulWidget { @@ -552,9 +550,9 @@ class MessageSelectionPositionerState extends State _headerHeight, ), child: Container( - constraints: const BoxConstraints( + constraints: BoxConstraints( minWidth: 200.0, - maxWidth: 400.0, + maxWidth: _toolbarMaxWidth, ), child: InstructionsInlineTooltip( instructionsEnum: diff --git a/lib/pangea/toolbar/widgets/message_token_text.dart b/lib/pangea/toolbar/widgets/message_token_text.dart index 809b7e52e..0943cc073 100644 --- a/lib/pangea/toolbar/widgets/message_token_text.dart +++ b/lib/pangea/toolbar/widgets/message_token_text.dart @@ -1,8 +1,4 @@ -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; -import 'package:flutter_linkify/flutter_linkify.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; @@ -10,10 +6,13 @@ import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/events/utils/message_text_util.dart'; import 'package:fluffychat/pangea/message_token_text/message_token_button.dart'; import 'package:fluffychat/pangea/practice_activities/message_analytics_controller.dart'; +import 'package:fluffychat/pangea/practice_activities/message_analytics_entry.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_linkify/flutter_linkify.dart'; /// Question - does this need to be stateful or does this work? /// Need to test. @@ -56,7 +55,6 @@ class MessageTokenText extends StatelessWidget { MessageAnalyticsEntry? get messageAnalyticsEntry => _tokens != null ? MessageAnalyticsController.get( _tokens!, - _pangeaMessageEvent, ) : null; @@ -184,7 +182,7 @@ class MessageTextWidget extends StatelessWidget { return textPainter.width; } - Color backgroundColor(TokenPosition tokenPosition) { + Color backgroundColor(BuildContext context, TokenPosition tokenPosition) { final hideTokenHighlights = messageAnalyticsEntry != null && (messageAnalyticsEntry!.hasHiddenWordActivity || messageAnalyticsEntry!.hasMessageMeaningActivity); @@ -193,7 +191,7 @@ class MessageTextWidget extends StatelessWidget { if (!hideTokenHighlights) { if (tokenPosition.selected) { - backgroundColor = AppConfig.primaryColor; + backgroundColor = Theme.of(context).colorScheme.primary; } // else if (tokenPosition.isHighlighted) { // backgroundColor = AppConfig.success.withAlpha(80); @@ -378,7 +376,7 @@ class MessageTextWidget extends StatelessWidget { : 0, width: tokenWidth, child: Container( - color: backgroundColor(tokenPosition), + color: backgroundColor(context, tokenPosition), ), ), ], diff --git a/lib/pangea/toolbar/widgets/toolbar_button_column.dart b/lib/pangea/toolbar/widgets/toolbar_button_column.dart index 1f26ac88e..8226a327a 100644 --- a/lib/pangea/toolbar/widgets/toolbar_button_column.dart +++ b/lib/pangea/toolbar/widgets/toolbar_button_column.dart @@ -1,11 +1,9 @@ -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/toolbar_button.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; class ToolbarButtonRow extends StatelessWidget { final MessageOverlayController overlayController; @@ -35,71 +33,65 @@ class ToolbarButtonRow extends StatelessWidget { return SizedBox( height: AppConfig.toolbarButtonsHeight, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 4.0, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - ToolbarButton( - mode: MessageMode.messageTranslation, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, - ), - ], + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.listening, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - spacing: 4.0, - children: [ - Container( - width: buttonSize + 4, - height: buttonSize + 4, - alignment: Alignment.center, - child: ToolbarButton( - mode: MessageMode.wordMorph, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, - ), - ), - Container( - width: buttonSize + 4, - height: buttonSize + 4, - alignment: Alignment.center, - child: ToolbarButton( - mode: MessageMode.wordMeaning, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, - ), - ), - Container( - width: buttonSize + 4, - height: buttonSize + 4, - alignment: Alignment.center, - child: ToolbarButton( - mode: MessageMode.listening, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, - ), - ), - Container( - width: buttonSize + 4, - height: buttonSize + 4, - alignment: Alignment.center, - child: ToolbarButton( - mode: MessageMode.wordEmoji, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, - ), - ), - ], + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.wordMorph, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), + ), + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.messageTranslation, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), + ), + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.wordMeaning, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), + ), + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.wordEmoji, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), ), ], ), diff --git a/lib/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart index e7b08b391..8fece5ae8 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart @@ -170,6 +170,7 @@ class MorphFocusWidgetState extends State { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, + spacing: 8.0, children: [ MorphFeatureDisplay( morphFeature: widget.morphFeature, @@ -214,6 +215,7 @@ class MorphFocusWidgetState extends State { MorphMeaningWidget( feature: widget.morphFeature, tag: widget.token.getMorphTag(widget.morphFeature)!, + style: Theme.of(context).textTheme.bodyLarge, ), ] else Text(L10n.of(context).nan), diff --git a/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart b/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart index df0c3523a..32e37bf33 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart @@ -1,15 +1,14 @@ -import 'package:flutter/material.dart'; - -import 'package:material_symbols_icons/symbols.dart'; - import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; +import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart'; +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; class MorphologicalListItem extends StatelessWidget { final MorphFeaturesEnum morphFeature; @@ -31,7 +30,9 @@ class MorphologicalListItem extends StatelessWidget { ) == true; - bool get isSelected => overlayController.toolbarMode == MessageMode.wordMorph; + bool get isSelected => + overlayController.toolbarMode == MessageMode.wordMorph && + overlayController.selectedMorph?.morph == morphFeature; String get morphTag => token.getMorphTag(morphFeature.name) ?? "X"; @@ -58,8 +59,8 @@ class MorphologicalListItem extends StatelessWidget { // view: ConstructTypeEnum.vocab, // ), // ), - onPressed: () => - overlayController.onMorphActivitySelect(token, morphFeature), + onPressed: () => overlayController + .onMorphActivitySelect(MorphSelection(token, morphFeature)), tooltip: shouldDoActivity ? morphFeature.getDisplayCopy(context) : getGrammarCopy( diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index a80534c29..7e9f30f83 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.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'; @@ -11,6 +9,7 @@ import 'package:fluffychat/pangea/learning_settings/constants/language_constants import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart'; import 'package:fluffychat/pangea/lemmas/lemma_emoji_row.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; +import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; @@ -19,6 +18,7 @@ import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_widget.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; class WordZoomWidget extends StatelessWidget { final PangeaToken token; @@ -38,6 +38,13 @@ class WordZoomWidget extends StatelessWidget { void onEditDone() => overlayController.initializeTokensAndMode(); + bool get hasEmojiActivity => + overlayController.messageAnalyticsEntry?.hasActivity( + ActivityTypeEnum.emoji, + _selectedToken, + ) == + true; + @override Widget build(BuildContext context) { return ConstrainedBox( @@ -64,6 +71,7 @@ class WordZoomWidget extends StatelessWidget { constraints: const BoxConstraints( minHeight: 40, ), + color: Theme.of(context).colorScheme.surface, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -114,21 +122,16 @@ class WordZoomWidget extends StatelessWidget { alignment: Alignment.center, child: LemmaEmojiRow( cId: _selectedToken.vocabConstructID, - onTapOverride: () => - overlayController.updateToolbarMode( - MessageMode.wordEmoji, - ), + onTapOverride: hasEmojiActivity + ? () => overlayController.updateToolbarMode( + MessageMode.wordEmoji, + ) + : null, isSelected: overlayController.toolbarMode == MessageMode.wordEmoji, emojiSetCallback: () => overlayController.setState(() {}), - shouldShowEmojis: overlayController - .messageAnalyticsEntry - ?.hasActivity( - MessageMode.wordEmoji.associatedActivityType!, - _selectedToken, - ) == - false, + shouldShowEmojis: !hasEmojiActivity, ), ), ],