From 802465c92ce02b3c76ccb49415ef8233cc084125 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:38:11 -0400 Subject: [PATCH] 3895 emoji sequence on clicking the words in a sentence of the target language (#4004) * cleanup * feat: toolbar emoji mode --- lib/pages/chat/chat_event_list.dart | 1 - lib/pages/chat/events/html_message.dart | 29 +-- lib/pages/chat/events/message.dart | 4 - lib/pages/chat/events/message_content.dart | 4 - .../room_analytics_extension.dart | 36 ++++ .../controllers/choreographer.dart | 6 - .../common/widgets/overlay_container.dart | 6 +- .../constructs/construct_identifier.dart | 47 +---- .../events/models/pangea_token_model.dart | 7 +- .../extensions/pangea_room_extension.dart | 2 + lib/pangea/lemmas/lemma_emoji_picker.dart | 46 +++++ lib/pangea/lemmas/lemma_reaction_picker.dart | 37 +--- lib/pangea/lemmas/user_set_lemma_info.dart | 6 +- .../token_emoji_button.dart | 89 +++++++++ ...button.dart => token_practice_button.dart} | 10 +- .../lemma_emoji_choice_item.dart | 9 +- .../message_morph_choice.dart | 5 +- .../practice_match_card.dart | 4 +- .../reading_assistance_input_bar.dart | 25 +-- .../toolbar/widgets/message_audio_card.dart | 21 ++- .../widgets/message_meaning_button.dart | 1 - .../widgets/message_selection_overlay.dart | 177 ++++++++++++------ .../widgets/message_selection_positioner.dart | 27 ++- .../toolbar/widgets/over_message_overlay.dart | 16 +- .../widgets/overlay_center_content.dart | 1 - .../toolbar/widgets/overlay_message.dart | 5 +- .../multiple_choice_activity.dart | 18 +- .../practice_activity_card.dart | 20 +- .../widgets/practice_mode_buttons.dart | 4 - .../widgets/reading_assistance_content.dart | 8 +- .../toolbar/widgets/select_mode_buttons.dart | 32 ++-- .../toolbar/widgets/toolbar_button.dart | 4 +- .../toolbar/widgets/word_card_switcher.dart | 20 +- .../widgets/word_zoom/new_word_overlay.dart | 7 +- .../widgets/word_zoom/word_zoom_widget.dart | 5 +- 35 files changed, 429 insertions(+), 310 deletions(-) create mode 100644 lib/pangea/lemmas/lemma_emoji_picker.dart create mode 100644 lib/pangea/message_token_text/token_emoji_button.dart rename lib/pangea/message_token_text/{message_token_button.dart => token_practice_button.dart} (97%) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 0afddf744..8f806f725 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -165,7 +165,6 @@ class ChatEventList extends StatelessWidget { controller.scrollToEventId(eventId), longPressSelect: controller.selectedEvents.isNotEmpty, // #Pangea - immersionMode: controller.choreographer.immersionMode, controller: controller, isButton: event.eventId == controller.buttonEventID, // Pangea# diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index a3275a7da..186266f03 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -13,11 +13,13 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.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/message_token_text/message_token_button.dart'; +import 'package:fluffychat/pangea/message_token_text/token_emoji_button.dart'; +import 'package:fluffychat/pangea/message_token_text/token_practice_button.dart'; import 'package:fluffychat/pangea/message_token_text/tokens_util.dart'; import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/utils/token_rendering_util.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart'; import 'package:fluffychat/utils/event_checkbox_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -44,9 +46,6 @@ class HtmlMessage extends StatelessWidget { final Event? prevEvent; final bool isTransitionAnimation; final ReadingAssistanceMode? readingAssistanceMode; - - final bool Function(PangeaToken)? isHighlighted; - final bool Function(PangeaToken)? isSelected; final void Function(PangeaToken)? onClick; // Pangea# @@ -68,8 +67,6 @@ class HtmlMessage extends StatelessWidget { required this.controller, this.nextEvent, this.prevEvent, - this.isHighlighted, - this.isSelected, this.onClick, this.isTransitionAnimation = false, this.readingAssistanceMode, @@ -276,7 +273,6 @@ class HtmlMessage extends StatelessWidget { return inverted.join().trim(); } - debugPrint("HTML after adding token tags: $result"); return result.join().trim(); } @@ -414,12 +410,12 @@ class HtmlMessage extends StatelessWidget { int.tryParse(node.attributes['length'] ?? '') ?? 0, ); - final selected = token != null && isSelected != null - ? isSelected!.call(token) + final selected = token != null && overlayController != null + ? overlayController!.isTokenSelected(token) : false; - final highlighted = token != null && isHighlighted != null - ? isHighlighted!.call(token) + final highlighted = token != null && overlayController != null + ? overlayController!.isTokenHighlighted(token) : false; final isNew = token != null && newTokens.contains(token.text); @@ -437,8 +433,17 @@ class HtmlMessage extends StatelessWidget { : PlaceholderAlignment.middle, child: Column( children: [ + if (token != null && + overlayController?.selectedMode == SelectMode.emoji) + TokenEmojiButton( + token: token, + eventId: event.eventId, + targetId: overlayController!.tokenEmojiPopupKey(token), + onSelect: () => + overlayController!.showTokenEmojiPopup(token), + ), if (renderer.showCenterStyling && token != null) - MessageTokenButton( + TokenPracticeButton( token: token, overlayController: overlayController, textStyle: renderer.style( diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 180202947..587c5a2ee 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -50,7 +50,6 @@ class Message extends StatelessWidget { final ScrollController scrollController; final List colors; // #Pangea - final bool immersionMode; final ChatController controller; final bool isButton; // Pangea# @@ -77,7 +76,6 @@ class Message extends StatelessWidget { required this.scrollController, required this.colors, // #Pangea - required this.immersionMode, required this.controller, this.isButton = false, // Pangea# @@ -737,8 +735,6 @@ class Message extends StatelessWidget { // #Pangea pangeaMessageEvent: pangeaMessageEvent, - immersionMode: - immersionMode, controller: controller, nextEvent: diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index de7601493..f00896646 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -38,7 +38,6 @@ class MessageContent extends StatelessWidget { //question: are there any performance benefits to using booleans //here rather than passing the choreographer? pangea rich text, a widget //further down in the chain is also using pangeaController so its not constant - final bool immersionMode; final MessageOverlayController? overlayController; final ChatController controller; final Event? nextEvent; @@ -58,7 +57,6 @@ class MessageContent extends StatelessWidget { required this.selected, // #Pangea this.pangeaMessageEvent, - required this.immersionMode, this.overlayController, required this.controller, this.nextEvent, @@ -362,8 +360,6 @@ class MessageContent extends StatelessWidget { pangeaMessageEvent: pangeaMessageEvent, nextEvent: nextEvent, prevEvent: prevEvent, - isHighlighted: overlayController?.isTokenHighlighted, - isSelected: overlayController?.isTokenSelected, onClick: event.isActivityMessage ? null : onClick, isTransitionAnimation: isTransitionAnimation, readingAssistanceMode: readingAssistanceMode, diff --git a/lib/pangea/analytics_misc/room_analytics_extension.dart b/lib/pangea/analytics_misc/room_analytics_extension.dart index 033e43061..2d4e6f6c7 100644 --- a/lib/pangea/analytics_misc/room_analytics_extension.dart +++ b/lib/pangea/analytics_misc/room_analytics_extension.dart @@ -269,6 +269,42 @@ extension AnalyticsRoomExtension on Room { } } + UserSetLemmaInfo? getUserSetLemmaInfo(ConstructIdentifier cId) { + final state = getState(PangeaEventTypes.userSetLemmaInfo, cId.string); + if (state == null) return null; + try { + return UserSetLemmaInfo.fromJson(state.content); + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + "roomID": id, + "stateContent": state.content, + "stateKey": state.stateKey, + }, + ); + return null; + } + } + + Future setUserSetLemmaInfo( + ConstructIdentifier cId, + UserSetLemmaInfo info, + ) async { + final syncFuture = client.onRoomState.stream.firstWhere((event) { + return event.roomId == id && + event.state.type == PangeaEventTypes.userSetLemmaInfo; + }); + client.setRoomStateWithKey( + id, + PangeaEventTypes.userSetLemmaInfo, + cId.string, + info.toJson(), + ); + await syncFuture.timeout(const Duration(seconds: 10)); + } + List get activityRoomIds { final state = getState(PangeaEventTypes.activityRoomIds); if (state?.content[ModelKey.roomIds] is List) { diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 06ab3b84c..70ee420f4 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -705,12 +705,6 @@ class Choreographer { chatController.room, ); - bool get immersionMode => - pangeaController.permissionsController.isToolEnabled( - ToolSetting.immersionMode, - chatController.room, - ); - bool get isAutoIGCEnabled => pangeaController.permissionsController.isToolEnabled( ToolSetting.autoIGC, diff --git a/lib/pangea/common/widgets/overlay_container.dart b/lib/pangea/common/widgets/overlay_container.dart index 88a2697fc..fa09779be 100644 --- a/lib/pangea/common/widgets/overlay_container.dart +++ b/lib/pangea/common/widgets/overlay_container.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; class OverlayContainer extends StatelessWidget { @@ -40,8 +42,8 @@ class OverlayContainer extends StatelessWidget { constraints: BoxConstraints( maxWidth: maxWidth, maxHeight: maxHeight, - minHeight: 100, - minWidth: 100, + minHeight: min(100, maxHeight), + minWidth: min(100, maxWidth), ), child: isScrollable ? SingleChildScrollView(child: content) : content, ); diff --git a/lib/pangea/constructs/construct_identifier.dart b/lib/pangea/constructs/construct_identifier.dart index d6d0fd301..48148f2b9 100644 --- a/lib/pangea/constructs/construct_identifier.dart +++ b/lib/pangea/constructs/construct_identifier.dart @@ -12,13 +12,13 @@ import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/emojis/emoji_stack.dart'; -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/constants/language_constants.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; -import 'package:fluffychat/pangea/message_token_text/message_token_button.dart'; +import 'package:fluffychat/pangea/message_token_text/token_practice_button.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; import 'package:fluffychat/pangea/morphs/parts_of_speech_enum.dart'; @@ -162,31 +162,9 @@ class ConstructIdentifier { UserSetLemmaInfo? get userLemmaInfo { switch (type) { case ConstructTypeEnum.vocab: - final dynamic lemmaInfoContent = MatrixState - .pangeaController.matrixState.client + return MatrixState.pangeaController.matrixState.client .analyticsRoomLocal() - ?.getState(PangeaEventTypes.userSetLemmaInfo, string) - ?.content; - if (lemmaInfoContent != null && lemmaInfoContent is Map) { - try { - return UserSetLemmaInfo.fromJson( - lemmaInfoContent as Map, - ); - } catch (e, s) { - debugger(when: kDebugMode); - ErrorHandler.logError( - e: e, - data: { - "construct": string, - "content": lemmaInfoContent, - }, - s: s, - ); - return null; - } - } else { - return null; - } + ?.getUserSetLemmaInfo(this); case ConstructTypeEnum.morph: debugger(when: kDebugMode); ErrorHandler.logError( @@ -204,23 +182,10 @@ class ConstructIdentifier { final analyticsRoom = await client.getMyAnalyticsRoom(l2); if (analyticsRoom == null) return; + if (userLemmaInfo == newLemmaInfo) return; try { - final syncFuture = client.onRoomState.stream.firstWhere((event) { - return event.roomId == analyticsRoom.id && - event.state.type == PangeaEventTypes.userSetLemmaInfo; - }); - - client.setRoomStateWithKey( - analyticsRoom.id, - PangeaEventTypes.userSetLemmaInfo, - string, - UserSetLemmaInfo( - emojis: newLemmaInfo.emojis ?? userLemmaInfo?.emojis, - meaning: newLemmaInfo.meaning ?? userLemmaInfo?.meaning, - ).toJson(), - ); - await syncFuture; + await analyticsRoom.setUserSetLemmaInfo(this, newLemmaInfo); } catch (err, s) { debugger(when: kDebugMode); ErrorHandler.logError( diff --git a/lib/pangea/events/models/pangea_token_model.dart b/lib/pangea/events/models/pangea_token_model.dart index c71bcdc55..9a75ab117 100644 --- a/lib/pangea/events/models/pangea_token_model.dart +++ b/lib/pangea/events/models/pangea_token_model.dart @@ -444,14 +444,9 @@ class PangeaToken { ConstructForm get vocabForm => ConstructForm(form: text.content, cId: vocabConstructID); - /// [setEmoji] sets the emoji for the lemma - /// NOTE: assumes that the language of the lemma is the same as the user's current l2 Future setEmoji(List emojis) => vocabConstructID.setUserLemmaInfo(UserSetLemmaInfo(emojis: emojis)); - Future setMeaning(String meaning) => - vocabConstructID.setUserLemmaInfo(UserSetLemmaInfo(meaning: meaning)); - /// [getEmoji] gets the emoji for the lemma /// NOTE: assumes that the language of the lemma is the same as the user's current l2 List getEmoji() => vocabConstructID.userSetEmoji; @@ -570,4 +565,6 @@ class PangeaToken { return daysSinceLastUseByType(a, morphFeature) * (vocabConstructID.isContentWord ? 10 : 9); } + + String get uniqueId => "${text.content}::${text.offset}"; } diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index e35aeb077..0bd028786 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -24,10 +24,12 @@ import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; import 'package:fluffychat/pangea/common/constants/model_keys.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/events/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/extensions/join_rule_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; diff --git a/lib/pangea/lemmas/lemma_emoji_picker.dart b/lib/pangea/lemmas/lemma_emoji_picker.dart new file mode 100644 index 000000000..d3c20b2fa --- /dev/null +++ b/lib/pangea/lemmas/lemma_emoji_picker.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart'; + +class LemmaEmojiPicker extends StatelessWidget { + final List emojis; + final Function(String) onSelect; + + final bool loading; + final Function(String)? disabled; + + const LemmaEmojiPicker({ + super.key, + required this.emojis, + required this.onSelect, + this.disabled, + this.loading = false, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 8, + ), + width: (40 * 5) + (4 * 5) + 16, // 5 items max + padding + child: Row( + spacing: 4.0, + mainAxisAlignment: MainAxisAlignment.center, + children: loading + ? List.generate(5, (_) => const LemmaEmojiChoicePlaceholder()) + : emojis.take(5).map((emoji) { + final isDisabled = disabled?.call(emoji) == true; + return Opacity( + opacity: isDisabled ? 0.33 : 1, + child: LemmaEmojiChoiceItem( + content: emoji, + onTap: isDisabled ? null : () => onSelect(emoji), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/lib/pangea/lemmas/lemma_reaction_picker.dart b/lib/pangea/lemmas/lemma_reaction_picker.dart index 037db9550..4544e79e3 100644 --- a/lib/pangea/lemmas/lemma_reaction_picker.dart +++ b/lib/pangea/lemmas/lemma_reaction_picker.dart @@ -4,7 +4,7 @@ import 'package:collection/collection.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart'; +import 'package:fluffychat/pangea/lemmas/lemma_emoji_picker.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -74,36 +74,11 @@ class LemmaReactionPicker extends StatelessWidget { ); } - return Container( - height: 50, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 4.0, - children: loading - ? [1, 2, 3, 4, 5] - .map( - (e) => const LemmaEmojiChoicePlaceholder(), - ) - .toList() - : emojis - .map( - (emoji) => Opacity( - opacity: sentReactions.contains( - emoji, - ) - ? 0.33 - : 1, - child: LemmaEmojiChoiceItem( - content: emoji, - onTap: () => sentReactions.contains(emoji) - ? null - : setEmoji(emoji, context), - ), - ), - ) - .toList(), - ), + return LemmaEmojiPicker( + emojis: emojis, + onSelect: (emoji) => setEmoji(emoji, context), + disabled: (emoji) => sentReactions.contains(emoji), + loading: loading, ); } } diff --git a/lib/pangea/lemmas/user_set_lemma_info.dart b/lib/pangea/lemmas/user_set_lemma_info.dart index 0394329c9..1387c89b9 100644 --- a/lib/pangea/lemmas/user_set_lemma_info.dart +++ b/lib/pangea/lemmas/user_set_lemma_info.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart'; + class UserSetLemmaInfo { final String? meaning; final List? emojis; @@ -26,9 +28,9 @@ class UserSetLemmaInfo { identical(this, other) || other is UserSetLemmaInfo && runtimeType == other.runtimeType && - emojis == other.emojis && + const ListEquality().equals(emojis, other.emojis) && meaning == other.meaning; @override - int get hashCode => emojis.hashCode ^ meaning.hashCode; + int get hashCode => meaning.hashCode ^ Object.hashAll(emojis ?? []); } diff --git a/lib/pangea/message_token_text/token_emoji_button.dart b/lib/pangea/message_token_text/token_emoji_button.dart new file mode 100644 index 000000000..ca1f93089 --- /dev/null +++ b/lib/pangea/message_token_text/token_emoji_button.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class TokenEmojiButton extends StatefulWidget { + final PangeaToken token; + final String eventId; + final String targetId; + final VoidCallback onSelect; + + const TokenEmojiButton({ + super.key, + required this.token, + required this.eventId, + required this.targetId, + required this.onSelect, + }); + + @override + State createState() => TokenEmojiButtonState(); +} + +class TokenEmojiButtonState extends State + with TickerProviderStateMixin { + final double buttonSize = 20.0; + AnimationController? _controller; + Animation? _sizeAnimation; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + vsync: this, + duration: FluffyThemes.animationDuration, + ); + + _sizeAnimation = Tween( + begin: 0, + end: buttonSize, + ).animate(CurvedAnimation(parent: _controller!, curve: Curves.easeOut)); + + _controller?.forward(); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final emoji = widget.token.vocabConstructID.userSetEmoji.firstOrNull; + if (_sizeAnimation != null) { + return CompositedTransformTarget( + link: MatrixState.pAnyState.layerLinkAndKey(widget.targetId).link, + child: AnimatedBuilder( + key: MatrixState.pAnyState.layerLinkAndKey(widget.targetId).key, + animation: _sizeAnimation!, + builder: (context, child) { + return Container( + height: _sizeAnimation!.value, + width: _sizeAnimation!.value, + alignment: Alignment.center, + child: InkWell( + onTap: widget.onSelect, + borderRadius: BorderRadius.circular(99.0), + child: emoji != null + ? Text( + emoji, + style: TextStyle(fontSize: buttonSize - 4.0), + ) + : Icon( + Icons.add_reaction_outlined, + size: buttonSize - 4.0, + ), + ), + ); + }, + ), + ); + } + + return const SizedBox.shrink(); + } +} diff --git a/lib/pangea/message_token_text/message_token_button.dart b/lib/pangea/message_token_text/token_practice_button.dart similarity index 97% rename from lib/pangea/message_token_text/message_token_button.dart rename to lib/pangea/message_token_text/token_practice_button.dart index e5aeccaf1..e1efc4070 100644 --- a/lib/pangea/message_token_text/message_token_button.dart +++ b/lib/pangea/message_token_text/token_practice_button.dart @@ -25,14 +25,14 @@ const double tokenButtonDefaultFontSize = 10; const int maxEmojisPerLemma = 1; const double estimatedEmojiWidthRatio = 2; -class MessageTokenButton extends StatefulWidget { +class TokenPracticeButton extends StatefulWidget { final MessageOverlayController? overlayController; final PangeaToken token; final TextStyle textStyle; final double width; final bool animateIn; - const MessageTokenButton({ + const TokenPracticeButton({ super.key, required this.overlayController, required this.token, @@ -42,10 +42,10 @@ class MessageTokenButton extends StatefulWidget { }); @override - MessageTokenButtonState createState() => MessageTokenButtonState(); + TokenPracticeButtonState createState() => TokenPracticeButtonState(); } -class MessageTokenButtonState extends State +class TokenPracticeButtonState extends State with TickerProviderStateMixin { AnimationController? _controller; Animation? _heightAnimation; @@ -102,7 +102,7 @@ class MessageTokenButtonState extends State } @override - void didUpdateWidget(covariant MessageTokenButton oldWidget) { + void didUpdateWidget(covariant TokenPracticeButton oldWidget) { super.didUpdateWidget(oldWidget); _setSelected(); if (_isEmpty != _wasEmpty) { diff --git a/lib/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart b/lib/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart index 499970db1..b1d01164b 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/lemma_emoji_choice_item.dart @@ -12,7 +12,7 @@ class LemmaEmojiChoiceItem extends StatefulWidget { }); final String content; - final Function onTap; + final VoidCallback? onTap; @override LemmaEmojiChoiceItemState createState() => LemmaEmojiChoiceItemState(); @@ -45,12 +45,7 @@ class LemmaEmojiChoiceItemState extends State { child: InkWell( onHover: (isHovered) => setState(() => _isHovered = isHovered), borderRadius: BorderRadius.circular(AppConfig.borderRadius), - onTap: () { - if (!mounted) { - return; - } - widget.onTap(); - }, + onTap: widget.onTap, child: Text( widget.content, style: Theme.of(context).textTheme.headlineSmall, 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 cbb14d55e..654d9c0e3 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 @@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/choreographer/widgets/choice_animation.dart'; import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; -import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; @@ -32,12 +31,10 @@ const int numberOfMorphDistractors = 3; class MessageMorphInputBarContent extends StatefulWidget { final MessageOverlayController overlayController; final PracticeActivityModel activity; - final PangeaMessageEvent pangeaMessageEvent; const MessageMorphInputBarContent({ super.key, required this.overlayController, - required this.pangeaMessageEvent, required this.activity, }); @@ -150,7 +147,7 @@ class MessageMorphInputBarContentState form: token.text.content, ), ), - widget.pangeaMessageEvent, + widget.overlayController.pangeaMessageEvent, () => overlay.setState(() {}), ); }, diff --git a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart index 4fbbc7384..260047e8a 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart @@ -69,10 +69,8 @@ class MatchActivityCard extends StatelessWidget { mainAxisSize: MainAxisSize.max, spacing: 4.0, children: [ - if (overlayController.toolbarMode == MessageMode.listening && - overlayController.pangeaMessageEvent != null) + if (overlayController.toolbarMode == MessageMode.listening) MessageAudioCard( - messageEvent: overlayController.pangeaMessageEvent!, overlayController: overlayController, ), Wrap( 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 8ee5e110d..801121126 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 @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_mode_locked_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; @@ -13,11 +12,9 @@ import 'package:fluffychat/pangea/toolbar/widgets/practice_mode_buttons.dart'; const double minContentHeight = 120; class ReadingAssistanceInputBar extends StatefulWidget { - final ChatController controller; final MessageOverlayController overlayController; const ReadingAssistanceInputBar( - this.controller, this.overlayController, { super.key, }); @@ -39,17 +36,15 @@ class ReadingAssistanceInputBarState extends State { Widget barContent(BuildContext context) { Widget? content; - final target = - overlayController.toolbarMode.associatedActivityType != null && - overlayController.pangeaMessageEvent != null - ? overlayController.practiceSelection?.getSelection( - overlayController.toolbarMode.associatedActivityType!, - overlayController.selectedMorph?.token, - overlayController.selectedMorph?.morph, - ) - : null; + final target = overlayController.toolbarMode.associatedActivityType != null + ? overlayController.practiceSelection?.getSelection( + overlayController.toolbarMode.associatedActivityType!, + overlayController.selectedMorph?.token, + overlayController.selectedMorph?.morph, + ) + : null; - if (overlayController.pangeaMessageEvent?.isAudioMessage == true) { + if (overlayController.pangeaMessageEvent.isAudioMessage == true) { return const SizedBox(); // return ReactionsPicker(controller); } else { @@ -72,7 +67,7 @@ class ReadingAssistanceInputBarState extends State { case MessageMode.messageTranslation: if (overlayController.isTranslationUnlocked) { content = MessageTranslationCard( - messageEvent: overlayController.pangeaMessageEvent!, + messageEvent: overlayController.pangeaMessageEvent, ); } else { content = MessageModeLockedCard(controller: overlayController); @@ -83,7 +78,6 @@ class ReadingAssistanceInputBarState extends State { case MessageMode.listening: if (target != null) { content = PracticeActivityCard( - pangeaMessageEvent: overlayController.pangeaMessageEvent!, targetTokensAndActivityType: target, overlayController: overlayController, ); @@ -96,7 +90,6 @@ class ReadingAssistanceInputBarState extends State { case MessageMode.wordMorph: if (target != null) { content = PracticeActivityCard( - pangeaMessageEvent: overlayController.pangeaMessageEvent!, targetTokensAndActivityType: target, overlayController: overlayController, ); diff --git a/lib/pangea/toolbar/widgets/message_audio_card.dart b/lib/pangea/toolbar/widgets/message_audio_card.dart index 3b854b5bf..da904cbab 100644 --- a/lib/pangea/toolbar/widgets/message_audio_card.dart +++ b/lib/pangea/toolbar/widgets/message_audio_card.dart @@ -15,13 +15,11 @@ import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller. import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; class MessageAudioCard extends StatefulWidget { - final PangeaMessageEvent messageEvent; final MessageOverlayController overlayController; final VoidCallback? onError; const MessageAudioCard({ super.key, - required this.messageEvent, required this.overlayController, this.onError, }); @@ -40,21 +38,24 @@ class MessageAudioCardState extends State { fetchAudio(); } + PangeaMessageEvent get messageEvent => + widget.overlayController.pangeaMessageEvent; + Future fetchAudio() async { if (!mounted) return; setState(() => _isLoading = true); try { - final String langCode = widget.messageEvent.messageDisplayLangCode; - final Event? localEvent = widget.messageEvent.getTextToSpeechLocal( + final String langCode = messageEvent.messageDisplayLangCode; + final Event? localEvent = messageEvent.getTextToSpeechLocal( langCode, - widget.messageEvent.messageDisplayText, + messageEvent.messageDisplayText, ); if (localEvent != null) { audioFile = await localEvent.getPangeaAudioFile(); } else { - audioFile = await widget.messageEvent.getMatrixAudioFile( + audioFile = await messageEvent.getMatrixAudioFile( langCode, ); } @@ -69,7 +70,7 @@ class MessageAudioCardState extends State { m: 'something wrong getting audio in MessageAudioCardState', data: { 'widget.messageEvent.messageDisplayLangCode': - widget.messageEvent.messageDisplayLangCode, + messageEvent.messageDisplayLangCode, }, ); if (mounted) setState(() => _isLoading = false); @@ -83,9 +84,9 @@ class MessageAudioCardState extends State { : audioFile != null ? AudioPlayerWidget( null, - eventId: "${widget.messageEvent.eventId}_practice", - roomId: widget.messageEvent.room.id, - senderId: widget.messageEvent.senderId, + eventId: "${messageEvent.eventId}_practice", + roomId: messageEvent.room.id, + senderId: messageEvent.senderId, matrixFile: audioFile, color: Theme.of(context).colorScheme.onPrimaryContainer, fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor, diff --git a/lib/pangea/toolbar/widgets/message_meaning_button.dart b/lib/pangea/toolbar/widgets/message_meaning_button.dart index 64acfd305..044d913a8 100644 --- a/lib/pangea/toolbar/widgets/message_meaning_button.dart +++ b/lib/pangea/toolbar/widgets/message_meaning_button.dart @@ -27,7 +27,6 @@ class MessageMeaningButton extends StatelessWidget { mode: MessageMode.messageMeaning, overlayController: overlayController, buttonSize: buttonSize, - onPressed: overlayController.updateToolbarMode, ), secondChild: Container( width: buttonSize, diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 6e5a68e90..ed5d1875d 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -15,10 +15,12 @@ import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/common/utils/overlay.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; +import 'package:fluffychat/pangea/lemmas/lemma_emoji_picker.dart'; import 'package:fluffychat/pangea/message_token_text/tokens_util.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; @@ -34,6 +36,9 @@ import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.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/pangea/toolbar/widgets/select_mode_buttons.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; /// Controls data at the top level of the toolbar (mainly token / toolbar mode selection) @@ -103,6 +108,8 @@ class MessageOverlayController extends State double maxWidth = AppConfig.toolbarMinWidth; + SelectMode? selectedMode; + ///////////////////////////////////// /// Lifecycle ///////////////////////////////////// @@ -127,12 +134,12 @@ class MessageOverlayController extends State Future initializeTokensAndMode() async { try { - if (pangeaMessageEvent?.event.messageType != MessageTypes.Text) { + if (pangeaMessageEvent.event.messageType != MessageTypes.Text) { return; } RepresentationEvent? repEvent = - pangeaMessageEvent?.messageDisplayRepresentation; + pangeaMessageEvent.messageDisplayRepresentation; if (repEvent == null || (repEvent.event == null && repEvent.tokens == null)) { @@ -153,7 +160,7 @@ class MessageOverlayController extends State e: e, s: s, data: { - "eventID": pangeaMessageEvent?.eventId, + "eventID": pangeaMessageEvent.eventId, }, ); } finally { @@ -259,20 +266,15 @@ class MessageOverlayController extends State if (selectedMorph != null) { selectedMorph = null; } - // close overlay of previous token - if (selectedToken != null) { - MatrixState.pAnyState.closeOverlay( - "${selectedToken!.text.uniqueKey}_toolbar", - ); - } _selectedSpan = selectedSpan; - if (mounted) setState(() {}); - - //Commented out so onSelectNewTokens can be manually called after animation is finished - // if (selectedToken != null && isNewToken(selectedToken!)) { - // _onSelectNewToken(selectedToken!); - // } + if (selectedMode == SelectMode.emoji && selectedToken != null) { + showTokenEmojiPopup(selectedToken!); + } + if (mounted) { + setState(() {}); + if (selectedToken != null) onSelectNewToken(selectedToken!); + } } void updateToolbarMode(MessageMode mode) => setState(() { @@ -331,16 +333,12 @@ class MessageOverlayController extends State ///////////////////////////////////// /// Getters //////////////////////////////////// - PangeaMessageEvent? get pangeaMessageEvent => PangeaMessageEvent( + PangeaMessageEvent get pangeaMessageEvent => PangeaMessageEvent( event: widget._event, timeline: widget._timeline, ownMessage: widget._event.room.client.userID == widget._event.senderId, ); - bool get showToolbarButtons => - pangeaMessageEvent != null && - pangeaMessageEvent!.event.messageType == MessageTypes.Text; - bool get hideWordCardContent => readingAssistanceMode == ReadingAssistanceMode.practiceMode; @@ -372,7 +370,7 @@ class MessageOverlayController extends State /// you have to complete one of the mode mini-games to unlock translation bool get isTranslationUnlocked => - pangeaMessageEvent?.ownMessage == true || + pangeaMessageEvent.ownMessage == true || !messageInUserL2 || isEmojiDone || isMeaningDone || @@ -383,35 +381,30 @@ class MessageOverlayController extends State isEmojiDone && isMeaningDone && isListeningDone && isMorphDone; PracticeSelection? get practiceSelection => - pangeaMessageEvent?.messageDisplayRepresentation?.tokens != null + pangeaMessageEvent.messageDisplayRepresentation?.tokens != null ? PracticeSelectionRepo.get( - pangeaMessageEvent!.messageDisplayLangCode, - pangeaMessageEvent!.messageDisplayRepresentation!.tokens!, + pangeaMessageEvent.messageDisplayLangCode, + pangeaMessageEvent.messageDisplayRepresentation!.tokens!, ) : null; bool get messageInUserL2 => - pangeaMessageEvent?.messageDisplayLangCode.split("-")[0] == + pangeaMessageEvent.messageDisplayLangCode.split("-")[0] == MatrixState.pangeaController.languageController.userL2?.langCodeShort; PangeaToken? get selectedToken { - if (pangeaMessageEvent?.isAudioMessage == true) { - final stt = pangeaMessageEvent!.getSpeechToTextLocal(); + if (pangeaMessageEvent.isAudioMessage == true) { + final stt = pangeaMessageEvent.getSpeechToTextLocal(); if (stt == null || stt.transcript.sttTokens.isEmpty) return null; return stt.transcript.sttTokens .firstWhereOrNull((t) => isTokenSelected(t.token)) ?.token; } - return pangeaMessageEvent?.messageDisplayRepresentation?.tokens + return pangeaMessageEvent.messageDisplayRepresentation?.tokens ?.firstWhereOrNull(isTokenSelected); } - /// Whether the overlay is currently displaying a selection - bool get isSelection => _selectedSpan != null || _highlightedTokens != null; - - PangeaTokenText? get selectedSpan => _selectedSpan; - bool get showingExtraContent => (showTranslation && translation != null) || (showSpeechTranslation && speechTranslation != null) || @@ -424,10 +417,8 @@ class MessageOverlayController extends State } if (event.messageType == MessageTypes.Text) { - return pangeaMessageEvent != null && - pangeaMessageEvent!.messageDisplayLangCode.split("-").first == - MatrixState - .pangeaController.languageController.userL2!.langCodeShort; + return pangeaMessageEvent.messageDisplayLangCode.split("-").first == + MatrixState.pangeaController.languageController.userL2!.langCodeShort; } return event.messageType == MessageTypes.Audio; @@ -459,18 +450,17 @@ class MessageOverlayController extends State Future _fetchNewRepEvent() async { final RepresentationEvent? repEvent = - pangeaMessageEvent?.messageDisplayRepresentation; + pangeaMessageEvent.messageDisplayRepresentation; if (repEvent != null) return repEvent; - final eventID = - await pangeaMessageEvent?.representationByDetectedLanguage(); + final eventID = await pangeaMessageEvent.representationByDetectedLanguage(); if (eventID == null) return null; final event = await widget._event.room.getEventById(eventID); if (event == null) return null; return RepresentationEvent( - timeline: pangeaMessageEvent!.timeline, - parentMessageEvent: pangeaMessageEvent!.event, + timeline: pangeaMessageEvent.timeline, + parentMessageEvent: pangeaMessageEvent.event, event: event, ); } @@ -493,6 +483,13 @@ class MessageOverlayController extends State setState(() {}); } + PracticeTarget? practiceTargetForToken(PangeaToken token) { + if (toolbarMode.associatedActivityType == null) return null; + return practiceSelection + ?.activities(toolbarMode.associatedActivityType!) + .firstWhereOrNull((a) => a.tokens.contains(token)); + } + void onClickOverlayMessageToken( PangeaToken token, ) { @@ -502,17 +499,16 @@ class MessageOverlayController extends State } /// we don't want to associate the audio with the text in this mode - if (pangeaMessageEvent?.messageDisplayLangCode != null && - practiceSelection?.hasActiveActivityByToken( - ActivityTypeEnum.wordFocusListening, - token, - ) == - false || + if (practiceSelection?.hasActiveActivityByToken( + ActivityTypeEnum.wordFocusListening, + token, + ) == + false || !hideWordCardContent) { TtsController.tryToSpeak( token.text.content, targetID: null, - langCode: pangeaMessageEvent!.messageDisplayLangCode, + langCode: pangeaMessageEvent.messageDisplayLangCode, ); } @@ -521,6 +517,9 @@ class MessageOverlayController extends State void onSelectNewToken(PangeaToken token) { if (!isNewToken(token)) return; + final future = + MatrixState.pangeaController.getAnalytics.analyticsStream.stream.first; + MatrixState.pangeaController.putAnalytics.setState( AnalyticsStream( eventId: event.eventId, @@ -544,16 +543,12 @@ class MessageOverlayController extends State ), ); - if (mounted) { - setState(() {}); - } - } - - PracticeTarget? practiceTargetForToken(PangeaToken token) { - if (toolbarMode.associatedActivityType == null) return null; - return practiceSelection - ?.activities(toolbarMode.associatedActivityType!) - .firstWhereOrNull((a) => a.tokens.contains(token)); + future.then((_) { + TokensUtil.clearNewTokenCache(); + if (mounted) { + setState(() {}); + } + }); } /// Whether the given token is currently selected or highlighted @@ -564,7 +559,7 @@ class MessageOverlayController extends State } bool isNewToken(PangeaToken token) => - TokensUtil.isNewToken(token, pangeaMessageEvent!); + TokensUtil.isNewToken(token, pangeaMessageEvent); bool isTokenHighlighted(PangeaToken token) { if (_highlightedTokens == null) return false; @@ -573,6 +568,12 @@ class MessageOverlayController extends State ); } + void setSelectMode(SelectMode? mode) { + if (!mounted) return; + if (selectedMode == mode) return; + setState(() => selectedMode = mode); + } + void setTranslation(String value) { if (mounted) { setState(() { @@ -636,6 +637,61 @@ class MessageOverlayController extends State } } + void showTokenEmojiPopup( + PangeaToken token, + ) { + OverlayUtil.showPositionedCard( + overlayKey: "overlay_emoji_selector_${event.eventId}", + context: context, + cardToShow: LemmaMeaningBuilder( + langCode: + MatrixState.pangeaController.languageController.activeL2Code()!, + constructId: 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) async { + final resp = await showFutureLoadingDialog( + context: context, + future: () => setTokenEmoji(token, emoji), + ); + if (mounted && !resp.isError) { + MatrixState.pAnyState.closeOverlay( + "overlay_emoji_selector_${event.eventId}", + ); + } + }, + loading: controller.isLoading, + ), + ), + ); + }, + ), + transformTargetId: tokenEmojiPopupKey(token), + closePrevOverlay: false, + addBorder: false, + maxWidth: (40 * 5) + (4 * 5) + 16, + maxHeight: 60, + ); + } + + Future setTokenEmoji(PangeaToken token, String emoji) async { + await token.setEmoji([emoji]); + if (mounted) setState(() {}); + } + + String tokenEmojiPopupKey(PangeaToken token) => + "${token.uniqueId}_${event.eventId}_emoji_button"; + ///////////////////////////////////// /// Build ///////////////////////////////////// @@ -647,7 +703,6 @@ class MessageOverlayController extends State event: widget._event, nextEvent: widget._nextEvent, prevEvent: widget._prevEvent, - pangeaMessageEvent: pangeaMessageEvent, initialSelectedToken: widget._initialSelectedToken, ); } diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index f43c95687..b22450b96 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -29,8 +29,6 @@ class MessageSelectionPositioner extends StatefulWidget { final MessageOverlayController overlayController; final ChatController chatController; final Event event; - - final PangeaMessageEvent? pangeaMessageEvent; final PangeaToken? initialSelectedToken; final Event? nextEvent; final Event? prevEvent; @@ -39,7 +37,6 @@ class MessageSelectionPositioner extends StatefulWidget { required this.overlayController, required this.chatController, required this.event, - this.pangeaMessageEvent, this.initialSelectedToken, this.nextEvent, this.prevEvent, @@ -59,11 +56,14 @@ class MessageSelectionPositionerState extends State ScrollController? scrollController; bool finishedTransition = false; - bool startedTransition = false; + bool _startedTransition = false; ReadingAssistanceMode readingAssistanceMode = ReadingAssistanceMode.selectMode; + PangeaMessageEvent get pangeaMessageEvent => + widget.overlayController.pangeaMessageEvent; + @override void initState() { super.initState(); @@ -151,7 +151,7 @@ class MessageSelectionPositionerState extends State return reactionsEvents.where((e) => !e.redacted).isNotEmpty; } - double get reactionsHeight { + double get _reactionsHeight { if (_reactionsRenderBox != null) { return _reactionsRenderBox!.size.height; } @@ -277,12 +277,11 @@ class MessageSelectionPositionerState extends State double get _contentHeight { final messageHeight = _overlayMessageSize?.height ?? originalMessageSize.height; - return messageHeight + reactionsHeight + AppConfig.toolbarMenuHeight + 4.0; + return messageHeight + _reactionsHeight + AppConfig.toolbarMenuHeight + 4.0; } double get overheadContentHeight { - return (widget.pangeaMessageEvent != null && - widget.overlayController.selectedToken != null + return (widget.overlayController.selectedToken != null ? AppConfig.toolbarMaxHeight : 40.0) + 4.0; @@ -290,8 +289,7 @@ class MessageSelectionPositionerState extends State double? get _wordCardLeftOffset { if (ownMessage) return null; - if (widget.pangeaMessageEvent != null && - widget.overlayController.selectedToken != null && + if (widget.overlayController.selectedToken != null && mediaQuery != null && (mediaQuery!.size.width < _toolbarMaxWidth + messageLeftOffset!)) { return mediaQuery!.size.width - _toolbarMaxWidth - 8.0; @@ -319,7 +317,7 @@ class MessageSelectionPositionerState extends State if (_screenHeight == null) return false; final bottomOffset = _originalMessageOffset.dy + originalMessageSize.height + - reactionsHeight + + _reactionsHeight + AppConfig.toolbarMenuHeight + 4.0; @@ -332,7 +330,7 @@ class MessageSelectionPositionerState extends State final messageHeight = originalMessageSize.height; final originalContentHeight = - messageHeight + reactionsHeight + AppConfig.toolbarMenuHeight + 8.0; + messageHeight + _reactionsHeight + AppConfig.toolbarMenuHeight + 8.0; final screenHeight = mediaQuery!.size.height - mediaQuery!.padding.bottom; @@ -359,7 +357,7 @@ class MessageSelectionPositionerState extends State void onStartedTransition() { if (mounted) { setState(() { - startedTransition = true; + _startedTransition = true; }); } } @@ -399,7 +397,7 @@ class MessageSelectionPositionerState extends State alignment: ownMessage ? Alignment.centerRight : Alignment.centerLeft, children: [ - if (!startedTransition) ...[ + if (!_startedTransition) ...[ OverMessageOverlay(controller: this), if (shouldScroll) Positioned( @@ -426,7 +424,6 @@ class MessageSelectionPositionerState extends State right: 0, bottom: 20, child: ReadingAssistanceInputBar( - widget.chatController, widget.overlayController, ), ), diff --git a/lib/pangea/toolbar/widgets/over_message_overlay.dart b/lib/pangea/toolbar/widgets/over_message_overlay.dart index 06cb2371c..a0e333df1 100644 --- a/lib/pangea/toolbar/widgets/over_message_overlay.dart +++ b/lib/pangea/toolbar/widgets/over_message_overlay.dart @@ -50,11 +50,19 @@ class OverMessageOverlay extends StatelessWidget { .link, child: OverlayCenterContent( event: controller.widget.event, - messageHeight: controller.originalMessageSize.height, + messageHeight: + controller.widget.overlayController.selectedMode == + SelectMode.practice + ? controller.originalMessageSize.height + : null, messageWidth: - controller.widget.overlayController.showingExtraContent - ? max(controller.originalMessageSize.width, 150) - : controller.originalMessageSize.width, + controller.widget.overlayController.selectedMode == + SelectMode.practice + ? controller.widget.overlayController + .showingExtraContent + ? max(controller.originalMessageSize.width, 150) + : controller.originalMessageSize.width + : null, overlayController: controller.widget.overlayController, chatController: controller.widget.chatController, nextEvent: controller.widget.nextEvent, diff --git a/lib/pangea/toolbar/widgets/overlay_center_content.dart b/lib/pangea/toolbar/widgets/overlay_center_content.dart index b2c49b412..954c4702f 100644 --- a/lib/pangea/toolbar/widgets/overlay_center_content.dart +++ b/lib/pangea/toolbar/widgets/overlay_center_content.dart @@ -71,7 +71,6 @@ class OverlayCenterContent extends StatelessWidget { child: OverlayMessage( key: overlayKey, event, - immersionMode: chatController.choreographer.immersionMode, controller: chatController, overlayController: overlayController, nextEvent: nextEvent, diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 34fdad126..976d38933 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -31,7 +31,6 @@ class OverlayMessage extends StatelessWidget { final Event? nextEvent; final Event? previousEvent; final Timeline timeline; - final bool immersionMode; final Animation? sizeAnimation; final double? messageWidth; @@ -42,7 +41,6 @@ class OverlayMessage extends StatelessWidget { const OverlayMessage( this.event, { - this.immersionMode = false, required this.overlayController, required this.controller, required this.timeline, @@ -145,7 +143,7 @@ class OverlayMessage extends StatelessWidget { isSubscribed != false; final showTranscription = - overlayController.pangeaMessageEvent?.isAudioMessage == true && + overlayController.pangeaMessageEvent.isAudioMessage == true && isSubscribed != false; final showSpeechTranslation = overlayController.showSpeechTranslation && @@ -348,7 +346,6 @@ class OverlayMessage extends StatelessWidget { borderRadius: borderRadius, timeline: timeline, pangeaMessageEvent: overlayController.pangeaMessageEvent, - immersionMode: immersionMode, overlayController: overlayController, controller: controller, nextEvent: nextEvent, diff --git a/lib/pangea/toolbar/widgets/practice_activity/multiple_choice_activity.dart b/lib/pangea/toolbar/widgets/practice_activity/multiple_choice_activity.dart index 145e25479..4bec6d586 100644 --- a/lib/pangea/toolbar/widgets/practice_activity/multiple_choice_activity.dart +++ b/lib/pangea/toolbar/widgets/practice_activity/multiple_choice_activity.dart @@ -3,8 +3,6 @@ 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/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart'; @@ -23,7 +21,6 @@ import 'package:fluffychat/widgets/matrix.dart'; class MultipleChoiceActivity extends StatefulWidget { final PracticeActivityCardState practiceCardController; final PracticeActivityModel currentActivity; - final Event event; final VoidCallback? onError; final MessageOverlayController overlayController; final String? initialSelectedChoice; @@ -33,7 +30,6 @@ class MultipleChoiceActivity extends StatefulWidget { super.key, required this.practiceCardController, required this.currentActivity, - required this.event, required this.overlayController, this.initialSelectedChoice, this.clearResponsesOnUpdate = false, @@ -119,9 +115,8 @@ class MultipleChoiceActivityState extends State { MatrixState.pangeaController.putAnalytics.setState( AnalyticsStream( // note - this maybe should be the activity event id - eventId: - widget.practiceCardController.widget.pangeaMessageEvent.eventId, - roomId: widget.practiceCardController.widget.pangeaMessageEvent.room.id, + eventId: widget.overlayController.event.eventId, + roomId: widget.overlayController.event.room.id, constructs: currentRecordModel!.latestResponse!.toUses( widget.practiceCardController.currentActivity!, widget.practiceCardController.metadata, @@ -216,7 +211,7 @@ class MultipleChoiceActivityState extends State { question, textAlign: TextAlign.center, style: AppConfig.messageTextStyle( - widget.event, + widget.overlayController.event, Theme.of(context).colorScheme.primary, ).merge(const TextStyle(fontStyle: FontStyle.italic)), ), @@ -226,15 +221,14 @@ class MultipleChoiceActivityState extends State { ActivityTypeEnum.wordFocusListening) WordAudioButton( text: practiceActivity.multipleChoiceContent!.answers.first, - uniqueID: "audio-activity-${widget.event.eventId}", + uniqueID: + "audio-activity-${widget.overlayController.event.eventId}", langCode: widget - .overlayController.pangeaMessageEvent!.messageDisplayLangCode, + .overlayController.pangeaMessageEvent.messageDisplayLangCode, ), if (practiceActivity.activityType == ActivityTypeEnum.hiddenWordListening) MessageAudioCard( - messageEvent: - widget.practiceCardController.widget.pangeaMessageEvent, overlayController: widget.overlayController, onError: widget.onError, ), diff --git a/lib/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart index 5ae92cb19..3728d3d1b 100644 --- a/lib/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart @@ -29,13 +29,11 @@ import 'package:fluffychat/widgets/matrix.dart'; /// Handles the activities associated with a message, /// their navigation, and the management of completion records class PracticeActivityCard extends StatefulWidget { - final PangeaMessageEvent pangeaMessageEvent; final PracticeTarget targetTokensAndActivityType; final MessageOverlayController overlayController; const PracticeActivityCard({ super.key, - required this.pangeaMessageEvent, required this.targetTokensAndActivityType, required this.overlayController, }); @@ -60,6 +58,9 @@ class PracticeActivityCardState extends State { PracticeRecord? get currentCompletionRecord => currentActivity?.record; + PangeaMessageEvent? get pangeaMessageEvent => + widget.overlayController.pangeaMessageEvent; + @override void initState() { super.initState(); @@ -142,7 +143,7 @@ class PracticeActivityCardState extends State { debugPrint( "fetching activity model of type: ${widget.targetTokensAndActivityType.activityType}", ); - debugger(when: kDebugMode); + if (pangeaMessageEvent == null) return null; // check if we already have an activity matching the specs final tokens = widget.targetTokensAndActivityType.tokens; final type = widget.targetTokensAndActivityType.activityType; @@ -167,9 +168,9 @@ class PracticeActivityCardState extends State { final req = MessageActivityRequest( userL1: MatrixState.pangeaController.languageController.userL1!.langCode, userL2: MatrixState.pangeaController.languageController.userL2!.langCode, - messageText: widget.pangeaMessageEvent.messageDisplayText, + messageText: pangeaMessageEvent!.messageDisplayText, messageTokens: - widget.pangeaMessageEvent.messageDisplayRepresentation?.tokens ?? [], + pangeaMessageEvent?.messageDisplayRepresentation?.tokens ?? [], activityQualityFeedback: activityFeedback, targetTokens: tokens, targetType: type, @@ -179,7 +180,7 @@ class PracticeActivityCardState extends State { final PracticeActivityModelResponse activityResponse = await practiceGenerationController.getPracticeActivity( req, - widget.pangeaMessageEvent, + pangeaMessageEvent, context, ); @@ -191,8 +192,8 @@ class PracticeActivityCardState extends State { } ConstructUseMetaData get metadata => ConstructUseMetaData( - eventId: widget.pangeaMessageEvent.eventId, - roomId: widget.pangeaMessageEvent.room.id, + eventId: widget.overlayController.event.eventId, + roomId: widget.overlayController.event.room.id, timeStamp: DateTime.now(), ); @@ -269,16 +270,13 @@ class PracticeActivityCardState extends State { return MessageMorphInputBarContent( overlayController: widget.overlayController, activity: currentActivity!, - pangeaMessageEvent: widget.overlayController.pangeaMessageEvent!, ); } return MultipleChoiceActivity( practiceCardController: this, currentActivity: currentActivity!, - event: widget.pangeaMessageEvent.event, onError: _onError, overlayController: widget.overlayController, - // initialSelectedChoice: selectedChoice, initialSelectedChoice: null, clearResponsesOnUpdate: currentActivity?.activityType == ActivityTypeEnum.emoji, diff --git a/lib/pangea/toolbar/widgets/practice_mode_buttons.dart b/lib/pangea/toolbar/widgets/practice_mode_buttons.dart index 77ec386ec..de58c3963 100644 --- a/lib/pangea/toolbar/widgets/practice_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/practice_mode_buttons.dart @@ -33,7 +33,6 @@ class PracticeModeButtons extends StatelessWidget { child: ToolbarButton( mode: MessageMode.listening, overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, buttonSize: buttonSize, ), ), @@ -44,7 +43,6 @@ class PracticeModeButtons extends StatelessWidget { child: ToolbarButton( mode: MessageMode.wordMorph, overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, buttonSize: buttonSize, ), ), @@ -55,7 +53,6 @@ class PracticeModeButtons extends StatelessWidget { child: ToolbarButton( mode: MessageMode.wordMeaning, overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, buttonSize: buttonSize, ), ), @@ -66,7 +63,6 @@ class PracticeModeButtons extends StatelessWidget { child: ToolbarButton( mode: MessageMode.wordEmoji, overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, buttonSize: buttonSize, ), ), diff --git a/lib/pangea/toolbar/widgets/reading_assistance_content.dart b/lib/pangea/toolbar/widgets/reading_assistance_content.dart index 77923c843..1c43ca62f 100644 --- a/lib/pangea/toolbar/widgets/reading_assistance_content.dart +++ b/lib/pangea/toolbar/widgets/reading_assistance_content.dart @@ -7,7 +7,6 @@ import 'package:matrix/matrix_api_lite/model/message_types.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.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/widgets/message_selection_overlay.dart'; @@ -20,13 +19,11 @@ import 'package:fluffychat/widgets/matrix.dart'; const double minCardHeight = 70; class ReadingAssistanceContent extends StatefulWidget { - final PangeaMessageEvent pangeaMessageEvent; final MessageOverlayController overlayController; final Duration animationDuration; const ReadingAssistanceContent({ super.key, - required this.pangeaMessageEvent, required this.overlayController, this.animationDuration = FluffyThemes.animationDuration, }); @@ -48,7 +45,6 @@ class ReadingAssistanceContentState extends State { if (widget.overlayController.practiceSelection?.hasHiddenWordActivity ?? false) { return PracticeActivityCard( - pangeaMessageEvent: widget.pangeaMessageEvent, overlayController: widget.overlayController, targetTokensAndActivityType: widget.overlayController.practiceSelection! .nextActivity(ActivityTypeEnum.hiddenWordListening)!, @@ -58,7 +54,6 @@ class ReadingAssistanceContentState extends State { if (widget.overlayController.practiceSelection?.hasMessageMeaningActivity ?? false) { return PracticeActivityCard( - pangeaMessageEvent: widget.pangeaMessageEvent, overlayController: widget.overlayController, targetTokensAndActivityType: widget.overlayController.practiceSelection! .nextActivity(ActivityTypeEnum.messageMeaning)!, @@ -121,7 +116,6 @@ class ReadingAssistanceContentState extends State { ) .key, token: widget.overlayController.selectedToken!, - messageEvent: widget.overlayController.pangeaMessageEvent!, overlayController: widget.overlayController, wordIsNew: widget.overlayController .isNewToken(widget.overlayController.selectedToken!), @@ -132,7 +126,7 @@ class ReadingAssistanceContentState extends State { @override Widget build(BuildContext context) { if (![MessageTypes.Text, MessageTypes.Audio].contains( - widget.pangeaMessageEvent.event.messageType, + widget.overlayController.pangeaMessageEvent.event.messageType, )) { return const SizedBox(); } diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index cc810c2d2..f3564ab87 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -28,6 +28,7 @@ enum SelectMode { audio(Icons.volume_up), translate(Icons.translate), practice(Symbols.fitness_center), + emoji(Icons.add_reaction_outlined), speechTranslation(Icons.translate); final IconData icon; @@ -43,6 +44,8 @@ enum SelectMode { return l10n.translationTooltip; case SelectMode.practice: return l10n.practice; + case SelectMode.emoji: + return l10n.emojis; } } } @@ -145,15 +148,13 @@ class SelectModeButtonsState extends State { SelectMode.audio, SelectMode.translate, SelectMode.practice, + SelectMode.emoji, ]; static List get audioModes => [ SelectMode.speechTranslation, - // SelectMode.practice, ]; - SelectMode? _selectedMode; - bool _isLoadingAudio = false; PangeaAudioFile? _audioBytes; File? _audioFile; @@ -172,6 +173,8 @@ class SelectModeButtonsState extends State { MatrixState? matrix; + SelectMode? get _selectedMode => widget.overlayController.selectedMode; + @override void initState() { super.initState(); @@ -218,20 +221,17 @@ class SelectModeButtonsState extends State { _clear(); if (mode == null) { - setState(() { - matrix?.audioPlayer?.stop(); - matrix?.audioPlayer?.seek(null); - _selectedMode = null; - }); + matrix?.audioPlayer?.stop(); + matrix?.audioPlayer?.seek(null); + widget.overlayController.setSelectMode(mode); return; } - setState( - () => _selectedMode = _selectedMode == mode && - (mode != SelectMode.audio || _audioError != null) - ? null - : mode, - ); + final selectedMode = _selectedMode == mode && + (mode != SelectMode.audio || _audioError != null) + ? null + : mode; + widget.overlayController.setSelectMode(selectedMode); if (_selectedMode == SelectMode.audio) { _playAudio(); @@ -302,7 +302,7 @@ class SelectModeButtonsState extends State { Future _playAudio() async { final playerID = - "${widget.overlayController.pangeaMessageEvent?.eventId}_button"; + "${widget.overlayController.pangeaMessageEvent.eventId}_button"; if (matrix?.audioPlayer != null && matrix?.voiceMessageEventId.value == playerID) { @@ -319,7 +319,7 @@ class SelectModeButtonsState extends State { matrix?.audioPlayer?.dispose(); matrix?.audioPlayer = AudioPlayer(); matrix?.voiceMessageEventId.value = - "${widget.overlayController.pangeaMessageEvent?.eventId}_button"; + "${widget.overlayController.pangeaMessageEvent.eventId}_button"; _onPlayerStateChanged = matrix?.audioPlayer?.playerStateStream.listen((state) { diff --git a/lib/pangea/toolbar/widgets/toolbar_button.dart b/lib/pangea/toolbar/widgets/toolbar_button.dart index 897199260..2c0f569af 100644 --- a/lib/pangea/toolbar/widgets/toolbar_button.dart +++ b/lib/pangea/toolbar/widgets/toolbar_button.dart @@ -8,14 +8,12 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart class ToolbarButton extends StatelessWidget { final MessageMode mode; final MessageOverlayController overlayController; - final void Function(MessageMode) onPressed; final double buttonSize; const ToolbarButton({ required this.mode, required this.overlayController, required this.buttonSize, - required this.onPressed, super.key, }); @@ -36,7 +34,7 @@ class ToolbarButton extends StatelessWidget { borderRadius: BorderRadius.circular(20), depressed: mode == overlayController.toolbarMode, color: color(context), - onPressed: () => onPressed(mode), + onPressed: () => overlayController.updateToolbarMode(mode), playSound: true, colorFactor: Theme.of(context).brightness == Brightness.light ? 0.55 : 0.3, diff --git a/lib/pangea/toolbar/widgets/word_card_switcher.dart b/lib/pangea/toolbar/widgets/word_card_switcher.dart index cd2285927..ce83e0ea3 100644 --- a/lib/pangea/toolbar/widgets/word_card_switcher.dart +++ b/lib/pangea/toolbar/widgets/word_card_switcher.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart'; class WordCardSwitcher extends StatelessWidget { final MessageSelectionPositionerState controller; @@ -14,15 +15,16 @@ class WordCardSwitcher extends StatelessWidget { alignment: controller.ownMessage ? Alignment.bottomRight : Alignment.bottomLeft, duration: FluffyThemes.animationDuration, - child: controller.widget.pangeaMessageEvent != null && - controller.widget.overlayController.selectedToken != null - ? ReadingAssistanceContent( - pangeaMessageEvent: controller.widget.pangeaMessageEvent!, - overlayController: controller.widget.overlayController, - ) - : MessageReactionPicker( - chatController: controller.widget.chatController, - ), + child: + controller.widget.overlayController.selectedMode == SelectMode.emoji + ? const SizedBox() + : controller.widget.overlayController.selectedToken != null + ? ReadingAssistanceContent( + overlayController: controller.widget.overlayController, + ) + : MessageReactionPicker( + chatController: controller.widget.chatController, + ), ); } } diff --git a/lib/pangea/toolbar/widgets/word_zoom/new_word_overlay.dart b/lib/pangea/toolbar/widgets/word_zoom/new_word_overlay.dart index d3b3d24a8..abb9e9691 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/new_word_overlay.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/new_word_overlay.dart @@ -43,11 +43,7 @@ class _NewWordOverlayState extends State _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 1850), - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - dispose(); - } - }); + ); _xpScaleAnim = CurvedAnimation( parent: _controller!, @@ -73,7 +69,6 @@ class _NewWordOverlayState extends State @override void dispose() { - widget.overlayController.onSelectNewToken(widget.token); _controller?.dispose(); MatrixState.pAnyState.closeOverlay(widget.transformTargetId); super.dispose(); 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 94a8166ad..13af99f51 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -22,14 +22,12 @@ import 'package:fluffychat/widgets/matrix.dart'; class WordZoomWidget extends StatelessWidget { final PangeaToken token; - final PangeaMessageEvent messageEvent; final MessageOverlayController overlayController; final bool wordIsNew; const WordZoomWidget({ super.key, required this.token, - required this.messageEvent, required this.overlayController, required this.wordIsNew, }); @@ -49,6 +47,8 @@ class WordZoomWidget extends StatelessWidget { LayerLink get layerLink => MatrixState.pAnyState.layerLinkAndKey(transformTargetId).link; + PangeaMessageEvent get messageEvent => overlayController.pangeaMessageEvent; + @override Widget build(BuildContext context) { // final GlobalKey cardKey = MatrixState.pAnyState @@ -273,7 +273,6 @@ class WordZoomWidget extends StatelessWidget { overlayColor: overlayColor, overlayController: overlayController, transformTargetId: transformTargetId, - //cardKey: cardKey, ) : const SizedBox.shrink(), ],