From 2897142b9d1d1d5ad5ce87bab920e3ab0d358e60 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 16 Dec 2025 14:19:17 -0500 Subject: [PATCH] show word card in image toolbar mode --- lib/pages/chat/events/html_message.dart | 11 ++- .../lemma_emoji_setter_mixin.dart | 10 ++- .../lemmas/lemma_highlight_emoji_row.dart | 1 + .../select_mode_controller.dart | 30 ++++++- .../token_emoji_button.dart | 86 ++++--------------- .../word_card/lemma_reaction_picker.dart | 8 +- .../word_card/reading_assistance_content.dart | 5 ++ .../toolbar/word_card/word_card_switcher.dart | 17 ++-- .../toolbar/word_card/word_zoom_widget.dart | 3 + 9 files changed, 81 insertions(+), 90 deletions(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 43497b0dc..f0e1f2838 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -446,8 +446,10 @@ class HtmlMessage extends StatelessWidget { enabled: token.lemma.saveVocab, targetId: overlayController!.tokenEmojiPopupKey(token), selectModeNotifier: overlayController!.selectedMode, - selectedTokenNotifier: - overlayController!.selectedTokenNotifier, + onTap: () => + overlayController!.onClickOverlayMessageToken(token), + constructEmojiNotifier: overlayController! + .selectModeController.constructEmojiNotifier, ), if (renderer.showCenterStyling && token != null && @@ -946,9 +948,10 @@ class HtmlMessage extends StatelessWidget { // Use TokenEmojiButton to ensure consistent vertical alignment for non-token elements (e.g., emojis) in practice mode. TokenEmojiButton( selectModeNotifier: overlayController!.selectedMode, - selectedTokenNotifier: - overlayController!.selectedTokenNotifier, + onTap: () {}, enabled: false, + constructEmojiNotifier: overlayController! + .selectModeController.constructEmojiNotifier, ), RichText( text: TextSpan( diff --git a/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart b/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart index 9e1b1d858..575fe1839 100644 --- a/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart +++ b/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart @@ -10,7 +10,7 @@ import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/widgets/matrix.dart'; -mixin LemmaEmojiSetter on State { +mixin LemmaEmojiSetter { Future setLemmaEmoji( ConstructIdentifier constructId, String emoji, @@ -26,11 +26,13 @@ mixin LemmaEmojiSetter on State { await constructId.setUserLemmaInfo( constructId.userLemmaInfo.copyWith(emojis: [emoji]), ); - - _showSnackbar(constructId, emoji); } - void _showSnackbar(ConstructIdentifier constructId, String emoji) { + void showLemmaEmojiSnackbar( + BuildContext context, + ConstructIdentifier constructId, + String emoji, + ) { if (InstructionsEnum.setLemmaEmoji.isToggledOff) return; InstructionsEnum.setLemmaEmoji.setToggledOff(true); diff --git a/lib/pangea/lemmas/lemma_highlight_emoji_row.dart b/lib/pangea/lemmas/lemma_highlight_emoji_row.dart index 142a37753..aa22b6b3e 100644 --- a/lib/pangea/lemmas/lemma_highlight_emoji_row.dart +++ b/lib/pangea/lemmas/lemma_highlight_emoji_row.dart @@ -93,6 +93,7 @@ class LemmaHighlightEmojiRowState extends State emoji, "emoji-choice-item-$emoji-${widget.cId.lemma}", ); + showLemmaEmojiSnackbar(context, widget.cId, emoji); } catch (e, s) { debugger(when: kDebugMode); ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s); diff --git a/lib/pangea/toolbar/reading_assistance/select_mode_controller.dart b/lib/pangea/toolbar/reading_assistance/select_mode_controller.dart index 184844e8d..3e210bed1 100644 --- a/lib/pangea/toolbar/reading_assistance/select_mode_controller.dart +++ b/lib/pangea/toolbar/reading_assistance/select_mode_controller.dart @@ -6,7 +6,10 @@ import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:fluffychat/pangea/analytics_misc/lemma_emoji_setter_mixin.dart'; import 'package:fluffychat/pangea/common/utils/async_state.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/speech_to_text/speech_to_text_response_model.dart'; import 'package:fluffychat/pangea/toolbar/message_practice/message_audio_card.dart'; @@ -71,7 +74,7 @@ class _AudioLoader extends AsyncLoader<(PangeaAudioFile, File?)> { } } -class SelectModeController { +class SelectModeController with LemmaEmojiSetter { final PangeaMessageEvent messageEvent; final _TranscriptionLoader _transcriptLoader; final _TranslationLoader _translationLoader; @@ -86,10 +89,14 @@ class SelectModeController { _sttTranslationLoader = _STTTranslationLoader(messageEvent); ValueNotifier selectedMode = ValueNotifier(null); + ValueNotifier<(ConstructIdentifier, String)?> constructEmojiNotifier = + ValueNotifier<(ConstructIdentifier, String)?>(null); + final StreamController contentChangedStream = StreamController.broadcast(); void dispose() { selectedMode.dispose(); + constructEmojiNotifier.dispose(); _transcriptLoader.dispose(); _translationLoader.dispose(); _sttTranslationLoader.dispose(); @@ -185,6 +192,27 @@ class SelectModeController { selectedMode.value = mode; } + Future setTokenEmoji( + ConstructIdentifier constructId, + String emoji, + String targetId, + ) async { + constructEmojiNotifier.value = (constructId, emoji); + try { + await setLemmaEmoji( + constructId, + emoji, + targetId, + ); + } catch (e, s) { + ErrorHandler.logError( + data: constructId.toJson(), + e: e, + s: s, + ); + } + } + Future fetchAudio() => _audioLoader.load(); Future fetchTranslation() => _translationLoader.load(); Future fetchTranscription() => _transcriptLoader.load(); diff --git a/lib/pangea/toolbar/reading_assistance/token_emoji_button.dart b/lib/pangea/toolbar/reading_assistance/token_emoji_button.dart index 45bd36caf..9b1c31e98 100644 --- a/lib/pangea/toolbar/reading_assistance/token_emoji_button.dart +++ b/lib/pangea/toolbar/reading_assistance/token_emoji_button.dart @@ -1,19 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/analytics_misc/lemma_emoji_setter_mixin.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/lemma_meaning_builder.dart'; -import 'package:fluffychat/pangea/toolbar/reading_assistance/lemma_emoji_picker.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance/select_mode_buttons.dart'; import 'package:fluffychat/widgets/matrix.dart'; class TokenEmojiButton extends StatefulWidget { final ValueNotifier selectModeNotifier; - final ValueNotifier selectedTokenNotifier; + final ValueNotifier<(ConstructIdentifier, String)?> constructEmojiNotifier; + final VoidCallback onTap; final PangeaToken? token; final String? targetId; @@ -22,7 +19,8 @@ class TokenEmojiButton extends StatefulWidget { const TokenEmojiButton({ super.key, required this.selectModeNotifier, - required this.selectedTokenNotifier, + required this.constructEmojiNotifier, + required this.onTap, this.token, this.targetId, this.enabled = true, @@ -49,14 +47,14 @@ class TokenEmojiButtonState extends State _initAnimation(); _prevMode = widget.selectModeNotifier.value; widget.selectModeNotifier.addListener(_onUpdateSelectMode); - widget.selectedTokenNotifier.addListener(_onSelectToken); + widget.constructEmojiNotifier.addListener(_onUpdateEmoji); } @override void dispose() { _controller?.dispose(); widget.selectModeNotifier.removeListener(_onUpdateSelectMode); - widget.selectedTokenNotifier.removeListener(_onSelectToken); + widget.constructEmojiNotifier.removeListener(_onUpdateEmoji); super.dispose(); } @@ -87,68 +85,18 @@ class TokenEmojiButtonState extends State _prevMode = mode; } - void _onSelectToken() { - final selected = widget.selectedTokenNotifier.value; - if (selected != null && selected == widget.token) { - showTokenEmojiPopup(); + void _onUpdateEmoji() { + final value = widget.constructEmojiNotifier.value; + if (value == null) return; + + final constructId = value.$1; + final emoji = value.$2; + + if (mounted && constructId == widget.token?.vocabConstructID) { + setState(() => _emoji = emoji); } } - void showTokenEmojiPopup() { - if (widget.targetId == null || widget.token == null) return; - OverlayUtil.showPositionedCard( - overlayKey: "overlay_emoji_selector", - context: context, - cardToShow: LemmaMeaningBuilder( - langCode: MatrixState.pangeaController.userController.userL2Code!, - constructId: widget.token!.vocabConstructID, - builder: (context, controller) { - return Material( - type: MaterialType.transparency, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ), - child: LemmaEmojiPicker( - emojis: controller.lemmaInfo?.emoji ?? [], - onSelect: (emoji) { - _setTokenEmoji(emoji); - MatrixState.pAnyState.closeOverlay("overlay_emoji_selector"); - }, - loading: controller.isLoading, - ), - ), - ); - }, - ), - transformTargetId: widget.targetId!, - closePrevOverlay: false, - addBorder: false, - maxWidth: (40 * 5) + (4 * 5) + 16, - maxHeight: 60, - ); - } - - void _setTokenEmoji(String emoji) { - setState(() => _emoji = emoji); - - if (widget.targetId == null || widget.token == null) return; - setLemmaEmoji( - widget.token!.vocabConstructID, - emoji, - widget.targetId, - ).catchError((e, s) { - ErrorHandler.logError( - data: widget.token!.toJson(), - e: e, - s: s, - ); - }); - } - @override Widget build(BuildContext context) { if (_sizeAnimation == null) { @@ -183,7 +131,7 @@ class TokenEmojiButtonState extends State child: child, builder: (context, child) { return InkWell( - onTap: showTokenEmojiPopup, + onTap: widget.onTap, borderRadius: BorderRadius.circular(99.0), child: Container( height: _sizeAnimation!.value, diff --git a/lib/pangea/toolbar/word_card/lemma_reaction_picker.dart b/lib/pangea/toolbar/word_card/lemma_reaction_picker.dart index 00ba9ee27..e2892da8d 100644 --- a/lib/pangea/toolbar/word_card/lemma_reaction_picker.dart +++ b/lib/pangea/toolbar/word_card/lemma_reaction_picker.dart @@ -11,16 +11,18 @@ import 'package:fluffychat/pangea/toolbar/reading_assistance/lemma_emoji_picker. class LemmaReactionPicker extends StatelessWidget { final Event? event; final ConstructIdentifier construct; + final Future Function(String)? setEmoji; final String langCode; const LemmaReactionPicker({ super.key, required this.construct, + required this.setEmoji, required this.langCode, this.event, }); - Future setEmoji( + Future onSelect( String emoji, List emojis, ) async { @@ -45,6 +47,8 @@ class LemmaReactionPicker extends StatelessWidget { ); try { + await setEmoji?.call(emoji); + if (reactionEvent != null) { await reactionEvent.redactEvent(); return; @@ -97,7 +101,7 @@ class LemmaReactionPicker extends StatelessWidget { return LemmaEmojiPicker( emojis: controller.lemmaInfo?.emoji ?? [], onSelect: event?.room.timeline != null - ? (emoji) => setEmoji( + ? (emoji) => onSelect( emoji, controller.lemmaInfo?.emoji ?? [], ) diff --git a/lib/pangea/toolbar/word_card/reading_assistance_content.dart b/lib/pangea/toolbar/word_card/reading_assistance_content.dart index 71d2e0d97..486a92239 100644 --- a/lib/pangea/toolbar/word_card/reading_assistance_content.dart +++ b/lib/pangea/toolbar/word_card/reading_assistance_content.dart @@ -51,6 +51,11 @@ class ReadingAssistanceContent extends StatelessWidget { onClose: () => overlayController.updateSelectedSpan(null), langCode: overlayController.pangeaMessageEvent.messageDisplayLangCode, onDismissNewWordOverlay: () => overlayController.setState(() {}), + setEmoji: (emoji) => overlayController.selectModeController.setTokenEmoji( + overlayController.selectedToken!.vocabConstructID, + emoji, + overlayController.tokenEmojiPopupKey(overlayController.selectedToken!), + ), onFlagTokenInfo: (LemmaInfoResponse lemmaInfo, String phonetics) { if (selectedTokenIndex < 0) return; final requestData = TokenInfoFeedbackRequestData( diff --git a/lib/pangea/toolbar/word_card/word_card_switcher.dart b/lib/pangea/toolbar/word_card/word_card_switcher.dart index 1e2ab854f..18551a02a 100644 --- a/lib/pangea/toolbar/word_card/word_card_switcher.dart +++ b/lib/pangea/toolbar/word_card/word_card_switcher.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/toolbar/layout/message_selection_positioner.dart'; -import 'package:fluffychat/pangea/toolbar/reading_assistance/select_mode_buttons.dart'; import 'package:fluffychat/pangea/toolbar/word_card/reading_assistance_content.dart'; class WordCardSwitcher extends StatelessWidget { @@ -19,15 +18,13 @@ class WordCardSwitcher extends StatelessWidget { ? Alignment.bottomRight : Alignment.bottomLeft, duration: FluffyThemes.animationDuration, - child: mode == SelectMode.emoji - ? const SizedBox() - : controller.widget.overlayController.selectedToken != null - ? ReadingAssistanceContent( - overlayController: controller.widget.overlayController, - ) - : MessageReactionPicker( - chatController: controller.widget.chatController, - ), + child: controller.widget.overlayController.selectedToken != null + ? ReadingAssistanceContent( + overlayController: controller.widget.overlayController, + ) + : MessageReactionPicker( + chatController: controller.widget.chatController, + ), ); }, ); diff --git a/lib/pangea/toolbar/word_card/word_zoom_widget.dart b/lib/pangea/toolbar/word_card/word_zoom_widget.dart index 3bb8b0bb9..2da2e272d 100644 --- a/lib/pangea/toolbar/word_card/word_zoom_widget.dart +++ b/lib/pangea/toolbar/word_card/word_zoom_widget.dart @@ -29,12 +29,14 @@ class WordZoomWidget extends StatelessWidget { final VoidCallback? onDismissNewWordOverlay; final Function(LemmaInfoResponse, String)? onFlagTokenInfo; + final Future Function(String)? setEmoji; const WordZoomWidget({ super.key, required this.token, required this.construct, required this.langCode, + this.setEmoji, this.onClose, this.wordIsNew = false, this.event, @@ -144,6 +146,7 @@ class WordZoomWidget extends StatelessWidget { construct: construct, langCode: langCode, event: event, + setEmoji: setEmoji, ), LemmaMeaningDisplay( langCode: langCode,