fix newlines, wip edits

This commit is contained in:
Henry Hiles 2025-12-27 23:42:30 -05:00
commit 9663995114
No known key found for this signature in database
6 changed files with 79 additions and 33 deletions

View file

@ -8,6 +8,7 @@ import "package:nexus/controllers/events_controller.dart";
import "package:nexus/helpers/extensions/event_to_message.dart";
import "package:nexus/helpers/extensions/list_to_messages.dart";
import "package:fluttertagger/fluttertagger.dart" as tagger;
import "package:nexus/models/relation_type.dart";
class RoomChatController extends AsyncNotifier<ChatController> {
final Room room;
@ -37,7 +38,10 @@ class RoomChatController extends AsyncNotifier<ChatController> {
(element) => element.id == event.relationshipEventId,
);
if (oldMessage == null || message == null) return;
return await updateMessage(oldMessage, message);
return await updateMessage(
oldMessage,
message.copyWith(id: oldMessage.id),
);
}
if (message != null) {
return await insertMessage(message);
@ -97,7 +101,8 @@ class RoomChatController extends AsyncNotifier<ChatController> {
Future<void> send(
String message, {
required Iterable<tagger.Tag> tags,
Message? replyTo,
required RelationType relationType,
Message? relation,
}) async {
var taggedMessage = message;
@ -113,7 +118,10 @@ class RoomChatController extends AsyncNotifier<ChatController> {
await room.sendTextEvent(
taggedMessage,
inReplyTo: replyTo == null ? null : await room.getEventById(replyTo.id),
editEventId: relationType == RelationType.edit ? relation?.id : null,
inReplyTo: (relationType == RelationType.reply && relation != null)
? await room.getEventById(relation.id)
: null,
);
}

View file

@ -25,8 +25,10 @@ extension EventToMessage on Event {
newContent?["formatted_body"] ??
newContent?["body"] ??
event.content["formatted_body"] ??
event.content["body"],
event.content["body"] ??
"",
"reply": await replyEvent?.toMessage(mustBeText: true),
"body": newContent?["body"] ?? event.content["body"],
"eventType": event.type,
"avatarUrl": sender.avatarUrl.toString(),
"displayName": sender.displayName ?? sender.id,
@ -69,7 +71,7 @@ extension EventToMessage on Event {
return switch (type) {
EventTypes.Encrypted => asText.copyWith(
text: "Unable to decrypt message.",
metadata: {"formatted": "Unable to decrypt message.", ...metadata},
metadata: {...metadata, "formatted": "Unable to decrypt message."},
),
(EventTypes.Sticker || EventTypes.Message) => switch (messageType) {
(MessageTypes.Sticker || MessageTypes.Image) => Message.image(

View file

@ -0,0 +1 @@
enum RelationType { edit, reply }

View file

@ -7,15 +7,18 @@ import "package:fluttertagger/fluttertagger.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:matrix/matrix.dart";
import "package:nexus/controllers/room_chat_controller.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/chat_page/mention_overlay.dart";
import "package:nexus/widgets/chat_page/reply_preview.dart";
import "package:nexus/widgets/chat_page/relation_preview.dart";
class ChatBox extends HookConsumerWidget {
final Message? replyToMessage;
final Message? relatedMessage;
final RelationType relationType;
final VoidCallback onDismiss;
final Room room;
const ChatBox({
required this.replyToMessage,
required this.relatedMessage,
required this.relationType,
required this.onDismiss,
required this.room,
super.key,
@ -28,14 +31,25 @@ class ChatBox extends HookConsumerWidget {
final triggerCharacter = useState("");
final query = useState("");
if (relationType == RelationType.edit &&
relatedMessage is TextMessage &&
controller.value.text.isEmpty) {
final text = (relatedMessage as TextMessage).text;
controller.value.text = relatedMessage?.replyToMessageId == null
? text
: text.split("\n\n").sublist(1).join("\n\n");
}
void send() {
ref
.watch(RoomChatController.provider(room).notifier)
.send(
controller.value.formattedText,
replyTo: replyToMessage,
relation: relatedMessage,
relationType: relationType,
tags: controller.value.tags,
);
onDismiss();
controller.value.text = "";
}
@ -71,8 +85,9 @@ class ChatBox extends HookConsumerWidget {
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Column(
children: [
ReplyPreview(
replyToMessage: replyToMessage,
RelationPreview(
relatedMessage: relatedMessage,
relationType: relationType,
onDismiss: onDismiss,
room: room,
),

View file

@ -4,14 +4,17 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:matrix/matrix.dart";
import "package:nexus/controllers/avatar_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
class ReplyPreview extends ConsumerWidget {
final Message? replyToMessage;
class RelationPreview extends ConsumerWidget {
final Message? relatedMessage;
final RelationType relationType;
final VoidCallback onDismiss;
final Room room;
const ReplyPreview({
required this.replyToMessage,
const RelationPreview({
required this.relatedMessage,
required this.relationType,
required this.onDismiss,
required this.room,
super.key,
@ -19,9 +22,9 @@ class ReplyPreview extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
if (relatedMessage == null) return SizedBox.shrink();
final theme = Theme.of(context);
if (replyToMessage == null) return SizedBox.shrink();
return Container(
color: theme.colorScheme.surfaceContainerHigh,
padding: EdgeInsets.symmetric(horizontal: 8),
@ -29,34 +32,40 @@ class ReplyPreview extends ConsumerWidget {
spacing: 8,
children: [
SizedBox(width: 4),
if (relationType == RelationType.edit)
Text(
"Editing message:",
style: TextStyle(fontWeight: FontWeight.bold),
),
AvatarOrHash(
ref
.watch(
AvatarController.provider(
replyToMessage!.metadata!["avatarUrl"],
relatedMessage!.metadata!["avatarUrl"],
),
)
.whenOrNull(data: (data) => data),
replyToMessage!.metadata!["displayName"].toString(),
relatedMessage!.metadata!["displayName"].toString(),
headers: room.client.headers,
height: 16,
),
Text(
replyToMessage!.metadata?["displayName"] ??
replyToMessage!.authorId,
relatedMessage!.metadata?["displayName"] ??
relatedMessage!.authorId,
style: theme.textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
Expanded(
child: (replyToMessage is TextMessage)
? Text(
(replyToMessage as TextMessage).text,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelMedium,
maxLines: 1,
)
: SizedBox(),
child: Text(
(relatedMessage is TextMessage)
? (relatedMessage as TextMessage).text
: relatedMessage?.metadata?["body"] ??
relatedMessage?.metadata?["eventType"],
overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelMedium,
maxLines: 1,
),
),
IconButton(
onPressed: onDismiss,

View file

@ -13,6 +13,7 @@ import "package:nexus/controllers/room_chat_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/show_context_menu.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/chat_page/chat_box.dart";
import "package:nexus/widgets/chat_page/html/html.dart";
import "package:nexus/widgets/chat_page/member_list.dart";
@ -34,6 +35,7 @@ class RoomChat extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final replyToMessage = useState<Message?>(null);
final memberListOpened = useState<bool>(showMembersByDefault);
final relationType = useState(RelationType.reply);
final theme = Theme.of(context);
final danger = theme.colorScheme.error;
@ -56,7 +58,10 @@ class RoomChat extends HookConsumerWidget {
List<PopupMenuEntry> getMessageOptions(Message message) => [
PopupMenuItem(
onTap: () => replyToMessage.value = message,
onTap: () {
replyToMessage.value = message;
relationType.value = RelationType.reply;
},
child: ListTile(
leading: Icon(Icons.reply),
title: Text("Reply"),
@ -64,7 +69,10 @@ class RoomChat extends HookConsumerWidget {
),
if (message.authorId == room.roomData.client.userID)
PopupMenuItem(
onTap: () {},
onTap: () {
replyToMessage.value = message;
relationType.value = RelationType.edit;
},
child: ListTile(
leading: Icon(Icons.edit),
title: Text("Edit"),
@ -217,7 +225,8 @@ class RoomChat extends HookConsumerWidget {
bottomPadding: 72,
),
composerBuilder: (_) => ChatBox(
replyToMessage: replyToMessage.value,
relationType: relationType.value,
relatedMessage: replyToMessage.value,
onDismiss: () =>
replyToMessage.value = null,
room: room.roomData,
@ -231,7 +240,8 @@ class RoomChat extends HookConsumerWidget {
MessageGroupStatus? groupStatus,
}) => FlyerChatTextMessage(
customWidget: Html(
message.metadata?["formatted"]
(message.metadata?["formatted"]
as String)
.replaceAllMapped(
RegExp(
regexLink,
@ -239,7 +249,8 @@ class RoomChat extends HookConsumerWidget {
),
(m) =>
"<a href=\"${m.group(0)!}\">${m.group(0)!}</a>",
) +
)
.replaceAll("\n", "<br/>") +
((message.editedAt != null)
? "<sub edited>(edited)</sub>"
: ""),