From 0c9081d79f1650fc3b602f5bea9553f32961a9d5 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Sat, 20 Jul 2024 10:23:12 +0200 Subject: [PATCH 1/5] feat(neon_framework): Export CapabilitiesBloc Signed-off-by: provokateurin --- packages/neon_framework/lib/blocs.dart | 1 + packages/neon_framework/lib/src/app.dart | 1 - packages/neon_framework/lib/src/blocs/accounts.dart | 1 - packages/neon_framework/lib/src/testing/mocks.dart | 1 - packages/neon_framework/lib/src/widgets/dialog.dart | 1 - packages/neon_framework/lib/src/widgets/drawer.dart | 1 - packages/neon_framework/test/drawer_test.dart | 1 - packages/neon_framework/test/router_test.dart | 1 - 8 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/neon_framework/lib/blocs.dart b/packages/neon_framework/lib/blocs.dart index f37187246da..375537c17ed 100644 --- a/packages/neon_framework/lib/blocs.dart +++ b/packages/neon_framework/lib/blocs.dart @@ -1,6 +1,7 @@ export 'package:neon_framework/src/bloc/bloc.dart'; export 'package:neon_framework/src/bloc/result.dart'; export 'package:neon_framework/src/blocs/apps.dart'; +export 'package:neon_framework/src/blocs/capabilities.dart'; export 'package:neon_framework/src/blocs/references.dart'; export 'package:neon_framework/src/blocs/timer.dart'; export 'package:neon_framework/src/blocs/user_details.dart'; diff --git a/packages/neon_framework/lib/src/app.dart b/packages/neon_framework/lib/src/app.dart index 8b5b5f3fec5..a7873b91ea0 100644 --- a/packages/neon_framework/lib/src/app.dart +++ b/packages/neon_framework/lib/src/app.dart @@ -10,7 +10,6 @@ import 'package:meta/meta.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/l10n/localizations.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/blocs/maintenance_mode.dart'; import 'package:neon_framework/src/blocs/unified_search.dart'; import 'package:neon_framework/src/models/account.dart'; diff --git a/packages/neon_framework/lib/src/blocs/accounts.dart b/packages/neon_framework/lib/src/blocs/accounts.dart index e2c434fa648..2429359339a 100644 --- a/packages/neon_framework/lib/src/blocs/accounts.dart +++ b/packages/neon_framework/lib/src/blocs/accounts.dart @@ -7,7 +7,6 @@ import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:neon_framework/blocs.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/blocs/maintenance_mode.dart'; import 'package:neon_framework/src/blocs/unified_search.dart'; import 'package:neon_framework/src/models/account.dart'; diff --git a/packages/neon_framework/lib/src/testing/mocks.dart b/packages/neon_framework/lib/src/testing/mocks.dart index fbff01deb81..ee196a218f7 100644 --- a/packages/neon_framework/lib/src/testing/mocks.dart +++ b/packages/neon_framework/lib/src/testing/mocks.dart @@ -9,7 +9,6 @@ import 'package:neon_framework/models.dart'; import 'package:neon_framework/platform.dart'; import 'package:neon_framework/settings.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/models/account_cache.dart'; import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/settings/models/exportable.dart'; diff --git a/packages/neon_framework/lib/src/widgets/dialog.dart b/packages/neon_framework/lib/src/widgets/dialog.dart index 7ffcc733a24..896e339cfa7 100644 --- a/packages/neon_framework/lib/src/widgets/dialog.dart +++ b/packages/neon_framework/lib/src/widgets/dialog.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/utils/global_options.dart'; import 'package:neon_framework/src/utils/relative_time.dart'; diff --git a/packages/neon_framework/lib/src/widgets/drawer.dart b/packages/neon_framework/lib/src/widgets/drawer.dart index 7042c7a8a42..40133537bdc 100644 --- a/packages/neon_framework/lib/src/widgets/drawer.dart +++ b/packages/neon_framework/lib/src/widgets/drawer.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/models.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/utils/provider.dart'; import 'package:neon_framework/src/widgets/drawer_destination.dart'; import 'package:neon_framework/src/widgets/error.dart'; diff --git a/packages/neon_framework/test/drawer_test.dart b/packages/neon_framework/test/drawer_test.dart index 5e565c20a63..a740ae61ccf 100644 --- a/packages/neon_framework/test/drawer_test.dart +++ b/packages/neon_framework/test/drawer_test.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/blocs.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/models/app_implementation.dart'; import 'package:neon_framework/src/utils/provider.dart'; import 'package:neon_framework/src/widgets/drawer.dart'; diff --git a/packages/neon_framework/test/router_test.dart b/packages/neon_framework/test/router_test.dart index 8c06816df42..7729da6d2c8 100644 --- a/packages/neon_framework/test/router_test.dart +++ b/packages/neon_framework/test/router_test.dart @@ -4,7 +4,6 @@ import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/router.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/utils.dart'; From b2121fc092d2c5620f0eaedea9018cc4efce765f Mon Sep 17 00:00:00 2001 From: provokateurin Date: Tue, 16 Jul 2024 12:38:00 +0200 Subject: [PATCH 2/5] test(nextcloud): Test editing spreed chat messages Signed-off-by: provokateurin --- .../fixtures/spreed/chat/edit_message.regexp | 18 ++++++++ packages/nextcloud/test/spreed_test.dart | 42 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 packages/nextcloud/test/fixtures/spreed/chat/edit_message.regexp diff --git a/packages/nextcloud/test/fixtures/spreed/chat/edit_message.regexp b/packages/nextcloud/test/fixtures/spreed/chat/edit_message.regexp new file mode 100644 index 00000000000..18b515b7475 --- /dev/null +++ b/packages/nextcloud/test/fixtures/spreed/chat/edit_message.regexp @@ -0,0 +1,18 @@ +POST http://localhost/ocs/v2\.php/apps/spreed/api/v4/room +accept: application/json +authorization: Bearer mock +content-type: application/json; charset=utf-8 +ocs-apirequest: true +\{"roomType":3,"invite":"","roomName":"Test","source":"","objectType":"","objectId":""\} +POST http://localhost/ocs/v2\.php/apps/spreed/api/v1/chat/[a-z0-9]{8} +accept: application/json +authorization: Bearer mock +content-type: application/json; charset=utf-8 +ocs-apirequest: true +\{"message":"bla","actorDisplayName":"","referenceId":"","replyTo":0,"silent":false\} +PUT http://localhost/ocs/v2\.php/apps/spreed/api/v1/chat/[a-z0-9]{8}/[0-9]+ +accept: application/json +authorization: Bearer mock +content-type: application/json; charset=utf-8 +ocs-apirequest: true +\{"message":"123"\} \ No newline at end of file diff --git a/packages/nextcloud/test/spreed_test.dart b/packages/nextcloud/test/spreed_test.dart index f814bbb7664..3fa8481b334 100644 --- a/packages/nextcloud/test/spreed_test.dart +++ b/packages/nextcloud/test/spreed_test.dart @@ -267,6 +267,48 @@ void main() { expect(response.body.ocs.data!.messageType, spreed.MessageType.comment); }); + test( + 'Edit message', + () async { + final startTime = DateTime.timestamp(); + final room = await createTestRoom(); + + final messageResponse = await client1.spreed.chat.sendMessage( + token: room.token, + $body: spreed.ChatSendMessageRequestApplicationJson( + (b) => b..message = 'bla', + ), + ); + + final response = await client1.spreed.chat.editMessage( + token: room.token, + messageId: messageResponse.body.ocs.data!.id, + $body: spreed.ChatEditMessageRequestApplicationJson( + (b) => b..message = '123', + ), + ); + expect(response.body.ocs.data.id, isPositive); + expect(response.body.ocs.data.actorType, spreed.ActorType.users); + expect(response.body.ocs.data.actorId, 'user1'); + expect(response.body.ocs.data.actorDisplayName, 'User One'); + expect(response.body.ocs.data.message, 'You edited a message'); + expect(response.body.ocs.data.messageType, spreed.MessageType.system); + expect(response.body.ocs.data.systemMessage, 'message_edited'); + expect(response.body.ocs.data.parent!.id, messageResponse.body.ocs.data!.id); + expect(response.body.ocs.data.parent!.actorType, spreed.ActorType.users); + expect(response.body.ocs.data.parent!.actorId, 'user1'); + expect(response.body.ocs.data.parent!.actorDisplayName, 'User One'); + expect(response.body.ocs.data.parent!.message, '123'); + expect(response.body.ocs.data.parent!.messageType, spreed.MessageType.comment); + expect(response.body.ocs.data.parent!.systemMessage, ''); + expect(response.body.ocs.data.parent!.lastEditTimestamp, closeTo(startTime.secondsSinceEpoch, 10)); + expect(response.body.ocs.data.parent!.lastEditActorId, 'user1'); + expect(response.body.ocs.data.parent!.lastEditActorDisplayName, 'User One'); + expect(response.body.ocs.data.parent!.lastEditActorType, spreed.ActorType.users); + }, + skip: preset.version < Version(19, 0, 0), + ); + group('Get messages', () { test('Directly', () async { final startTime = DateTime.timestamp(); From cf6ba3cfaa1ff05679713899da52aab51e03200e Mon Sep 17 00:00:00 2001 From: provokateurin Date: Tue, 16 Jul 2024 12:06:37 +0200 Subject: [PATCH 3/5] test(neon_talk): Remove unused golden image Signed-off-by: provokateurin --- .../test/goldens/message_system_message_hide.png | Bin 3268 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/neon/neon_talk/test/goldens/message_system_message_hide.png diff --git a/packages/neon/neon_talk/test/goldens/message_system_message_hide.png b/packages/neon/neon_talk/test/goldens/message_system_message_hide.png deleted file mode 100644 index 1f8704416d307019fe2a05a0456f4fce53d005e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3268 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i1B%QlYbpRzjKx9jP7LeL$-D$|Sc;uI zLpXq-h9jkefq^H_)5S5QV$R#^hJpta7!Eib*ndAxy+DxdpJ>Y2KmYc!GcY(L+~osm zyqD;}z|hdc$jHE8#KywFa6&?Wfk7d8RB$vvMiawmMi?y_MoY!f8ez0H9IXvUYs1mn gaI`kWUK_G^Oy!t2SEoc5*r8zXboFyt=akR{0N4QH;{X5v From 8df88333576061ca0fb84b24dcc7fc88123e3595 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Tue, 16 Jul 2024 10:39:53 +0200 Subject: [PATCH 4/5] feat(neon_talk): Show edited messages Signed-off-by: provokateurin --- packages/neon/neon_talk/lib/l10n/en.arb | 12 ++++ .../neon_talk/lib/l10n/localizations.dart | 12 ++++ .../neon_talk/lib/l10n/localizations_en.dart | 8 +++ .../neon_talk/lib/src/widgets/message.dart | 29 +++++++-- ...essage_comment_message_separate_edited.png | Bin 0 -> 7655 bytes .../neon/neon_talk/test/message_test.dart | 55 ++++++++++++++++++ 6 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 packages/neon/neon_talk/test/goldens/message_comment_message_separate_edited.png diff --git a/packages/neon/neon_talk/lib/l10n/en.arb b/packages/neon/neon_talk/lib/l10n/en.arb index a8c0a3b4ffd..a030843e9c9 100644 --- a/packages/neon/neon_talk/lib/l10n/en.arb +++ b/packages/neon/neon_talk/lib/l10n/en.arb @@ -18,6 +18,18 @@ "roomMessageReply": "Reply", "roomMessageReaction": "Add reaction", "roomMessageDelete": "Delete", + "roomMessageEdited": "edited", + "roomMessageLastEdited": "Last edited by {name} at {time}", + "@roomMessageLastEdited": { + "placeholders": { + "name": { + "type": "String" + }, + "time": { + "type": "String" + } + } + }, "reactionsAddNew": "Add a new reaction", "reactionsLoading": "Loading reactions", "roomsCreateNew": "Create new room" diff --git a/packages/neon/neon_talk/lib/l10n/localizations.dart b/packages/neon/neon_talk/lib/l10n/localizations.dart index 43d0315ebb3..21f01641ed7 100644 --- a/packages/neon/neon_talk/lib/l10n/localizations.dart +++ b/packages/neon/neon_talk/lib/l10n/localizations.dart @@ -167,6 +167,18 @@ abstract class TalkLocalizations { /// **'Delete'** String get roomMessageDelete; + /// No description provided for @roomMessageEdited. + /// + /// In en, this message translates to: + /// **'edited'** + String get roomMessageEdited; + + /// No description provided for @roomMessageLastEdited. + /// + /// In en, this message translates to: + /// **'Last edited by {name} at {time}'** + String roomMessageLastEdited(String name, String time); + /// No description provided for @reactionsAddNew. /// /// In en, this message translates to: diff --git a/packages/neon/neon_talk/lib/l10n/localizations_en.dart b/packages/neon/neon_talk/lib/l10n/localizations_en.dart index c80d1d704d3..1b7fd96876a 100644 --- a/packages/neon/neon_talk/lib/l10n/localizations_en.dart +++ b/packages/neon/neon_talk/lib/l10n/localizations_en.dart @@ -56,6 +56,14 @@ class TalkLocalizationsEn extends TalkLocalizations { @override String get roomMessageDelete => 'Delete'; + @override + String get roomMessageEdited => 'edited'; + + @override + String roomMessageLastEdited(String name, String time) { + return 'Last edited by $name at $time'; + } + @override String get reactionsAddNew => 'Add a new reaction'; diff --git a/packages/neon/neon_talk/lib/src/widgets/message.dart b/packages/neon/neon_talk/lib/src/widgets/message.dart index 05d66fdfa52..5492692be7d 100644 --- a/packages/neon/neon_talk/lib/src/widgets/message.dart +++ b/packages/neon/neon_talk/lib/src/widgets/message.dart @@ -459,19 +459,40 @@ class _TalkCommentMessageState extends State { final separateMessages = widget.chatMessage.actorId != widget.previousChatMessage?.actorId || widget.previousChatMessage?.messageType == spreed.MessageType.system || previousDate == null || - date.difference(previousDate) > const Duration(minutes: 3); + date.difference(previousDate) > const Duration(minutes: 3) || + widget.chatMessage.lastEditTimestamp != null; - Widget? displayName; + Widget? label; Widget? avatar; Widget? time; if (separateMessages) { - displayName = Text( + label = Text( getActorDisplayName(TalkLocalizations.of(context), widget.chatMessage), style: textTheme.labelLarge!.copyWith( color: labelColor, ), ); + if (widget.chatMessage.lastEditTimestamp != null && widget.chatMessage.lastEditActorDisplayName != null) { + label = Row( + children: [ + label, + Tooltip( + message: TalkLocalizations.of(context).roomMessageLastEdited( + widget.chatMessage.lastEditActorDisplayName!, + DateFormat.yMd().add_jm().format(widget.chatMessage.parsedLastEditTimestamp!), + ), + child: Text( + ' (${TalkLocalizations.of(context).roomMessageEdited})', + style: textTheme.labelLarge!.copyWith( + color: labelColor, + ), + ), + ), + ], + ); + } + if (!widget.isParent) { avatar = TalkActorAvatar( actorId: widget.chatMessage.actorId, @@ -562,7 +583,7 @@ class _TalkCommentMessageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (displayName != null) displayName, + if (label != null) label, if (time != null) time, ], ), diff --git a/packages/neon/neon_talk/test/goldens/message_comment_message_separate_edited.png b/packages/neon/neon_talk/test/goldens/message_comment_message_separate_edited.png new file mode 100644 index 0000000000000000000000000000000000000000..c45e226e4c9573f5bc3d7ef2ad347f09def18c54 GIT binary patch literal 7655 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 literal 0 HcmV?d00001 diff --git a/packages/neon/neon_talk/test/message_test.dart b/packages/neon/neon_talk/test/message_test.dart index bc1eb2ba8c2..0b08df20d3f 100644 --- a/packages/neon/neon_talk/test/message_test.dart +++ b/packages/neon/neon_talk/test/message_test.dart @@ -816,6 +816,61 @@ void main() { matchesGoldenFile('goldens/message_comment_message_separate_system_message.png'), ); }); + + testWidgets('Edited', (tester) async { + final account = MockAccount(); + when(() => account.id).thenReturn(''); + when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); + + final previousChatMessage = MockChatMessage(); + when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); + when(() => previousChatMessage.timestamp).thenReturn(0); + when(() => previousChatMessage.actorId).thenReturn('test'); + + final chatMessage = MockChatMessage(); + when(() => chatMessage.timestamp).thenReturn(0); + when(() => chatMessage.actorId).thenReturn('test'); + when(() => chatMessage.actorType).thenReturn(spreed.ActorType.users); + when(() => chatMessage.actorDisplayName).thenReturn('test'); + when(() => chatMessage.messageType).thenReturn(spreed.MessageType.comment); + when(() => chatMessage.message).thenReturn('abc'); + when(() => chatMessage.reactions).thenReturn(BuiltMap({'😀': 1, '😊': 23})); + when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + when(() => chatMessage.lastEditTimestamp).thenReturn(0); + when(() => chatMessage.lastEditActorDisplayName).thenReturn('test'); + + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + + await tester.pumpWidgetWithAccessibility( + wrapWidget( + providers: [ + Provider.value(value: account), + NeonProvider.value(value: roomBloc), + NeonProvider.value(value: referencesBloc), + ], + child: TalkCommentMessage( + room: room, + chatMessage: chatMessage, + lastCommonRead: null, + previousChatMessage: previousChatMessage, + ), + ), + ); + expect(find.byType(TalkActorAvatar), findsOne); + expect(find.text('1:00 AM'), findsOne); + expect(find.byTooltip('1/1/1970 1:00 AM'), findsOne); + expect(find.text('test'), findsOne); + expect(find.text(' (edited)'), findsOne); + expect(find.byTooltip('Last edited by test at 1/1/1970 1:00 AM'), findsOne); + expect(find.text('abc', findRichText: true), findsOne); + expect(find.byType(TalkReactions), findsOne); + expect(find.byType(SelectionArea), findsOne); + await expectLater( + find.byType(TalkCommentMessage), + matchesGoldenFile('goldens/message_comment_message_separate_edited.png'), + ); + }); }); group('Menu', () { From 45e423e2b58058db03bb1b3099041a77176c8215 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Tue, 16 Jul 2024 12:06:15 +0200 Subject: [PATCH 5/5] feat(neon_talk): Enable editing of chat messages Signed-off-by: provokateurin --- packages/neon/neon_talk/lib/l10n/en.arb | 1 + .../neon_talk/lib/l10n/localizations.dart | 6 + .../neon_talk/lib/l10n/localizations_en.dart | 3 + .../neon/neon_talk/lib/src/blocs/room.dart | 78 ++++-- .../neon/neon_talk/lib/src/utils/helpers.dart | 16 ++ .../neon_talk/lib/src/widgets/message.dart | 14 + .../lib/src/widgets/message_input.dart | 96 +++++-- .../test/goldens/message_input_edit.png | Bin 0 -> 32266 bytes .../test/goldens/message_input_reply.png | Bin 0 -> 32338 bytes .../test/goldens/room_page_reply.png | Bin 34568 -> 0 bytes .../neon_talk/test/message_input_test.dart | 117 ++++++++- .../neon/neon_talk/test/message_test.dart | 243 ++++++++++++++++++ .../neon/neon_talk/test/room_bloc_test.dart | 78 +++++- .../neon/neon_talk/test/room_page_test.dart | 48 +--- 14 files changed, 599 insertions(+), 101 deletions(-) create mode 100644 packages/neon/neon_talk/test/goldens/message_input_edit.png create mode 100644 packages/neon/neon_talk/test/goldens/message_input_reply.png delete mode 100644 packages/neon/neon_talk/test/goldens/room_page_reply.png diff --git a/packages/neon/neon_talk/lib/l10n/en.arb b/packages/neon/neon_talk/lib/l10n/en.arb index a030843e9c9..dbf6c38449f 100644 --- a/packages/neon/neon_talk/lib/l10n/en.arb +++ b/packages/neon/neon_talk/lib/l10n/en.arb @@ -18,6 +18,7 @@ "roomMessageReply": "Reply", "roomMessageReaction": "Add reaction", "roomMessageDelete": "Delete", + "roomMessageEdit": "Edit", "roomMessageEdited": "edited", "roomMessageLastEdited": "Last edited by {name} at {time}", "@roomMessageLastEdited": { diff --git a/packages/neon/neon_talk/lib/l10n/localizations.dart b/packages/neon/neon_talk/lib/l10n/localizations.dart index 21f01641ed7..78c77d23739 100644 --- a/packages/neon/neon_talk/lib/l10n/localizations.dart +++ b/packages/neon/neon_talk/lib/l10n/localizations.dart @@ -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: diff --git a/packages/neon/neon_talk/lib/l10n/localizations_en.dart b/packages/neon/neon_talk/lib/l10n/localizations_en.dart index 1b7fd96876a..5fbffd304a8 100644 --- a/packages/neon/neon_talk/lib/l10n/localizations_en.dart +++ b/packages/neon/neon_talk/lib/l10n/localizations_en.dart @@ -56,6 +56,9 @@ class TalkLocalizationsEn extends TalkLocalizations { @override String get roomMessageDelete => 'Delete'; + @override + String get roomMessageEdit => 'Edit'; + @override String get roomMessageEdited => 'edited'; diff --git a/packages/neon/neon_talk/lib/src/blocs/room.dart b/packages/neon/neon_talk/lib/src/blocs/room.dart index 93e38080d29..c623d99152b 100644 --- a/packages/neon/neon_talk/lib/src/blocs/room.dart +++ b/packages/neon/neon_talk/lib/src/blocs/room.dart @@ -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); @@ -61,6 +67,9 @@ abstract class TalkRoomBloc implements InteractiveBloc { /// Current chat message to reply to. BehaviorSubject get replyTo; + + /// Current chat message that is edited. + BehaviorSubject get editing; } class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { @@ -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; @@ -192,6 +204,7 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { unawaited(lastCommonRead.close()); unawaited(reactions.close()); unawaited(replyTo.close()); + unawaited(editing.close()); super.dispose(); } @@ -234,29 +247,48 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { @override Future 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]); } }, @@ -330,6 +362,7 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { @override void setReplyChatMessage(spreed.$ChatMessageInterface chatMessage) { + editing.add(null); replyTo.add(chatMessage); } @@ -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 deleteMessage(spreed.$ChatMessageInterface chatMessage) async { await wrapAction( diff --git a/packages/neon/neon_talk/lib/src/utils/helpers.dart b/packages/neon/neon_talk/lib/src/utils/helpers.dart index 5afeb5369c6..2706ffec784 100644 --- a/packages/neon/neon_talk/lib/src/utils/helpers.dart +++ b/packages/neon/neon_talk/lib/src/utils/helpers.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:neon_framework/blocs.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:nextcloud/utils.dart'; import 'package:timezone/timezone.dart' as tz; @@ -47,3 +50,16 @@ extension $ReactionInterfaceHelpers on spreed.$ReactionInterface { /// Parsed equivalent of [timestamp]. tz.TZDateTime get parsedTimestamp => DateTimeUtils.fromSecondsSinceEpoch(tz.local, timestamp); } + +/// Returns if the Talk [feature] is supported on the instance. +bool hasFeature(BuildContext context, String feature) { + final capabilitiesBloc = NeonProvider.of(context); + final capabilities = capabilitiesBloc + .capabilities.valueOrNull?.data?.capabilities.spreedPublicCapabilities?.spreedPublicCapabilities0?.spreed; + + if (capabilities == null) { + return false; + } + + return capabilities.features.contains(feature); +} diff --git a/packages/neon/neon_talk/lib/src/widgets/message.dart b/packages/neon/neon_talk/lib/src/widgets/message.dart index 5492692be7d..4efd5d38546 100644 --- a/packages/neon/neon_talk/lib/src/widgets/message.dart +++ b/packages/neon/neon_talk/lib/src/widgets/message.dart @@ -715,6 +715,20 @@ class _TalkCommentMessageState extends State { NeonProvider.of(context).setReplyChatMessage(chatMessage); }, ), + if (chatMessage.messageType != spreed.MessageType.commentDeleted && + chatMessage.actorId == room.actorId && + hasFeature(context, 'edit-messages')) + MenuItemButton( + leadingIcon: const Icon(Icons.edit), + child: Text(TalkLocalizations.of(context).roomMessageEdit), + onPressed: () { + setState(() { + menuOpen = false; + }); + + NeonProvider.of(context).setEditChatMessage(chatMessage); + }, + ), if (chatMessage.messageType != spreed.MessageType.commentDeleted && chatMessage.actorId == room.actorId) MenuItemButton( leadingIcon: const Icon(Icons.delete_forever), diff --git a/packages/neon/neon_talk/lib/src/widgets/message_input.dart b/packages/neon/neon_talk/lib/src/widgets/message_input.dart index e59ebf68ffd..1fcc7c2468c 100644 --- a/packages/neon/neon_talk/lib/src/widgets/message_input.dart +++ b/packages/neon/neon_talk/lib/src/widgets/message_input.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; @@ -52,6 +54,8 @@ class _TalkMessageInputState extends State { }, ); late TalkRoomBloc bloc; + late StreamSubscription replyToSubscription; + late StreamSubscription editingSubscription; @override void initState() { @@ -59,15 +63,28 @@ class _TalkMessageInputState extends State { bloc = NeonProvider.of(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(); @@ -117,28 +134,31 @@ class _TalkMessageInputState extends State { 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, + ); + }, ); }, ); @@ -230,6 +250,7 @@ class _TalkMessageInputState extends State { mainAxisSize: MainAxisSize.min, children: [ replyTo, + editing, inputField, ], ); @@ -258,6 +279,35 @@ class _TalkMessageInputState extends State { 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 { diff --git a/packages/neon/neon_talk/test/goldens/message_input_edit.png b/packages/neon/neon_talk/test/goldens/message_input_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..d4c496864e13c2f7375cd078f2ca6fab6bc06b31 GIT binary patch literal 32266 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 Sudft?HPv<03NBmy{l5Sr)o(8V literal 0 HcmV?d00001 diff --git a/packages/neon/neon_talk/test/goldens/message_input_reply.png b/packages/neon/neon_talk/test/goldens/message_input_reply.png new file mode 100644 index 0000000000000000000000000000000000000000..37a705e390d652ba80333916edab704381670b30 GIT binary patch literal 32338 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 K2)|+W`2PSJS?IC= literal 0 HcmV?d00001 diff --git a/packages/neon/neon_talk/test/goldens/room_page_reply.png b/packages/neon/neon_talk/test/goldens/room_page_reply.png deleted file mode 100644 index 0c3edf26fa6814c990848c5aaa0be03078e9abe4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34568 zcmeIb2T)YY*DpGlz<>w{s0b)gl88u7dIS+cP>>`!iAo%h9FL-8P;v%AL1BO)=O7X! zgGf#~Lxur|oagl@d*J`7zE|(oefQO^`!RKnD0_AHTEAYo*^SQw1sT#47f!%nFjCpO zQc5t`aXlD}n1tjQ_)byR#u4y^$VN%#A6RDV#VPQQBR2oYJ|Y1h7ZM|H80->ER_fLx zhuGPnksz%J*AZ-6RNQtg3%gG^lW2I@y%UDA$G%rLzJ58HbnEFCs#ny=5NFy?lvOt= zulVo;|G4>}>*kqkea*{9tV3{B7LTQ*FQnS@H`fHeJ9j_!QNcyIByQ1guDM{XxvVIi ziy2ZKTiKhlt+B!rWja-QBrki{Tg7tAoY0$(W@yoHz}%mYUR~MRN1pNZHiL$n^Wjz3e~S%Y?3O_W-AZiqMjtG!cN*^=P8(J*Uf zDJXuzOWxz?E^$>eg+A}h5p`&6^9^$9-2IO!^?B3x9_PIz@zY?gI z;)3{i{P{k4w}p#}ic0W@3#X*i>GsW@-+xA5n?V;tp>O*Nc5-)whic3i$;=aM*Hv=W z&ErUDo~Pv+{zwZNJaK-^94g}i+e)E3Cz;FISFa1Y zECTxZulF$(W8|4nt9##<#B^n!1Z4o|kU%`IPxgtcBh*Y{`WP>6_bi1T(Jr6uv|i-; z_eJp{MBm?o-f8R-xvKJC;?LcJb?6kYOMQc~0(QXxm<>ngQ2#nh$vU*rL`z6?dT`pk zV?APTUKzo!YFuQMvuW7U%AJQ4QLnpvPiM#DcU(LFAH;vdHp4)qdbq$Yco`$eplNHx zFEXlp?vd@=e6a*BvKg-{&_@4^Te+a$qU4e<=gCidM4eLLLM7c}%pEn+55tJi@W*2k%~lotwnIo`=nMd$6jE}@3n z^oG~@qr_?~QR`b2O{8hstE5YB}#T>P}8)lDV35 zK3^IWB0s=LKa#7NU!45!{IIGQvv<_p*PSaP#whK_Y8pZ^e2{5rjYDLT%+=A1MBNhR z0l{iKEF-0ZEx)IoMA0JG2L@%sCuTAr%wgJvwEC{=&F9If3z@6>ld(dM=HlxskFE2) z)C?_VnJu+4aAjihj~8&qcbB}$OHS>4xvKwFOWf#3zlP~2-(P)H6XaKLT}Jgk$;o-! zysiz2FQVXCiy=W;piy@g8lUHgKX(f5%Xa4ewWXDF!_4Wz?t~r<6#8(^Lx`Aj0U`5R z_bS{!j%a3CJc)Ip?Cw^ue(@#D6I!{@2U&EUsGHQR%yH~zSVjt4lq;>Mt+%bwL9xyg-T*D5?tm(Faj8qdW$FKoF> z41i%V?#mI&tX$;X6-yFTkB83a-mm`HV)TU-@<@s3Ik%r*TBXznBptLpRT6uMy2CO? z!@5#ZCo|<@8WvpW3?Pep5k`_e;zX;LVadjm#fnZLo+`?3F*3d!#c)F$+Q6z&Z{$XL z1Q9?NZy|SG`Qd10Q`&~?S|bHd$hKZYlB6d%(dY%#HOSF~WIXH)S*jOWwhgl9V&Ptw zgs2j|Nz&Z`mFt9(wPX4e`-X{0tXa_X{XOkst)7n1apj=NVxFfy(fCal_SOcv)PCTqPj8^zqv(3v_wsZV;$am|q7ez2JRoK~hAdS*RA zai>%az7lnc#(bn$$MO&&Aa1m)>AvEZ^oS6qMc@WILk9b|$%aRkNUHod#sw?-mUhF# zO*tUAe)|%v^O}OSKL7cEn0U_)A+Wg#5HvS?#9vHfw?;!MqZV;vmE*5X63m@qT|Xat zCAJ_}8QJN+BqO_^>X~28`!DC@V+0LR_iEM;T4W^Y%)oLKq!mkF^2xiWU!S}mlxsOJ zvW{N=tmw)Cw)u_-gB~!)p6;0Vmow!BItiqZUNhj0cmb<8TI#+ncs@w;g}b`S*q^{` zOBqLotgsh;*14L`Q-|I#gBIB^5Uk$NP1t#%M}b&9qA*y+L@ZZT zaGDRw6(;>mvzX56+ulw+j8Nm@7bhxQ)HZ?4Wyzl;T~mZsPrEGIZ0?5m(ZbwpL%H+2 z%d|@}BIeEsh*y1ZaW9`bK0L)akzncMX}{KP7$hKGt*N7{7|0_a4yi-c3p{+x4ePlk z!OU!m2?{DI_XG1xO3796+c&65-c4ze&|sCVd-*P#pq_Sr-gesk?!p;9$Sx;oW03t> zAw=D#d@E!9)C9Zjrag>kJH7ifF-S@s(^U+cAuiC( z56^|>+mQF{QiwZ&tds2)0H7J+U zF7*(?Pc0_iS*)0zx1Rm`5cYFCGYHualDmaD8TwYri4t=Ce2J z>J9PMit?Gm43jmso*86F0sH894_SD@?u4+y0$Tm;!XS4>9=Bg7Gs*&y81itKUzBk! zq)1g5%pG;y#A-t8$ugb$qVs+X>h*pXo2#?v8{2p>&WJ>xnQFr*=)Lemrn0RRV zL~$wgmZz>B`9tyZ$2UjKpfM{lp8T$Wojb`XY=<$ogDA9zePv7z6cLY;wDfeg-NPqqWDPGqGAr*ub z@soFt=1EjVr~fA&UVyRlVDt@a`=Sx;8O}z*w@Fe8Ii1-SoY~r`wN6LEJ z)7IfJgC6N)-07n8CQwKMaSQRaW246SshpCDu;#7+`1nbuHFFrTUi_ahjQSu?rvNyU z0GxZ^yzoXglBO2kfUGI?y;zT>^k)^rM@rW zPd=4eaU8$3T4AR&aaW6mw=SK}V@iaa&}8Tk$xJ0~Kyf_4eM>bxlS%&i@(hXSyQ|!V zJB-qqg`@J&mS4yPniiz!cAZCT(=444@%&okbI&dx*vvPVPD-oZSoU2|Zsul|2R`t$ zwK>G~B$!}7Pvc2uRz%44xJg5b)27&kxNO2WNua5pye66H1`k;TSZx~`|JidrR@~&> zEw-#xBCP#Wr%a$&am`suU9daKZ#a;f^;J{Dr+;m>8`mdNaY=OipMsMKNxetZQv2un z5ax-tc-i1sF;@{46@KCG@xUBp7Y2)METz>IV_jLl*4CmD2BJvStK%$$N**Mz?1}-) zZD2*%Ps@9RG1oWUzVXD94}vVxl@U>B%TWbf(a@X2n$6IF{(n4GW*&K{un4pMe3oH& za?j(npJqe^9#xmMHYlDte#Le|X-`6=u4@odga(fbl_v7BiSc_0QNo0&ZRDyGhY-xg z1JsCa_!H>+nC>2ubl|JstsC_TeC$@I?>4~~`S{Px(q-Q2X(Pb&yvX;ZBeP+-kUyCW ztdLfZBSi*s_rB~p*7S7)@pl9aHW?`K=SlEreDZz8DvS`*?h_A-X*tiXDVV8auxz#IIYOj zZ|3W^C~j4Q(!908UnbcFW0TNkGttT-8&1E4{hL+LguHM$tg1vTF)#6b>8#fLkW7sl z#H1@6!;0uSNfS+PGV-R$QunLgraV}cU_|9_+y0_*R3mW!7S0>Vkl?hQ^r&j7j z1qg!>%n^bi0dZjdh%x=`J}BnhydFc|?S@YO;6<9OXY|AB)MOP8 zBbYT}(+jVcmqq(at5ZHySvDyX>RDojD4TNtldo#jWP|XOQHsO%T#m;1Q?eyfuJzMx z=O8kRRFHQiVpYB`!Z>9ExtUiH%p>K4OUy$H@yW{sw+eISZy}kvhDw(L8Y`R~dy2TB zkf%8CcVZgHdPi~twl;+g+LpwC6wrH6bqOOu4qLlq2O>d9d500f_=>|3haL4cC58xv zJY?^vd!5&1IfSqmx}Goy#Qs|WMcYNuGOyAphPxy~TnZft@kX8&t+;nYUDC)gaw}If z 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 diff --git a/packages/neon/neon_talk/test/message_input_test.dart b/packages/neon/neon_talk/test/message_input_test.dart index 3c90e646456..fd3503258d3 100644 --- a/packages/neon/neon_talk/test/message_input_test.dart +++ b/packages/neon/neon_talk/test/message_input_test.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:convert'; +import 'package:built_collection/built_collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; @@ -13,11 +15,14 @@ import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; import 'package:neon_talk/l10n/localizations.dart'; import 'package:neon_talk/src/blocs/room.dart'; +import 'package:neon_talk/src/widgets/message.dart'; import 'package:neon_talk/src/widgets/message_input.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:timezone/data/latest.dart' as tzdata; +import 'package:timezone/timezone.dart' as tz; import 'testing.dart'; @@ -49,20 +54,44 @@ Account mockTalkAccount() { } void main() { + late Account account; late TalkRoomBloc bloc; late spreed.Room room; + late BehaviorSubject replyTo; + late BehaviorSubject editing; + late ReferencesBloc referencesBloc; setUpAll(() { KeyboardVisibilityTesting.setVisibilityForTesting(true); + + tzdata.initializeTimeZones(); + tz.setLocalLocation(tz.getLocation('Europe/Berlin')); }); setUp(() { FakeNeonStorage.setup(); + account = mockTalkAccount(); + + replyTo = BehaviorSubject.seeded(null); + editing = BehaviorSubject.seeded(null); + bloc = MockRoomBloc(); - when(() => bloc.replyTo).thenAnswer((_) => BehaviorSubject.seeded(null)); + when(() => bloc.room).thenAnswer((_) => BehaviorSubject.seeded(Result.success(room))); + when(() => bloc.replyTo).thenAnswer((_) => replyTo); + when(() => bloc.editing).thenAnswer((_) => editing); room = MockRoom(); + when(() => room.token).thenReturn('token'); + + referencesBloc = MockReferencesBloc(); + when(() => referencesBloc.referenceRegex).thenAnswer((_) => BehaviorSubject.seeded(Result.success(null))); + when(() => referencesBloc.references).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + }); + + tearDown(() { + unawaited(replyTo.close()); + unawaited(editing.close()); }); testWidgets('Cupertino no emoji button', (tester) async { @@ -128,12 +157,6 @@ void main() { }); testWidgets('Mention suggestions', (tester) async { - final account = mockTalkAccount(); - - final room = MockRoom(); - when(() => room.token).thenReturn('token'); - when(() => bloc.room).thenAnswer((_) => BehaviorSubject.seeded(Result.success(room))); - await tester.pumpWidgetWithAccessibility( TestApp( localizationsDelegates: TalkLocalizations.localizationsDelegates, @@ -167,12 +190,6 @@ void main() { }); testWidgets('Multiline', (tester) async { - final account = mockTalkAccount(); - - final room = MockRoom(); - when(() => room.token).thenReturn('token'); - when(() => bloc.room).thenAnswer((_) => BehaviorSubject.seeded(Result.success(room))); - await tester.pumpWidgetWithAccessibility( TestApp( localizationsDelegates: TalkLocalizations.localizationsDelegates, @@ -211,4 +228,78 @@ void main() { verify(() => bloc.sendMessage('123\n456')).called(1); }); + + testWidgets('Reply', (tester) async { + await tester.pumpWidgetWithAccessibility( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: bloc), + NeonProvider.value(value: referencesBloc), + Provider.value(value: account), + ], + child: TalkMessageInput( + room: room, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(TalkParentMessage), findsNothing); + + final chatMessage = MockChatMessage(); + when(() => chatMessage.messageType).thenReturn(spreed.MessageType.comment); + when(() => chatMessage.timestamp).thenReturn(0); + when(() => chatMessage.actorId).thenReturn('test'); + when(() => chatMessage.actorDisplayName).thenReturn('test'); + when(() => chatMessage.message).thenReturn('message'); + when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + + replyTo.add(chatMessage); + await tester.pumpAndSettle(); + expect(find.byType(TalkParentMessage), findsOne); + await expectLater(find.byType(TestApp), matchesGoldenFile('goldens/message_input_reply.png')); + + await tester.tap(find.byIcon(Icons.close)); + verify(() => bloc.removeReplyChatMessage()).called(1); + }); + + testWidgets('Edit', (tester) async { + await tester.pumpWidgetWithAccessibility( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: bloc), + NeonProvider.value(value: referencesBloc), + Provider.value(value: account), + ], + child: TalkMessageInput( + room: room, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(TalkParentMessage), findsNothing); + + final chatMessage = MockChatMessage(); + when(() => chatMessage.messageType).thenReturn(spreed.MessageType.comment); + when(() => chatMessage.timestamp).thenReturn(0); + when(() => chatMessage.actorId).thenReturn('test'); + when(() => chatMessage.actorDisplayName).thenReturn('test'); + when(() => chatMessage.message).thenReturn('message'); + when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + + editing.add(chatMessage); + await tester.pumpAndSettle(); + expect(find.byType(TalkParentMessage), findsOne); + expect(find.text('message'), findsExactly(2)); + await expectLater(find.byType(TestApp), matchesGoldenFile('goldens/message_input_edit.png')); + + await tester.tap(find.byIcon(Icons.close)); + verify(() => bloc.removeEditChatMessage()).called(1); + expect(find.text('message'), findsOne); + }); } diff --git a/packages/neon/neon_talk/test/message_test.dart b/packages/neon/neon_talk/test/message_test.dart index 0b08df20d3f..347b466f292 100644 --- a/packages/neon/neon_talk/test/message_test.dart +++ b/packages/neon/neon_talk/test/message_test.dart @@ -20,6 +20,7 @@ import 'package:neon_talk/src/widgets/rich_object/deck_card.dart'; import 'package:neon_talk/src/widgets/rich_object/fallback.dart'; import 'package:neon_talk/src/widgets/rich_object/file.dart'; import 'package:neon_talk/src/widgets/rich_object/mention.dart'; +import 'package:nextcloud/core.dart' as core; import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:provider/provider.dart'; @@ -42,6 +43,46 @@ Widget wrapWidget({ child: child, ); +core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data buildCapabilities(core.SpreedCapabilities spreedCapabilities) => + core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data( + (b) => b + ..version.update( + (b) => b + ..major = 0 + ..minor = 0 + ..micro = 0 + ..string = '' + ..edition = '' + ..extendedSupport = false, + ) + ..capabilities = ( + commentsCapabilities: null, + coreCapabilities: null, + corePublicCapabilities: null, + davCapabilities: null, + dropAccountCapabilities: null, + filesCapabilities: null, + filesSharingCapabilities: null, + filesTrashbinCapabilities: null, + filesVersionsCapabilities: null, + notesCapabilities: null, + notificationsCapabilities: null, + provisioningApiCapabilities: null, + sharebymailCapabilities: null, + spreedCapabilities: null, + spreedPublicCapabilities: ( + builtListNever: null, + spreedPublicCapabilities0: core.SpreedPublicCapabilities0( + (b) => b.spreed.replace(spreedCapabilities), + ), + ) as core.SpreedPublicCapabilities, + systemtagsCapabilities: null, + themingPublicCapabilities: null, + userStatusCapabilities: null, + weatherStatusCapabilities: null, + ), + ); + void main() { late spreed.Room room; late ReferencesBloc referencesBloc; @@ -877,6 +918,8 @@ void main() { late Account account; late spreed.ChatMessage chatMessage; late TalkRoomBloc roomBloc; + late core.SpreedCapabilities capabilities; + late CapabilitiesBloc capabilitiesBloc; setUp(() { account = MockAccount(); @@ -898,6 +941,54 @@ void main() { roomBloc = MockRoomBloc(); when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + + capabilities = core.SpreedCapabilities( + (b) => b + ..features.replace(['edit-messages']) + ..config.update( + (b) => b + ..attachments.update( + (b) => b.allowed = false, + ) + ..call.update( + (b) => b + ..enabled = false + ..breakoutRooms = false + ..recording = false + ..recordingConsent = 0 + ..canUploadBackground = false + ..sipEnabled = false + ..sipDialoutEnabled = false + ..canEnableSip = false, + ) + ..chat.update( + (b) => b + ..maxLength = 0 + ..readPrivacy = 0 + ..hasTranslationProviders = false + ..typingPrivacy = 0, + ) + ..conversations.update( + (b) => b.canCreate = false, + ) + ..previews.update( + (b) => b..maxGifSize = 0, + ) + ..signaling.update( + (b) => b..sessionPingLimit = 0, + ), + ) + ..version = '', + ); + + capabilitiesBloc = MockCapabilitiesBloc(); + when(() => capabilitiesBloc.capabilities).thenAnswer( + (_) => BehaviorSubject.seeded( + Result.success( + buildCapabilities(capabilities), + ), + ), + ); }); group('Add reaction', () { @@ -914,6 +1005,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -958,6 +1050,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -992,6 +1085,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -1026,6 +1120,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -1064,6 +1159,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -1096,6 +1192,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -1118,6 +1215,149 @@ void main() { }); }); + group('Edit', () { + testWidgets('Comment self', (tester) async { + when(() => room.readOnly).thenReturn(0); + when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary); + when(() => room.actorId).thenReturn('test'); + + await tester.pumpWidgetWithAccessibility( + wrapWidget( + providers: [ + Provider.value(value: account), + NeonProvider.value(value: roomBloc), + NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), + ], + child: TalkCommentMessage( + room: room, + chatMessage: chatMessage, + lastCommonRead: 0, + ), + ), + ); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(TalkCommentMessage))); + await tester.pumpAndSettle(); + + await tester.tap(find.byIcon(Icons.more_vert)); + await tester.pumpAndSettle(); + + await tester.runAsync(() async { + await tester.tap(find.byIcon(Icons.edit)); + await tester.pumpAndSettle(); + + verify(() => roomBloc.setEditChatMessage(chatMessage)).called(1); + }); + }); + + testWidgets('Comment other', (tester) async { + when(() => room.readOnly).thenReturn(0); + when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary); + when(() => room.actorId).thenReturn('other'); + + await tester.pumpWidgetWithAccessibility( + wrapWidget( + providers: [ + Provider.value(value: account), + NeonProvider.value(value: roomBloc), + NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), + ], + child: TalkCommentMessage( + room: room, + chatMessage: chatMessage, + lastCommonRead: 0, + ), + ), + ); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(TalkCommentMessage))); + await tester.pumpAndSettle(); + + await tester.tap(find.byIcon(Icons.more_vert)); + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.edit), findsNothing); + }); + + testWidgets('Deleted', (tester) async { + when(() => room.readOnly).thenReturn(0); + when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary); + + when(() => chatMessage.messageType).thenReturn(spreed.MessageType.commentDeleted); + + await tester.pumpWidgetWithAccessibility( + wrapWidget( + providers: [ + Provider.value(value: account), + NeonProvider.value(value: roomBloc), + NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), + ], + child: TalkCommentMessage( + room: room, + chatMessage: chatMessage, + lastCommonRead: 0, + ), + ), + ); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(TalkCommentMessage))); + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.more_vert), findsNothing); + }); + + testWidgets('No feature', (tester) async { + when(() => room.readOnly).thenReturn(0); + when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary); + when(() => room.actorId).thenReturn('test'); + + capabilities = capabilities.rebuild((b) => b.features.clear()); + + await tester.pumpWidgetWithAccessibility( + wrapWidget( + providers: [ + Provider.value(value: account), + NeonProvider.value(value: roomBloc), + NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), + ], + child: TalkCommentMessage( + room: room, + chatMessage: chatMessage, + lastCommonRead: 0, + ), + ), + ); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(TalkCommentMessage))); + await tester.pumpAndSettle(); + + await tester.tap(find.byIcon(Icons.more_vert)); + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.edit), findsNothing); + }); + }); + group('Delete', () { testWidgets('Comment self', (tester) async { when(() => room.readOnly).thenReturn(0); @@ -1130,6 +1370,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -1168,6 +1409,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, @@ -1202,6 +1444,7 @@ void main() { Provider.value(value: account), NeonProvider.value(value: roomBloc), NeonProvider.value(value: referencesBloc), + NeonProvider.value(value: capabilitiesBloc), ], child: TalkCommentMessage( room: room, diff --git a/packages/neon/neon_talk/test/room_bloc_test.dart b/packages/neon/neon_talk/test/room_bloc_test.dart index bdb379f24f7..100ae132809 100644 --- a/packages/neon/neon_talk/test/room_bloc_test.dart +++ b/packages/neon/neon_talk/test/room_bloc_test.dart @@ -42,7 +42,7 @@ Account mockTalkAccount() { ), }, RegExp(r'/ocs/v2\.php/apps/spreed/api/v1/chat/abcd/([0-9]+)'): { - 'delete': (match, queryParameters) { + 'delete': (match, bodyBytes) { final id = int.parse(match.group(1)!); return Response( @@ -66,6 +66,32 @@ Account mockTalkAccount() { }, ); }, + 'put': (match, bodyBytes) { + final id = int.parse(match.group(1)!); + final data = json.decode(utf8.decode(bodyBytes)) as Map; + final message = data['message'] as String; + + return Response( + json.encode({ + 'ocs': { + 'meta': {'status': '', 'statuscode': 0}, + 'data': getChatMessage( + id: messageCount++, + systemMessage: 'message_edited', + messageType: spreed.MessageType.system, + parent: getChatMessage( + id: id, + message: message, + ), + ), + }, + }), + 200, + headers: { + 'content-type': 'application/json', + }, + ); + }, }, RegExp(r'/ocs/v2\.php/apps/spreed/api/v1/chat/abcd'): { 'get': (match, bodyBytes) async { @@ -385,6 +411,14 @@ void main() { null, ]), ); + expect( + roomBloc.editing, + emitsInOrder([ + null, + null, + null, + ]), + ); // The delay is necessary to avoid a race condition with loading twice at the same time await Future.delayed(const Duration(milliseconds: 1)); @@ -396,6 +430,48 @@ void main() { ..sendMessage(''); }); + test('Edit', () async { + final message = MockChatMessage(); + when(() => message.id).thenReturn(1); + + expect( + roomBloc.messages.transformResult((e) => BuiltList(e.map((m) => m.message))), + emitsInOrder([ + Result>.loading(), + Result.success(BuiltList(['', '', ''])), + Result.success(BuiltList(['', 'test', ''])), + ]), + ); + + expect( + roomBloc.editing, + emitsInOrder([ + null, + message, + null, + message, + null, + ]), + ); + expect( + roomBloc.replyTo, + emitsInOrder([ + null, + null, + null, + ]), + ); + + // The delay is necessary to avoid a race condition with loading twice at the same time + await Future.delayed(const Duration(milliseconds: 1)); + + roomBloc + ..setEditChatMessage(message) + ..removeEditChatMessage() + ..setEditChatMessage(message) + ..sendMessage('test'); + }); + test('addReaction', () async { expect( roomBloc.messages.transformResult((e) => BuiltList>(e.map((m) => m.reactions))), diff --git a/packages/neon/neon_talk/test/room_page_test.dart b/packages/neon/neon_talk/test/room_page_test.dart index adcf7935ce1..c43005ca7f6 100644 --- a/packages/neon/neon_talk/test/room_page_test.dart +++ b/packages/neon/neon_talk/test/room_page_test.dart @@ -54,6 +54,7 @@ void main() { .thenAnswer((_) => BehaviorSubject.seeded(Result.success(BuiltList()))); when(() => bloc.lastCommonRead).thenAnswer((_) => BehaviorSubject.seeded(0)); when(() => bloc.replyTo).thenAnswer((_) => BehaviorSubject.seeded(null)); + when(() => bloc.editing).thenAnswer((_) => BehaviorSubject.seeded(null)); referencesBloc = MockReferencesBloc(); when(() => referencesBloc.referenceRegex).thenAnswer((_) => BehaviorSubject.seeded(Result.success(null))); @@ -216,51 +217,4 @@ void main() { expect(find.byIcon(Icons.emoji_emotions_outlined), findsNothing); await expectLater(find.byType(TestApp), matchesGoldenFile('goldens/room_page_read_only.png')); }); - - testWidgets('Reply', (tester) async { - final replyTo = BehaviorSubject.seeded(null); - - when(() => bloc.replyTo).thenAnswer((_) => replyTo); - - final account = MockAccount(); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); - - await tester.pumpWidgetWithAccessibility( - TestApp( - localizationsDelegates: TalkLocalizations.localizationsDelegates, - supportedLocales: TalkLocalizations.supportedLocales, - appThemes: const [ - TalkTheme(), - ], - providers: [ - Provider.value(value: account), - NeonProvider.value(value: bloc), - NeonProvider.value(value: referencesBloc), - ], - child: const TalkRoomPage(), - ), - ); - await tester.pumpAndSettle(); - - expect(find.byType(TalkParentMessage), findsNothing); - - final message = MockChatMessage(); - when(() => message.messageType).thenReturn(spreed.MessageType.comment); - when(() => message.timestamp).thenReturn(0); - when(() => message.actorId).thenReturn('test'); - when(() => message.actorDisplayName).thenReturn('test'); - when(() => message.message).thenReturn('abc'); - when(() => message.messageParameters).thenReturn(BuiltMap()); - - replyTo.add(message); - await tester.pumpAndSettle(); - - expect(find.byType(TalkParentMessage), findsOne); - await expectLater(find.byType(TestApp), matchesGoldenFile('goldens/room_page_reply.png')); - - await tester.tap(find.byIcon(Icons.close)); - verify(() => bloc.removeReplyChatMessage()).called(1); - - unawaited(replyTo.close()); - }); }