Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
The one with the braid
ec6efe2a88 fix: properly scale emote-only messages with custom emotes
Signed-off-by: The one with the braid <info@braid.business>
2024-01-02 07:08:29 +01:00
2 changed files with 131 additions and 85 deletions

View file

@ -1,4 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide Element;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_highlighter/flutter_highlighter.dart'; import 'package:flutter_highlighter/flutter_highlighter.dart';
@ -19,12 +19,14 @@ class HtmlMessage extends StatelessWidget {
final String html; final String html;
final Room room; final Room room;
final Color textColor; final Color textColor;
final bool isEmojiOnly;
const HtmlMessage({ const HtmlMessage({
super.key, super.key,
required this.html, required this.html,
required this.room, required this.room,
this.textColor = Colors.black, this.textColor = Colors.black,
this.isEmojiOnly = false,
}); });
dom.Node _linkifyHtml(dom.Node element) { dom.Node _linkifyHtml(dom.Node element) {
@ -74,7 +76,9 @@ class HtmlMessage extends StatelessWidget {
'', '',
); );
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; double fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
if (isEmojiOnly) fontSize *= 3;
final linkColor = textColor.withAlpha(150); final linkColor = textColor.withAlpha(150);
@ -91,83 +95,86 @@ class HtmlMessage extends StatelessWidget {
final element = _linkifyHtml(HtmlParser.parseHTML(renderHtml)); final element = _linkifyHtml(HtmlParser.parseHTML(renderHtml));
// there is no need to pre-validate the html, as we validate it while rendering // there is no need to pre-validate the html, as we validate it while rendering
return Html.fromElement( return MouseRegion(
documentElement: element as dom.Element, cursor: SystemMouseCursors.text,
style: { child: Html.fromElement(
'*': Style( documentElement: element as dom.Element,
color: textColor, style: {
margin: Margins.all(0), '*': Style(
fontSize: FontSize(fontSize), color: textColor,
), margin: Margins.all(0),
'a': Style(color: linkColor, textDecorationColor: linkColor), fontSize: FontSize(fontSize),
'h1': Style( ),
fontSize: FontSize(fontSize * 2), 'a': Style(color: linkColor, textDecorationColor: linkColor),
lineHeight: LineHeight.number(1.5), 'h1': Style(
fontWeight: FontWeight.w600, fontSize: FontSize(fontSize * 2),
), lineHeight: LineHeight.number(1.5),
'h2': Style( fontWeight: FontWeight.w600,
fontSize: FontSize(fontSize * 1.75), ),
lineHeight: LineHeight.number(1.5), 'h2': Style(
fontWeight: FontWeight.w500, fontSize: FontSize(fontSize * 1.75),
), lineHeight: LineHeight.number(1.5),
'h3': Style( fontWeight: FontWeight.w500,
fontSize: FontSize(fontSize * 1.5), ),
lineHeight: LineHeight.number(1.5), 'h3': Style(
), fontSize: FontSize(fontSize * 1.5),
'h4': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize * 1.25), ),
lineHeight: LineHeight.number(1.5), 'h4': Style(
), fontSize: FontSize(fontSize * 1.25),
'h5': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize * 1.25), ),
lineHeight: LineHeight.number(1.5), 'h5': Style(
), fontSize: FontSize(fontSize * 1.25),
'h6': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize), ),
lineHeight: LineHeight.number(1.5), 'h6': Style(
), fontSize: FontSize(fontSize),
'blockquote': blockquoteStyle, lineHeight: LineHeight.number(1.5),
'tg-forward': blockquoteStyle, ),
'hr': Style( 'blockquote': blockquoteStyle,
border: Border.all(color: textColor, width: 0.5), 'tg-forward': blockquoteStyle,
), 'hr': Style(
'table': Style( border: Border.all(color: textColor, width: 0.5),
border: Border.all(color: textColor, width: 0.5), ),
), 'table': Style(
'tr': Style( border: Border.all(color: textColor, width: 0.5),
border: Border.all(color: textColor, width: 0.5), ),
), 'tr': Style(
'td': Style( border: Border.all(color: textColor, width: 0.5),
border: Border.all(color: textColor, width: 0.5), ),
padding: HtmlPaddings.all(2), 'td': Style(
), border: Border.all(color: textColor, width: 0.5),
'th': Style( padding: HtmlPaddings.all(2),
border: Border.all(color: textColor, width: 0.5), ),
), 'th': Style(
}, border: Border.all(color: textColor, width: 0.5),
extensions: [ ),
RoomPillExtension(context, room), },
CodeExtension(fontSize: fontSize), extensions: [
MatrixMathExtension( RoomPillExtension(context, room),
style: TextStyle(fontSize: fontSize, color: textColor), CodeExtension(fontSize: fontSize),
), MatrixMathExtension(
const TableHtmlExtension(), style: TextStyle(fontSize: fontSize, color: textColor),
SpoilerExtension(textColor: textColor), ),
const ImageExtension(), const TableHtmlExtension(),
FontColorExtension(), SpoilerExtension(textColor: textColor),
], const ImageExtension(),
onLinkTap: (url, _, element) => UrlLauncher( FontColorExtension(),
context, ],
url, onLinkTap: (url, _, element) => UrlLauncher(
element?.text, context,
).launchUrl(), url,
onlyRenderTheseTags: const { element?.text,
...allowedHtmlTags, ).launchUrl(),
// Needed to make it work properly onlyRenderTheseTags: const {
'body', ...allowedHtmlTags,
'html', // Needed to make it work properly
}, 'body',
shrinkWrap: true, 'html',
},
shrinkWrap: true,
),
); );
} }
@ -268,8 +275,12 @@ class FontColorExtension extends HtmlExtension {
class ImageExtension extends HtmlExtension { class ImageExtension extends HtmlExtension {
final double defaultDimension; final double defaultDimension;
final bool isEmojiOnly;
const ImageExtension({this.defaultDimension = 64}); const ImageExtension({
this.defaultDimension = 64,
this.isEmojiOnly = false,
});
@override @override
Set<String> get supportedTags => {'img'}; Set<String> get supportedTags => {'img'};
@ -281,9 +292,23 @@ class ImageExtension extends HtmlExtension {
return TextSpan(text: context.attributes['alt']); return TextSpan(text: context.attributes['alt']);
} }
final width = double.tryParse(context.attributes['width'] ?? ''); double? width, height;
final height = double.tryParse(context.attributes['height'] ?? '');
// in case it's an emoji only message or a custom emoji image,
// force the default font size
if (isEmojiOnly) {
width = height =
AppConfig.messageFontSize * AppConfig.fontSizeFactor * 3 * 1.2;
} else if (context.attributes.containsKey('data-mx-emoticon') ||
context.attributes.containsKey('data-mx-emoji')) {
// in case the image is a custom emote, get the surrounding font size
width = height = (tryGetParentFontSize(context) ??
FontSize(AppConfig.messageFontSize * AppConfig.fontSizeFactor))
.emValue;
} else {
width = double.tryParse(context.attributes['width'] ?? '');
height = double.tryParse(context.attributes['height'] ?? '');
}
return WidgetSpan( return WidgetSpan(
child: SizedBox( child: SizedBox(
width: width ?? height ?? defaultDimension, width: width ?? height ?? defaultDimension,
@ -344,6 +369,7 @@ class MatrixMathExtension extends HtmlExtension {
final TextStyle? style; final TextStyle? style;
MatrixMathExtension({this.style}); MatrixMathExtension({this.style});
@override @override
Set<String> get supportedTags => {'div'}; Set<String> get supportedTags => {'div'};
@ -377,6 +403,7 @@ class CodeExtension extends HtmlExtension {
final double fontSize; final double fontSize;
CodeExtension({required this.fontSize}); CodeExtension({required this.fontSize});
@override @override
Set<String> get supportedTags => {'code'}; Set<String> get supportedTags => {'code'};
@ -414,6 +441,7 @@ class RoomPillExtension extends HtmlExtension {
final BuildContext context; final BuildContext context;
RoomPillExtension(this.context, this.room); RoomPillExtension(this.context, this.room);
@override @override
Set<String> get supportedTags => {'a'}; Set<String> get supportedTags => {'a'};
@ -525,3 +553,15 @@ class MatrixPill extends StatelessWidget {
); );
} }
} }
FontSize? tryGetParentFontSize(ExtensionContext context) {
var currentElement = context.element;
while (currentElement?.parent != null) {
currentElement = currentElement?.parent;
final size = context.parser.style[(currentElement!.localName!)]?.fontSize;
if (size != null) {
return size;
}
}
return null;
}

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:emojis/emoji.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -104,6 +105,13 @@ class MessageContent extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
final buttonTextColor = textColor; final buttonTextColor = textColor;
final bigEmotes = (event.onlyEmotes ||
emojiRegex.allMatches(event.text).map((e) => e[0]).join() ==
event.text) &&
event.numberEmotes > 0 &&
event.numberEmotes <= 10;
switch (event.type) { switch (event.type) {
case EventTypes.Message: case EventTypes.Message:
case EventTypes.Encrypted: case EventTypes.Encrypted:
@ -158,6 +166,7 @@ class MessageContent extends StatelessWidget {
html: html, html: html,
textColor: textColor, textColor: textColor,
room: event.room, room: event.room,
isEmojiOnly: bigEmotes,
); );
} }
// else we fall through to the normal message rendering // else we fall through to the normal message rendering
@ -233,9 +242,6 @@ class MessageContent extends StatelessWidget {
}, },
); );
} }
final bigEmotes = event.onlyEmotes &&
event.numberEmotes > 0 &&
event.numberEmotes <= 10;
return FutureBuilder<String>( return FutureBuilder<String>(
future: event.calcLocalizedBody( future: event.calcLocalizedBody(
MatrixLocals(L10n.of(context)!), MatrixLocals(L10n.of(context)!),