diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart
index 9cddf5570..dda308453 100644
--- a/lib/pages/chat/events/html_message.dart
+++ b/lib/pages/chat/events/html_message.dart
@@ -20,6 +20,7 @@ import 'package:fluffychat/pangea/toolbar/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/token_emoji_button.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/token_rendering_util.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/tokens_util.dart';
+import 'package:fluffychat/pangea/toolbar/reading_assistance/underline_text_widget.dart';
import 'package:fluffychat/utils/event_checkbox_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
@@ -395,17 +396,6 @@ class HtmlMessage extends StatelessWidget {
if (!allowedHtmlTags.contains(node.localName)) return const TextSpan();
// #Pangea
- final renderer = TokenRenderingUtil(
- existingStyle: pangeaMessageEvent != null
- ? textStyle.merge(
- AppConfig.messageTextStyle(
- pangeaMessageEvent!.event,
- textColor,
- ),
- )
- : textStyle,
- );
-
double fontSize = this.fontSize;
if (readingAssistanceMode == ReadingAssistanceMode.practiceMode) {
fontSize = (overlayController != null && overlayController!.maxWidth > 600
@@ -414,6 +404,19 @@ class HtmlMessage extends StatelessWidget {
this.fontSize;
}
+ final existingStyle = pangeaMessageEvent != null
+ ? textStyle
+ .merge(
+ AppConfig.messageTextStyle(
+ pangeaMessageEvent!.event,
+ textColor,
+ ),
+ )
+ .copyWith(fontSize: fontSize)
+ : textStyle.copyWith(fontSize: fontSize);
+
+ final renderer = TokenRenderingUtil();
+
final underlineColor = Theme.of(context).colorScheme.primary.withAlpha(200);
final newTokens =
@@ -443,7 +446,8 @@ class HtmlMessage extends StatelessWidget {
final tokenWidth = renderer.tokenTextWidthForContainer(
node.text,
Theme.of(context).colorScheme.primary.withAlpha(200),
- fontSize: fontSize,
+ existingStyle,
+ fontSize,
);
return TextSpan(
@@ -472,10 +476,7 @@ class HtmlMessage extends StatelessWidget {
TokenPracticeButton(
token: token,
controller: overlayController!.practiceController,
- textStyle: renderer.style(
- fontSize: fontSize,
- underlineColor: underlineColor,
- ),
+ textStyle: existingStyle,
width: tokenWidth,
textColor: textColor,
),
@@ -496,28 +497,19 @@ class HtmlMessage extends StatelessWidget {
: null,
child: HoverBuilder(
builder: (context, hovered) {
- return RichText(
+ return UnderlineText(
+ text: node.text.trim(),
+ style: existingStyle,
+ linkStyle: linkStyle,
textDirection: pangeaMessageEvent?.textDirection,
- text: TextSpan(
- children: [
- LinkifySpan(
- text: node.text.trim(),
- style: renderer.style(
- fontSize: fontSize,
- underlineColor: underlineColor,
- selected: selected,
- highlighted: highlighted,
- isNew: isNew,
- practiceMode: readingAssistanceMode ==
- ReadingAssistanceMode.practiceMode,
- hovered: hovered,
- ),
- linkStyle: linkStyle,
- onOpen: (url) =>
- UrlLauncher(context, url.url)
- .launchUrl(),
- ),
- ],
+ underlineColor: TokenRenderingUtil.underlineColor(
+ underlineColor,
+ selected: selected,
+ highlighted: highlighted,
+ isNew: isNew,
+ practiceMode: readingAssistanceMode ==
+ ReadingAssistanceMode.practiceMode,
+ hovered: hovered,
),
);
},
@@ -669,10 +661,7 @@ class HtmlMessage extends StatelessWidget {
// const TextSpan(text: '• '),
TextSpan(
text: '• ',
- style: renderer.style(
- underlineColor: underlineColor,
- fontSize: fontSize,
- ),
+ style: existingStyle,
),
// Pangea#
if (node.parent?.localName == 'ol')
@@ -681,10 +670,7 @@ class HtmlMessage extends StatelessWidget {
'${(node.parent?.nodes.whereType().toList().indexOf(node) ?? 0) + (int.tryParse(node.parent?.attributes['start'] ?? '1') ?? 1)}. ',
// #Pangea
// style: textStyle,
- style: renderer.style(
- underlineColor: underlineColor,
- fontSize: fontSize,
- ),
+ style: existingStyle,
// Pangea#
),
if (node.className == 'task-list-item')
diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart
index bd78b0538..a7fe9e7f6 100644
--- a/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart
+++ b/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart
@@ -8,6 +8,7 @@ import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/token_rendering_util.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/tokens_util.dart';
+import 'package:fluffychat/pangea/toolbar/reading_assistance/underline_text_widget.dart';
import 'package:fluffychat/pangea/toolbar/token_rendering_mixin.dart';
import 'package:fluffychat/pangea/toolbar/word_card/word_zoom_widget.dart';
import 'package:fluffychat/widgets/hover_builder.dart';
@@ -143,12 +144,6 @@ class _VocabChipsState extends State<_VocabChips> with TokenRenderingMixin {
tokens,
widget.activityLangCode,
);
- final renderer = TokenRenderingUtil(
- existingStyle: TextStyle(
- color: Theme.of(context).colorScheme.onSurface,
- fontSize: 14.0,
- ),
- );
return Wrap(
spacing: 4.0,
@@ -186,13 +181,14 @@ class _VocabChipsState extends State<_VocabChips> with TokenRenderingMixin {
color: color,
borderRadius: BorderRadius.circular(20),
),
- child: Text(
- v.lemma,
- style: renderer.style(
- underlineColor: Theme.of(context)
- .colorScheme
- .primary
- .withAlpha(200),
+ child: UnderlineText(
+ text: v.lemma,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.onSurface,
+ fontSize: 14.0,
+ ),
+ underlineColor: TokenRenderingUtil.underlineColor(
+ Theme.of(context).colorScheme.primary.withAlpha(200),
isNew: isNew,
selected: _selectedVocab == v,
hovered: hovered,
diff --git a/lib/pangea/toolbar/reading_assistance/stt_transcript_tokens.dart b/lib/pangea/toolbar/reading_assistance/stt_transcript_tokens.dart
index 36be582af..2a25706b9 100644
--- a/lib/pangea/toolbar/reading_assistance/stt_transcript_tokens.dart
+++ b/lib/pangea/toolbar/reading_assistance/stt_transcript_tokens.dart
@@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_response_model.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/token_rendering_util.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/tokens_util.dart';
+import 'package:fluffychat/pangea/toolbar/reading_assistance/underline_text_widget.dart';
import 'package:fluffychat/widgets/hover_builder.dart';
class SttTranscriptTokens extends StatelessWidget {
@@ -38,10 +39,6 @@ class SttTranscriptTokens extends StatelessWidget {
}
final messageCharacters = model.transcript.text.characters;
- final renderer = TokenRenderingUtil(
- existingStyle: (style ?? DefaultTextStyle.of(context).style),
- );
-
final newTokens = TokensUtil.getNewTokens(
eventId,
tokens,
@@ -76,18 +73,14 @@ class SttTranscriptTokens extends StatelessWidget {
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: onClick != null ? () => onClick?.call(token) : null,
- child: RichText(
- text: TextSpan(
- text: text,
- style: renderer.style(
- underlineColor: Theme.of(context)
- .colorScheme
- .primary
- .withAlpha(200),
- hovered: hovered,
- selected: selected,
- isNew: newTokens.any((t) => t == token.text),
- ),
+ child: UnderlineText(
+ text: text,
+ style: style ?? DefaultTextStyle.of(context).style,
+ underlineColor: TokenRenderingUtil.underlineColor(
+ Theme.of(context).colorScheme.primary.withAlpha(200),
+ selected: selected,
+ hovered: hovered,
+ isNew: newTokens.any((t) => t == token.text),
),
),
),
diff --git a/lib/pangea/toolbar/reading_assistance/token_rendering_util.dart b/lib/pangea/toolbar/reading_assistance/token_rendering_util.dart
index 2b51b0ff6..6891fe345 100644
--- a/lib/pangea/toolbar/reading_assistance/token_rendering_util.dart
+++ b/lib/pangea/toolbar/reading_assistance/token_rendering_util.dart
@@ -3,42 +3,16 @@ import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
class TokenRenderingUtil {
- final TextStyle existingStyle;
-
- TokenRenderingUtil({
- required this.existingStyle,
- });
+ TokenRenderingUtil();
static final Map _tokensWidthCache = {};
- TextStyle style({
- required Color underlineColor,
- double? fontSize,
- bool selected = false,
- bool highlighted = false,
- bool isNew = false,
- bool practiceMode = false,
- bool hovered = false,
- }) =>
- existingStyle.copyWith(
- fontSize: fontSize,
- decoration: TextDecoration.underline,
- decorationThickness: 4,
- decorationColor: _underlineColor(
- underlineColor,
- selected: selected,
- highlighted: highlighted,
- isNew: isNew,
- practiceMode: practiceMode,
- hovered: hovered,
- ),
- );
-
double tokenTextWidthForContainer(
String text,
- Color underlineColor, {
- double? fontSize,
- }) {
+ Color underlineColor,
+ TextStyle style,
+ double fontSize,
+ ) {
final tokenSizeKey = "$text-$fontSize";
if (_tokensWidthCache.containsKey(tokenSizeKey)) {
return _tokensWidthCache[tokenSizeKey]!;
@@ -47,10 +21,7 @@ class TokenRenderingUtil {
final textPainter = TextPainter(
text: TextSpan(
text: text,
- style: style(
- underlineColor: underlineColor,
- fontSize: fontSize,
- ),
+ style: style,
),
maxLines: 1,
textDirection: TextDirection.ltr,
@@ -62,7 +33,7 @@ class TokenRenderingUtil {
return width;
}
- Color _underlineColor(
+ static Color underlineColor(
Color underlineColor, {
bool selected = false,
bool highlighted = false,
diff --git a/lib/pangea/toolbar/reading_assistance/underline_text_widget.dart b/lib/pangea/toolbar/reading_assistance/underline_text_widget.dart
new file mode 100644
index 000000000..b64c4e410
--- /dev/null
+++ b/lib/pangea/toolbar/reading_assistance/underline_text_widget.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+
+import 'package:flutter_linkify/flutter_linkify.dart';
+
+import 'package:fluffychat/utils/url_launcher.dart';
+
+class UnderlineText extends StatelessWidget {
+ final String text;
+ final TextStyle style;
+ final TextStyle? linkStyle;
+ final TextDirection? textDirection;
+ final Color? underlineColor;
+
+ const UnderlineText({
+ super.key,
+ required this.text,
+ required this.style,
+ this.linkStyle,
+ this.textDirection,
+ this.underlineColor,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ alignment: Alignment.bottomLeft,
+ children: [
+ RichText(
+ textDirection: textDirection,
+ text: TextSpan(
+ children: [
+ LinkifySpan(
+ text: text,
+ style: style,
+ linkStyle: linkStyle,
+ onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
+ ),
+ ],
+ ),
+ ),
+ Positioned(
+ bottom: 2, // fixed distance from baseline
+ left: 0,
+ right: 0,
+ child: Container(
+ height: 3,
+ color: underlineColor ?? Colors.transparent,
+ ),
+ ),
+ ],
+ );
+ }
+}