diff --git a/lib/components/html_message.dart b/lib/components/html_message.dart new file mode 100644 index 0000000..a0b3092 --- /dev/null +++ b/lib/components/html_message.dart @@ -0,0 +1,41 @@ +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:flutter_matrix_html/flutter_html.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'matrix.dart'; + +class HtmlMessage extends StatelessWidget { + final String html; + final Color textColor; + final int maxLines; + + const HtmlMessage({this.html, this.textColor, this.maxLines}); + + @override + Widget build(BuildContext context) { + // there is no need to pre-validate the html, as we validate it while rendering + + return Html( + data: html, + defaultTextStyle: TextStyle(color: textColor), + shrinkToFit: true, + maxLines: maxLines, + onLinkTap: (String url) { + if (url == null || url.isEmpty) { + return; + } + launch(url); + }, + getMxcUrl: (String mxc, double width, double height) { + final ratio = MediaQuery.of(context).devicePixelRatio; + return Uri.parse(mxc)?.getThumbnail( + Matrix.of(context).client, + width: (width ?? 800) * ratio, + height: (height ?? 800) * ratio, + method: ThumbnailMethod.scale, + ); + }, + ); + } +} diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index 4c3c49d..e032daa 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -55,6 +55,7 @@ class MatrixState extends State { String activeRoomId; File wallpaper; + bool renderHtml = false; String jitsiInstance = 'https://meet.jit.si/'; @@ -189,6 +190,9 @@ class MatrixState extends State { wallpaper = file; } }); + client.storeAPI.getItem("chat.fluffy.renderHtml").then((final render) async { + renderHtml = render == "1"; + }); } super.initState(); } diff --git a/lib/components/message_content.dart b/lib/components/message_content.dart index 12319a1..cf60e73 100644 --- a/lib/components/message_content.dart +++ b/lib/components/message_content.dart @@ -8,6 +8,7 @@ import 'package:link_text/link_text.dart'; import 'package:url_launcher/url_launcher.dart'; import 'matrix.dart'; import 'message_download_content.dart'; +import 'html_message.dart'; class MessageContent extends StatelessWidget { final Event event; @@ -36,13 +37,30 @@ class MessageContent extends StatelessWidget { case MessageTypes.Video: case MessageTypes.File: return MessageDownloadContent(event, textColor); - case MessageTypes.BadEncrypted: case MessageTypes.Text: + case MessageTypes.Notice: + case MessageTypes.Emote: + if ( + Matrix.of(context).renderHtml && !event.redacted && + event.content['format'] == 'org.matrix.custom.html' && + event.content['formatted_body'] is String + ) { + String html = event.content['formatted_body']; + if (event.messageType == MessageTypes.Emote) { + html = "* $html"; + } + return HtmlMessage( + html: html, + textColor: textColor, + ); + } + // else we fall through to the normal message rendering + continue textmessage; + case MessageTypes.BadEncrypted: case MessageTypes.Reply: case MessageTypes.Location: case MessageTypes.None: - case MessageTypes.Notice: - case MessageTypes.Emote: + textmessage: default: if (event.content['msgtype'] == Matrix.callNamespace) { return RaisedButton( @@ -76,5 +94,6 @@ class MessageContent extends StatelessWidget { ), ); } + return Container(); // else flutter analyze complains } } diff --git a/lib/components/reply_content.dart b/lib/components/reply_content.dart index ed39323..1cc56aa 100644 --- a/lib/components/reply_content.dart +++ b/lib/components/reply_content.dart @@ -2,6 +2,9 @@ import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:flutter/material.dart'; +import 'html_message.dart'; +import 'matrix.dart'; + class ReplyContent extends StatelessWidget { final Event replyEvent; final bool lightText; @@ -11,6 +14,40 @@ class ReplyContent extends StatelessWidget { @override Widget build(BuildContext context) { + Widget replyBody; + if ( + replyEvent != null && Matrix.of(context).renderHtml && + [EventTypes.Message, EventTypes.Encrypted].contains(replyEvent.type) && + [MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote].contains(replyEvent.messageType) && + !replyEvent.redacted && replyEvent.content['format'] == 'org.matrix.custom.html' && replyEvent.content['formatted_body'] is String + ) { + String html = replyEvent.content['formatted_body']; + if (replyEvent.messageType == MessageTypes.Emote) { + html = "* $html"; + } + replyBody = HtmlMessage( + html: html, + textColor: lightText + ? Colors.white + : Theme.of(context).textTheme.bodyText2.color, + maxLines: 1, + ); + } else { + replyBody = Text( + replyEvent?.getLocalizedBody( + L10n.of(context), + withSenderNamePrefix: false, + hideReply: true, + ) ?? + "", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + color: lightText + ? Colors.white + : Theme.of(context).textTheme.bodyText2.color), + ); + } return Row( children: [ Container( @@ -34,20 +71,7 @@ class ReplyContent extends StatelessWidget { lightText ? Colors.white : Theme.of(context).primaryColor, ), ), - Text( - replyEvent?.getLocalizedBody( - L10n.of(context), - withSenderNamePrefix: false, - hideReply: true, - ) ?? - "", - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: TextStyle( - color: lightText - ? Colors.white - : Theme.of(context).textTheme.bodyText2.color), - ), + replyBody, ], ), ), diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 310f80f..91aa4b8 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -248,6 +248,11 @@ "type": "text", "placeholders": {} }, + "Chat": "Chat", + "@Chat": { + "type": "text", + "placeholders": {} + }, "Chat details": "Gruppeninfo", "@Chat details": { "type": "text", @@ -847,6 +852,11 @@ "type": "text", "placeholders": {} }, + "Render rich message content": "Zeige Nachrichtenformatierungen an", + "@Render rich message content": { + "type": "text", + "placeholders": {} + }, "redactedAnEvent": "{username} hat ein Event enternt", "@redactedAnEvent": { "type": "text", @@ -1317,4 +1327,4 @@ "type": "text", "placeholders": {} } -} \ No newline at end of file +} diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index d696772..f924921 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2020-05-09T12:56:54.540935", + "@@last_modified": "2020-05-09T13:02:58.452942", "About": "About", "@About": { "type": "text", @@ -248,6 +248,11 @@ "type": "text", "placeholders": {} }, + "Chat": "Chat", + "@Chat": { + "type": "text", + "placeholders": {} + }, "Chat details": "Chat details", "@Chat details": { "type": "text", @@ -852,6 +857,11 @@ "type": "text", "placeholders": {} }, + "Render rich message content": "Render rich message content", + "@Render rich message content": { + "type": "text", + "placeholders": {} + }, "Recording": "Recording", "@Recording": { "type": "text", @@ -1327,4 +1337,4 @@ "type": "text", "placeholders": {} } -} \ No newline at end of file +} diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index a64f0ce..172de07 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -206,6 +206,8 @@ class L10n extends MatrixLocalizations { String get channelCorruptedDecryptError => Intl.message("The encryption has been corrupted"); + String get chat => Intl.message('Chat'); + String get chatDetails => Intl.message('Chat details'); String get chooseAStrongPassword => Intl.message("Choose a strong password"); @@ -524,6 +526,8 @@ class L10n extends MatrixLocalizations { String get rejoin => Intl.message("Rejoin"); + String get renderRichContent => Intl.message("Render rich message content"); + String get recording => Intl.message("Recording"); String redactedAnEvent(String username) => Intl.message( diff --git a/lib/l10n/messages_de.dart b/lib/l10n/messages_de.dart index 952c2ed..7d38c7f 100644 --- a/lib/l10n/messages_de.dart +++ b/lib/l10n/messages_de.dart @@ -164,6 +164,7 @@ class MessageLookup extends MessageLookupByLibrary { "Change wallpaper" : MessageLookupByLibrary.simpleMessage("Hintergrund ändern"), "Change your style" : MessageLookupByLibrary.simpleMessage("Ändere Deinen Style"), "Changelog" : MessageLookupByLibrary.simpleMessage("Changelog"), + "Chat" : MessageLookupByLibrary.simpleMessage("Chat"), "Chat details" : MessageLookupByLibrary.simpleMessage("Gruppeninfo"), "Choose a strong password" : MessageLookupByLibrary.simpleMessage("Wähle ein sicheres Passwort"), "Choose a username" : MessageLookupByLibrary.simpleMessage("Wähle einen Benutzernamen"), @@ -261,6 +262,7 @@ class MessageLookup extends MessageLookupByLibrary { "Remove device" : MessageLookupByLibrary.simpleMessage("Gerät entfernen"), "Remove exile" : MessageLookupByLibrary.simpleMessage("Verbannung aufheben"), "Remove message" : MessageLookupByLibrary.simpleMessage("Nachricht entfernen"), + "Render rich message content" : MessageLookupByLibrary.simpleMessage("Zeige Nachrichtenformatierungen an"), "Reply" : MessageLookupByLibrary.simpleMessage("Antworten"), "Request permission" : MessageLookupByLibrary.simpleMessage("Berechtigung anfragen"), "Request to read older messages" : MessageLookupByLibrary.simpleMessage("Anfrage um ältere Nachrichten zu lesen"), diff --git a/lib/l10n/messages_messages.dart b/lib/l10n/messages_messages.dart index 15cf8f4..18c5b56 100644 --- a/lib/l10n/messages_messages.dart +++ b/lib/l10n/messages_messages.dart @@ -164,6 +164,7 @@ class MessageLookup extends MessageLookupByLibrary { "Change wallpaper" : MessageLookupByLibrary.simpleMessage("Change wallpaper"), "Change your style" : MessageLookupByLibrary.simpleMessage("Change your style"), "Changelog" : MessageLookupByLibrary.simpleMessage("Changelog"), + "Chat" : MessageLookupByLibrary.simpleMessage("Chat"), "Chat details" : MessageLookupByLibrary.simpleMessage("Chat details"), "Choose a strong password" : MessageLookupByLibrary.simpleMessage("Choose a strong password"), "Choose a username" : MessageLookupByLibrary.simpleMessage("Choose a username"), @@ -263,6 +264,7 @@ class MessageLookup extends MessageLookupByLibrary { "Remove device" : MessageLookupByLibrary.simpleMessage("Remove device"), "Remove exile" : MessageLookupByLibrary.simpleMessage("Remove exile"), "Remove message" : MessageLookupByLibrary.simpleMessage("Remove message"), + "Render rich message content" : MessageLookupByLibrary.simpleMessage("Render rich message content"), "Reply" : MessageLookupByLibrary.simpleMessage("Reply"), "Request permission" : MessageLookupByLibrary.simpleMessage("Request permission"), "Request to read older messages" : MessageLookupByLibrary.simpleMessage("Request to read older messages"), diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 9956984..69305d5 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -210,6 +210,28 @@ class _SettingsState extends State { ); }), Divider(thickness: 1), + ListTile( + title: Text( + L10n.of(context).chat, + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ListTile( + title: Text(L10n.of(context).renderRichContent), + trailing: Switch( + value: Matrix.of(context).renderHtml, + activeColor: Theme.of(context).primaryColor, + onChanged: (bool newValue) async { + Matrix.of(context).renderHtml = newValue; + await client.storeAPI.setItem("chat.fluffy.renderHtml", newValue ? "1" : "0"); + setState(() => null); + }, + ), + ), + Divider(thickness: 1), ListTile( title: Text( L10n.of(context).account, diff --git a/pubspec.lock b/pubspec.lock index 77f6bd0..e2de28e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -181,6 +181,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_matrix_html: + dependency: "direct main" + description: + name: flutter_matrix_html + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.5" flutter_secure_storage: dependency: "direct main" description: @@ -738,7 +745,7 @@ packages: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.5.2" + version: "0.5.3" webview_flutter: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9d83647..395866e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: open_file: ^3.0.1 mime_type: ^0.3.0 flutter_styled_toast: ^1.2.1 + flutter_matrix_html: ^0.0.5 intl: ^0.16.0 intl_translation: ^0.17.9