From a51149199a6ff5d5c47eb119678fd853398e79f8 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Fri, 15 Nov 2024 14:39:56 +0100 Subject: [PATCH] fix(talk_app): Add missing read-only and chat permissions checks for reactions and message input Signed-off-by: provokateurin --- .../packages/talk_app/lib/src/pages/room.dart | 6 +- .../talk_app/lib/src/widgets/message.dart | 1 + .../talk_app/lib/src/widgets/reactions.dart | 81 ++++++++++-------- .../goldens/room_page_no_chat_permission.png | Bin 0 -> 24967 bytes .../talk_app/test/reactions_test.dart | 57 ++++++++++++ .../talk_app/test/room_page_test.dart | 23 ++++- 6 files changed, 130 insertions(+), 38 deletions(-) create mode 100644 packages/neon_framework/packages/talk_app/test/goldens/room_page_no_chat_permission.png diff --git a/packages/neon_framework/packages/talk_app/lib/src/pages/room.dart b/packages/neon_framework/packages/talk_app/lib/src/pages/room.dart index 85c8c25951f..7852157cbc0 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/pages/room.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/pages/room.dart @@ -6,6 +6,7 @@ import 'package:intl/intl.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/utils.dart'; import 'package:neon_framework/widgets.dart'; +import 'package:nextcloud/spreed.dart' as spreed; import 'package:talk_app/src/blocs/room.dart'; import 'package:talk_app/src/theme.dart'; import 'package:talk_app/src/utils/helpers.dart'; @@ -175,7 +176,10 @@ class _TalkRoomPageState extends State { ), ); - if (room.readOnly == 0) { + if (room.readOnly == 0 && + spreed.ParticipantPermission.values + .byBinary(room.permissions) + .contains(spreed.ParticipantPermission.canSendMessageAndShareAndReact)) { body = Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.end, diff --git a/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart b/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart index a8db63d66d1..9b937ba5fbf 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart @@ -436,6 +436,7 @@ class _TalkCommentMessageState extends State { ), if (!widget.isParent && widget.chatMessage.reactions.isNotEmpty) TalkReactions( + room: widget.room, chatMessage: widget.chatMessage, ), ] diff --git a/packages/neon_framework/packages/talk_app/lib/src/widgets/reactions.dart b/packages/neon_framework/packages/talk_app/lib/src/widgets/reactions.dart index 946ed63000c..cf57e22c742 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/widgets/reactions.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/widgets/reactions.dart @@ -12,10 +12,14 @@ import 'package:talk_app/src/widgets/reactions_overview_dialog.dart'; class TalkReactions extends StatelessWidget { /// Creates new Talk reactions. const TalkReactions({ + required this.room, required this.chatMessage, super.key, }); + /// {@macro TalkMessage.room} + final spreed.Room room; + /// The chat message to display the reactions for. final spreed.$ChatMessageInterface chatMessage; @@ -23,6 +27,11 @@ class TalkReactions extends StatelessWidget { Widget build(BuildContext context) { final bloc = NeonProvider.of(context); + final canUpdateReactions = room.readOnly == 0 && + spreed.ParticipantPermission.values + .byBinary(room.permissions) + .contains(spreed.ParticipantPermission.canSendMessageAndShareAndReact); + const shape = RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(50)), ); @@ -61,46 +70,50 @@ class TalkReactions extends StatelessWidget { bottom: -2.5, right: 10, ), - onPressed: () { - if (isSelf) { - bloc.removeReaction(chatMessage, reaction.key); - } else { - bloc.addReaction(chatMessage, reaction.key); - } - }, + onPressed: canUpdateReactions + ? () { + if (isSelf) { + bloc.removeReaction(chatMessage, reaction.key); + } else { + bloc.addReaction(chatMessage, reaction.key); + } + } + : null, ), ); } - children.add( - ActionChip( - shape: shape, - avatar: Icon( - Icons.add_reaction_outlined, - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 16, - ), - label: const SizedBox(), - padding: EdgeInsets.zero, - labelPadding: const EdgeInsets.symmetric(vertical: -2.5), - tooltip: TalkLocalizations.of(context).reactionsAddNew, - onPressed: () async { - final reaction = await showDialog( - context: context, - builder: (context) => const NeonEmojiPickerDialog(), - ); - if (reaction == null) { - return; - } + if (canUpdateReactions) { + children.add( + ActionChip( + shape: shape, + avatar: Icon( + Icons.add_reaction_outlined, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 16, + ), + label: const SizedBox(), + padding: EdgeInsets.zero, + labelPadding: const EdgeInsets.symmetric(vertical: -2.5), + tooltip: TalkLocalizations.of(context).reactionsAddNew, + onPressed: () async { + final reaction = await showDialog( + context: context, + builder: (context) => const NeonEmojiPickerDialog(), + ); + if (reaction == null) { + return; + } - if (!context.mounted) { - return; - } + if (!context.mounted) { + return; + } - bloc.addReaction(chatMessage, reaction); - }, - ), - ); + bloc.addReaction(chatMessage, reaction); + }, + ), + ); + } if (chatMessage.reactions.isNotEmpty) { children.add( diff --git a/packages/neon_framework/packages/talk_app/test/goldens/room_page_no_chat_permission.png b/packages/neon_framework/packages/talk_app/test/goldens/room_page_no_chat_permission.png new file mode 100644 index 0000000000000000000000000000000000000000..4d4b8fc7a349ba9a4068d560714b61a9a83e55c1 GIT binary patch literal 24967 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 L80VjK{pCLa)bA0l literal 0 HcmV?d00001 diff --git a/packages/neon_framework/packages/talk_app/test/reactions_test.dart b/packages/neon_framework/packages/talk_app/test/reactions_test.dart index 608f66b91c1..5561043dfb0 100644 --- a/packages/neon_framework/packages/talk_app/test/reactions_test.dart +++ b/packages/neon_framework/packages/talk_app/test/reactions_test.dart @@ -31,6 +31,7 @@ spreed.Reaction getReaction() { } void main() { + late spreed.Room room; late spreed.ChatMessage chatMessage; late TalkRoomBloc bloc; @@ -42,6 +43,10 @@ void main() { }); setUp(() { + room = MockRoom(); + when(() => room.readOnly).thenReturn(0); + when(() => room.permissions).thenReturn(spreed.ParticipantPermission.canSendMessageAndShareAndReact.binary); + chatMessage = MockChatMessage(); when(() => chatMessage.id).thenReturn(0); when(() => chatMessage.reactions).thenReturn(BuiltMap({'😀': 1, '😊': 2})); @@ -70,6 +75,7 @@ void main() { NeonProvider.value(value: bloc), ], child: TalkReactions( + room: room, chatMessage: chatMessage, ), ), @@ -90,6 +96,7 @@ void main() { NeonProvider.value(value: bloc), ], child: TalkReactions( + room: room, chatMessage: chatMessage, ), ), @@ -109,6 +116,7 @@ void main() { NeonProvider.value(value: bloc), ], child: TalkReactions( + room: room, chatMessage: chatMessage, ), ), @@ -130,6 +138,7 @@ void main() { NeonProvider.value(value: bloc), ], child: TalkReactions( + room: room, chatMessage: chatMessage, ), ), @@ -156,6 +165,7 @@ void main() { NeonProvider.value(value: bloc), ], child: TalkReactions( + room: room, chatMessage: chatMessage, ), ), @@ -183,6 +193,7 @@ void main() { Provider.value(value: account), ], child: TalkReactions( + room: room, chatMessage: chatMessage, ), ), @@ -195,4 +206,50 @@ void main() { expect(find.byType(TalkReactionsOverviewDialog), findsOne); }); }); + + testWidgets('Read-only', (tester) async { + when(() => room.readOnly).thenReturn(1); + + await tester.pumpWidgetWithAccessibility( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: bloc), + ], + child: TalkReactions( + room: room, + chatMessage: chatMessage, + ), + ), + ); + + expect(find.byIcon(Icons.add_reaction_outlined), findsNothing); + + await tester.tap(find.text('😀'), warnIfMissed: false); + verifyNever(() => bloc.addReaction(chatMessage, '😀')); + }); + + testWidgets('No chat permission', (tester) async { + when(() => room.permissions).thenReturn(0); + + await tester.pumpWidgetWithAccessibility( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: bloc), + ], + child: TalkReactions( + room: room, + chatMessage: chatMessage, + ), + ), + ); + + expect(find.byIcon(Icons.add_reaction_outlined), findsNothing); + + await tester.tap(find.text('😀'), warnIfMissed: false); + verifyNever(() => bloc.addReaction(chatMessage, '😀')); + }); } diff --git a/packages/neon_framework/packages/talk_app/test/room_page_test.dart b/packages/neon_framework/packages/talk_app/test/room_page_test.dart index 00d50a564bf..ba124538f0f 100644 --- a/packages/neon_framework/packages/talk_app/test/room_page_test.dart +++ b/packages/neon_framework/packages/talk_app/test/room_page_test.dart @@ -4,7 +4,6 @@ import 'package:built_collection/built_collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/models.dart'; @@ -18,6 +17,7 @@ import 'package:talk_app/src/blocs/room.dart'; import 'package:talk_app/src/pages/room.dart'; import 'package:talk_app/src/theme.dart'; import 'package:talk_app/src/widgets/message.dart'; +import 'package:talk_app/src/widgets/message_input.dart'; import 'package:timezone/data/latest.dart' as tzdata; import 'package:timezone/timezone.dart' as tz; @@ -215,8 +215,25 @@ void main() { ), ); - expect(find.byType(TypeAheadField), findsNothing); - expect(find.byIcon(Icons.emoji_emotions_outlined), findsNothing); + expect(find.byType(TalkMessageInput), findsNothing); await expectLater(find.byType(TestApp), matchesGoldenFile('goldens/room_page_read_only.png')); }); + + testWidgets('No chat permission', (tester) async { + when(() => room.permissions).thenReturn(0); + + await tester.pumpWidgetWithAccessibility( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: bloc), + ], + child: const TalkRoomPage(), + ), + ); + + expect(find.byType(TalkMessageInput), findsNothing); + await expectLater(find.byType(TestApp), matchesGoldenFile('goldens/room_page_no_chat_permission.png')); + }); }