diff --git a/lib/controllers/attachment.dart b/lib/controllers/attachment.dart index 2cabbae..1bcc811 100644 --- a/lib/controllers/attachment.dart +++ b/lib/controllers/attachment.dart @@ -29,6 +29,8 @@ class AttachmentController(final String roomId) state = (filename, content); } + void set((String, MessageContent?)? attachment) => state = attachment; + static final provider = NotifierProvider.family .autoDispose( AttachmentController.new, diff --git a/lib/controllers/room_chat.dart b/lib/controllers/room_chat.dart index 23cfb8f..8b2d64d 100644 --- a/lib/controllers/room_chat.dart +++ b/lib/controllers/room_chat.dart @@ -8,6 +8,7 @@ import "package:fluttertagger/fluttertagger.dart"; import "package:nexus/controllers/attachment.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/rooms.dart"; +import "package:nexus/helpers/extensions/is_media.dart"; import "package:nexus/helpers/extensions/send_failed.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/image_pack.dart"; @@ -231,22 +232,24 @@ class RoomChatController(final ChatParams info) required RelationType relationType, int? relatedEventRowId, }) async { - Content? baseContent; + final events = ref.read(RoomsController.provider)[info.roomId]?.events; final relatedEvent = relatedEventRowId == null ? null - : ref.read( - RoomsController.provider.select( - (rooms) => rooms[info.roomId]?.events[relatedEventRowId], - ), - ); + : events?[relatedEventRowId]; - if (relationType == .edit && relatedEventRowId != null) { - baseContent = relatedEvent?.content; - } else { - final provider = AttachmentController.provider(info.roomId); - baseContent = ref.read(provider)?.$2; - ref.invalidate(provider); - } + final attachmentProvider = AttachmentController.provider(info.roomId); + final attachment = ref.read(attachmentProvider)?.$2; + ref.invalidate(attachmentProvider); + + // Edits of media use the attachment, which starts as the current media + final latestContent = relatedEvent == null + ? null + : (events?[relatedEvent.lastEditRowId] ?? relatedEvent).content; + final baseContent = + attachment ?? + (relationType == .edit && latestContent?.isMedia == false + ? latestContent + : null); // Replaces each formatted tag, like "@id#name#", with its ID final taggedMessage = tags.fold( diff --git a/lib/helpers/extensions/is_media.dart b/lib/helpers/extensions/is_media.dart new file mode 100644 index 0000000..dceea43 --- /dev/null +++ b/lib/helpers/extensions/is_media.dart @@ -0,0 +1,12 @@ +import "package:nexus/models/content/content.dart"; +import "package:nexus/models/content/message.dart"; + +extension IsMedia on Content { + bool get isMedia => switch (this) { + ImageMessageContent() || + VideoMessageContent() || + AudioMessageContent() || + FileMessageContent() => true, + _ => false, + }; +} diff --git a/lib/widgets/composer/composer.dart b/lib/widgets/composer/composer.dart index af60155..507f602 100644 --- a/lib/widgets/composer/composer.dart +++ b/lib/widgets/composer/composer.dart @@ -3,6 +3,8 @@ import "dart:io"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:nexus/controllers/rooms.dart"; import "package:nexus/widgets/emoji_picker.dart"; +import "package:nexus/helpers/extensions/get_filename.dart"; +import "package:nexus/helpers/extensions/is_media.dart"; import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; import "package:nexus/widgets/adaptive_menu_button.dart"; import "package:material_ui/material_ui.dart"; @@ -111,6 +113,8 @@ class const Composer( ); final textBefore = useState(null); + // Wrapped, since no attachment is a draft too + final attachmentBefore = useState<((String, MessageContent?)?,)?>(null); /// The sticker to send, which replaces the text field until it is sent or /// removed, as stickers can't have a caption @@ -132,16 +136,24 @@ class const Composer( final isEditing = relationType == .edit && relatedEvent != null; useEffect(() { + final attachmentProvider = AttachmentController.provider(roomId); + if (relationType == .edit && relatedEvent != null) { textBefore.value ??= controller.text; + attachmentBefore.value ??= (ref.read(attachmentProvider),); sticker.value = null; poll.value = null; location.value = null; + final content = relatedEvent.content; final text = relatedEvent.localContent?.editSource ?? - switch (relatedEvent.content) { - TextMessageContent(:final body) => body, + switch (content) { + TextMessageContent(:final body) || + ImageMessageContent(:final body) || + VideoMessageContent(:final body) || + AudioMessageContent(:final body) || + FileMessageContent(:final body) => body, _ => "", }; @@ -149,12 +161,34 @@ class const Composer( text: text, selection: .collapsed(offset: text.length), ); - } else if (textBefore.value case final draft?) { - controller.value = .new( - text: draft, - selection: .collapsed(offset: draft.length), + + Future.microtask( + () => ref + .read(attachmentProvider.notifier) + .set( + content is MessageContent && content.isMedia + ? ( + content.filename ?? content.body ?? "Attachment", + content, + ) + : null, + ), ); - textBefore.value = null; + } else { + if (textBefore.value case final draft?) { + controller.value = .new( + text: draft, + selection: .collapsed(offset: draft.length), + ); + textBefore.value = null; + } + + if (attachmentBefore.value case (final draft,)) { + Future.microtask( + () => ref.read(attachmentProvider.notifier).set(draft), + ); + attachmentBefore.value = null; + } } return null;