Skip to content

Commit

Permalink
feat: add edit possibility and fix timer draft discardal
Browse files Browse the repository at this point in the history
  • Loading branch information
henri2h committed Feb 9, 2025
1 parent f2a8451 commit 46d599a
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,23 @@ import '../../../../utils/matrix_widget.dart';
class AdvancedMessageComposer extends StatefulWidget {
final Room? room;
final String? userId;
final Event? reply;
final VoidCallback removeReply;
final Event? replyEvent;
// The original event that we want to edit
final Event? editEvent;
// The last modification of the event which we want to edit. This is used to get the latest edit text
final Event? editDisplayEvent;

final VoidCallback cancelEditAndReply;
final void Function(Room)? onRoomCreated;
final bool isMobile;

const AdvancedMessageComposer({
super.key,
required this.room,
required this.reply,
required this.removeReply,
required this.replyEvent,
required this.editEvent,
required this.editDisplayEvent,
required this.cancelEditAndReply,
required this.isMobile,
this.userId,
required this.onRoomCreated,
Expand Down Expand Up @@ -123,21 +130,32 @@ class AdvancedMessageComposerState extends State<AdvancedMessageComposer> {

bool get writingUsername => controller.text.contains("@");

Event? _cachedEditEvent;
@override
Widget build(BuildContext context) {
// See if a new edit event has been set. If yes, then use it to populate the messeage composer field
if (_cachedEditEvent != widget.editEvent) {
_cachedEditEvent = widget.editEvent;
if (widget.editEvent != null) {
controller.text = (widget.editDisplayEvent ?? widget.editEvent)!
.calcUnlocalizedBody(hideEdit: true, hideReply: true);
}
}

final room = widget.room;
Event? reply = widget.reply;
Event? reply = widget.replyEvent;
final client = Matrix.of(context).client;

final matrixComposerWidget = MessageComposer(
room: room,
userId: widget.userId,
onRoomCreated: widget.onRoomCreated,
inReplyTo: reply,
editEvent: widget.editEvent,
inputStream: inputStream.stream,
controller: controller,
onSend: () {
widget.removeReply();
widget.cancelEditAndReply();
setState(() {});
});

Expand Down Expand Up @@ -204,6 +222,23 @@ class AdvancedMessageComposerState extends State<AdvancedMessageComposer> {
});
}
}),
if (_cachedEditEvent != null)
Card(
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text("Edit message"),
)),
IconButton(
onPressed: () {
widget.cancelEditAndReply();
},
icon: Icon(Icons.close))
],
),
),
if (reply != null)
Padding(
padding: const EdgeInsets.all(4.0),
Expand Down Expand Up @@ -246,7 +281,7 @@ class AdvancedMessageComposerState extends State<AdvancedMessageComposer> {
IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
widget.removeReply();
widget.cancelEditAndReply();
setState(() {});
})
],
Expand Down
24 changes: 17 additions & 7 deletions lib/features/chat/widgets/message_composer/message_composer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class MessageComposer extends StatefulWidget {
final Room? room;
final String? userId;
final Event? inReplyTo;
final Event? editEvent;
final VoidCallback? onSend;
final String hintText;
// Allows showing a progress indicator when sending a message. This will prevent sending other messages when the previous message hasn't been sent.
Expand Down Expand Up @@ -52,6 +53,7 @@ class MessageComposer extends StatefulWidget {
this.overrideSending,
this.onRoomCreated,
this.onEdit,
this.editEvent,
this.loadSavedText = true,
this.inputStream,
this.controller,
Expand Down Expand Up @@ -219,13 +221,15 @@ class MessageComposerState extends State<MessageComposer> {
_isTyping = false;
});

await setMessageDraftImmediate("");
// Test if the input is supposed to be a command
if (!text.replaceAll(" ", "").startsWith("/")) {
// Don't send if it's a command
if (text != "") {
widget.onSend?.call();
if (widget.overrideSending == null) {
await room?.sendTextEvent(text, inReplyTo: onReplyTo);
await room?.sendTextEvent(text,
inReplyTo: onReplyTo, editEventId: widget.editEvent?.eventId);
} else {
await widget.overrideSending!(text);
}
Expand All @@ -244,8 +248,6 @@ class MessageComposerState extends State<MessageComposer> {
setState(() {
_isSending = false;
});

setMessageDraft("");
}

Future<void> _sendMessageOrCreate() async {
Expand Down Expand Up @@ -274,18 +276,26 @@ class MessageComposerState extends State<MessageComposer> {

Timer? editTimer;

void setMessageDraft(String text) {
Future<void> _setMessageDraft(String text) async {
final client = Matrix.of(context).client;
await client.setDraft(
message: text, roomId: room?.id ?? widget.userId ?? '');
}

Future<void> setMessageDraftImmediate(String text) async {
editTimer?.cancel();
await _setMessageDraft(text);
}

void setMessageDraftTimer(String text) {
editTimer?.cancel();
editTimer = Timer(const Duration(milliseconds: 600), () async {
await client.setDraft(
message: text, roomId: room?.id ?? widget.userId ?? '');
await _setMessageDraft(text);
});
}

void onEdit(String text) {
setMessageDraft(text);
setMessageDraftTimer(text);

widget.onEdit?.call(text);
}
Expand Down
87 changes: 51 additions & 36 deletions lib/features/chat/widgets/room/room_event_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:matrix/matrix.dart';
import 'package:pasteboard/pasteboard.dart';
import 'package:piaf/utils/date_time_extension.dart';

import '../../../../partials/popup_route_wrapper.dart';
Expand Down Expand Up @@ -41,8 +42,9 @@ class RoomEventItem extends StatelessWidget {
required this.t,
required this.filteredEvents,
required this.i,
required this.onReplyEventPressed,
required this.onJumpToReplyCallback,
required this.onReply,
this.onEdit,
this.eventsToAnimateStream,
this.fullyReadEventId,
required this.isDirectChat});
Expand All @@ -55,8 +57,10 @@ class RoomEventItem extends StatelessWidget {
final bool displayTime;
final bool displayPadding;
final int i;
final void Function(Event) onReplyEventPressed;
final void Function(Event) onJumpToReplyCallback;
final void Function(Event) onReply;
final void Function(Event)? onEdit;

// To display an annimation when this event is selected
final Stream<String>? eventsToAnimateStream;
final String? fullyReadEventId;
Expand Down Expand Up @@ -168,12 +172,17 @@ class RoomEventItem extends StatelessWidget {
anchorKeyContext: context,
useAnimation: false,
offset: offset,
maxHeight: 150,
maxHeight: 350,
builder: (rect) {
return ReactionBox(rect, event: event);
return ReactionBox(
rect,
event: event,
onEdit: onEdit != null ? () => onEdit!(event) : null,
onReply: () => onReply(oldEvent),
);
}));
},
onReplyEventPressed: onReplyEventPressed,
onReplyEventPressed: onJumpToReplyCallback,
onReply: (_) => onReply(oldEvent)),

// Disable read receipts in large group as it's quit costly
Expand Down Expand Up @@ -219,9 +228,12 @@ class RoomEventItem extends StatelessWidget {
}

class ReactionBox extends StatefulWidget {
const ReactionBox(this.rect, {super.key, required this.event});
const ReactionBox(this.rect,
{super.key, required this.event, this.onEdit, this.onReply});
final Rect rect;
final Event event;
final void Function()? onEdit;
final void Function()? onReply;

@override
State<ReactionBox> createState() => _ReactionBoxState();
Expand Down Expand Up @@ -252,8 +264,37 @@ class _ReactionBoxState extends State<ReactionBox> {
height: 100,
selectedEmoji: _selectedEmoji,
selectedEdge: _emojiPickerEdge,
enableDelete:
widget.event.canRedact && !widget.event.redacted,
onReply: widget.onReply,
onCopy: () {
Pasteboard.writeText(widget.event.body);
},
onEdit: widget.event.senderId ==
widget.event.room.client.userID
? widget.onEdit
: null,
onDelete: widget.event.canRedact && !widget.event.redacted
? () async {
if (mounted) {
final result = await showTextInputDialog(
useRootNavigator: false,
context: context,
title: "Confirm removal",
message:
"Are you sure you wish to remove this event? This cannot be undone.",
okLabel: "Remove",
textFields: [
const DialogTextField(
hintText: "Reason (optional)",
initialText: "")
],
);
if (result?.isNotEmpty ?? false) {
await widget.event
.redactEvent(reason: result?.first);
}
}
}
: null,
//enableEdit: _selectedEvent?.canRedact ?? false,
//enableReply: true,
)))));
Expand Down Expand Up @@ -335,34 +376,8 @@ class _ReactionBoxState extends State<ReactionBox> {
}

