diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index ffc30572..c67b1e7d 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -34,6 +34,10 @@ - `UserListView` `filter` property now is non-nullable. +✅ Added + +- `MessageListViewThemeData` now accepts a `DecorationImage` as a background image for `MessageListView`. + 🐞 Fixed - [[#668]](https://github.com/GetStream/stream-chat-flutter/issues/668): Fix `MessageInput` rendering errors in case 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..d53cc716 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, ); } 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..1148d6ca 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,8 @@ +import 'dart:convert'; +import 'dart:typed_data'; + 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'; @@ -20,7 +24,8 @@ void main() { .thenAnswer((_) => const Stream.empty()); when(() => channelClientState.messagesStream) .thenAnswer((_) => const Stream.empty()); - when(() => channelClientState.messages).thenReturn([]); + when(() => channelClientState.messages) + .thenReturn([Message(text: 'Hello World!')]); when(() => channelClientState.isUpToDate).thenReturn(true); }); @@ -46,4 +51,52 @@ void main() { expect(find.byType(MessageListView), findsOneWidget); expect(find.byKey(emptyWidgetKey), findsOneWidget); }); + + testWidgets('renders empty message list view with custom background', + (tester) async { + const emptyWidgetKey = Key('empty_widget'); + + await tester.pumpWidget( + MaterialApp( + home: DefaultAssetBundle( + bundle: _TestAssetBundle(), + child: StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.light().copyWith( + messageListViewTheme: const MessageListViewThemeData( + backgroundColor: Colors.grey, + backgroundImage: DecorationImage( + image: AssetImage('assets/background.png'), + fit: BoxFit.cover, + ))), + child: StreamChannel( + channel: channel, + child: MessageListView( + emptyBuilder: (_) => Container(key: emptyWidgetKey), + ), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(MessageListView), findsOneWidget); + expect(find.byKey(emptyWidgetKey), findsOneWidget); + expect(find.byType(DecorationImage, skipOffstage: false), findsOneWidget); + }); +} + +class _TestAssetBundle extends CachingAssetBundle { + @override + Future load(String key) async { + if (key == 'assets/background.png') { + return ByteData.view( + Uint8List.fromList( + utf8.encode('Background'), + ).buffer, + ); + } + return ByteData(0); + } } 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, + ), +);