Skip to content

Commit

Permalink
feat(neon_talk): Enable editing of chat messages
Browse files Browse the repository at this point in the history
Signed-off-by: provokateurin <kate@provokateurin.de>
  • Loading branch information
provokateurin committed Jul 17, 2024
1 parent 5aff881 commit 7ac8f48
Show file tree
Hide file tree
Showing 13 changed files with 437 additions and 101 deletions.
1 change: 1 addition & 0 deletions packages/neon/neon_talk/lib/l10n/en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"roomMessageReply": "Reply",
"roomMessageReaction": "Add reaction",
"roomMessageDelete": "Delete",
"roomMessageEdit": "Edit",
"roomMessageEdited": "edited",
"roomMessageLastEdited": "Last edited by {name} at {time}",
"@roomMessageLastEdited": {
Expand Down
6 changes: 6 additions & 0 deletions packages/neon/neon_talk/lib/l10n/localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ abstract class TalkLocalizations {
/// **'Delete'**
String get roomMessageDelete;

/// No description provided for @roomMessageEdit.
///
/// In en, this message translates to:
/// **'Edit'**
String get roomMessageEdit;

/// No description provided for @roomMessageEdited.
///
/// In en, this message translates to:
Expand Down
3 changes: 3 additions & 0 deletions packages/neon/neon_talk/lib/l10n/localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class TalkLocalizationsEn extends TalkLocalizations {
@override
String get roomMessageDelete => 'Delete';

@override
String get roomMessageEdit => 'Edit';

@override
String get roomMessageEdited => 'edited';

Expand Down
78 changes: 61 additions & 17 deletions packages/neon/neon_talk/lib/src/blocs/room.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@ abstract class TalkRoomBloc implements InteractiveBloc {
/// Sets a [chatMessage] as the message to [replyTo].
void setReplyChatMessage(spreed.$ChatMessageInterface chatMessage);

/// Sets a [chatMessage] as the message to [editing].
void setEditChatMessage(spreed.$ChatMessageInterface chatMessage);

/// Removes the current [replyTo] chat message.
void removeReplyChatMessage();

/// Removes the current [editing] chat message.
void removeEditChatMessage();

/// Deletes a chat messages.
void deleteMessage(spreed.$ChatMessageInterface chatMessage);

Expand All @@ -61,6 +67,9 @@ abstract class TalkRoomBloc implements InteractiveBloc {

/// Current chat message to reply to.
BehaviorSubject<spreed.$ChatMessageInterface?> get replyTo;

/// Current chat message that is edited.
BehaviorSubject<spreed.$ChatMessageInterface?> get editing;
}

class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {
Expand Down Expand Up @@ -182,6 +191,9 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {
@override
final replyTo = BehaviorSubject.seeded(null);

@override
final editing = BehaviorSubject.seeded(null);

@override
void dispose() {
pollLoop = false;
Expand All @@ -192,6 +204,7 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {
unawaited(lastCommonRead.close());
unawaited(reactions.close());
unawaited(replyTo.close());
unawaited(editing.close());
super.dispose();
}

Expand Down Expand Up @@ -234,29 +247,48 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {

@override
Future<void> sendMessage(String message) async {
final replyToId = replyTo.value?.id;
replyTo.add(null);

await wrapAction(
() async {
final response = await account.client.spreed.chat.sendMessage(
token: token,
$body: spreed.ChatSendMessageRequestApplicationJson(
(b) {
b.message = message;
if (replyToId != null) {
b.replyTo = replyToId;
}
},
),
);
late spreed.ChatMessageWithParent? m;
late String? lastCommonRead;

updateLastCommonRead(response.headers.xChatLastCommonRead);
final editingId = editing.value?.id;
if (editingId != null) {
editing.add(null);

final m = response.body.ocs.data;
final response = await account.client.spreed.chat.editMessage(
token: token,
messageId: editingId,
$body: spreed.ChatEditMessageRequestApplicationJson(
(b) => b.message = message,
),
);

m = response.body.ocs.data;
lastCommonRead = response.headers.xChatLastCommonRead;
} else {
final replyToId = replyTo.value?.id;
replyTo.add(null);

final response = await account.client.spreed.chat.sendMessage(
token: token,
$body: spreed.ChatSendMessageRequestApplicationJson(
(b) {
b.message = message;
if (replyToId != null) {
b.replyTo = replyToId;
}
},
),
);

m = response.body.ocs.data;
lastCommonRead = response.headers.xChatLastCommonRead;
}

updateLastCommonRead(lastCommonRead);
if (m != null) {
updateLastKnownMessageId(m.id);

prependMessages([m]);
}
},
Expand Down Expand Up @@ -330,6 +362,7 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {

@override
void setReplyChatMessage(spreed.$ChatMessageInterface chatMessage) {
editing.add(null);
replyTo.add(chatMessage);
}

Expand All @@ -338,6 +371,17 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {
replyTo.add(null);
}

@override
void setEditChatMessage(spreed.$ChatMessageInterface chatMessage) {
replyTo.add(null);
editing.add(chatMessage);
}

@override
void removeEditChatMessage() {
editing.add(null);
}

@override
Future<void> deleteMessage(spreed.$ChatMessageInterface chatMessage) async {
await wrapAction(
Expand Down
12 changes: 12 additions & 0 deletions packages/neon/neon_talk/lib/src/widgets/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,18 @@ class _TalkCommentMessageState extends State<TalkCommentMessage> {
NeonProvider.of<TalkRoomBloc>(context).setReplyChatMessage(chatMessage);
},
),
if (chatMessage.messageType != spreed.MessageType.commentDeleted && chatMessage.actorId == room.actorId)
MenuItemButton(
leadingIcon: const Icon(Icons.edit),
child: Text(TalkLocalizations.of(context).roomMessageEdit),
onPressed: () {
setState(() {
menuOpen = false;
});

NeonProvider.of<TalkRoomBloc>(context).setEditChatMessage(chatMessage);
},
),
if (chatMessage.messageType != spreed.MessageType.commentDeleted && chatMessage.actorId == room.actorId)
MenuItemButton(
leadingIcon: const Icon(Icons.delete_forever),
Expand Down
96 changes: 73 additions & 23 deletions packages/neon/neon_talk/lib/src/widgets/message_input.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
Expand Down Expand Up @@ -52,22 +54,37 @@ class _TalkMessageInputState extends State<TalkMessageInput> {
},
);
late TalkRoomBloc bloc;
late StreamSubscription<spreed.$ChatMessageInterface?> replyToSubscription;
late StreamSubscription<spreed.$ChatMessageInterface?> editingSubscription;

@override
void initState() {
super.initState();

bloc = NeonProvider.of<TalkRoomBloc>(context);

bloc.replyTo.listen((replyTo) {
replyToSubscription = bloc.replyTo.listen((replyTo) {
if (replyTo != null) {
focusNode.requestFocus();
}
});
editingSubscription = bloc.editing.listen((editing) {
if (editing != null) {
controller
..text = editing.message
..selection = TextSelection(
baseOffset: editing.message.length,
extentOffset: editing.message.length,
);
focusNode.requestFocus();
}
});
}

@override
void dispose() {
unawaited(replyToSubscription.cancel());
unawaited(editingSubscription.cancel());
controller.dispose();
focusNode.dispose();
super.dispose();
Expand Down Expand Up @@ -117,28 +134,31 @@ class _TalkMessageInputState extends State<TalkMessageInput> {
return const SizedBox();
}

return Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Row(
children: [
const SizedBox.square(
dimension: 40,
),
Expanded(
child: TalkParentMessage(
room: widget.room,
parentChatMessage: replyToSnapshot.requireData!,
lastCommonRead: null,
),
),
IconButton(
onPressed: () {
bloc.removeReplyChatMessage();
},
icon: const Icon(Icons.close),
),
],
),
return buildContextMessage(
chatMessage: replyToSnapshot.requireData!,
onDismiss: bloc.removeReplyChatMessage,
);
},
);

final editing = StreamBuilder(
stream: bloc.editing,
builder: (context, editingSnapshot) {
if (!editingSnapshot.hasData) {
return const SizedBox();
}

return buildContextMessage(
chatMessage: editingSnapshot.requireData!,
onDismiss: () {
bloc.removeEditChatMessage();
controller
..text = ''
..selection = const TextSelection(
baseOffset: 0,
extentOffset: 0,
);
},
);
},
);
Expand Down Expand Up @@ -230,6 +250,7 @@ class _TalkMessageInputState extends State<TalkMessageInput> {
mainAxisSize: MainAxisSize.min,
children: [
replyTo,
editing,
inputField,
],
);
Expand Down Expand Up @@ -258,6 +279,35 @@ class _TalkMessageInputState extends State<TalkMessageInput> {
leading: icon,
);
}

Widget buildContextMessage({
required spreed.$ChatMessageInterface chatMessage,
required VoidCallback onDismiss,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Row(
children: [
const SizedBox.square(
dimension: 40,
),
Expanded(
child: TalkParentMessage(
room: widget.room,
parentChatMessage: chatMessage,
lastCommonRead: null,
),
),
IconButton(
onPressed: () {
onDismiss();
},
icon: const Icon(Icons.close),
),
],
),
);
}
}

class _Suggestion {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading

0 comments on commit 7ac8f48

Please sign in to comment.