feat(ui): add the ability to add a background image in MessageListView

This commit is contained in:
Efthymios Sarmpanis
2021-10-09 20:06:27 +03:00
parent 0146d2cc85
commit 7d90dc0cfe
8 changed files with 128 additions and 9 deletions
@@ -34,6 +34,10 @@
- `UserListView` `filter` property now is non-nullable. - `UserListView` `filter` property now is non-nullable.
✅ Added
- `MessageListViewThemeData` now accepts a `DecorationImage` as a background image for `MessageListView`.
🐞 Fixed 🐞 Fixed
- [[#668]](https://github.com/GetStream/stream-chat-flutter/issues/668): Fix `MessageInput` rendering errors in case - [[#668]](https://github.com/GetStream/stream-chat-flutter/issues/668): Fix `MessageInput` rendering errors in case
Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

@@ -63,6 +63,12 @@ class MyApp extends StatelessWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
messageListViewTheme: const MessageListViewThemeData(
backgroundColor: Colors.grey,
backgroundImage: DecorationImage(
image: AssetImage('assets/background_doodle.png'),
fit: BoxFit.cover,
)),
otherMessageTheme: MessageThemeData( otherMessageTheme: MessageThemeData(
messageBackgroundColor: colorTheme.textHighEmphasis, messageBackgroundColor: colorTheme.textHighEmphasis,
messageTextStyle: TextStyle( messageTextStyle: TextStyle(
@@ -47,8 +47,8 @@ flutter:
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: assets:
# - images/a_dot_burr.jpeg - assets/background_doodle.png
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
@@ -638,10 +638,14 @@ class _MessageListViewState extends State<MessageListView> {
); );
final backgroundColor = MessageListViewTheme.of(context).backgroundColor; final backgroundColor = MessageListViewTheme.of(context).backgroundColor;
final backgroundImage = MessageListViewTheme.of(context).backgroundImage;
if (backgroundColor != null) { if (backgroundColor != null || backgroundImage != null) {
return ColoredBox( return Container(
color: backgroundColor, decoration: BoxDecoration(
color: backgroundColor,
image: backgroundImage,
),
child: child, child: child,
); );
} }
@@ -60,17 +60,23 @@ class MessageListViewThemeData with Diagnosticable {
/// Creates a [MessageListViewThemeData]. /// Creates a [MessageListViewThemeData].
const MessageListViewThemeData({ const MessageListViewThemeData({
this.backgroundColor, this.backgroundColor,
this.backgroundImage,
}); });
/// The color of the [MessageListView] background. /// The color of the [MessageListView] background.
final Color? backgroundColor; final Color? backgroundColor;
/// The image of the [MessageListView] background.
final DecorationImage? backgroundImage;
/// Copies this [MessageListViewThemeData] to another. /// Copies this [MessageListViewThemeData] to another.
MessageListViewThemeData copyWith({ MessageListViewThemeData copyWith({
Color? backgroundColor, Color? backgroundColor,
DecorationImage? backgroundImage,
}) => }) =>
MessageListViewThemeData( MessageListViewThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor, backgroundColor: backgroundColor ?? this.backgroundColor,
backgroundImage: backgroundImage ?? this.backgroundImage,
); );
/// Linearly interpolate between two [MessageListView] themes. /// Linearly interpolate between two [MessageListView] themes.
@@ -83,6 +89,7 @@ class MessageListViewThemeData with Diagnosticable {
) => ) =>
MessageListViewThemeData( MessageListViewThemeData(
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
backgroundImage: t < 0.5 ? a.backgroundImage : b.backgroundImage,
); );
/// Merges one [MessageListViewThemeData] with another. /// Merges one [MessageListViewThemeData] with another.
@@ -90,6 +97,7 @@ class MessageListViewThemeData with Diagnosticable {
if (other == null) return this; if (other == null) return this;
return copyWith( return copyWith(
backgroundColor: other.backgroundColor, backgroundColor: other.backgroundColor,
backgroundImage: other.backgroundImage,
); );
} }
@@ -98,14 +106,20 @@ class MessageListViewThemeData with Diagnosticable {
identical(this, other) || identical(this, other) ||
other is MessageListViewThemeData && other is MessageListViewThemeData &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
backgroundColor == other.backgroundColor; backgroundColor == other.backgroundColor &&
backgroundImage == other.backgroundImage;
@override @override
int get hashCode => backgroundColor.hashCode; int get hashCode => backgroundColor.hashCode + backgroundImage.hashCode;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(ColorProperty('backgroundColor', backgroundColor)); properties
..add(ColorProperty('backgroundColor', backgroundColor))
..add(
DiagnosticsProperty<DecorationImage>('backgroundImage', backgroundImage,
defaultValue: null),
);
} }
} }
@@ -1,4 +1,8 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -20,7 +24,8 @@ void main() {
.thenAnswer((_) => const Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => channelClientState.messagesStream) when(() => channelClientState.messagesStream)
.thenAnswer((_) => const Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => channelClientState.messages).thenReturn([]); when(() => channelClientState.messages)
.thenReturn([Message(text: 'Hello World!')]);
when(() => channelClientState.isUpToDate).thenReturn(true); when(() => channelClientState.isUpToDate).thenReturn(true);
}); });
@@ -46,4 +51,52 @@ void main() {
expect(find.byType(MessageListView), findsOneWidget); expect(find.byType(MessageListView), findsOneWidget);
expect(find.byKey(emptyWidgetKey), 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<ByteData> load(String key) async {
if (key == 'assets/background.png') {
return ByteData.view(
Uint8List.fromList(
utf8.encode('Background'),
).buffer,
);
}
return ByteData(0);
}
} }
@@ -109,6 +109,37 @@ void main() {
expect(messageListViewTheme.backgroundColor, expect(messageListViewTheme.backgroundColor,
_messageListViewThemeDataControlDark.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( final _messageListViewThemeDataControl = MessageListViewThemeData(
@@ -122,3 +153,10 @@ const _messageListViewThemeDataControlHalfLerp = MessageListViewThemeData(
final _messageListViewThemeDataControlDark = MessageListViewThemeData( final _messageListViewThemeDataControlDark = MessageListViewThemeData(
backgroundColor: ColorTheme.dark().barsBg, backgroundColor: ColorTheme.dark().barsBg,
); );
const _messageListViewThemeDataImage = MessageListViewThemeData(
backgroundImage: DecorationImage(
image: AssetImage('example/assets/background_doodle.png'),
fit: BoxFit.cover,
),
);