diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index d8c7b9be8..ddef26757 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Element; import 'package:collection/collection.dart'; import 'package:flutter_highlighter/flutter_highlighter.dart'; @@ -19,12 +19,14 @@ class HtmlMessage extends StatelessWidget { final String html; final Room room; final Color textColor; + final bool isEmojiOnly; const HtmlMessage({ super.key, required this.html, required this.room, this.textColor = Colors.black, + this.isEmojiOnly = false, }); 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); @@ -91,83 +95,86 @@ 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), - ), - '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, + return MouseRegion( + cursor: SystemMouseCursors.text, + 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, + ), ); } @@ -268,8 +275,12 @@ class FontColorExtension extends HtmlExtension { class ImageExtension extends HtmlExtension { final double defaultDimension; + final bool isEmojiOnly; - const ImageExtension({this.defaultDimension = 64}); + const ImageExtension({ + this.defaultDimension = 64, + this.isEmojiOnly = false, + }); @override Set get supportedTags => {'img'}; @@ -281,9 +292,23 @@ class ImageExtension extends HtmlExtension { return TextSpan(text: context.attributes['alt']); } - final width = double.tryParse(context.attributes['width'] ?? ''); - final height = double.tryParse(context.attributes['height'] ?? ''); + double? width, 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( child: SizedBox( width: width ?? height ?? defaultDimension, @@ -344,6 +369,7 @@ class MatrixMathExtension extends HtmlExtension { final TextStyle? style; MatrixMathExtension({this.style}); + @override Set get supportedTags => {'div'}; @@ -377,6 +403,7 @@ class CodeExtension extends HtmlExtension { final double fontSize; CodeExtension({required this.fontSize}); + @override Set get supportedTags => {'code'}; @@ -414,6 +441,7 @@ class RoomPillExtension extends HtmlExtension { final BuildContext context; RoomPillExtension(this.context, this.room); + @override Set 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; +} diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 2ed0993a0..7765c7f53 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:emojis/emoji.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:matrix/matrix.dart'; @@ -104,6 +105,13 @@ class MessageContent extends StatelessWidget { Widget build(BuildContext context) { final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; 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) { case EventTypes.Message: case EventTypes.Encrypted: @@ -158,6 +166,7 @@ class MessageContent extends StatelessWidget { html: html, textColor: textColor, room: event.room, + isEmojiOnly: bigEmotes, ); } // 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( future: event.calcLocalizedBody( MatrixLocals(L10n.of(context)!),