dedupe replies

This commit is contained in:
Henry Hiles 2025-11-12 14:33:37 -05:00
commit 1d03b09775
No known key found for this signature in database
7 changed files with 86 additions and 41 deletions

View file

@ -43,9 +43,11 @@ class RoomChatController extends AsyncNotifier<ChatController> {
return controller.updateMessage(message, newMessage);
}
Future<void> send(String message) async {
await room.sendTextEvent(message);
}
Future<void> send(String message, {String? replyTo}) async =>
await room.sendTextEvent(
message,
inReplyTo: replyTo == null ? null : await room.getEventById(replyTo),
);
Future<chat.User> resolveUser(String id) async {
final user = await room.client.getUserProfile(id);

View file

@ -5,6 +5,7 @@ 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({
@ -60,11 +61,16 @@ extension ToMessage on Event {
);
}
final formatted = convert(
formattedText.isEmpty ? body : formattedText,
ignore: replyId == null ? null : ["mx-reply"],
);
final asText = Message.text(
metadata: metadata,
id: eventId,
authorId: senderId,
text: body,
text: formatted,
replyToMessageId: replyId,
deliveredAt: originServerTs,
);
@ -77,6 +83,7 @@ extension ToMessage on Event {
metadata: metadata,
id: eventId,
authorId: senderId,
text: formatted,
source: (await getAttachmentUri()).toString(),
replyToMessageId: replyId,
deliveredAt: originServerTs,
@ -85,7 +92,7 @@ extension ToMessage on Event {
metadata: metadata,
id: eventId,
authorId: senderId,
text: body,
text: formatted,
replyToMessageId: replyId,
source: (await getAttachmentUri()).toString(),
deliveredAt: originServerTs,

View file

@ -8,9 +8,7 @@ import "package:dynamic_system_colors/dynamic_system_colors.dart";
import "package:window_size/window_size.dart";
void main() async {
ScaledWidgetsFlutterBinding.ensureInitialized(
scaleFactor: (size) => size.width > 1080 ? 1.3 : 1,
);
ScaledWidgetsFlutterBinding.ensureInitialized(scaleFactor: (size) => 1);
await windowManager.ensureInitialized();
await windowManager.waitUntilReadyToShow(

View file

@ -4,6 +4,7 @@ import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_chat_ui/flutter_chat_ui.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_link_previewer/flutter_link_previewer.dart";
import "package:flyer_chat_file_message/flyer_chat_file_message.dart";
import "package:flyer_chat_image_message/flyer_chat_image_message.dart";
@ -21,8 +22,23 @@ class RoomChat extends HookConsumerWidget {
final bool isDesktop;
const RoomChat({required this.isDesktop, super.key});
void showContextMenu({
required BuildContext context,
required Offset globalPosition,
required VoidCallback onTap,
}) => showMenu(
context: context,
position: RelativeRect.fromRect(
Rect.fromPoints(globalPosition, globalPosition),
Offset.zero & (context.findRenderObject() as RenderBox).size,
),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
items: [PopupMenuItem(onTap: onTap, child: Text("Reply"))],
);
@override
Widget build(BuildContext context, WidgetRef ref) {
final replyToMessageId = useState<String?>(null);
final urlRegex = RegExp(r"https?://[^\s\]\(\)]+");
final theme = Theme.of(context);
return ref
@ -73,6 +89,28 @@ class RoomChat extends HookConsumerWidget {
onPrimary: theme.colorScheme.onPrimaryContainer,
),
),
onMessageSecondaryTap:
(
context,
message, {
required details,
required index,
}) => showContextMenu(
context: context,
globalPosition: details.globalPosition,
onTap: () => replyToMessageId.value = message.id,
),
onMessageLongPress:
(
context,
message, {
required details,
required index,
}) => showContextMenu(
context: context,
globalPosition: details.globalPosition,
onTap: () => replyToMessageId.value = message.id,
),
builders: Builders(
composerBuilder: (_) => Composer(
sendIconColor: theme.colorScheme.primary,
@ -101,19 +139,8 @@ class RoomChat extends HookConsumerWidget {
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => Column(
crossAxisAlignment: isSentByMe
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
spacing: 8,
children: [
SizedBox(height: 8),
FlyerChatTextMessage(
topWidget: TopWidget(
message,
headers: headers,
),
}) => FlyerChatTextMessage(
topWidget: TopWidget(message, headers: headers),
message: message.copyWith(
text: message.text.replaceAllMapped(
urlRegex,
@ -130,8 +157,6 @@ class RoomChat extends HookConsumerWidget {
sentLinksColor: Colors.blue,
receivedLinksColor: Colors.blue,
),
],
),
linkPreviewBuilder: (_, message, isSentByMe) =>
LinkPreview(
text:
@ -196,9 +221,12 @@ class RoomChat extends HookConsumerWidget {
index: index,
),
),
onMessageSend: ref
onMessageSend: (message) {
ref
.watch(controllerProvider.notifier)
.send,
.send(message, replyTo: replyToMessageId.value);
replyToMessageId.value = null;
},
resolveUser: ref
.watch(controllerProvider.notifier)
.resolveUser,

View file

@ -73,6 +73,7 @@ class TopWidget extends ConsumerWidget {
replyText,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.labelMedium,
maxLines: 1,
),
),
],

View file

@ -491,7 +491,7 @@ packages:
description:
path: "packages/flutter_chat_ui"
ref: HEAD
resolved-ref: c1ef794e78e56308872ec377c91645a483204a02
resolved-ref: f6718923519db812762ff27eb402f70076d8676c
url: "https://github.com/Henry-Hiles/flutter_chat_ui"
source: git
version: "2.9.1"
@ -694,6 +694,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.15.6"
html2md:
dependency: "direct main"
description:
name: html2md
sha256: "465cf8ffa1b510fe0e97941579bf5b22e2d575f2cecb500a9c0254efe33a8036"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
html_unescape:
dependency: transitive
description:

View file

@ -53,6 +53,7 @@ dependencies:
sqflite_common_ffi: ^2.3.6
color_hash: ^1.0.1
scaled_app: ^2.3.0
html2md: ^1.3.2
dev_dependencies:
build_runner: ^2.4.11