add html support

This commit is contained in:
Henry Hiles 2025-11-14 22:05:35 -05:00
commit 8d3c657ff6
No known key found for this signature in database
6 changed files with 58 additions and 55 deletions

View file

@ -1,12 +1,10 @@
import "package:flutter/material.dart";
import "package:flutter/widgets.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:matrix/matrix.dart";
import "package:nexus/models/full_room.dart";
import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/widgets/loading.dart";
import "package:html2md/html2md.dart";
extension BetterWhen<T> on AsyncValue<T> {
Widget betterWhen({
@ -39,6 +37,7 @@ extension ToMessage on Event {
? relationshipEventId
: null;
final metadata = {
"formatted": formattedText.isEmpty ? body : formattedText,
"eventType": type,
"displayName": senderFromMemoryOrFallback.displayName,
"txnId": transactionId,
@ -49,34 +48,34 @@ extension ToMessage on Event {
metadata: metadata,
id: eventId,
authorId: senderId,
text: "~~This message has been redacted.~~",
text: "<s>This message has been redacted.</s>",
deletedAt: redactedBecause?.originServerTs,
);
}
final formatted = convert(
formattedText.isEmpty ? body : formattedText,
ignore: replyId == null ? null : ["mx-reply"],
);
final asText = Message.text(
metadata: metadata,
id: eventId,
authorId: senderId,
text: formatted,
replyToMessageId: replyId,
deliveredAt: originServerTs,
);
final asText =
Message.text(
metadata: metadata,
id: eventId,
authorId: senderId,
text: body,
replyToMessageId: replyId,
deliveredAt: originServerTs,
)
as TextMessage;
if (mustBeText) return asText;
return switch (type) {
EventTypes.Encrypted => asText.copyWith(
text: "Unable to decrypt message.",
),
EventTypes.Message => switch (messageType) {
MessageTypes.Image => Message.image(
metadata: metadata,
id: eventId,
authorId: senderId,
text: formatted,
text: text,
source: (await getAttachmentUri()).toString(),
replyToMessageId: replyId,
deliveredAt: originServerTs,
@ -85,7 +84,7 @@ extension ToMessage on Event {
metadata: metadata,
id: eventId,
authorId: senderId,
text: formatted,
text: text,
replyToMessageId: replyId,
source: (await getAttachmentUri()).toString(),
deliveredAt: originServerTs,

View file

@ -6,16 +6,16 @@ class LaunchHelper {
final Ref ref;
LaunchHelper(this.ref);
Future<void> launchUrl(Uri url, {bool useWebview = false}) async {
Future<bool> launchUrl(Uri url, {bool useWebview = false}) async {
try {
await ul.launchUrl(
return await ul.launchUrl(
url,
mode: useWebview
? ul.LaunchMode.inAppBrowserView
: ul.LaunchMode.externalApplication,
);
} on PlatformException catch (_) {
// Ignore missing intent handler error
return false;
}
}

View file

@ -38,14 +38,15 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(room.title, overflow: TextOverflow.ellipsis),
Text(
room.roomData.topic,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
if (room.roomData.topic.isNotEmpty)
Text(
room.roomData.topic,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
actions: [

View file

@ -17,6 +17,7 @@ import "package:nexus/widgets/chat_box.dart";
import "package:nexus/widgets/member_list.dart";
import "package:nexus/widgets/room_appbar.dart";
import "package:nexus/widgets/top_widget.dart";
import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";
class RoomChat extends HookConsumerWidget {
final bool isDesktop;
@ -122,26 +123,24 @@ class RoomChat extends HookConsumerWidget {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatTextMessage(
customWidget: HtmlWidget(
message.metadata?["formatted"],
customWidgetBuilder: (element) =>
element.localName == "mx-reply"
? SizedBox.shrink()
: null,
onTapUrl: (url) => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(url)),
),
topWidget: TopWidget(
message,
headers: room.roomData.client.headers,
groupStatus: groupStatus,
),
message: message.copyWith(
text: message.text.replaceAllMapped(
urlRegex,
(match) =>
"[${match.group(0)}](${match.group(0)})",
),
),
message: message,
showTime: true,
index: index,
onLinkTap: (url, _) => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(url)),
linksDecoration: TextDecoration.underline,
sentLinksColor: Colors.blue,
receivedLinksColor: Colors.blue,
),
linkPreviewBuilder: (_, message, isSentByMe) =>
LinkPreview(