diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index d499a5b82..f31568064 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -23,7 +23,6 @@ import 'package:fluffychat/pangea/models/message_data_models.dart'; import 'package:fluffychat/pangea/models/student_analytics_summary_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; -import 'package:fluffychat/pangea/utils/instructions.dart'; import 'package:fluffychat/pangea/utils/report_message.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; @@ -1282,11 +1281,6 @@ class ChatController extends State if (choreographer.itController.isOpen) { return; } - pangeaController.instructions.show( - context, - InstructionsEnum.understandingMessages, - event.eventId, - ); // Pangea# if (!event.redacted) { if (selectedEvents.contains(event)) { diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index d719d7652..c8d5b886d 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -1,11 +1,13 @@ import 'package:fluffychat/pages/chat/events/video_player.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/models/pangea_message_event.dart'; +import 'package:fluffychat/pangea/utils/show_defintion_util.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_rich_text.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; @@ -36,9 +38,10 @@ class MessageContent extends StatelessWidget { final LanguageModel? selectedDisplayLang; final bool immersionMode; final bool definitions; + ShowDefintionUtil? messageToolbar; // Pangea# - const MessageContent( + MessageContent( this.event, { this.onInfoTab, super.key, @@ -121,6 +124,18 @@ class MessageContent extends StatelessWidget { @override Widget build(BuildContext context) { + // #Pangea + messageToolbar = ShowDefintionUtil( + targetId: pangeaMessageEvent.eventId, + room: pangeaMessageEvent.room, + langCode: selectedDisplayLang?.langCode ?? + MatrixState.pangeaController.languageController.activeL2Code( + roomID: pangeaMessageEvent.room.id, + ) ?? + LanguageModel.unknown.langCode, + messageText: "", + ); + // Pangea# final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; final buttonTextColor = textColor; switch (event.type) { @@ -263,55 +278,74 @@ class MessageContent extends StatelessWidget { height: 1.3, ); if (pangeaMessageEvent.showRichText) { - return PangeaRichText( - existingStyle: messageTextStyle, - selected: selected, - pangeaMessageEvent: pangeaMessageEvent, - immersionMode: immersionMode, - definitions: definitions, - selectedDisplayLang: selectedDisplayLang, + return MouseRegion( + onHover: messageToolbar?.onMouseRegionUpdate, + child: PangeaRichText( + existingStyle: messageTextStyle, + selected: selected, + pangeaMessageEvent: pangeaMessageEvent, + immersionMode: immersionMode, + definitions: definitions, + selectedDisplayLang: selectedDisplayLang, + messageToolbar: messageToolbar, + ), ); } - //Pangea# - return FutureBuilder( - future: event.calcLocalizedBody( - MatrixLocals(L10n.of(context)!), - hideReply: true, - ), - builder: (context, snapshot) { - // #Pangea - if (!snapshot.hasData) { - return Text( - event.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - hideReply: true, - ), - style: messageTextStyle, - ); - } + return MouseRegion( + onHover: messageToolbar?.onMouseRegionUpdate, + child: FutureBuilder( // Pangea# - return Linkify( - text: snapshot.data ?? + future: event.calcLocalizedBody( + MatrixLocals(L10n.of(context)!), + hideReply: true, + ), + builder: (context, snapshot) { + // #Pangea + if (!snapshot.hasData) { + return Text( event.calcLocalizedBodyFallback( MatrixLocals(L10n.of(context)!), hideReply: true, ), - style: TextStyle( - color: textColor, - fontSize: bigEmotes ? fontSize * 3 : fontSize, - decoration: - event.redacted ? TextDecoration.lineThrough : null, - ), - options: const LinkifyOptions(humanize: false), - linkStyle: TextStyle( - color: textColor.withAlpha(150), - fontSize: bigEmotes ? fontSize * 3 : fontSize, - decoration: TextDecoration.underline, - decorationColor: textColor.withAlpha(150), - ), - onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), - ); - }, + style: messageTextStyle, + ); + } + // return Linkify( + final String messageText = snapshot.data ?? + event.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + ); + messageToolbar?.messageText = messageText; + return SelectableLinkify( + // Pangea# + text: messageText, + focusNode: messageToolbar?.focusNode, + contextMenuBuilder: messageToolbar?.contextMenuOverride, + // text: snapshot.data ?? + // event.calcLocalizedBodyFallback( + // MatrixLocals(L10n.of(context)!), + // hideReply: true, + // ), + style: TextStyle( + color: textColor, + fontSize: bigEmotes ? fontSize * 3 : fontSize, + decoration: + event.redacted ? TextDecoration.lineThrough : null, + ), + options: const LinkifyOptions(humanize: false), + linkStyle: TextStyle( + color: textColor.withAlpha(150), + fontSize: bigEmotes ? fontSize * 3 : fontSize, + decoration: TextDecoration.underline, + decorationColor: textColor.withAlpha(150), + ), + onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), + onSelectionChanged: (selection, cause) => messageToolbar + ?.onTextSelection(selection, cause, context), + ); + }, + ), ); } case EventTypes.CallInvite: diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index 40253a00d..39b1d0c26 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../../config/app_config.dart'; @@ -96,7 +95,6 @@ class InstructionsController { enum InstructionsEnum { itInstructions, clickMessage, - understandingMessages, blurMeansTranslate, } @@ -107,8 +105,6 @@ extension Copy on InstructionsEnum { return L10n.of(context)!.itInstructionsTitle; case InstructionsEnum.clickMessage: return L10n.of(context)!.clickMessageTitle; - case InstructionsEnum.understandingMessages: - return L10n.of(context)!.understandingMessagesTitle; case InstructionsEnum.blurMeansTranslate: return L10n.of(context)!.blurMeansTranslateTitle; } @@ -120,8 +116,6 @@ extension Copy on InstructionsEnum { return L10n.of(context)!.itInstructionsBody; case InstructionsEnum.clickMessage: return L10n.of(context)!.clickMessageBody; - case InstructionsEnum.understandingMessages: - return L10n.of(context)!.understandingMessagesBody; case InstructionsEnum.blurMeansTranslate: return L10n.of(context)!.blurMeansTranslateBody; } diff --git a/lib/pangea/utils/show_defintion_util.dart b/lib/pangea/utils/show_defintion_util.dart index 000251102..59a095eed 100644 --- a/lib/pangea/utils/show_defintion_util.dart +++ b/lib/pangea/utils/show_defintion_util.dart @@ -11,13 +11,15 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; class ShowDefintionUtil { - final String messageText; + String messageText; final String langCode; final String targetId; final FocusNode focusNode = FocusNode(); final Room room; TextSelection? textSelection; bool inCooldown = false; + double? dx; + double? dy; ShowDefintionUtil({ required this.targetId, @@ -93,17 +95,18 @@ class ShowDefintionUtil { Future showToolbar(BuildContext context) async { final LayerLinkAndKey layerLinkAndKey = MatrixState.pAnyState.layerLinkAndKey(targetId); - final RenderBox? targetRenderBox = - (layerLinkAndKey.key.currentContext!.findRenderObject() as RenderBox?); - final Size? transformTargetSize = targetRenderBox?.size; - Offset? transformTargetOffset; - if (transformTargetSize != null) { - transformTargetOffset = Offset( - (transformTargetSize.width / 2) - 65, - transformTargetSize.height * -1, - ); + final RenderObject? targetRenderBox = + layerLinkAndKey.key.currentContext!.findRenderObject(); + final Offset transformTargetOffset = + (targetRenderBox as RenderBox).localToGlobal(Offset.zero); + + if (dx != null && dx! > MediaQuery.of(context).size.width - 130) { + dx = MediaQuery.of(context).size.width - 130; } + final double xOffset = dx != null ? dx! - transformTargetOffset.dx : 0; + final double yOffset = + dy != null ? dy! - transformTargetOffset.dy + 10 : 10; OverlayUtil.showOverlay( context: context, @@ -124,7 +127,28 @@ class ShowDefintionUtil { ), size: const Size(130, 45), transformTargetId: targetId, - offset: transformTargetOffset, + offset: Offset(xOffset, yOffset), + ); + } + + void onMouseRegionUpdate(PointerEvent event) { + dx = event.position.dx; + dy = event.position.dy; + } + + Widget contextMenuOverride(BuildContext context, EditableTextState selection) { + return AdaptiveTextSelectionToolbar.buttonItems( + anchors: selection.contextMenuAnchors, + buttonItems: [ + ...selection.contextMenuButtonItems, + ContextMenuButtonItem( + label: L10n.of(context)!.showDefinition, + onPressed: () { + showDefinition(context); + focusNode.unfocus(); + }, + ), + ], ); } } diff --git a/lib/pangea/widgets/igc/pangea_rich_text.dart b/lib/pangea/widgets/igc/pangea_rich_text.dart index 781f476bd..65810c572 100644 --- a/lib/pangea/widgets/igc/pangea_rich_text.dart +++ b/lib/pangea/widgets/igc/pangea_rich_text.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:developer'; import 'dart:ui'; @@ -13,13 +12,13 @@ import 'package:fluffychat/pangea/utils/show_defintion_util.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import '../../models/igc_text_data_model.dart'; import '../../models/language_detection_model.dart'; import '../../models/pangea_match_model.dart'; import '../../models/pangea_representation_event.dart'; +import '../../utils/bot_style.dart'; import '../../utils/instructions.dart'; class PangeaRichText extends StatefulWidget { @@ -30,6 +29,7 @@ class PangeaRichText extends StatefulWidget { final bool immersionMode; final bool definitions; final Choreographer? choreographer; + final ShowDefintionUtil? messageToolbar; const PangeaRichText({ super.key, @@ -40,6 +40,7 @@ class PangeaRichText extends StatefulWidget { required this.definitions, this.choreographer, this.existingStyle, + this.messageToolbar, }); @override @@ -52,32 +53,30 @@ class PangeaRichTextState extends State { bool _fetchingTokens = false; double get blur => _fetchingRepresentation && widget.immersionMode ? 5 : 0; List textSpan = []; - ShowDefintionUtil? messageToolbar; @override void initState() { super.initState(); - setState(() => textSpan = getTextSpan(context)); + updateTextSpan(); } @override void didUpdateWidget(PangeaRichText oldWidget) { super.didUpdateWidget(oldWidget); - setState(() => textSpan = getTextSpan(context)); + updateTextSpan(); + } + + void updateTextSpan() { + setState(() { + textSpan = getTextSpan(context); + widget.messageToolbar?.messageText = textSpan.map((e) => e.text).join(); + }); } @override Widget build(BuildContext context) { //TODO - take out of build function of every message // if (areLanguagesSet) { - messageToolbar = ShowDefintionUtil( - targetId: widget.pangeaMessageEvent.eventId, - room: widget.pangeaMessageEvent.room, - langCode: widget.selectedDisplayLang?.langCode ?? - userL2LangCode ?? - LanguageKeys.unknownLanguage, - messageText: textSpan.map((x) => x.text).join(), - ); if (!widget.selected && widget.selectedDisplayLang != null && @@ -95,51 +94,33 @@ class PangeaRichTextState extends State { ); } - final TextSpan richTextSpan = TextSpan( - children: [ - ...textSpan, - if (widget.selected && (_fetchingRepresentation || _fetchingTokens)) - const WidgetSpan( - child: Padding( - padding: EdgeInsets.only(left: 5.0), - child: SizedBox( - height: 14, - width: 14, - child: CircularProgressIndicator( - strokeWidth: 2.0, - color: AppConfig.secondaryColor, + final Widget richText = SelectableText.rich( + onSelectionChanged: (selection, cause) => + widget.messageToolbar?.onTextSelection(selection, cause, context), + focusNode: widget.messageToolbar?.focusNode, + contextMenuBuilder: widget.messageToolbar?.contextMenuOverride, + TextSpan( + children: [ + ...textSpan, + if (widget.selected && (_fetchingRepresentation || _fetchingTokens)) + // if (widget.selected) + const WidgetSpan( + child: Padding( + padding: EdgeInsets.only(left: 5.0), + child: SizedBox( + height: 14, + width: 14, + child: CircularProgressIndicator( + strokeWidth: 2.0, + color: AppConfig.secondaryColor, + ), ), ), ), - ), - ], + ], + ), ); - final Widget richText = widget.selected - ? SelectableText.rich( - richTextSpan, - onSelectionChanged: (selection, cause) => kIsWeb - ? messageToolbar?.onTextSelection(selection, cause, context) - : null, - focusNode: messageToolbar?.focusNode, - contextMenuBuilder: (context, selection) { - return AdaptiveTextSelectionToolbar.buttonItems( - anchors: selection.contextMenuAnchors, - buttonItems: [ - ...selection.contextMenuButtonItems, - ContextMenuButtonItem( - label: L10n.of(context)!.showDefinition, - onPressed: () { - messageToolbar?.showDefinition(context); - messageToolbar?.focusNode.unfocus(); - }, - ), - ], - ); - }, - ) - : RichText(text: richTextSpan); - return blur > 0 ? ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), @@ -222,8 +203,8 @@ class PangeaRichTextState extends State { userL1: userL1LangCode ?? LanguageKeys.unknownLanguage, ).constructTokenSpan( context: context, - defaultStyle: widget.existingStyle, - handleClick: true, + defaultStyle: textStyle(repEvent, context), + handleClick: false, spanCardModel: null, transformTargetId: widget.pangeaMessageEvent.eventId, room: widget.pangeaMessageEvent.room, @@ -244,10 +225,20 @@ class PangeaRichTextState extends State { [ TextSpan( text: repEvent.text, - style: widget.existingStyle, + style: textStyle(repEvent, context), ), ]; + TextStyle? textStyle(RepresentationEvent repEvent, BuildContext context) => + // !repEvent.botAuthored + true + ? widget.existingStyle + : BotStyle.text( + context, + existingStyle: widget.existingStyle, + setColor: false, + ); + bool get areLanguagesSet => userL2LangCode != null && userL2LangCode != LanguageKeys.unknownLanguage; @@ -279,75 +270,4 @@ class PangeaRichTextState extends State { Future onSentenceRewrite(String sentenceRewrite) async { debugPrint("PTODO implement onSentenceRewrite"); } - - // void onTextSelection( - // TextSelection selection, - // SelectionChangedCause? _, - // ) => - // selection.isCollapsed - // ? clearTextSelection() - // : setTextSelection(selection); - - // void setTextSelection(TextSelection selection) { - // textSelection = selection; - // if (BrowserContextMenu.enabled && kIsWeb) { - // BrowserContextMenu.disableContextMenu(); - // } - // kIsWeb ? showToolbar() : showDefinition(); - // } - - // void clearTextSelection() { - // textSelection = null; - // if (kIsWeb && !BrowserContextMenu.enabled) { - // BrowserContextMenu.enableContextMenu(); - // } - // } - - // void showToolbar() async { - // if (toolbarShowing || !kIsWeb) return; - // toolbarShowing = true; - // await Future.delayed(const Duration(seconds: 2)); - - // final toolbarFuture = MessageToolbar.showToolbar( - // context, - // widget.pangeaMessageEvent.eventId, - // _focusNode.offset, - // ); - - // final resp = await toolbarFuture; - // toolbarShowing = false; - - // switch (resp) { - // case null: - // break; - // case 1: - // showDefinition(); - // break; - // default: - // break; - // } - // } - - // void showDefinition() { - // final String messageText = textSpan.map((x) => x.text).join(); - // final String fullText = textSelection!.textInside(messageText); - // final String langCode = widget.selectedDisplayLang?.langCode ?? - // userL2LangCode ?? - // LanguageKeys.unknownLanguage; - - // OverlayUtil.showPositionedCard( - // context: context, - // cardToShow: WordDataCard( - // word: fullText, - // wordLang: langCode, - // fullText: messageText, - // fullTextLang: langCode, - // hasInfo: false, - // room: widget.pangeaMessageEvent.room, - // ), - // cardSize: const Size(300, 300), - // transformTargetId: widget.pangeaMessageEvent.eventId, - // backDropToDismiss: false, - // ); - // } }