show word card in image toolbar mode

This commit is contained in:
ggurdin 2025-12-16 14:19:17 -05:00
parent 60f572520d
commit 2897142b9d
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
9 changed files with 81 additions and 90 deletions

View file

@ -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(

View file

@ -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<T extends StatefulWidget> on State<T> {
mixin LemmaEmojiSetter {
Future<void> setLemmaEmoji(
ConstructIdentifier constructId,
String emoji,
@ -26,11 +26,13 @@ mixin LemmaEmojiSetter<T extends StatefulWidget> on State<T> {
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);

View file

@ -93,6 +93,7 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow>
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);

View file

@ -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<SelectMode?> selectedMode = ValueNotifier<SelectMode?>(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<void> 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<void> fetchAudio() => _audioLoader.load();
Future<void> fetchTranslation() => _translationLoader.load();
Future<void> fetchTranscription() => _transcriptLoader.load();

View file

@ -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<SelectMode?> selectModeNotifier;
final ValueNotifier<PangeaToken?> 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<TokenEmojiButton>
_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<TokenEmojiButton>
_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<TokenEmojiButton>
child: child,
builder: (context, child) {
return InkWell(
onTap: showTokenEmojiPopup,
onTap: widget.onTap,
borderRadius: BorderRadius.circular(99.0),
child: Container(
height: _sizeAnimation!.value,

View file

@ -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<void> Function(String)? setEmoji;
final String langCode;
const LemmaReactionPicker({
super.key,
required this.construct,
required this.setEmoji,
required this.langCode,
this.event,
});
Future<void> setEmoji(
Future<void> onSelect(
String emoji,
List<String> 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 ?? [],
)

View file

@ -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(

View file

@ -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,
),
);
},
);

View file

@ -29,12 +29,14 @@ class WordZoomWidget extends StatelessWidget {
final VoidCallback? onDismissNewWordOverlay;
final Function(LemmaInfoResponse, String)? onFlagTokenInfo;
final Future<void> 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,