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 00000000000..4d4b8fc7a34 Binary files /dev/null and b/packages/neon_framework/packages/talk_app/test/goldens/room_page_no_chat_permission.png differ 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')); + }); }