diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index ffc30572..0f3b914a 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -2,6 +2,10 @@ - Updated Dart SDK constraints to `>=2.14.0 <3.0.0` +✅ Added + +- `MessageListViewThemeData` now accepts a `DecorationImage` as a background image for `MessageListView`. + ## 3.1.1 - Updated `stream_chat_flutter_core` dependency to [`3.1.1`](https://pub.dev/packages/stream_chat_flutter_core/changelog). diff --git a/packages/stream_chat_flutter/example/assets/background_doodle.png b/packages/stream_chat_flutter/example/assets/background_doodle.png new file mode 100644 index 00000000..9ddf754f Binary files /dev/null and b/packages/stream_chat_flutter/example/assets/background_doodle.png differ diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart index af59d300..423bd7db 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart @@ -63,6 +63,12 @@ class MyApp extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), ), + messageListViewTheme: const MessageListViewThemeData( + backgroundColor: Colors.grey, + backgroundImage: DecorationImage( + image: AssetImage('assets/background_doodle.png'), + fit: BoxFit.cover, + )), otherMessageTheme: MessageThemeData( messageBackgroundColor: colorTheme.textHighEmphasis, messageTextStyle: TextStyle( diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 9c7ae6ee..9977c198 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -47,8 +47,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg + assets: + - assets/background_doodle.png # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index ad2ff455..bc903a1c 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -638,10 +638,14 @@ class _MessageListViewState extends State { ); final backgroundColor = MessageListViewTheme.of(context).backgroundColor; + final backgroundImage = MessageListViewTheme.of(context).backgroundImage; - if (backgroundColor != null) { - return ColoredBox( - color: backgroundColor, + if (backgroundColor != null || backgroundImage != null) { + return Container( + decoration: BoxDecoration( + color: backgroundColor, + image: backgroundImage, + ), child: child, ); } @@ -965,7 +969,7 @@ class _MessageListViewState extends State { } final userId = StreamChat.of(context).currentUser!.id; - final isMyMessage = message.user!.id == userId; + final isMyMessage = message.user?.id == userId; final nextMessage = index - 1 >= 0 ? messages[index - 1] : null; final isNextUserSame = nextMessage != null && message.user!.id == nextMessage.user!.id; diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 45ea8122..5ba78a26 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -987,7 +987,7 @@ class _MessageWidgetState extends State return widget.usernameBuilder!(context, widget.message); } return Text( - widget.message.user!.name, + widget.message.user?.name ?? '', maxLines: 1, key: usernameKey, style: widget.messageTheme.messageAuthorStyle, diff --git a/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart index 8ad8ea9d..1e968fed 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart @@ -60,17 +60,23 @@ class MessageListViewThemeData with Diagnosticable { /// Creates a [MessageListViewThemeData]. const MessageListViewThemeData({ this.backgroundColor, + this.backgroundImage, }); /// The color of the [MessageListView] background. final Color? backgroundColor; + /// The image of the [MessageListView] background. + final DecorationImage? backgroundImage; + /// Copies this [MessageListViewThemeData] to another. MessageListViewThemeData copyWith({ Color? backgroundColor, + DecorationImage? backgroundImage, }) => MessageListViewThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, + backgroundImage: backgroundImage ?? this.backgroundImage, ); /// Linearly interpolate between two [MessageListView] themes. @@ -83,6 +89,7 @@ class MessageListViewThemeData with Diagnosticable { ) => MessageListViewThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundImage: t < 0.5 ? a.backgroundImage : b.backgroundImage, ); /// Merges one [MessageListViewThemeData] with another. @@ -90,6 +97,7 @@ class MessageListViewThemeData with Diagnosticable { if (other == null) return this; return copyWith( backgroundColor: other.backgroundColor, + backgroundImage: other.backgroundImage, ); } @@ -98,14 +106,20 @@ class MessageListViewThemeData with Diagnosticable { identical(this, other) || other is MessageListViewThemeData && runtimeType == other.runtimeType && - backgroundColor == other.backgroundColor; + backgroundColor == other.backgroundColor && + backgroundImage == other.backgroundImage; @override - int get hashCode => backgroundColor.hashCode; + int get hashCode => backgroundColor.hashCode + backgroundImage.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties + ..add(ColorProperty('backgroundColor', backgroundColor)) + ..add( + DiagnosticsProperty('backgroundImage', backgroundImage, + defaultValue: null), + ); } } diff --git a/packages/stream_chat_flutter/test/src/message_list_view_test.dart b/packages/stream_chat_flutter/test/src/message_list_view_test.dart index 8217f6de..b82d53b3 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view_test.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -8,13 +9,21 @@ import 'mocks.dart'; void main() { late StreamChatClient client; late Channel channel; + late ChannelClientState channelClientState; + late ClientState clientState; setUp(() { client = MockClient(); + clientState = MockClientState(); + when(() => client.state).thenAnswer((_) => clientState); + when(() => clientState.currentUser).thenReturn(OwnUser(id: 'testid')); + when(() => clientState.currentUserStream) + .thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); channel = MockChannel(); when(() => channel.on(any(), any(), any(), any())) .thenAnswer((_) => const Stream.empty()); - final channelClientState = MockChannelState(); + channelClientState = MockChannelState(); + when(() => channel.client).thenReturn(client); when(() => channel.state).thenReturn(channelClientState); when(() => channelClientState.threadsStream) .thenAnswer((_) => const Stream.empty()); @@ -22,6 +31,17 @@ void main() { .thenAnswer((_) => const Stream.empty()); when(() => channelClientState.messages).thenReturn([]); when(() => channelClientState.isUpToDate).thenReturn(true); + when(() => channelClientState.isUpToDateStream) + .thenAnswer((_) => Stream.value(true)); + when(() => channelClientState.unreadCountStream) + .thenAnswer((_) => Stream.value(0)); + when(() => channelClientState.unreadCount).thenReturn(0); + when(() => channelClientState.readStream) + .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.read).thenReturn([]); + when(() => channelClientState.membersStream) + .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.members).thenReturn([]); }); // https://github.com/GetStream/stream-chat-flutter/issues/674 @@ -46,4 +66,65 @@ void main() { expect(find.byType(MessageListView), findsOneWidget); expect(find.byKey(emptyWidgetKey), findsOneWidget); }); + + testWidgets('renders a non empty message list view with custom background', + (tester) async { + final message = Message( + id: 'message1', + text: 'Hello world!', + user: User( + id: 'user', + name: 'Test User', + ), + ); + + when(() => channelClientState.messagesStream).thenAnswer( + (_) => Stream.value([message]), + ); + when(() => channelClientState.messages).thenReturn([message]); + + const nonEmptyWidgetKey = Key('non_empty_widget'); + await tester.runAsync(() async { + await tester.pumpWidget( + MaterialApp( + home: DefaultAssetBundle( + bundle: rootBundle, + child: StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.light().copyWith( + messageListViewTheme: const MessageListViewThemeData( + backgroundColor: Colors.grey, + backgroundImage: DecorationImage( + image: AssetImage('images/placeholder.png'), + fit: BoxFit.none, + ), + ), + ), + child: StreamChannel( + channel: channel, + child: const MessageListView( + key: nonEmptyWidgetKey, + ), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + }); + + bool findBackground(Widget widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration! as BoxDecoration).image != null; + + expect(find.byType(MessageListView), findsOneWidget); + expect(find.byKey(nonEmptyWidgetKey), findsOneWidget); + expect( + find.byWidgetPredicate( + findBackground, + description: 'findBackground', + ), + findsOneWidget); + }); } diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 28e00e4b..443f770c 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -5,6 +5,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class MockClient extends Mock implements StreamChatClient { MockClient() { when(() => wsConnectionStatus).thenReturn(ConnectionStatus.connected); + when(() => wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); } } diff --git a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart index 476f5063..94becc06 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart @@ -109,6 +109,37 @@ void main() { expect(messageListViewTheme.backgroundColor, _messageListViewThemeDataControlDark.backgroundColor); }); + + testWidgets( + 'Pass backgroundImage to MessageListViewThemeData return backgroundImage', + (WidgetTester tester) async { + late BuildContext _context; + await tester.pumpWidget( + MaterialApp( + builder: (context, child) => StreamChat( + client: MockStreamChatClient(), + streamChatThemeData: StreamChatThemeData.light() + .copyWith(messageListViewTheme: _messageListViewThemeDataImage), + child: child, + ), + home: Builder( + builder: (BuildContext context) { + _context = context; + return Scaffold( + body: StreamChannel( + channel: MockChannel(), + child: const MessageListView(), + ), + ); + }, + ), + ), + ); + + final messageListViewTheme = MessageListViewTheme.of(_context); + expect(messageListViewTheme.backgroundImage, + _messageListViewThemeDataImage.backgroundImage); + }); } final _messageListViewThemeDataControl = MessageListViewThemeData( @@ -122,3 +153,10 @@ const _messageListViewThemeDataControlHalfLerp = MessageListViewThemeData( final _messageListViewThemeDataControlDark = MessageListViewThemeData( backgroundColor: ColorTheme.dark().barsBg, ); + +const _messageListViewThemeDataImage = MessageListViewThemeData( + backgroundImage: DecorationImage( + image: AssetImage('example/assets/background_doodle.png'), + fit: BoxFit.cover, + ), +);