Skip to content

Commit

Permalink
feat(neon_talk): Enable message deletion
Browse files Browse the repository at this point in the history
Signed-off-by: provokateurin <kate@provokateurin.de>
  • Loading branch information
provokateurin committed Jun 24, 2024
1 parent 7b6d81e commit 1ec45aa
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 12 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 @@ -17,6 +17,7 @@
"roomMessageSend": "Send message",
"roomMessageReply": "Reply",
"roomMessageReaction": "Add reaction",
"roomMessageDelete": "Delete",
"reactionsAddNew": "Add a new reaction",
"reactionsLoading": "Loading reactions",
"roomsCreateNew": "Create new room"
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 @@ -161,6 +161,12 @@ abstract class TalkLocalizations {
/// **'Add reaction'**
String get roomMessageReaction;

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

/// No description provided for @reactionsAddNew.
///
/// 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 @@ -53,6 +53,9 @@ class TalkLocalizationsEn extends TalkLocalizations {
@override
String get roomMessageReaction => 'Add reaction';

@override
String get roomMessageDelete => 'Delete';

@override
String get reactionsAddNew => 'Add a new reaction';

Expand Down
32 changes: 25 additions & 7 deletions packages/neon/neon_talk/lib/src/blocs/room.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ abstract class TalkRoomBloc implements InteractiveBloc {
/// Removes the current [replyTo] chat message.
void removeReplyChatMessage();

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

/// The current room data.
BehaviorSubject<Result<spreed.Room>> get room;

Expand Down Expand Up @@ -321,6 +324,26 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {
replyTo.add(null);
}

@override
Future<void> deleteMessage(spreed.$ChatMessageInterface chatMessage) async {
await wrapAction(
() async {
final response = await account.client.spreed.chat.deleteMessage(
token: token,
messageId: chatMessage.id,
);

updateLastCommonRead(response.headers.xChatLastCommonRead);

final m = response.body.ocs.data;
updateLastKnownMessageId(m.id);

prependMessages([m]);
},
refresh: () async {},
);
}

void updateLastCommonRead(String? header) {
if (header != null) {
final id = int.parse(header);
Expand All @@ -342,13 +365,8 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc {
if (messages.hasValue) {
final result = messages.value;
if (result.hasData) {
final lastMessageID = newMessages.lastOrNull?.id;

if (lastMessageID == null) {
builder.addAll(result.requireData);
} else {
builder.addAll(result.requireData.where((message) => message.id < lastMessageID));
}
final lastMessageID = newMessages.last.id;
builder.addAll(result.requireData.where((message) => message.id < lastMessageID));
}

// Skip messages without parents as we can't know which message should be updated
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 @@ -620,6 +620,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.delete_forever),
child: Text(TalkLocalizations.of(context).roomMessageDelete),
onPressed: () {
setState(() {
menuOpen = false;
});

NeonProvider.of<TalkRoomBloc>(context).deleteMessage(chatMessage);
},
),
];

if (children.isEmpty) {
Expand Down
121 changes: 118 additions & 3 deletions packages/neon/neon_talk/test/message_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,7 @@ void main() {

when(() => room.readOnly).thenReturn(0);
when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary);
when(() => room.actorId).thenReturn('test');

await tester.pumpWidgetWithAccessibility(
wrapWidget(
Expand Down Expand Up @@ -796,6 +797,7 @@ void main() {

when(() => room.readOnly).thenReturn(1);
when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary);
when(() => room.actorId).thenReturn('test');

await tester.pumpWidgetWithAccessibility(
wrapWidget(
Expand All @@ -818,14 +820,17 @@ void main() {
await gesture.moveTo(tester.getCenter(find.byType(TalkCommentMessage)));
await tester.pumpAndSettle();

expect(find.byIcon(Icons.more_vert), findsNothing);
await tester.tap(find.byIcon(Icons.more_vert));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.add_reaction_outlined), findsNothing);
});

testWidgets('No permission', (tester) async {
SharedPreferences.setMockInitialValues({});

when(() => room.readOnly).thenReturn(0);
when(() => room.permissions).thenReturn(0);
when(() => room.actorId).thenReturn('test');

await tester.pumpWidgetWithAccessibility(
wrapWidget(
Expand All @@ -848,14 +853,17 @@ void main() {
await gesture.moveTo(tester.getCenter(find.byType(TalkCommentMessage)));
await tester.pumpAndSettle();

expect(find.byIcon(Icons.more_vert), findsNothing);
await tester.tap(find.byIcon(Icons.more_vert));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.add_reaction_outlined), findsNothing);
});
});

group('Reply', () {
testWidgets('Allowed', (tester) async {
when(() => room.readOnly).thenReturn(0);
when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary);
when(() => room.actorId).thenReturn('test');

await tester.pumpWidgetWithAccessibility(
wrapWidget(
Expand Down Expand Up @@ -892,6 +900,7 @@ void main() {
testWidgets('Read-only', (tester) async {
when(() => room.readOnly).thenReturn(1);
when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary);
when(() => room.actorId).thenReturn('test');

await tester.pumpWidgetWithAccessibility(
wrapWidget(
Expand All @@ -914,12 +923,118 @@ void main() {
await gesture.moveTo(tester.getCenter(find.byType(TalkCommentMessage)));
await tester.pumpAndSettle();

expect(find.byIcon(Icons.more_vert), findsNothing);
await tester.tap(find.byIcon(Icons.more_vert));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.reply), findsNothing);
});

testWidgets('No permission', (tester) async {
when(() => room.readOnly).thenReturn(0);
when(() => room.permissions).thenReturn(0);
when(() => room.actorId).thenReturn('test');

await tester.pumpWidgetWithAccessibility(
wrapWidget(
providers: [
Provider<Account>.value(value: account),
NeonProvider<TalkRoomBloc>.value(value: roomBloc),
],
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.reply), findsNothing);
});
});

group('Delete', () {
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<Account>.value(value: account),
NeonProvider<TalkRoomBloc>.value(value: roomBloc),
],
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.delete_forever));
await tester.pumpAndSettle();

verify(() => roomBloc.deleteMessage(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<Account>.value(value: account),
NeonProvider<TalkRoomBloc>.value(value: roomBloc),
],
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.delete_forever), 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(
Expand Down
51 changes: 51 additions & 0 deletions packages/neon/neon_talk/test/room_bloc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:neon_framework/models.dart';
import 'package:neon_framework/testing.dart';
import 'package:neon_talk/src/blocs/room.dart';
import 'package:neon_talk/src/blocs/talk.dart';
import 'package:nextcloud/spreed.dart' as spreed;

import 'testing.dart';

Expand Down Expand Up @@ -38,6 +39,29 @@ Account mockTalkAccount() {
200,
),
},
RegExp(r'/ocs/v2\.php/apps/spreed/api/v1/chat/abcd/([0-9]+)'): {
'delete': (match, queryParameters) {
final id = int.parse(match.group(1)!);

return Response(
json.encode({
'ocs': {
'meta': {'status': '', 'statuscode': 0},
'data': getChatMessage(
id: messageCount++,
systemMessage: 'message_deleted',
messageType: spreed.MessageType.system,
parent: getChatMessage(
id: id,
messageType: spreed.MessageType.commentDeleted,
),
),
},
}),
200,
);
},
},
RegExp(r'/ocs/v2\.php/apps/spreed/api/v1/chat/abcd'): {
'get': (match, queryParameters) async {
final lookIntoFuture = queryParameters['lookIntoFuture']!.single == '1';
Expand Down Expand Up @@ -525,6 +549,33 @@ void main() {
roomBloc.loadReactions(message);
});

test('deleteMessage', () async {
expect(
roomBloc.messages.transformResult((e) => BuiltList<spreed.MessageType>(e.map((m) => m.messageType))),
emitsInOrder([
Result<BuiltList<spreed.MessageType>>.loading(),
Result.success(
BuiltList<spreed.MessageType>([
spreed.MessageType.comment,
spreed.MessageType.comment,
spreed.MessageType.comment,
]),
),
Result.success(
BuiltList<spreed.MessageType>([
spreed.MessageType.comment,
spreed.MessageType.commentDeleted,
spreed.MessageType.comment,
]),
),
]),
);

final message = MockChatMessage();
when(() => message.id).thenReturn(1);
roomBloc.deleteMessage(message);
});

test('polling', () async {
expect(
roomBloc.messages.transformResult(
Expand Down
1 change: 1 addition & 0 deletions packages/neon/neon_talk/test/room_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void main() {
when(() => room.readOnly).thenReturn(0);
when(() => room.isCustomAvatar).thenReturn(false);
when(() => room.type).thenReturn(spreed.RoomType.group.value);
when(() => room.actorId).thenReturn('');

bloc = MockRoomBloc();
when(() => bloc.errors).thenAnswer((_) => StreamController<Object>().stream);
Expand Down
5 changes: 3 additions & 2 deletions packages/neon/neon_talk/test/testing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,19 @@ Map<String, dynamic> getChatMessage({
Map<String, dynamic>? reactions,
String? systemMessage,
Map<String, dynamic>? parent,
spreed.MessageType? messageType,
}) =>
{
'actorDisplayName': '',
'actorId': '',
'actorType': spreed.ActorType.users.name,
'actorType': spreed.ActorType.users.value,
'expirationTimestamp': 0,
'id': id ?? 0,
'isReplyable': false,
'markdown': false,
'message': message ?? '',
'messageParameters': <dynamic, dynamic>{},
'messageType': spreed.MessageType.comment.name,
'messageType': (messageType ?? spreed.MessageType.comment).value,
'reactions': reactions ?? <dynamic, dynamic>{},
'referenceId': '',
'systemMessage': systemMessage ?? '',
Expand Down

0 comments on commit 1ec45aa

Please sign in to comment.