diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart
index d05374dc3..ede3ddb6b 100644
--- a/lib/pages/chat/events/html_message.dart
+++ b/lib/pages/chat/events/html_message.dart
@@ -166,28 +166,23 @@ class HtmlMessage extends StatelessWidget {
(token) => token.text.offset == offset && token.text.length == length,
);
- /// Wrap token spans in token tags so styling / functions can be applied
- dom.Node _tokenizeHtml(
- dom.Node element,
- String fullHtml,
- List remainingTokens,
- ) {
+ String _addTokenTags() {
final regex = RegExp(r'(<[^>]+>)');
- final matches = regex.allMatches(fullHtml);
+ final matches = regex.allMatches(html);
final List result = [];
int lastEnd = 0;
for (final match in matches) {
if (match.start > lastEnd) {
- result.add(fullHtml.substring(lastEnd, match.start)); // Text before tag
+ result.add(html.substring(lastEnd, match.start)); // Text before tag
}
result.add(match.group(0)!); // The tag itself
lastEnd = match.end;
}
- if (lastEnd < fullHtml.length) {
- result.add(fullHtml.substring(lastEnd)); // Remaining text after last tag
+ if (lastEnd < html.length) {
+ result.add(html.substring(lastEnd)); // Remaining text after last tag
}
for (final PangeaToken token in tokens ?? []) {
@@ -217,7 +212,7 @@ class HtmlMessage extends StatelessWidget {
]);
}
- return dom.Element.html('${result.join()}');
+ return result.join();
}
// Pangea#
@@ -834,10 +829,7 @@ class HtmlMessage extends StatelessWidget {
// maxLines: limitHeight ? 64 : null,Add commentMore actions
// overflow: TextOverflow.fade,
// );
- dom.Node parsed = parser.parse(html).body ?? dom.Element.html('');
- if (tokens != null) {
- parsed = _tokenizeHtml(parsed, html, List.from(tokens!));
- }
+ final parsed = parser.parse(_addTokenTags()).body ?? dom.Element.html('');
return SelectionArea(
child: GestureDetector(
onTap: () {
diff --git a/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart b/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart
index eb8aae600..995260793 100644
--- a/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart
+++ b/lib/pangea/chat_list/utils/get_chat_list_item_subtitle.dart
@@ -6,8 +6,6 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
-import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart';
-import 'package:fluffychat/pangea/toolbar/widgets/message_token_text.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../../utils/matrix_sdk_extensions/matrix_locals.dart';
@@ -88,26 +86,9 @@ class ChatListItemSubtitle extends StatelessWidget {
if (snapshot.hasData) {
final messageEventAndTokens = snapshot.data as MessageEventAndTokens;
final pangeaMessageEvent = messageEventAndTokens.event;
- final tokens = messageEventAndTokens.tokens;
-
- final analyticsEntry = tokens != null
- ? PracticeSelectionRepo.get(
- messageEventAndTokens.event.messageDisplayLangCode,
- tokens,
- )
- : null;
-
- return MessageTextWidget(
- pangeaMessageEvent: pangeaMessageEvent,
- existingStyle: style,
- messageAnalyticsEntry: analyticsEntry,
- isSelected: null,
- onClick: null,
- softWrap: false,
- maxLines: pangeaMessageEvent.room.notificationCount >= 1 ? 2 : 1,
- overflow: TextOverflow.ellipsis,
- isMessage: false,
- textScaler: MediaQuery.textScalerOf(context),
+ return Text(
+ pangeaMessageEvent.messageDisplayText,
+ style: style,
);
}
diff --git a/lib/pangea/events/utils/message_text_util.dart b/lib/pangea/events/utils/message_text_util.dart
deleted file mode 100644
index 25c18c035..000000000
--- a/lib/pangea/events/utils/message_text_util.dart
+++ /dev/null
@@ -1,177 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:fluffychat/pangea/common/utils/error_handler.dart';
-import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
-import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
-import 'package:fluffychat/pangea/practice_activities/practice_selection.dart';
-
-class TokenPosition {
- /// Start index of the full substring in the message
- final int start;
-
- /// End index of the full substring in the message
- final int end;
-
- /// Start index of the token in the message
- final int tokenStart;
-
- /// End index of the token in the message
- final int tokenEnd;
-
- final bool hideContent;
- final PangeaToken? token;
- final bool isHighlighted;
- final bool selected;
-
- const TokenPosition({
- required this.start,
- required this.end,
- required this.tokenStart,
- required this.tokenEnd,
- required this.hideContent,
- required this.selected,
- required this.isHighlighted,
- this.token,
- });
-}
-
-class MessageTextUtil {
- static final Map> _tokenPositionsCache = {};
-
- static List? getTokenPositions(
- PangeaMessageEvent pangeaMessageEvent, {
- PracticeSelection? messageAnalyticsEntry,
- bool Function(PangeaToken)? isSelected,
- bool Function(PangeaToken)? isHighlighted,
- }) {
- try {
- if (pangeaMessageEvent.messageDisplayRepresentation?.tokens == null) {
- return null;
- }
-
- final cacheKey = pangeaMessageEvent.event
- .getDisplayEvent(pangeaMessageEvent.timeline)
- .eventId;
-
- if (_tokenPositionsCache.containsKey(cacheKey)) {
- return _tokenPositionsCache[cacheKey]!
- .map(
- (t) => TokenPosition(
- start: t.start,
- end: t.end,
- tokenStart: t.tokenStart,
- tokenEnd: t.tokenEnd,
- hideContent: t.hideContent,
- selected: t.token != null
- ? isSelected?.call(t.token!) ?? false
- : false,
- isHighlighted: t.token != null
- ? isHighlighted?.call(t.token!) ?? false
- : false,
- token: t.token,
- ),
- )
- .toList();
- }
-
- // Convert the entire message into a list of characters
- final Characters messageCharacters =
- pangeaMessageEvent.messageDisplayText.characters;
-
- // When building token positions, use grapheme cluster indices
- final List tokenPositions = [];
- int globalIndex = 0;
-
- final tokens = pangeaMessageEvent.messageDisplayRepresentation!.tokens!;
- int pointer = 0;
- while (pointer < tokens.length) {
- PangeaToken token = tokens[pointer];
- final start = token.start;
- final end = token.end;
-
- // Calculate the number of grapheme clusters up to the start and end positions
- final int startIndex = messageCharacters.take(start).length;
- int endIndex = messageCharacters.take(end).length;
-
- final hasHiddenContent =
- messageAnalyticsEntry?.hasHiddenWordActivity ?? false;
-
- // if this is white space, add position without token
- if (globalIndex < startIndex) {
- tokenPositions.add(
- TokenPosition(
- start: globalIndex,
- end: startIndex,
- tokenStart: globalIndex,
- tokenEnd: startIndex,
- hideContent: false,
- selected: (isSelected?.call(token) ?? false) && !hasHiddenContent,
- isHighlighted: isHighlighted?.call(token) ?? false,
- ),
- );
- }
-
- // group tokens with punctuation before and after so punctuation doesn't cause newline
- int nextTokenPointer = pointer + 1;
- while (nextTokenPointer < tokens.length) {
- final nextToken = tokens[nextTokenPointer];
- if (token.pos == 'PUNCT' && token.end == nextToken.start) {
- token = nextToken;
- nextTokenPointer++;
- endIndex = messageCharacters.take(nextToken.end).length;
- continue;
- }
- break;
- }
-
- while (nextTokenPointer < tokens.length) {
- final nextToken = tokens[nextTokenPointer];
-
- if (nextToken.pos == 'PUNCT' && token.end == nextToken.start) {
- nextTokenPointer++;
- endIndex = messageCharacters.take(nextToken.end).length;
- continue;
- }
- break;
- }
-
- final hideContent =
- messageAnalyticsEntry?.isTokenInHiddenWordActivity(token) ?? false;
-
- tokenPositions.add(
- TokenPosition(
- start: startIndex,
- end: endIndex,
- tokenStart: messageCharacters.take(token.start).length,
- tokenEnd: messageCharacters.take(token.end).length,
- token: token,
- hideContent: hideContent,
- selected: (isSelected?.call(token) ?? false) &&
- !hideContent &&
- !hasHiddenContent,
- isHighlighted: isHighlighted?.call(token) ?? false,
- ),
- );
-
- globalIndex = endIndex;
- pointer = nextTokenPointer;
- continue;
- }
-
- _tokenPositionsCache[cacheKey] = tokenPositions;
-
- return tokenPositions;
- } catch (err, s) {
- ErrorHandler.logError(
- e: err,
- s: s,
- data: {
- 'pangeaMessageEvent': pangeaMessageEvent,
- 'messageAnalyticsEntry': messageAnalyticsEntry,
- 'isSelected': isSelected,
- },
- );
- return null;
- }
- }
-}
diff --git a/lib/pangea/toolbar/widgets/message_token_text.dart b/lib/pangea/toolbar/widgets/message_token_text.dart
deleted file mode 100644
index 0440f98e0..000000000
--- a/lib/pangea/toolbar/widgets/message_token_text.dart
+++ /dev/null
@@ -1,419 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:collection/collection.dart';
-import 'package:flutter_linkify/flutter_linkify.dart';
-
-import 'package:fluffychat/pangea/common/utils/any_state_holder.dart';
-import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
-import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
-import 'package:fluffychat/pangea/events/utils/message_text_util.dart';
-import 'package:fluffychat/pangea/message_token_text/message_token_button.dart';
-import 'package:fluffychat/pangea/practice_activities/practice_selection.dart';
-import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart';
-import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
-import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
-import 'package:fluffychat/pangea/toolbar/utils/token_rendering_util.dart';
-import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
-import 'package:fluffychat/utils/url_launcher.dart';
-import 'package:fluffychat/widgets/matrix.dart';
-
-/// Question - does this need to be stateful or does this work?
-/// Need to test.
-///
-
-class MessageTokenText extends StatelessWidget {
- final PangeaMessageEvent _pangeaMessageEvent;
- final TextStyle _style;
-
- final bool Function(PangeaToken)? _isSelected;
- final void Function(PangeaToken)? _onClick;
- final bool Function(PangeaToken)? _isHighlighted;
- final MessageOverlayController? _overlayController;
- final bool _isTransitionAnimation;
- final ReadingAssistanceMode? readingAssistanceMode;
-
- const MessageTokenText({
- super.key,
- required PangeaMessageEvent pangeaMessageEvent,
- required List? tokens,
- required TextStyle style,
- required void Function(PangeaToken)? onClick,
- bool Function(PangeaToken)? isSelected,
- bool Function(PangeaToken)? isHighlighted,
- MessageMode? messageMode,
- MessageOverlayController? overlayController,
- bool isTransitionAnimation = false,
- this.readingAssistanceMode,
- }) : _onClick = onClick,
- _isSelected = isSelected,
- _style = style,
- _pangeaMessageEvent = pangeaMessageEvent,
- _isHighlighted = isHighlighted,
- _overlayController = overlayController,
- _isTransitionAnimation = isTransitionAnimation;
-
- List? get _tokens =>
- _pangeaMessageEvent.messageDisplayRepresentation?.tokens;
-
- PracticeSelection? get messageAnalyticsEntry => _tokens != null
- ? PracticeSelectionRepo.get(
- _pangeaMessageEvent.messageDisplayLangCode,
- _tokens!,
- )
- : null;
-
- void callOnClick(TokenPosition tokenPosition) {
- _onClick != null && tokenPosition.token != null
- ? _onClick!(tokenPosition.token!)
- : null;
- }
-
- @override
- Widget build(BuildContext context) {
- if (_tokens == null) {
- return Text(
- _pangeaMessageEvent.messageDisplayText,
- style: _style,
- textScaler: TextScaler.noScaling,
- );
- }
-
- return MessageTextWidget(
- pangeaMessageEvent: _pangeaMessageEvent,
- existingStyle: _style,
- messageAnalyticsEntry: messageAnalyticsEntry,
- isSelected: _isSelected,
- onClick: callOnClick,
- isHighlighted: _isHighlighted,
- overlayController: _overlayController,
- isTransitionAnimation: _isTransitionAnimation,
- readingAssistanceMode: readingAssistanceMode,
- );
- }
-}
-
-class MessageTextWidget extends StatelessWidget {
- final PangeaMessageEvent pangeaMessageEvent;
- final TextStyle existingStyle;
- final PracticeSelection? messageAnalyticsEntry;
- final bool Function(PangeaToken)? isSelected;
- final void Function(TokenPosition tokenPosition)? onClick;
- final bool Function(PangeaToken)? isHighlighted;
-
- final bool? softWrap;
- final int? maxLines;
- final TextOverflow? overflow;
-
- final MessageOverlayController? overlayController;
- final bool isTransitionAnimation;
- final bool isMessage;
- final ReadingAssistanceMode? readingAssistanceMode;
-
- final TextScaler? textScaler;
-
- const MessageTextWidget({
- super.key,
- required this.pangeaMessageEvent,
- required this.existingStyle,
- this.messageAnalyticsEntry,
- this.isSelected,
- this.onClick,
- this.softWrap,
- this.maxLines,
- this.overflow,
- this.isHighlighted,
- this.overlayController,
- this.isTransitionAnimation = false,
- this.isMessage = true,
- this.readingAssistanceMode,
- this.textScaler,
- });
-
- @override
- Widget build(BuildContext context) {
- final renderer = TokenRenderingUtil(
- pangeaMessageEvent: pangeaMessageEvent,
- readingAssistanceMode: readingAssistanceMode,
- existingStyle: existingStyle,
- overlayController: overlayController,
- isTransitionAnimation: isTransitionAnimation,
- );
-
- final Characters messageCharacters =
- pangeaMessageEvent.messageDisplayText.characters;
-
- final tokenPositions = MessageTextUtil.getTokenPositions(
- pangeaMessageEvent,
- messageAnalyticsEntry: messageAnalyticsEntry,
- isSelected: isSelected,
- isHighlighted: isHighlighted,
- );
-
- if (tokenPositions == null) {
- return Text(
- pangeaMessageEvent.messageDisplayText,
- style: renderer.style(context),
- softWrap: softWrap,
- maxLines: maxLines,
- overflow: overflow,
- textScaler: textScaler,
- );
- }
-
- final theme = Theme.of(context);
- final ownMessage =
- pangeaMessageEvent.senderId == Matrix.of(context).client.userID;
- final linkColor = theme.brightness == Brightness.light
- ? theme.colorScheme.primary
- : ownMessage
- ? theme.colorScheme.onPrimary
- : theme.colorScheme.onSurface;
-
- return RichText(
- softWrap: softWrap ?? true,
- maxLines: maxLines,
- overflow: overflow ?? TextOverflow.clip,
- textScaler: textScaler ?? TextScaler.noScaling,
- text: TextSpan(
- children:
- tokenPositions.mapIndexed((int i, TokenPosition tokenPosition) {
- final substring = messageCharacters
- .skip(tokenPosition.start)
- .take(tokenPosition.end - tokenPosition.start)
- .toString();
-
- if (tokenPosition.token?.pos == 'SPACE') {
- return const TextSpan(text: '\n');
- }
-
- if (tokenPosition.token != null) {
- // if the tokenPosition is a combination of the token and preceding / following punctuation
- // split them so that only the token itself is highlighted when clicked
- String start = '';
- String middle = '';
- String end = '';
-
- final startSplitIndex =
- tokenPosition.tokenStart - tokenPosition.start;
- final endSplitIndex = tokenPosition.tokenEnd - tokenPosition.start;
-
- start = substring.characters.take(startSplitIndex).toString();
- end = substring.characters.skip(endSplitIndex).toString();
- middle = substring.characters
- .skip(startSplitIndex)
- .take(endSplitIndex - startSplitIndex)
- .toString();
-
- final token = tokenPosition.token!;
-
- final tokenWidth = renderer.tokenTextWidthForContainer(
- context,
- token.text.content,
- );
-
- return WidgetSpan(
- child: CompositedTransformTarget(
- link: renderer.assignTokenKey
- ? MatrixState.pAnyState
- .layerLinkAndKey(token.text.uniqueKey)
- .link
- : LayerLinkAndKey(token.hashCode.toString()).link,
- child: Column(
- key: renderer.assignTokenKey
- ? MatrixState.pAnyState
- .layerLinkAndKey(token.text.uniqueKey)
- .key
- : null,
- children: [
- if (renderer.showCenterStyling)
- MessageTokenButton(
- token: token,
- overlayController: overlayController,
- textStyle: renderer.style(context),
- width: tokenWidth,
- animateIn: isTransitionAnimation,
- practiceTargetForToken: overlayController
- ?.toolbarMode.associatedActivityType !=
- null
- ? overlayController?.practiceSelection
- ?.activities(
- overlayController!
- .toolbarMode.associatedActivityType!,
- )
- .firstWhereOrNull(
- (a) => a.tokens.contains(token),
- )
- : null,
- ),
- MouseRegion(
- cursor: SystemMouseCursors.click,
- child: GestureDetector(
- behavior: HitTestBehavior.translucent,
- onTap: onClick != null
- ? () => onClick?.call(tokenPosition)
- : null,
- child: RichText(
- text: TextSpan(
- children: [
- if (start.isNotEmpty)
- LinkifySpan(
- text: start,
- style: renderer.style(
- context,
- color: renderer.backgroundColor(
- context,
- tokenPosition.selected,
- ),
- ),
- linkStyle: TextStyle(
- decoration: TextDecoration.underline,
- color: linkColor,
- ),
- onOpen: (url) =>
- UrlLauncher(context, url.url).launchUrl(),
- ),
- // tokenPosition.hideContent
- // ? WidgetSpan(
- // alignment: PlaceholderAlignment.middle,
- // child: GestureDetector(
- // onTap: onClick != null
- // ? () => onClick?.call(tokenPosition)
- // : null,
- // child: HiddenText(
- // text: middle,
- // style: style(context),
- // ),
- // ),
- // )
- // :
- LinkifySpan(
- text: middle,
- style: renderer.style(
- context,
- color: renderer.backgroundColor(
- context,
- tokenPosition.selected,
- ),
- ),
- linkStyle: TextStyle(
- decoration: TextDecoration.underline,
- color: linkColor,
- ),
- onOpen: (url) =>
- UrlLauncher(context, url.url).launchUrl(),
- ),
- if (end.isNotEmpty)
- LinkifySpan(
- text: end,
- style: renderer.style(
- context,
- color: renderer.backgroundColor(
- context,
- tokenPosition.selected,
- ),
- ),
- linkStyle: TextStyle(
- decoration: TextDecoration.underline,
- color: linkColor,
- ),
- onOpen: (url) =>
- UrlLauncher(context, url.url).launchUrl(),
- ),
- ],
- ),
- ),
- ),
- ),
- // AnimatedContainer(
- // duration: const Duration(
- // milliseconds: AppConfig.overlayAnimationDuration,
- // ),
- // height: overlayController != null && isTransitionAnimation
- // ? 4
- // : 0,
- // width: tokenWidth,
- // child: Container(
- // color: backgroundColor(context, tokenPosition),
- // ),
- // ),
- ],
- ),
- ),
- );
- } else {
- // if ((i > 0 || i < tokenPositions.length - 1) &&
- // tokenPositions[i + 1].hideContent &&
- // tokenPositions[i - 1].hideContent) {
- // return WidgetSpan(
- // child: GestureDetector(
- // onTap: onClick != null
- // ? () => onClick?.call(tokenPosition)
- // : null,
- // child: HiddenText(
- // text: substring,
- // style: style(context),
- // ),
- // ),
- // );
- // }
- return LinkifySpan(
- text: substring,
- style: renderer.style(context),
- options: const LinkifyOptions(humanize: false),
- linkStyle: TextStyle(
- decoration: TextDecoration.underline,
- color: Theme.of(context).colorScheme.primary,
- ),
- onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
- );
- }
- }).toList(),
- ),
- );
- }
-}
-
-// class HiddenText extends StatelessWidget {
-// final String text;
-// final TextStyle style;
-
-// const HiddenText({
-// super.key,
-// required this.text,
-// required this.style,
-// });
-
-// @override
-// Widget build(BuildContext context) {
-// final TextPainter textPainter = TextPainter(
-// text: TextSpan(text: text, style: style),
-// textDirection: TextDirection.ltr,
-// )..layout();
-
-// final textWidth = textPainter.size.width;
-// final textHeight = textPainter.size.height;
-
-// textPainter.dispose();
-
-// return SizedBox(
-// height: textHeight,
-// child: Stack(
-// children: [
-// Container(
-// width: textWidth,
-// height: textHeight,
-// color: Colors.transparent,
-// ),
-// Positioned(
-// bottom: 0,
-// child: Container(
-// width: textWidth,
-// height: 1,
-// color: style.color,
-// ),
-// ),
-// ],
-// ),
-// );
-// }
-// }