Merge pull request #50 from pangeachat/new-message-selection
Select Text to Show Defintions
This commit is contained in:
commit
15631c9ee4
19 changed files with 785 additions and 484 deletions
|
|
@ -3872,5 +3872,6 @@
|
|||
"@chooseAUsername": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
}
|
||||
},
|
||||
"showDefinition": "Show Definition"
|
||||
}
|
||||
|
|
@ -63,10 +63,10 @@ abstract class AppConfig {
|
|||
static const bool enableSentry = true;
|
||||
static const String sentryDns =
|
||||
'https://8591d0d863b646feb4f3dda7e5dcab38@o256755.ingest.sentry.io/5243143';
|
||||
//#Pangea
|
||||
// #Pangea
|
||||
static bool renderHtml = false;
|
||||
// static bool renderHtml = true;
|
||||
//Pangea#
|
||||
// Pangea#
|
||||
static bool hideRedactedEvents = false;
|
||||
static bool hideUnknownEvents = true;
|
||||
static bool hideUnimportantStateEvents = true;
|
||||
|
|
|
|||
|
|
@ -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<ChatPageWithRoom>
|
|||
if (choreographer.itController.isOpen) {
|
||||
return;
|
||||
}
|
||||
pangeaController.instructions.show(
|
||||
context,
|
||||
InstructionsEnum.understandingMessages,
|
||||
event.eventId,
|
||||
);
|
||||
// Pangea#
|
||||
if (!event.redacted) {
|
||||
if (selectedEvents.contains(event)) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String>(
|
||||
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<String>(
|
||||
// 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:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,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';
|
||||
|
||||
|
|
@ -858,7 +861,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,
|
||||
|
|
@ -888,17 +891,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'<br />\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'<br />\n?'), '\n')) !=
|
||||
event['body']) {
|
||||
event['format'] = 'org.matrix.custom.html';
|
||||
event['formatted_body'] = html;
|
||||
}
|
||||
}
|
||||
return sendEvent(
|
||||
event,
|
||||
txid: txid,
|
||||
|
|
|
|||
|
|
@ -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<int> getMatchIndicesForToken(PangeaToken token) =>
|
||||
matchIndicesByOffset(token.text.offset);
|
||||
List<int> matchIndicesByOffset(int offset) {
|
||||
final List<int> matchesForOffset = [];
|
||||
for (final (index, match) in matches.indexed) {
|
||||
if (match.isOffsetInMatchSpan(offset)) {
|
||||
matchesForOffset.add(index);
|
||||
}
|
||||
}
|
||||
return matchesForOffset;
|
||||
}
|
||||
|
||||
int getTopMatchIndexForOffset(int offset) {
|
||||
final List<int> 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<int> matchIndicesByOffset(int offset) {
|
||||
final List<int> 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<MatchToken> getMatchTokens() {
|
||||
final List<MatchToken> 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<TextSpan> 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<MatchToken> 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});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
159
lib/pangea/utils/show_defintion_util.dart
Normal file
159
lib/pangea/utils/show_defintion_util.dart
Normal file
|
|
@ -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<dynamic> 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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<PangeaRichText> {
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
bool _fetchingRepresentation = false;
|
||||
bool _fetchingTokens = false;
|
||||
double get blur => _fetchingRepresentation && widget.immersionMode ? 5 : 0;
|
||||
List<TextSpan> 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<PangeaRichText> {
|
|||
);
|
||||
}
|
||||
|
||||
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<PangeaRichText> {
|
|||
: richText;
|
||||
}
|
||||
|
||||
List<TextSpan> 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<PangeaRichText> {
|
|||
)
|
||||
.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<PangeaRichText> {
|
|||
"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<TextSpan> simpleText(String text) => [
|
||||
TextSpan(
|
||||
text: text,
|
||||
style: widget.existingStyle,
|
||||
),
|
||||
];
|
||||
|
||||
List<TextSpan> 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ flutter:
|
|||
generate: true
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- .env
|
||||
- assets/
|
||||
# #Pangea
|
||||
- assets/pangea/
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue