diff --git a/packages/neon/neon_talk/lib/src/pages/main.dart b/packages/neon/neon_talk/lib/src/pages/main.dart index a495ca6fb0c..4214fd9f266 100644 --- a/packages/neon/neon_talk/lib/src/pages/main.dart +++ b/packages/neon/neon_talk/lib/src/pages/main.dart @@ -6,6 +6,7 @@ import 'package:neon_framework/utils.dart'; import 'package:neon_framework/widgets.dart'; import 'package:neon_talk/src/blocs/talk.dart'; import 'package:neon_talk/src/widgets/message_preview.dart'; +import 'package:neon_talk/src/widgets/room_avatar.dart'; import 'package:neon_talk/src/widgets/unread_indicator.dart'; import 'package:nextcloud/spreed.dart' as spreed; @@ -73,6 +74,9 @@ class _TalkMainPageState extends State { } return ListTile( + leading: TalkRoomAvatar( + room: room, + ), title: Text(room.displayName), subtitle: subtitle, trailing: trailing, diff --git a/packages/neon/neon_talk/lib/src/widgets/room_avatar.dart b/packages/neon/neon_talk/lib/src/widgets/room_avatar.dart new file mode 100644 index 00000000000..6360c5d579a --- /dev/null +++ b/packages/neon/neon_talk/lib/src/widgets/room_avatar.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.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; + +/// Displays the avatar of the [room]. +/// +/// If the room has a custom avatar it will be displayed. If that is not the case and it is a +/// [spreed.RoomType.oneToOne] the user avatar will be shown, otherwise an appropriate icon is displayed. +class TalkRoomAvatar extends StatelessWidget { + /// Creates a new Talk room avatar. + const TalkRoomAvatar({ + required this.room, + super.key, + }); + + /// The room to display the avatar for. + final spreed.Room room; + + @override + Widget build(BuildContext context) { + final account = NeonProvider.of(context).activeAccount.value!; + + if (room.isCustomAvatar ?? false) { + final brightness = Theme.of(context).brightness; + + return CircleAvatar( + child: ClipOval( + child: NeonApiImage.withAccount( + account: account, + getImage: (client) => switch (brightness) { + Brightness.dark => client.spreed.avatar.getAvatarDarkRaw(token: room.token), + Brightness.light => client.spreed.avatar.getAvatarRaw(token: room.token), + }, + cacheKey: 'talk-room-${room.token}-avatar-$brightness', + etag: room.avatarVersion, + expires: null, + ), + ), + ); + } + + return switch (spreed.RoomType.fromValue(room.type)) { + spreed.RoomType.oneToOne => NeonUserAvatar( + account: account, + username: room.name, + ), + spreed.RoomType.group => _buildIconAvatar(Icons.group), + spreed.RoomType.public => _buildIconAvatar(Icons.link), + spreed.RoomType.changelog => _buildIconAvatar(Icons.text_snippet_outlined), + spreed.RoomType.oneToOneFormer => _buildIconAvatar(Icons.lock), + spreed.RoomType.noteToSelf => _buildIconAvatar(Icons.edit_note), + }; + } + + Widget _buildIconAvatar(IconData icon) => CircleAvatar( + child: Icon( + icon, + ), + ); +} diff --git a/packages/neon/neon_talk/test/room_avatar_test.dart b/packages/neon/neon_talk/test/room_avatar_test.dart new file mode 100644 index 00000000000..4b3bccd9117 --- /dev/null +++ b/packages/neon/neon_talk/test/room_avatar_test.dart @@ -0,0 +1,101 @@ +import 'package:built_collection/built_collection.dart'; +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/testing.dart'; +import 'package:neon_framework/utils.dart'; +import 'package:neon_framework/widgets.dart'; +import 'package:neon_talk/src/widgets/room_avatar.dart'; +import 'package:nextcloud/nextcloud.dart'; +import 'package:nextcloud/spreed.dart' as spreed; +import 'package:rxdart/subjects.dart'; + +class MockRoom extends Mock implements spreed.Room {} + +Widget wrapWidget(AccountsBloc accountsBloc, Widget child) => MaterialApp( + home: NeonProvider( + create: (_) => accountsBloc, + child: child, + ), + ); + +void main() { + setUpAll(() { + final storage = MockNeonStorage(); + when(() => storage.requestCache).thenReturn(null); + }); + + testWidgets('Custom avatar', (tester) async { + final account = MockAccount(); + when(() => account.id).thenReturn(''); + when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); + + final accountsBloc = MockAccountsBloc(); + when(() => accountsBloc.activeAccount).thenAnswer((_) => BehaviorSubject.seeded(account)); + + final room = MockRoom(); + when(() => room.isCustomAvatar).thenReturn(true); + when(() => room.token).thenReturn('abc123'); + when(() => room.avatarVersion).thenReturn(''); + + await tester.pumpWidget( + wrapWidget( + accountsBloc, + TalkRoomAvatar( + room: room, + ), + ), + ); + expect(find.byType(NeonApiImage), findsOne); + }); + + testWidgets('One to one', (tester) async { + final account = MockAccount(); + when(() => account.id).thenReturn(''); + when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); + + final userStatusBloc = MockUserStatusBloc(); + when(() => userStatusBloc.statuses).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + + final accountsBloc = MockAccountsBloc(); + when(() => accountsBloc.activeAccount).thenAnswer((_) => BehaviorSubject.seeded(account)); + when(() => accountsBloc.getUserStatusBlocFor(account)).thenReturn(userStatusBloc); + + final room = MockRoom(); + when(() => room.isCustomAvatar).thenReturn(false); + when(() => room.type).thenReturn(spreed.RoomType.oneToOne.value); + when(() => room.name).thenReturn(''); + + await tester.pumpWidget( + wrapWidget( + accountsBloc, + TalkRoomAvatar( + room: room, + ), + ), + ); + expect(find.byType(NeonUserAvatar), findsOne); + }); + + testWidgets('Other', (tester) async { + final account = MockAccount(); + + final accountsBloc = MockAccountsBloc(); + when(() => accountsBloc.activeAccount).thenAnswer((_) => BehaviorSubject.seeded(account)); + + final room = MockRoom(); + when(() => room.isCustomAvatar).thenReturn(false); + when(() => room.type).thenReturn(spreed.RoomType.group.value); + + await tester.pumpWidget( + wrapWidget( + accountsBloc, + TalkRoomAvatar( + room: room, + ), + ), + ); + expect(find.byIcon(Icons.group), findsOne); + }); +}