diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index d9ff2a82d..c13820a29 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -138,6 +138,16 @@ class MessageContent extends StatelessWidget { if (overlayController != null) { overlayController?.onClickOverlayMessageToken(token); return; + } else { + Future.delayed( + const Duration( + milliseconds: AppConfig.overlayAnimationDuration, + ), () { + controller.choreographer.tts.tryToSpeak( + token.text.content, + langCode: pangeaMessageEvent!.messageDisplayLangCode, + ); + }); } controller.showToolbar( diff --git a/lib/pangea/choreographer/widgets/choice_array.dart b/lib/pangea/choreographer/widgets/choice_array.dart index bc2b6d58c..36be9c2b1 100644 --- a/lib/pangea/choreographer/widgets/choice_array.dart +++ b/lib/pangea/choreographer/widgets/choice_array.dart @@ -116,7 +116,6 @@ class ChoicesArrayState extends State { widget.langCode != null) { widget.tts?.tryToSpeak( value, - context, targetID: null, langCode: widget.langCode!, ); diff --git a/lib/pangea/common/utils/overlay.dart b/lib/pangea/common/utils/overlay.dart index 633fe82c0..73dcc1042 100644 --- a/lib/pangea/common/utils/overlay.dart +++ b/lib/pangea/common/utils/overlay.dart @@ -149,9 +149,9 @@ class OverlayUtil { transformTargetOffset.dy + (transformTargetSize.height / 2); final halfMaxWidth = maxWidth / 2; - final hasLeftOverflow = (horizontalMidpoint - halfMaxWidth) < 0; + final hasLeftOverflow = (horizontalMidpoint - halfMaxWidth) < 10; final hasRightOverflow = (horizontalMidpoint + halfMaxWidth) > - (MediaQuery.of(context).size.width - columnWidth); + (MediaQuery.of(context).size.width - columnWidth - 10); hasTopOverflow = (verticalMidpoint - maxHeight) < 0; double xOffset = 0; diff --git a/lib/pangea/toolbar/controllers/tts_controller.dart b/lib/pangea/toolbar/controllers/tts_controller.dart index e672f06af..5597ff101 100644 --- a/lib/pangea/toolbar/controllers/tts_controller.dart +++ b/lib/pangea/toolbar/controllers/tts_controller.dart @@ -159,11 +159,11 @@ class TtsController { /// A safer version of speak, that handles the case of /// the language not being supported by the TTS engine Future tryToSpeak( - String text, - BuildContext context, { + String text, { required String langCode, // Target ID for where to show warning popup String? targetID, + BuildContext? context, }) async { chatController?.stopAudioStream.add(null); await _setSpeakingLanguage(langCode); @@ -180,7 +180,7 @@ class TtsController { text, langCode, )); - } else if (targetID != null) { + } else if (targetID != null && context != null) { await _showTTSDisabledPopup(context, targetID); } } 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 d01fc7b0c..795e25857 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 @@ -66,7 +66,7 @@ class PracticeMatchItemState extends State { if (l2 != null) { await tts.tryToSpeak( widget.audioContent!, - context, + context: context, targetID: 'word-audio-button', langCode: l2, ); 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 df62c8944..618a9e919 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 @@ -6,12 +6,12 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; +import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_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'; const double minContentHeight = 120; @@ -26,6 +26,11 @@ class ReadingAssistanceInputBar extends StatelessWidget { }); Widget barContent(BuildContext context) { + if (overlayController.readingAssistanceMode != + ReadingAssistanceMode.practiceMode) { + return ReactionsPicker(controller); + } + Widget? content; final target = overlayController.toolbarMode.associatedActivityType != null && @@ -84,12 +89,6 @@ class ReadingAssistanceInputBar extends StatelessWidget { targetTokensAndActivityType: target, overlayController: overlayController, ); - } else if (overlayController.selectedMorph != null) { - content = MorphFocusWidget( - morphFeature: overlayController.selectedMorph!.morph, - pangeaMessageEvent: overlayController.pangeaMessageEvent!, - overlayController: overlayController, - ); } else { content = Center( child: Text( diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 0372c4115..284914c5a 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -534,7 +534,8 @@ class MessageOverlayController extends State void onClickOverlayMessageToken( PangeaToken token, ) { - if (practiceSelection?.hasHiddenWordActivity == true) { + if (practiceSelection?.hasHiddenWordActivity == true || + readingAssistanceMode == ReadingAssistanceMode.practiceMode) { return; } @@ -548,7 +549,6 @@ class MessageOverlayController extends State !hideWordCardContent) { widget.chatController.choreographer.tts.tryToSpeak( token.text.content, - context, targetID: null, langCode: pangeaMessageEvent!.messageDisplayLangCode, ); diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index a2e29cca2..e612379eb 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -430,7 +430,7 @@ class MessageSelectionPositionerState extends State } else { return Offset( _ownMessage ? _messageRightOffset : _messageLeftOffset, - _footerHeight + AppConfig.toolbarSpacing, + _footerHeight + (AppConfig.toolbarSpacing * 2), ); } } diff --git a/lib/pangea/toolbar/widgets/overlay_center_content.dart b/lib/pangea/toolbar/widgets/overlay_center_content.dart index 4908fc1c3..ec9c4dbfb 100644 --- a/lib/pangea/toolbar/widgets/overlay_center_content.dart +++ b/lib/pangea/toolbar/widgets/overlay_center_content.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/message_reactions.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; @@ -55,8 +53,6 @@ class OverlayCenterContent extends StatelessWidget { @override Widget build(BuildContext context) { - final showTranslation = overlayController.showTranslation && - overlayController.translationText != null; return IgnorePointer( ignoring: !isTransitionAnimation && readingAssistanceMode != ReadingAssistanceMode.practiceMode, @@ -70,80 +66,31 @@ class OverlayCenterContent extends StatelessWidget { ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - Stack( - alignment: Alignment.topCenter, - children: [ - if (overlayController.readingAssistanceMode == - ReadingAssistanceMode.selectMode) - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - color: Theme.of(context).colorScheme.primaryContainer, - ), - padding: EdgeInsets.all( - showTranslation ? 8.0 : 0.0, - ), - constraints: BoxConstraints( - maxWidth: messageWidth ?? maxWidth, - ), - child: Column( - children: [ - AnimatedContainer( - duration: FluffyThemes.animationDuration, - height: showTranslation ? messageHeight : 0, - width: showTranslation ? messageWidth : 0, - ), - AnimatedSize( - duration: FluffyThemes.animationDuration, - child: SizedBox( - width: messageWidth, - child: showTranslation - ? Text( - overlayController.translationText!, - style: AppConfig.messageTextStyle( - event, - Theme.of(context) - .colorScheme - .onPrimaryContainer, - ), - textAlign: TextAlign.center, - ) - : const SizedBox(), - ), - ), - ], - ), - ), - MeasureRenderBox( - onChange: onChangeMessageSize, - child: OverlayMessage( - event, - pangeaMessageEvent: pangeaMessageEvent, - immersionMode: chatController.choreographer.immersionMode, - controller: chatController, - overlayController: overlayController, - nextEvent: nextEvent, - prevEvent: prevEvent, - timeline: chatController.timeline!, - sizeAnimation: sizeAnimation, - // there's a split seconds between when the transition animation starts and - // when the sizeAnimation is set when the original dimensions need to be enforced - messageWidth: - (sizeAnimation == null && isTransitionAnimation) - ? messageWidth - : null, - messageHeight: - (sizeAnimation == null && isTransitionAnimation) - ? messageHeight - : null, - maxHeight: maxHeight, - isTransitionAnimation: isTransitionAnimation, - readingAssistanceMode: readingAssistanceMode, - ), - ), - ], + MeasureRenderBox( + onChange: onChangeMessageSize, + child: OverlayMessage( + event, + pangeaMessageEvent: pangeaMessageEvent, + immersionMode: chatController.choreographer.immersionMode, + controller: chatController, + overlayController: overlayController, + nextEvent: nextEvent, + prevEvent: prevEvent, + timeline: chatController.timeline!, + sizeAnimation: sizeAnimation, + // there's a split seconds between when the transition animation starts and + // when the sizeAnimation is set when the original dimensions need to be enforced + messageWidth: (sizeAnimation == null && isTransitionAnimation) + ? messageWidth + : null, + messageHeight: + (sizeAnimation == null && isTransitionAnimation) + ? messageHeight + : null, + maxHeight: maxHeight, + isTransitionAnimation: isTransitionAnimation, + readingAssistanceMode: readingAssistanceMode, + ), ), if (hasReactions) Padding( diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 9544fc954..a52f6f09c 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -124,6 +124,9 @@ class OverlayMessage extends StatelessWidget { ? ThemeData.dark().colorScheme.onPrimary : theme.colorScheme.onSurface; + final showTranslation = overlayController.showTranslation && + overlayController.translationText != null; + final content = Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular( @@ -253,18 +256,48 @@ class OverlayMessage extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: borderRadius, ), - child: sizeAnimation != null - ? AnimatedBuilder( - animation: sizeAnimation!, - builder: (context, child) { - return SizedBox( - height: sizeAnimation!.value.height, - width: sizeAnimation!.value.width, - child: content, - ); - }, - ) - : content, + child: Container( + decoration: BoxDecoration( + borderRadius: borderRadius, + color: color, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + sizeAnimation != null + ? AnimatedBuilder( + animation: sizeAnimation!, + builder: (context, child) { + return SizedBox( + height: sizeAnimation!.value.height, + width: sizeAnimation!.value.width, + child: content, + ); + }, + ) + : content, + if (showTranslation) + SizedBox( + width: messageWidth, + child: Padding( + padding: const EdgeInsets.fromLTRB( + 12.0, + 20.0, + 12.0, + 12.0, + ), + child: Text( + overlayController.translationText!, + style: + AppConfig.messageTextStyle(event, textColor).copyWith( + fontStyle: FontStyle.italic, + ), + ), + ), + ), + ], + ), + ), ); } } diff --git a/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart b/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart index 455361c09..080fed3d9 100644 --- a/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart +++ b/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart @@ -87,7 +87,7 @@ class WordAudioButtonState extends State { if (widget.langCode != null) { await tts.tryToSpeak( widget.text, - context, + context: context, targetID: 'word-audio-button-${widget.uniqueID}', langCode: widget.langCode!, ); diff --git a/lib/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart b/lib/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart index 19716f2aa..6a4f586ad 100644 --- a/lib/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart +++ b/lib/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart @@ -83,7 +83,7 @@ class WordAudioButtonState extends State { if (l2 != null) { await tts.tryToSpeak( widget.text, - context, + context: context, targetID: 'text-audio-button-${widget.uniqueID}', langCode: l2, ); diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 3c0f3cc82..fcd901e49 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -10,7 +10,6 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/events/audio_player.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; @@ -113,7 +112,6 @@ class SelectModeButtonsState extends State { if (_selectedMode == SelectMode.translate) { widget.overlayController.setShowTranslation(false, null); - await Future.delayed(FluffyThemes.animationDuration); } setState( @@ -245,7 +243,7 @@ class SelectModeButtonsState extends State { return Icon( _audioPlayer.playerState.playing == true ? Icons.pause_outlined - : Icons.play_arrow, + : Icons.volume_up, size: 20, color: mode == _selectedMode ? Colors.white : null, ); @@ -294,6 +292,9 @@ class SelectModeButtonsState extends State { color: Theme.of(context).colorScheme.primaryContainer, onPressed: () => _updateMode(mode), playSound: true, + colorFactor: Theme.of(context).brightness == Brightness.light + ? 0.55 + : 0.3, child: Container( height: buttonSize, width: buttonSize, diff --git a/lib/pangea/toolbar/widgets/toolbar_button.dart b/lib/pangea/toolbar/widgets/toolbar_button.dart index e9c396729..897199260 100644 --- a/lib/pangea/toolbar/widgets/toolbar_button.dart +++ b/lib/pangea/toolbar/widgets/toolbar_button.dart @@ -38,6 +38,8 @@ class ToolbarButton extends StatelessWidget { color: color(context), onPressed: () => onPressed(mode), playSound: true, + colorFactor: + Theme.of(context).brightness == Brightness.light ? 0.55 : 0.3, child: AnimatedContainer( duration: FluffyThemes.animationDuration, height: 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 deleted file mode 100644 index 9e196c453..000000000 --- a/lib/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart +++ /dev/null @@ -1,165 +0,0 @@ -// stateful widget that displays morphological label and a shimmer effect while the text is loading -// takes a token and morphological feature as input - -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; -import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; -import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; -import 'package:fluffychat/pangea/constructs/construct_level_enum.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/construct_xp_widget.dart'; -import 'package:fluffychat/pangea/morphs/edit_morph_widget.dart'; -import 'package:fluffychat/pangea/morphs/morph_feature_display.dart'; -import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; -import 'package:fluffychat/pangea/morphs/morph_tag_display.dart'; -import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; - -class MorphFocusWidget extends StatefulWidget { - final MorphFeaturesEnum morphFeature; - final PangeaMessageEvent pangeaMessageEvent; - final MessageOverlayController overlayController; - - const MorphFocusWidget({ - required this.morphFeature, - required this.pangeaMessageEvent, - required this.overlayController, - super.key, - }); - - @override - MorphFocusWidgetState createState() => MorphFocusWidgetState(); -} - -class MorphFocusWidgetState extends State { - PangeaToken get token => widget.overlayController.selectedToken!; - - bool _editMode = false; - - /// the morphological tag that the user has selected in edit mode - String _selectedMorphTag = ""; - - final ScrollController _scrollController = ScrollController(); - - void _resetMorphTag() { - setState( - () => _selectedMorphTag = token.getMorphTag(widget.morphFeature) ?? "X", - ); - } - - @override - void initState() { - super.initState(); - _resetMorphTag(); - } - - @override - void didUpdateWidget(MorphFocusWidget oldWidget) { - if (widget.morphFeature != oldWidget.morphFeature) { - _resetMorphTag(); - setState(() => _editMode = false); - } - super.didUpdateWidget(oldWidget); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - void _enterEditMode() { - setState(() { - _editMode = true; - }); - } - - ConstructIdentifier get _id { - return ConstructIdentifier( - lemma: _selectedMorphTag, - type: ConstructTypeEnum.morph, - category: widget.morphFeature.name, - ); - } - - @override - Widget build(BuildContext context) { - if (!_editMode) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - spacing: 8.0, - children: [ - MorphFeatureDisplay( - morphFeature: widget.morphFeature, - ), - if (token.getMorphTag(widget.morphFeature) != null) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Tooltip( - triggerMode: TooltipTriggerMode.tap, - message: L10n.of(context).doubleClickToEdit, - child: GestureDetector( - onLongPress: _enterEditMode, - onDoubleTap: _enterEditMode, - child: MorphTagDisplay( - morphFeature: widget.morphFeature, - morphTag: token.getMorphTag(widget.morphFeature) ?? - L10n.of(context).nan, - textColor: Theme.of(context).brightness == - Brightness.light - ? _id.constructUses.lemmaCategory.darkColor(context) - : _id.constructUses.lemmaCategory.color(context), - ), - ), - ), - const SizedBox(width: 6), - ConstructXpWidget( - id: _id, - onTap: () => showDialog( - context: context, - builder: (context) => AnalyticsPopupWrapper( - constructZoom: _id, - view: ConstructTypeEnum.morph, - ), - ), - ), - ], - ), - MorphMeaningWidget( - feature: widget.morphFeature, - tag: token.getMorphTag(widget.morphFeature)!, - style: Theme.of(context).textTheme.bodyLarge, - ), - ] else - Text(L10n.of(context).nan), - ], - ); - } - - return Padding( - padding: const EdgeInsets.all(4.0), - child: EditMorphWidget( - token: token, - pangeaMessageEvent: widget.pangeaMessageEvent, - morphFeature: widget.morphFeature, - onClose: () { - setState(() => _editMode = false); - widget.overlayController.onMorphActivitySelect( - MorphSelection( - token, - widget.morphFeature, - ), - ); - }, - ), - ); - } -} 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 b26d446b9..b9d220784 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart @@ -1,16 +1,29 @@ +import 'dart:developer'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:material_symbols_icons/symbols.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/common/utils/overlay.dart'; +import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/lemmas/construct_xp_widget.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/morphs/morph_meaning/morph_info_repo.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:fluffychat/widgets/matrix.dart'; class MorphologicalListItem extends StatelessWidget { final MorphFeaturesEnum morphFeature; @@ -41,40 +54,223 @@ class MorphologicalListItem extends StatelessWidget { String get morphTag => token.getMorphTag(morphFeature) ?? "X"; + ConstructIdentifier get cId => + token.morphIdByFeature(morphFeature) ?? + ConstructIdentifier( + type: ConstructTypeEnum.morph, + category: morphFeature.name, + lemma: morphTag, + ); + + void _openDefintionPopup(BuildContext context) async { + const width = 300.0; + const height = 150.0; + + try { + OverlayUtil.showPositionedCard( + context: context, + cardToShow: MorphMeaningPopup( + cId: cId, + width: width, + height: height, + ), + transformTargetId: cId.string, + backDropToDismiss: true, + borderColor: Theme.of(context).colorScheme.primary, + closePrevOverlay: false, + addBorder: false, + maxHeight: height, + maxWidth: width, + ); + } catch (e, s) { + debugger(when: kDebugMode); + ErrorHandler.logError( + data: cId.toJson(), + e: e, + s: s, + ); + } + } + @override Widget build(BuildContext context) { - return SizedBox( - width: 40, - height: 40, - child: WordZoomActivityButton( - icon: shouldDoActivity - ? const Icon(Symbols.toys_and_games) - : MorphIcon( - morphFeature: morphFeature, - morphTag: token.getMorphTag(morphFeature), - size: const Size(24, 24), - ), - isSelected: isSelected, - onPressed: () => overlayController - .onMorphActivitySelect(MorphSelection(token, morphFeature)), - onLongPress: () { - overlayController - .onMorphActivitySelect(MorphSelection(token, morphFeature)); - editMorph(); - }, - onDoubleTap: () { - overlayController - .onMorphActivitySelect(MorphSelection(token, morphFeature)); - editMorph(); - }, - tooltip: shouldDoActivity - ? morphFeature.getDisplayCopy(context) - : getGrammarCopy( - category: morphFeature.name, - lemma: morphTag, - context: context, - ), - opacity: isSelected ? 1 : 0.7, + return CompositedTransformTarget( + link: MatrixState.pAnyState.layerLinkAndKey(cId.string).link, + child: SizedBox( + key: MatrixState.pAnyState.layerLinkAndKey(cId.string).key, + width: 40, + height: 40, + child: WordZoomActivityButton( + icon: shouldDoActivity + ? const Icon(Symbols.toys_and_games) + : MorphIcon( + morphFeature: morphFeature, + morphTag: token.getMorphTag(morphFeature), + size: const Size(24, 24), + ), + isSelected: isSelected, + onPressed: () { + overlayController + .onMorphActivitySelect(MorphSelection(token, morphFeature)); + _openDefintionPopup(context); + }, + onLongPress: () { + overlayController + .onMorphActivitySelect(MorphSelection(token, morphFeature)); + editMorph(); + }, + tooltip: shouldDoActivity + ? morphFeature.getDisplayCopy(context) + : getGrammarCopy( + category: morphFeature.name, + lemma: morphTag, + context: context, + ), + opacity: isSelected ? 1 : 0.7, + ), + ), + ); + } +} + +class MorphMeaningPopup extends StatefulWidget { + final ConstructIdentifier cId; + final double width; + final double height; + + const MorphMeaningPopup({ + super.key, + required this.cId, + required this.width, + required this.height, + }); + + @override + State createState() => MorphMeaningPopupState(); +} + +class MorphMeaningPopupState extends State { + MorphFeaturesEnum get _morphFeature => + MorphFeaturesEnumExtension.fromString(widget.cId.category); + + String get _morphTag => widget.cId.lemma; + + String? _defintion; + + @override + void initState() { + super.initState(); + _fetchDefinition(); + } + + @override + void didUpdateWidget(covariant MorphMeaningPopup oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.cId != widget.cId) { + _fetchDefinition(); + } + } + + Future _fetchDefinition() async { + try { + final response = await MorphInfoRepo.get( + feature: _morphFeature, + tag: _morphTag, + ); + + if (mounted) { + setState( + () => _defintion = response ?? L10n.of(context).meaningNotFound, + ); + } + } catch (e, s) { + debugger(when: kDebugMode); + ErrorHandler.logError( + data: widget.cId.toJson(), + e: e, + s: s, + ); + } + } + + @override + Widget build(BuildContext context) { + return Material( + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + child: Container( + padding: const EdgeInsets.all(8), + height: widget.height, + width: widget.width, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.onSurface.withAlpha(50), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + alignment: Alignment.center, + child: SingleChildScrollView( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 16.0, + children: [ + SizedBox( + width: 24.0, + height: 24.0, + child: MorphIcon( + morphFeature: _morphFeature, + morphTag: _morphTag, + ), + ), + Text( + getGrammarCopy( + category: _morphFeature.name, + lemma: _morphTag, + context: context, + ) ?? + _morphTag, + style: Theme.of(context).textTheme.titleMedium, + ), + SizedBox( + width: 24.0, + height: 24.0, + child: ConstructXpWidget( + id: widget.cId, + onTap: () => showDialog( + context: context, + builder: (context) => AnalyticsPopupWrapper( + constructZoom: widget.cId, + view: ConstructTypeEnum.morph, + backButtonOverride: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + ), + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: _defintion != null + ? Text( + _defintion!, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ) + : const LinearProgressIndicator(), + ), + ], + ), + ), ), ); } 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 edaa128cd..19e63ce2b 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -76,8 +76,9 @@ class WordZoomWidget extends StatelessWidget { children: [ //@ggurdin - might need to play with size to properly center IconButton( - onPressed: () => - overlayController.onClickOverlayMessageToken(token), + onPressed: () => overlayController.updateSelectedSpan( + token.text, + ), icon: const Icon(Icons.close), ), LemmaWidget(