fix: add custom token underline widget to make all text underlines equal height (#5170)
This commit is contained in:
parent
ebe22129bc
commit
bb73892c18
5 changed files with 109 additions and 110 deletions
|
|
@ -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<dom.Element>().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')
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<String, double> _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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue