diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb
index eac6e15dd..c15822da5 100644
--- a/assets/l10n/intl_en.arb
+++ b/assets/l10n/intl_en.arb
@@ -3881,5 +3881,6 @@
"conversationTopic": "Conversation topic",
"enableModeration": "Enable moderation",
"enableModerationDesc": "Enable automatic moderation to review messages before they are sent",
- "conversationLanguageLevel": "What is the language level of this conversation?"
+ "conversationLanguageLevel": "What is the language level of this conversation?",
+ "showDefinition": "Show Definition"
}
\ No newline at end of file
diff --git a/ios/FluffyChat Share/FluffyChat Share.entitlements b/ios/FluffyChat Share/FluffyChat Share.entitlements
index 932f3e01e..2eb7e333a 100644
--- a/ios/FluffyChat Share/FluffyChat Share.entitlements
+++ b/ios/FluffyChat Share/FluffyChat Share.entitlements
@@ -3,8 +3,6 @@
com.apple.security.application-groups
-
- group.im.fluffychat.app
-
+
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 4f8d4d245..8c6e56146 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 11.0
+ 12.0
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index e5c2d6a2b..73d46da15 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -457,7 +457,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -546,7 +546,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -595,7 +595,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index b52b2e698..a6b826db2 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
+
+
-
-
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/html_message.dart b/lib/pages/chat/events/html_message.dart
index ae67c5813..2ea75ab74 100644
--- a/lib/pages/chat/events/html_message.dart
+++ b/lib/pages/chat/events/html_message.dart
@@ -1,6 +1,10 @@
-import 'package:flutter/material.dart';
-
import 'package:collection/collection.dart';
+import 'package:fluffychat/config/app_config.dart';
+import 'package:fluffychat/pangea/utils/show_defintion_util.dart';
+import 'package:fluffychat/widgets/avatar.dart';
+import 'package:fluffychat/widgets/mxc_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
import 'package:flutter_highlighter/flutter_highlighter.dart';
import 'package:flutter_highlighter/themes/shades-of-purple.dart';
import 'package:flutter_html/flutter_html.dart';
@@ -10,21 +14,24 @@ import 'package:html/dom.dart' as dom;
import 'package:linkify/linkify.dart';
import 'package:matrix/matrix.dart';
-import 'package:fluffychat/config/app_config.dart';
-import 'package:fluffychat/widgets/avatar.dart';
-import 'package:fluffychat/widgets/mxc_image.dart';
import '../../../utils/url_launcher.dart';
class HtmlMessage extends StatelessWidget {
final String html;
final Room room;
final Color textColor;
+ // #Pangea
+ final ShowDefintionUtil? messageToolbar;
+ // Pangea#
const HtmlMessage({
super.key,
required this.html,
required this.room,
this.textColor = Colors.black,
+ // #Pangea
+ this.messageToolbar,
+ // Pangea#
});
dom.Node _linkifyHtml(dom.Node element) {
@@ -92,84 +99,108 @@ class HtmlMessage extends StatelessWidget {
final element = _linkifyHtml(HtmlParser.parseHTML(renderHtml));
// there is no need to pre-validate the html, as we validate it while rendering
- return Html.fromElement(
- documentElement: element as dom.Element,
- style: {
- '*': Style(
- color: textColor,
- margin: Margins.all(0),
- fontSize: FontSize(fontSize),
+ // #Pangea
+ return MouseRegion(
+ onHover: messageToolbar?.onMouseRegionUpdate,
+ child: SelectionArea(
+ onSelectionChanged: (SelectedContent? selection) =>
+ messageToolbar?.onTextSelection(
+ selectedContent: selection,
+ context: context,
),
- 'a': Style(color: linkColor, textDecorationColor: linkColor),
- 'h1': Style(
- fontSize: FontSize(fontSize * 2),
- lineHeight: LineHeight.number(1.5),
- fontWeight: FontWeight.w600,
+ focusNode: messageToolbar?.focusNode,
+ contextMenuBuilder: (context, state) =>
+ messageToolbar?.contextMenuOverride(
+ context: context,
+ contentSelection: state,
+ ) ??
+ const SizedBox(),
+ // Pangea#
+ child: Html.fromElement(
+ documentElement: element as dom.Element,
+ style: {
+ '*': Style(
+ color: textColor,
+ margin: Margins.all(0),
+ fontSize: FontSize(fontSize),
+ ),
+ 'a': Style(color: linkColor, textDecorationColor: linkColor),
+ 'h1': Style(
+ fontSize: FontSize(fontSize * 2),
+ lineHeight: LineHeight.number(1.5),
+ fontWeight: FontWeight.w600,
+ ),
+ 'h2': Style(
+ fontSize: FontSize(fontSize * 1.75),
+ lineHeight: LineHeight.number(1.5),
+ fontWeight: FontWeight.w500,
+ ),
+ 'h3': Style(
+ fontSize: FontSize(fontSize * 1.5),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'h4': Style(
+ fontSize: FontSize(fontSize * 1.25),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'h5': Style(
+ fontSize: FontSize(fontSize * 1.25),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'h6': Style(
+ fontSize: FontSize(fontSize),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'blockquote': blockquoteStyle,
+ 'tg-forward': blockquoteStyle,
+ 'hr': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ 'table': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ 'tr': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ 'td': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ padding: HtmlPaddings.all(2),
+ ),
+ 'th': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ },
+ extensions: [
+ RoomPillExtension(context, room),
+ CodeExtension(fontSize: fontSize),
+ MatrixMathExtension(
+ style: TextStyle(fontSize: fontSize, color: textColor),
+ ),
+ const TableHtmlExtension(),
+ SpoilerExtension(textColor: textColor),
+ const ImageExtension(),
+ FontColorExtension(),
+ ],
+ onLinkTap: (url, _, element) => UrlLauncher(
+ context,
+ url,
+ element?.text,
+ ).launchUrl(),
+ onlyRenderTheseTags: const {
+ ...allowedHtmlTags,
+ // Needed to make it work properly
+ 'body',
+ 'html',
+ },
+ shrinkWrap: true,
),
- 'h2': Style(
- fontSize: FontSize(fontSize * 1.75),
- lineHeight: LineHeight.number(1.5),
- fontWeight: FontWeight.w500,
- ),
- 'h3': Style(
- fontSize: FontSize(fontSize * 1.5),
- lineHeight: LineHeight.number(1.5),
- ),
- 'h4': Style(
- fontSize: FontSize(fontSize * 1.25),
- lineHeight: LineHeight.number(1.5),
- ),
- 'h5': Style(
- fontSize: FontSize(fontSize * 1.25),
- lineHeight: LineHeight.number(1.5),
- ),
- 'h6': Style(
- fontSize: FontSize(fontSize),
- lineHeight: LineHeight.number(1.5),
- ),
- 'blockquote': blockquoteStyle,
- 'tg-forward': blockquoteStyle,
- 'hr': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- 'table': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- 'tr': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- 'td': Style(
- border: Border.all(color: textColor, width: 0.5),
- padding: HtmlPaddings.all(2),
- ),
- 'th': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- },
- extensions: [
- RoomPillExtension(context, room),
- CodeExtension(fontSize: fontSize),
- MatrixMathExtension(
- style: TextStyle(fontSize: fontSize, color: textColor),
- ),
- const TableHtmlExtension(),
- SpoilerExtension(textColor: textColor),
- const ImageExtension(),
- FontColorExtension(),
- ],
- onLinkTap: (url, _, element) => UrlLauncher(
- context,
- url,
- element?.text,
- ).launchUrl(),
- onlyRenderTheseTags: const {
- ...allowedHtmlTags,
- // Needed to make it work properly
- 'body',
- 'html',
- },
- shrinkWrap: true,
+ ),
);
+ // ),
+ // ],
+ // ),
+ // ),
+ // );
}
/// Keep in sync with: https://spec.matrix.org/v1.6/client-server-api/#mroommessage-msgtypes
diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart
index b78a2150c..0f6765df2 100644
--- a/lib/pages/chat/events/message_content.dart
+++ b/lib/pages/chat/events/message_content.dart
@@ -1,23 +1,24 @@
-import 'package:flutter/material.dart';
-
-import 'package:flutter_gen/gen_l10n/l10n.dart';
-import 'package:flutter_linkify/flutter_linkify.dart';
-import 'package:matrix/matrix.dart';
-
+import 'package:fluffychat/pages/chat/events/html_message.dart';
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';
+import 'package:matrix/matrix.dart';
+
import '../../../config/app_config.dart';
import '../../../utils/platform_infos.dart';
import '../../../utils/url_launcher.dart';
import 'audio_player.dart';
import 'cute_events.dart';
-import 'html_message.dart';
import 'image_bubble.dart';
import 'map_bubble.dart';
import 'message_download_content.dart';
@@ -37,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,
@@ -122,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) {
@@ -168,16 +182,25 @@ class MessageContent extends StatelessWidget {
case MessageTypes.Notice:
case MessageTypes.Emote:
if (AppConfig.renderHtml &&
- !event.redacted &&
- event.isRichMessage) {
+ !event.redacted &&
+ event.isRichMessage
+ // #Pangea
+ &&
+ !pangeaMessageEvent.showRichText
+ // Pangea#
+ ) {
var html = event.formattedText;
if (event.messageType == MessageTypes.Emote) {
html = '* $html';
}
+ // #Pangea
+ messageToolbar?.messageText = html;
+ // Pangea#
return HtmlMessage(
html: html,
textColor: textColor,
room: event.room,
+ messageToolbar: messageToolbar,
);
}
// else we fall through to the normal message rendering
@@ -264,55 +287,83 @@ 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(
+ style: 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: (context, state) =>
+ messageToolbar?.contextMenuOverride(
+ context: context,
+ textSelection: state,
+ ) ??
+ const SizedBox(),
+ // 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(
+ selectedText: selection,
+ cause: cause,
+ context: context,
+ ),
+ );
+ },
+ ),
);
}
case EventTypes.CallInvite:
diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart
index bf26b474f..4c869ecd7 100644
--- a/lib/pangea/extensions/pangea_room_extension.dart
+++ b/lib/pangea/extensions/pangea_room_extension.dart
@@ -11,7 +11,10 @@ import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+// import markdown.dart
+import 'package:html_unescape/html_unescape.dart';
import 'package:matrix/matrix.dart';
+import 'package:matrix/src/utils/markdown.dart';
import 'package:matrix/src/utils/space_child.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
@@ -859,7 +862,7 @@ extension PangeaRoom on Room {
String? txid,
Event? inReplyTo,
String? editEventId,
- bool parseMarkdown = false,
+ bool parseMarkdown = true,
bool parseCommands = false,
String msgtype = MessageTypes.Text,
String? threadRootEventId,
@@ -889,17 +892,19 @@ extension PangeaRoom on Room {
ModelKey.tokensWritten: tokensWritten?.toJson(),
ModelKey.useType: useType?.string,
};
- // if (parseMarkdown) {
- // final html = markdown(event['body'],
- // getEmotePacks: () => getImagePacksFlat(ImagePackUsage.emoticon),
- // getMention: getMention);
- // // if the decoded html is the same as the body, there is no need in sending a formatted message
- // if (HtmlUnescape().convert(html.replaceAll(RegExp(r'
\n?'), '\n')) !=
- // event['body']) {
- // event['format'] = 'org.matrix.custom.html';
- // event['formatted_body'] = html;
- // }
- // }
+ if (parseMarkdown) {
+ final html = markdown(
+ event['body'],
+ getEmotePacks: () => getImagePacksFlat(ImagePackUsage.emoticon),
+ getMention: getMention,
+ );
+ // if the decoded html is the same as the body, there is no need in sending a formatted message
+ if (HtmlUnescape().convert(html.replaceAll(RegExp(r'
\n?'), '\n')) !=
+ event['body']) {
+ event['format'] = 'org.matrix.custom.html';
+ event['formatted_body'] = html;
+ }
+ }
return sendEvent(
event,
txid: txid,
diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart
index 04be9b485..549b8afa5 100644
--- a/lib/pangea/models/igc_text_data_model.dart
+++ b/lib/pangea/models/igc_text_data_model.dart
@@ -1,20 +1,18 @@
import 'dart:developer';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-
-import 'package:matrix/matrix.dart';
-import 'package:sentry_flutter/sentry_flutter.dart';
-
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/models/span_card_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
+import 'package:fluffychat/pangea/utils/overlay.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:matrix/matrix.dart';
+import 'package:sentry_flutter/sentry_flutter.dart';
+
import '../constants/model_keys.dart';
-import '../utils/overlay.dart';
import '../widgets/igc/span_card.dart';
-import '../widgets/igc/word_data_card.dart';
import 'language_detection_model.dart';
// import 'package:language_tool/language_tool.dart';
@@ -150,35 +148,30 @@ class IGCTextData {
}
}
- int tokenIndexByOffset(
- cursorOffset,
- ) =>
- tokens.indexWhere(
+ int tokenIndexByOffset(cursorOffset) => tokens.indexWhere(
(token) =>
- token.text.offset <= cursorOffset &&
- cursorOffset <= token.text.offset + token.text.length,
+ token.text.offset <= cursorOffset && cursorOffset <= token.end,
);
- List getMatchIndicesForToken(PangeaToken token) =>
- matchIndicesByOffset(token.text.offset);
+ List matchIndicesByOffset(int offset) {
+ final List matchesForOffset = [];
+ for (final (index, match) in matches.indexed) {
+ if (match.isOffsetInMatchSpan(offset)) {
+ matchesForOffset.add(index);
+ }
+ }
+ return matchesForOffset;
+ }
int getTopMatchIndexForOffset(int offset) {
final List matchesForToken = matchIndicesByOffset(offset);
- if (matchesForToken.isEmpty) return -1;
- for (final matchIndex in matchesForToken) {
+ final int matchIndex = matchesForToken.indexWhere((matchIndex) {
final match = matches[matchIndex];
- if (enableIT) {
- if (match.isITStart || match.isl1SpanMatch) {
- return matchIndex;
- }
- }
- if (enableIGC) {
- if (match.isGrammarMatch) {
- return matchIndex;
- }
- }
- }
- return -1;
+ return (enableIT && (match.isITStart || match.isl1SpanMatch)) ||
+ (enableIGC && match.isGrammarMatch);
+ });
+ if (matchIndex == -1) return -1;
+ return matchesForToken[matchIndex];
}
PangeaMatch? getTopMatchForToken(PangeaToken token) {
@@ -187,23 +180,8 @@ class IGCTextData {
return matches[topMatchIndex];
}
- List matchIndicesByOffset(int offset) {
- final List matchesForOffset = [];
-
- for (final (index, match) in matches.indexed) {
- if (match.isOffsetInMatchSpan(offset)) {
- matchesForOffset.add(index);
- }
- }
-
- return matchesForOffset;
- }
-
- int getAfterTokenSpacingByIndex(
- int tokenIndex,
- ) {
- final int endOfToken =
- tokens[tokenIndex].text.offset + tokens[tokenIndex].text.length;
+ int getAfterTokenSpacingByIndex(int tokenIndex) {
+ final int endOfToken = tokens[tokenIndex].end;
if (tokenIndex + 1 < tokens.length) {
final spaceBetween = tokens[tokenIndex + 1].text.offset - endOfToken;
@@ -218,7 +196,7 @@ class IGCTextData {
),
);
ErrorHandler.logError(
- m: "wierd token lengths for ${tokens[tokenIndex].text.content} and ${tokens[tokenIndex + 1].text.content}",
+ m: "weird token lengths for ${tokens[tokenIndex].text.content} and ${tokens[tokenIndex + 1].text.content}",
);
return 0;
}
@@ -234,20 +212,42 @@ class IGCTextData {
decorationThickness: 5,
);
- static const _hasDefinitionStyle = TextStyle(
- decoration: TextDecoration.underline,
- decorationColor: Color.fromARGB(148, 83, 97, 255),
- decorationThickness: 4,
- );
- static TextStyle hasDefinitionStyle(TextStyle? existingStyle) =>
- existingStyle?.merge(_hasDefinitionStyle) ?? _hasDefinitionStyle;
+ List getMatchTokens() {
+ final List matchTokens = [];
+ int? endTokenIndex;
+ PangeaMatch? topMatch;
+ for (final (i, token) in tokens.indexed) {
+ if (endTokenIndex != null) {
+ if (i <= endTokenIndex) {
+ matchTokens.add(
+ MatchToken(
+ token: token,
+ match: topMatch,
+ ),
+ );
+ continue;
+ }
+ endTokenIndex = null;
+ }
+ topMatch = getTopMatchForToken(token);
+ if (topMatch != null) {
+ endTokenIndex = tokens.indexWhere((e) => e.end >= topMatch!.end, i);
+ }
+ matchTokens.add(
+ MatchToken(
+ token: token,
+ match: topMatch,
+ ),
+ );
+ }
+ return matchTokens;
+ }
//PTODO - handle multitoken spans
List constructTokenSpan({
required BuildContext context,
TextStyle? defaultStyle,
required SpanCardModel? spanCardModel,
- required bool showTokens,
required bool handleClick,
required String transformTargetId,
required Room room,
@@ -263,73 +263,77 @@ class IGCTextData {
];
}
- // or could make big strings for non-match text and therefore make less textspans.
- // would that be more performant?
- tokens.asMap().forEach(
- (index, token) {
- final PangeaMatch? topTokenMatch = getTopMatchForToken(
- tokens[index],
- );
- // if (index == 3) {
- // debugPrint(
- // "constructing span with topTokenMatch: ${topTokenMatch?.match.rule.id}");
- // }
+ final List matchTokens = getMatchTokens();
- final Widget cardToShow = spanCardModel != null && topTokenMatch != null
- ? SpanCard(
- scm: spanCardModel,
- )
- : WordDataCard(
- fullText: originalInput,
- fullTextLang: detections.first.langCode,
- word: token.text.content,
- wordLang: detections.first.langCode,
- hasInfo: token.hasInfo,
- room: room,
- );
+ for (int tokenIndex = 0; tokenIndex < matchTokens.length; tokenIndex++) {
+ final MatchToken matchToken = matchTokens[tokenIndex];
+ final Widget? cardToShow =
+ matchToken.match != null && spanCardModel != null
+ ? SpanCard(scm: spanCardModel)
+ : null;
- final TextStyle tokenStyle = topTokenMatch != null
- ? topTokenMatch.textStyle(defaultStyle)
- : hasDefinitionStyle(defaultStyle);
+ int nextTokenIndex = matchTokens.indexWhere(
+ (e) => matchToken.match != null
+ ? e.match != matchToken.match
+ : e.match != null,
+ tokenIndex,
+ );
+ if (nextTokenIndex < 0) {
+ nextTokenIndex = matchTokens.length;
+ }
+
+ final String matchText = originalInput.substring(
+ matchTokens[tokenIndex].token.text.offset,
+ matchTokens[nextTokenIndex - 1].token.end,
+ );
+
+ items.add(
+ TextSpan(
+ text: matchText,
+ style: matchTokens[tokenIndex].match?.textStyle(defaultStyle) ??
+ defaultStyle,
+ recognizer: handleClick && cardToShow != null
+ ? (TapGestureRecognizer()
+ ..onTapDown = (details) => OverlayUtil.showPositionedCard(
+ context: context,
+ cardToShow: cardToShow,
+ cardSize:
+ matchTokens[tokenIndex].match?.isITStart ?? false
+ ? const Size(350, 220)
+ : const Size(350, 400),
+ transformTargetId: transformTargetId,
+ ))
+ : null,
+ ),
+ );
+
+ final String beforeNextToken = originalInput.substring(
+ matchTokens[nextTokenIndex - 1].token.end,
+ nextTokenIndex < matchTokens.length
+ ? matchTokens[nextTokenIndex].token.text.offset
+ : originalInput.length,
+ );
+
+ if (beforeNextToken.isNotEmpty) {
items.add(
TextSpan(
- text: token.text.content,
- style: tokenStyle,
- recognizer: handleClick
- ? (TapGestureRecognizer()
- ..onTapDown = (details) => OverlayUtil.showPositionedCard(
- context: context,
- cardToShow: cardToShow,
- cardSize: topTokenMatch?.isITStart ?? false
- ? const Size(350, 220)
- : const Size(350, 400),
- transformTargetId: transformTargetId,
- ))
- : null,
+ text: beforeNextToken,
+ style: defaultStyle,
),
);
+ }
- final int charBetween = getAfterTokenSpacingByIndex(
- index,
- );
-
- if (charBetween > 0) {
- items.add(
- TextSpan(
- text: " " * charBetween,
- style: topTokenMatch != null &&
- token.text.offset + token.text.length + charBetween <=
- topTokenMatch.match.offset +
- topTokenMatch.match.length
- ? tokenStyle
- : defaultStyle,
- ),
- );
- }
- },
- );
+ tokenIndex = nextTokenIndex - 1;
+ }
return items;
}
}
+
+class MatchToken {
+ final PangeaToken token;
+ final PangeaMatch? match;
+
+ MatchToken({required this.token, this.match});
+}
diff --git a/lib/pangea/models/pangea_match_model.dart b/lib/pangea/models/pangea_match_model.dart
index c7e97f4c0..827816890 100644
--- a/lib/pangea/models/pangea_match_model.dart
+++ b/lib/pangea/models/pangea_match_model.dart
@@ -1,10 +1,10 @@
import 'dart:developer';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
import 'package:fluffychat/pangea/enum/span_data_type.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+
import '../constants/match_rule_ids.dart';
import 'igc_text_data_model.dart';
import 'span_data.dart';
@@ -127,4 +127,9 @@ class PangeaMatch {
IGCTextData.underlineStyle(underlineColor);
PangeaMatch get copyWith => PangeaMatch.fromJson(toJson());
+
+ int get beginning => match.offset < 0 ? 0 : match.offset;
+ int get end => match.offset + match.length > match.fullText.length
+ ? match.fullText.length
+ : match.offset + match.length;
}
diff --git a/lib/pangea/models/pangea_message_event.dart b/lib/pangea/models/pangea_message_event.dart
index aa4b745cd..a9ac18464 100644
--- a/lib/pangea/models/pangea_message_event.dart
+++ b/lib/pangea/models/pangea_message_event.dart
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
+import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/constants/pangea_message_types.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
@@ -272,6 +273,10 @@ class PangeaMessageEvent {
//each match is turned into an activity that other students can access
//they're not told the answer but have to find it themselves
//the message has a blank piece which they fill in themselves
+
+ // replication of logic from message_content.dart
+ bool get isHtml =>
+ AppConfig.renderHtml && !_event.redacted && _event.isRichMessage;
}
class URLFinder {
diff --git a/lib/pangea/models/pangea_representation_event.dart b/lib/pangea/models/pangea_representation_event.dart
index 64b16fa79..857abb8f5 100644
--- a/lib/pangea/models/pangea_representation_event.dart
+++ b/lib/pangea/models/pangea_representation_event.dart
@@ -7,6 +7,7 @@ import 'package:fluffychat/pangea/repo/tokens_repo.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
+import 'package:matrix/src/utils/markdown.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../widgets/matrix.dart';
@@ -161,4 +162,8 @@ class RepresentationEvent {
return _choreo;
}
+
+ String? formatBody() {
+ return markdown(content.text);
+ }
}
diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart
index 217cdc55b..19eaba750 100644
--- a/lib/pangea/models/pangea_token_model.dart
+++ b/lib/pangea/models/pangea_token_model.dart
@@ -1,7 +1,6 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
-
import 'package:sentry_flutter/sentry_flutter.dart';
import '../constants/model_keys.dart';
@@ -65,6 +64,8 @@ class PangeaToken {
_hasInfoKey: hasInfo,
_lemmaKey: lemmas.map((e) => e.toJson()).toList(),
};
+
+ int get end => text.offset + text.length;
}
class PangeaTokenText {
diff --git a/lib/pangea/utils/any_state_holder.dart b/lib/pangea/utils/any_state_holder.dart
index 46efff974..e7ee11451 100644
--- a/lib/pangea/utils/any_state_holder.dart
+++ b/lib/pangea/utils/any_state_holder.dart
@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
-
import 'package:sentry_flutter/sentry_flutter.dart';
import '../models/widget_measurement.dart';
@@ -38,6 +37,12 @@ class PangeaAnyState {
_layerLinkAndKeys.remove(transformTargetId);
}
+ void openOverlay(OverlayEntry entry, BuildContext context) {
+ closeOverlay();
+ overlay = entry;
+ Overlay.of(context).insert(overlay!);
+ }
+
void closeOverlay() {
if (overlay != null) {
overlay!.remove();
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/overlay.dart b/lib/pangea/utils/overlay.dart
index 33dea2018..2d8c465eb 100644
--- a/lib/pangea/utils/overlay.dart
+++ b/lib/pangea/utils/overlay.dart
@@ -1,17 +1,65 @@
import 'dart:developer';
import 'dart:math';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/widgets/common_widgets/overlay_container.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+
import '../../config/themes.dart';
import '../../widgets/matrix.dart';
import 'error_handler.dart';
class OverlayUtil {
+ static showOverlay({
+ required BuildContext context,
+ required Widget child,
+ required Size size,
+ required String transformTargetId,
+ Offset? offset,
+ backDropToDismiss = true,
+ Color? borderColor,
+ }) {
+ try {
+ MatrixState.pAnyState.closeOverlay();
+ final LayerLinkAndKey layerLinkAndKey =
+ MatrixState.pAnyState.layerLinkAndKey(transformTargetId);
+
+ final OverlayEntry entry = OverlayEntry(
+ builder: (context) => Stack(
+ children: [
+ // GestureDetector to detect when dismissed by clicking outside
+ Positioned.fill(
+ child: GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: () {
+ MatrixState.pAnyState.closeOverlay();
+ },
+ ),
+ ),
+ if (backDropToDismiss) const TransparentBackdrop(),
+ Positioned(
+ width: size.width,
+ height: size.height,
+ child: CompositedTransformFollower(
+ link: layerLinkAndKey.link,
+ showWhenUnlinked: false,
+ offset: offset ?? Offset.zero,
+ child: child,
+ ),
+ ),
+ ],
+ ),
+ );
+
+ MatrixState.pAnyState.openOverlay(entry, context);
+ } catch (err, stack) {
+ debugger(when: kDebugMode);
+ ErrorHandler.logError(e: err, s: stack);
+ }
+ }
+
static showPositionedCard({
required BuildContext context,
required Widget cardToShow,
@@ -21,8 +69,6 @@ class OverlayUtil {
Color? borderColor,
}) {
try {
- MatrixState.pAnyState.closeOverlay();
-
final LayerLinkAndKey layerLinkAndKey =
MatrixState.pAnyState.layerLinkAndKey(transformTargetId);
@@ -31,34 +77,25 @@ class OverlayUtil {
transformTargetKey: layerLinkAndKey.key,
);
- MatrixState.pAnyState.overlay = OverlayEntry(
- builder: (context) => Stack(
- children: [
- if (backDropToDismiss) const TransparentBackdrop(),
- Positioned(
- width: cardSize.width,
- height: cardSize.height,
- child: CompositedTransformFollower(
- link: layerLinkAndKey.link,
- showWhenUnlinked: false,
- offset: cardOffset,
- child: Material(
- borderOnForeground: false,
- color: Colors.transparent,
- clipBehavior: Clip.antiAlias,
- child: OverlayContainer(
- cardToShow: cardToShow,
- borderColor: borderColor,
- ),
- ),
- ),
- ),
- ],
+ final Widget child = Material(
+ borderOnForeground: false,
+ color: Colors.transparent,
+ clipBehavior: Clip.antiAlias,
+ child: OverlayContainer(
+ cardToShow: cardToShow,
+ borderColor: borderColor,
),
);
- Overlay.of(layerLinkAndKey.key.currentContext!)
- .insert(MatrixState.pAnyState.overlay!);
+ showOverlay(
+ context: context,
+ child: child,
+ size: cardSize,
+ transformTargetId: transformTargetId,
+ offset: cardOffset,
+ backDropToDismiss: backDropToDismiss,
+ borderColor: borderColor,
+ );
} catch (err, stack) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: err, s: stack);
@@ -132,6 +169,8 @@ class OverlayUtil {
return Offset(dx, dy);
}
+
+ static bool get isOverlayOpen => MatrixState.pAnyState.overlay != null;
}
class TransparentBackdrop extends StatelessWidget {
diff --git a/lib/pangea/utils/show_defintion_util.dart b/lib/pangea/utils/show_defintion_util.dart
new file mode 100644
index 000000000..6aa3ba29f
--- /dev/null
+++ b/lib/pangea/utils/show_defintion_util.dart
@@ -0,0 +1,159 @@
+import 'dart:async';
+
+import 'package:fluffychat/pangea/utils/any_state_holder.dart';
+import 'package:fluffychat/pangea/utils/overlay.dart';
+import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
+import 'package:fluffychat/widgets/matrix.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_gen/gen_l10n/l10n.dart';
+import 'package:matrix/matrix.dart';
+
+class ShowDefintionUtil {
+ String messageText;
+ final String langCode;
+ final String targetId;
+ final FocusNode focusNode = FocusNode();
+ final Room room;
+ String? textSelection;
+ bool inCooldown = false;
+ double? dx;
+ double? dy;
+
+ ShowDefintionUtil({
+ required this.targetId,
+ required this.room,
+ required this.langCode,
+ required this.messageText,
+ });
+
+ void onTextSelection({
+ required BuildContext context,
+ TextSelection? selectedText,
+ SelectedContent? selectedContent,
+ SelectionChangedCause? cause,
+ }) {
+ if ((selectedText == null && selectedContent == null) ||
+ selectedText?.isCollapsed == true) {
+ clearTextSelection();
+ return;
+ }
+ textSelection = selectedText != null
+ ? selectedText.textInside(messageText)
+ : selectedContent!.plainText;
+
+ if (BrowserContextMenu.enabled && kIsWeb) {
+ BrowserContextMenu.disableContextMenu();
+ }
+
+ if (kIsWeb && cause != SelectionChangedCause.tap) {
+ handleToolbar(context);
+ }
+ }
+
+ void clearTextSelection() {
+ textSelection = null;
+ if (kIsWeb && !BrowserContextMenu.enabled) {
+ BrowserContextMenu.enableContextMenu();
+ }
+ }
+
+ void handleToolbar(BuildContext context) async {
+ if (inCooldown || OverlayUtil.isOverlayOpen || !kIsWeb) return;
+ inCooldown = true;
+ Timer(const Duration(milliseconds: 750), () => inCooldown = false);
+ await Future.delayed(const Duration(milliseconds: 750));
+ showToolbar(context);
+ }
+
+ void showDefinition(BuildContext context) {
+ if (textSelection == null) return;
+ OverlayUtil.showPositionedCard(
+ context: context,
+ cardToShow: WordDataCard(
+ word: textSelection!,
+ wordLang: langCode,
+ fullText: messageText,
+ fullTextLang: langCode,
+ hasInfo: false,
+ room: room,
+ ),
+ cardSize: const Size(300, 300),
+ transformTargetId: targetId,
+ backDropToDismiss: false,
+ );
+ }
+
+ // web toolbar
+ Future showToolbar(BuildContext context) async {
+ final LayerLinkAndKey layerLinkAndKey =
+ MatrixState.pAnyState.layerLinkAndKey(targetId);
+
+ 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,
+ child: ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ minimumSize: Size.zero,
+ padding: EdgeInsets.zero,
+ ),
+ onPressed: () {
+ showDefinition(context);
+ },
+ child: Text(
+ L10n.of(context)!.showDefinition,
+ style: const TextStyle(
+ fontSize: 14,
+ ),
+ ),
+ ),
+ size: const Size(130, 45),
+ transformTargetId: targetId,
+ offset: Offset(xOffset, yOffset),
+ );
+ }
+
+ void onMouseRegionUpdate(PointerEvent event) {
+ dx = event.position.dx;
+ dy = event.position.dy;
+ }
+
+ Widget contextMenuOverride({
+ required BuildContext context,
+ EditableTextState? textSelection,
+ SelectableRegionState? contentSelection,
+ }) {
+ if (textSelection == null && contentSelection == null) {
+ return const SizedBox();
+ }
+ return AdaptiveTextSelectionToolbar.buttonItems(
+ anchors: textSelection?.contextMenuAnchors ??
+ contentSelection!.contextMenuAnchors,
+ buttonItems: [
+ if (textSelection != null) ...textSelection.contextMenuButtonItems,
+ if (contentSelection != null)
+ ...contentSelection.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 746c65cc2..782af3d8b 100644
--- a/lib/pangea/widgets/igc/pangea_rich_text.dart
+++ b/lib/pangea/widgets/igc/pangea_rich_text.dart
@@ -1,34 +1,33 @@
import 'dart:developer';
import 'dart:ui';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
-import 'package:sentry_flutter/sentry_flutter.dart';
-
import 'package:fluffychat/config/app_config.dart';
+import 'package:fluffychat/pages/chat/events/html_message.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/constants/language_keys.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/models/pangea_message_event.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
+import 'package:fluffychat/pangea/utils/show_defintion_util.dart';
import 'package:fluffychat/widgets/matrix.dart';
-import '../../models/igc_text_data_model.dart';
-import '../../models/language_detection_model.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:sentry_flutter/sentry_flutter.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 {
final PangeaMessageEvent pangeaMessageEvent;
- final TextStyle? existingStyle;
+ final TextStyle? style;
final bool selected;
final LanguageModel? selectedDisplayLang;
final bool immersionMode;
final bool definitions;
final Choreographer? choreographer;
+ final ShowDefintionUtil? messageToolbar;
const PangeaRichText({
super.key,
@@ -38,7 +37,8 @@ class PangeaRichText extends StatefulWidget {
required this.immersionMode,
required this.definitions,
this.choreographer,
- this.existingStyle,
+ this.style,
+ this.messageToolbar,
});
@override
@@ -48,20 +48,26 @@ class PangeaRichText extends StatefulWidget {
class PangeaRichTextState extends State {
final PangeaController pangeaController = MatrixState.pangeaController;
bool _fetchingRepresentation = false;
- bool _fetchingTokens = false;
double get blur => _fetchingRepresentation && widget.immersionMode ? 5 : 0;
- List textSpan = [];
+ String textSpan = "";
@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;
+ });
}
@override
@@ -85,28 +91,48 @@ class PangeaRichTextState extends State {
);
}
- final Widget richText = RichText(
- text: 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.pangeaMessageEvent.isHtml
+ ? HtmlMessage(
+ html: textSpan,
+ room: widget.pangeaMessageEvent.room,
+ textColor: widget.style?.color ?? Colors.black,
+ messageToolbar: widget.messageToolbar,
+ )
+ : SelectableText.rich(
+ onSelectionChanged: (selection, cause) =>
+ widget.messageToolbar?.onTextSelection(
+ selectedText: selection,
+ cause: cause,
+ context: context,
),
- ],
- ),
- );
+ focusNode: widget.messageToolbar?.focusNode,
+ contextMenuBuilder: (context, state) =>
+ widget.messageToolbar?.contextMenuOverride(
+ context: context,
+ textSelection: state,
+ ) ??
+ const SizedBox(),
+ TextSpan(
+ text: textSpan,
+ style: widget.style,
+ children: [
+ if (widget.selected && (_fetchingRepresentation))
+ const WidgetSpan(
+ child: Padding(
+ padding: EdgeInsets.only(left: 5.0),
+ child: SizedBox(
+ height: 14,
+ width: 14,
+ child: CircularProgressIndicator(
+ strokeWidth: 2.0,
+ color: AppConfig.secondaryColor,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
return blur > 0
? ImageFiltered(
@@ -116,17 +142,17 @@ class PangeaRichTextState extends State {
: richText;
}
- List getTextSpan(BuildContext context) {
+ String getTextSpan(BuildContext context) {
final String? displayLangCode =
widget.selected ? widget.selectedDisplayLang?.langCode : userL2LangCode;
if (displayLangCode == null || !widget.immersionMode) {
- return simpleText(widget.pangeaMessageEvent.body);
+ return widget.pangeaMessageEvent.body;
}
if (widget.pangeaMessageEvent.eventId.contains("webdebug")) {
debugger(when: kDebugMode);
- return simpleText(widget.pangeaMessageEvent.body);
+ return widget.pangeaMessageEvent.body;
}
final RepresentationEvent? repEvent =
@@ -145,7 +171,7 @@ class PangeaRichTextState extends State {
)
.onError((error, stackTrace) => ErrorHandler.logError())
.whenComplete(() => setState(() => _fetchingRepresentation = false));
- return simpleText(widget.pangeaMessageEvent.body);
+ return widget.pangeaMessageEvent.body;
}
if (repEvent.event?.eventId.contains("web") ?? false) {
@@ -158,75 +184,13 @@ class PangeaRichTextState extends State {
"representationByLanguageGlobal returned RepEvent with event ID containing 'web' - ${repEvent.event?.eventId}",
),
);
- // debugger(when: kDebugMode);
- return textWithBotStyle(repEvent, context);
}
- if (!widget.selected ||
- displayLangCode != userL2LangCode ||
- !widget.definitions) {
- return textWithBotStyle(repEvent, context);
- }
-
- if (repEvent.tokens == null) {
- setState(() => _fetchingTokens = true);
- repEvent
- .tokensGlobal(context)
- .onError((error, stackTrace) => ErrorHandler.logError())
- .whenComplete(() => setState(() => _fetchingTokens = false));
-
- return textWithBotStyle(repEvent, context);
- }
-
- return IGCTextData(
- originalInput: repEvent.text,
- fullTextCorrection: repEvent.text,
- matches: [],
- detections: [LanguageDetection(langCode: displayLangCode)],
- tokens: repEvent.tokens!,
- enableIT: true,
- enableIGC: true,
- userL2: userL2LangCode ?? LanguageKeys.unknownLanguage,
- userL1: userL1LangCode ?? LanguageKeys.unknownLanguage,
- ).constructTokenSpan(
- context: context,
- defaultStyle: textStyle(repEvent, context),
- handleClick: true,
- spanCardModel: null,
- showTokens: widget.definitions,
- transformTargetId: widget.pangeaMessageEvent.eventId,
- room: widget.pangeaMessageEvent.room,
- );
+ return widget.pangeaMessageEvent.isHtml
+ ? repEvent.formatBody() ?? repEvent.text
+ : repEvent.text;
}
- List simpleText(String text) => [
- TextSpan(
- text: text,
- style: widget.existingStyle,
- ),
- ];
-
- List textWithBotStyle(
- RepresentationEvent repEvent,
- BuildContext context,
- ) =>
- [
- TextSpan(
- text: repEvent.text,
- 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;
diff --git a/lib/pangea/widgets/igc/pangea_text_controller.dart b/lib/pangea/widgets/igc/pangea_text_controller.dart
index 5e64b4bef..886274316 100644
--- a/lib/pangea/widgets/igc/pangea_text_controller.dart
+++ b/lib/pangea/widgets/igc/pangea_text_controller.dart
@@ -1,13 +1,11 @@
import 'dart:developer';
+import 'package:fluffychat/pangea/widgets/igc/span_card.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:fluffychat/pangea/widgets/igc/span_card.dart';
-import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
import '../../choreographer/controllers/choreographer.dart';
import '../../enum/edit_type.dart';
-import '../../models/pangea_token_model.dart';
import '../../models/span_card_model.dart';
import '../../models/widget_measurement.dart';
import '../../utils/overlay.dart';
@@ -53,12 +51,11 @@ class PangeaTextController extends TextEditingController {
if (tokenIndex == -1) return;
- final PangeaToken token = choreographer.igc.igcTextData!.tokens[tokenIndex];
final int matchIndex =
choreographer.igc.igcTextData!.getTopMatchIndexForOffset(
selection.baseOffset,
);
- final Widget cardToShow = matchIndex != -1
+ final Widget? cardToShow = matchIndex != -1
? SpanCard(
scm: SpanCardModel(
// igcTextData: choreographer.igc.igcTextData!,
@@ -80,27 +77,19 @@ class PangeaTextController extends TextEditingController {
),
roomId: choreographer.roomId,
)
- : WordDataCard(
- fullText: text,
- fullTextLang:
- choreographer.igc.igcTextData!.detections.first.langCode,
- word: token.text.content,
- //Note: this assumes that the token must be in the target language
- //since it didn't have a match
- wordLang: choreographer.itController.targetLangCode,
- hasInfo: token.hasInfo,
- room: choreographer.chatController.room,
- );
+ : null;
- OverlayUtil.showPositionedCard(
- context: context,
- cardSize: matchIndex != -1 &&
- choreographer.igc.igcTextData!.matches[matchIndex].isITStart
- ? const Size(350, 220)
- : const Size(350, 400),
- cardToShow: cardToShow,
- transformTargetId: choreographer.inputTransformTargetKey,
- );
+ if (cardToShow != null) {
+ OverlayUtil.showPositionedCard(
+ context: context,
+ cardSize: matchIndex != -1 &&
+ choreographer.igc.igcTextData!.matches[matchIndex].isITStart
+ ? const Size(350, 220)
+ : const Size(350, 400),
+ cardToShow: cardToShow,
+ transformTargetId: choreographer.inputTransformTargetKey,
+ );
+ }
}
@override
@@ -139,7 +128,6 @@ class PangeaTextController extends TextEditingController {
...choreographer.igc.igcTextData!.constructTokenSpan(
context: context,
defaultStyle: style,
- showTokens: choreographer.definitionsEnabled,
spanCardModel: null,
handleClick: false,
transformTargetId: choreographer.inputTransformTargetKey,
diff --git a/needed-translations.txt b/needed-translations.txt
index cdb97658e..becd1bd8f 100644
--- a/needed-translations.txt
+++ b/needed-translations.txt
@@ -765,7 +765,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"bn": [
@@ -1539,7 +1540,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"bo": [
@@ -2313,7 +2315,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ca": [
@@ -3082,7 +3085,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"cs": [
@@ -3851,7 +3855,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"de": [
@@ -4620,7 +4625,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"el": [
@@ -5394,7 +5400,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"eo": [
@@ -6163,7 +6170,12 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
+ ],
+
+ "es": [
+ "showDefinition"
],
"et": [
@@ -6932,7 +6944,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"eu": [
@@ -7701,7 +7714,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"fa": [
@@ -8470,7 +8484,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"fi": [
@@ -9239,7 +9254,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"fr": [
@@ -10008,7 +10024,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ga": [
@@ -10777,7 +10794,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"gl": [
@@ -11546,7 +11564,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"he": [
@@ -12315,7 +12334,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"hi": [
@@ -13089,7 +13109,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"hr": [
@@ -13858,7 +13879,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"hu": [
@@ -14627,7 +14649,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"id": [
@@ -15396,7 +15419,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ie": [
@@ -16167,7 +16191,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"it": [
@@ -16936,7 +16961,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ja": [
@@ -17705,7 +17731,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ko": [
@@ -18474,7 +18501,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"lt": [
@@ -19243,7 +19271,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"lv": [
@@ -20017,7 +20046,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"nb": [
@@ -20786,7 +20816,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"nl": [
@@ -21555,7 +21586,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"pl": [
@@ -22324,7 +22356,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"pt": [
@@ -23098,7 +23131,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"pt_BR": [
@@ -23867,7 +23901,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"pt_PT": [
@@ -24636,7 +24671,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ro": [
@@ -25405,7 +25441,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ru": [
@@ -26174,7 +26211,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"sk": [
@@ -26944,7 +26982,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"sl": [
@@ -27716,7 +27755,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"sr": [
@@ -28485,7 +28525,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"sv": [
@@ -29254,7 +29295,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"ta": [
@@ -30028,7 +30070,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"th": [
@@ -30802,7 +30845,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"tr": [
@@ -31571,7 +31615,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"uk": [
@@ -32340,7 +32385,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"vi": [
@@ -33112,7 +33158,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"zh": [
@@ -33881,7 +33928,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
],
"zh_Hant": [
@@ -34650,6 +34698,7 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
- "emptyInviteWarning"
+ "emptyInviteWarning",
+ "showDefinition"
]
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 2816ac01d..d37aa3845 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -142,6 +142,7 @@ flutter:
generate: true
uses-material-design: true
assets:
+ - .env
- assets/
# #Pangea
- assets/pangea/