if (_selectedEmoji != null) {
switch (_selectedEmoji) {
case "reply":
case "edit":
break;
case "delete":
if (mounted) {
final result = await showTextInputDialog(
useRootNavigator: false,
context: context,
title: "Confirm removal",
message:
"Are you sure you wish to remove this event? This cannot be undone.",
okLabel: "Remove",
textFields: [
const DialogTextField(
hintText: "Reason (optional)", initialText: "")
],
);
if (result?.isNotEmpty ?? false) {
await widget.event.redactEvent(reason: result?.first);
}
}

break;
default:
await widget.event.room
.sendReaction(widget.event.eventId, _selectedEmoji!);
}
await widget.event.room
.sendReaction(widget.event.eventId, _selectedEmoji!);
}
}
}
Expand Down
26 changes: 23 additions & 3 deletions lib/features/chat/widgets/room/room_timeline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:piaf/features/chat/widgets/message_composer/advanced_message_composer.dart';
import 'package:piaf/partials/matrix/matrix_image_avatar.dart';
import 'package:piaf/partials/minestrix_chat.dart';
import 'package:scrollview_observer/scrollview_observer.dart';

import '../../../../utils/matrix_widget.dart';
Expand Down Expand Up @@ -49,6 +50,8 @@ class RoomTimelineState extends State<RoomTimeline> {
StreamController<String> eventsToAnimate = StreamController.broadcast();

Event? composerReplyToEvent;
Event? composerEditEvent;
Event? composerEditDisplayEvent;
Room? room;
List<Event> filteredEvents = [];

Expand Down Expand Up @@ -217,7 +220,7 @@ class RoomTimelineState extends State<RoomTimeline> {
filteredEvents: filteredEvents,
t: widget.timeline,
i: index,
onReplyEventPressed: (event) async {
onJumpToReplyCallback: (event) async {
// Jump to index
final index = filteredEvents.indexOf(
event.getDisplayEvent(widget.timeline!));
Expand All @@ -239,8 +242,21 @@ class RoomTimelineState extends State<RoomTimeline> {
}
},
eventsToAnimateStream: eventsToAnimate.stream,
onEdit: (Event event) {
setState(() {
// Update the edit event and remove the reply reference if existing
composerReplyToEvent = null;
composerEditEvent = filteredEvents[
index]; // Get the original event
composerEditDisplayEvent =
event; // The last edit version of the event
});
},
onReply: (Event event) => setState(() {
// update the reply event reference and remove the edit event if existing
composerReplyToEvent = event;
composerEditDisplayEvent = null;
composerEditEvent = null;
}),
fullyReadEventId: initialFullyReadEventId,
);
Expand Down Expand Up @@ -299,9 +315,13 @@ class RoomTimelineState extends State<RoomTimeline> {
isMobile: widget.isMobile,
userId: widget.userId,
onRoomCreated: widget.onRoomCreated,
reply: composerReplyToEvent,
removeReply: () {
replyEvent: composerReplyToEvent,
editEvent: composerEditEvent,
editDisplayEvent: composerEditDisplayEvent,
cancelEditAndReply: () {
setState(() {
composerEditEvent = null;
composerEditDisplayEvent = null;
composerReplyToEvent = null;
});
}),
Expand Down
Loading

0 comments on commit 46d599a

Please sign in to comment.