Compare commits
1 commit
main
...
braid/cust
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec6efe2a88 |
2 changed files with 131 additions and 85 deletions
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)!),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue