diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index efac9508..4fe9e6e9 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,5 +1,11 @@ ## Upcoming +✅ Added + +- [#516](https://github.com/GetStream/stream-chat-flutter/issues/516): Added `StreamChatThemeData.placeholderUserImage` for + building a widget when the `UserAvatar` image is loading + + 🔄 Changed Theming has been upgraded! Most theme classes now have `InheritedTheme` classes diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index 5fe56dbc..f7a05d7e 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -54,6 +54,7 @@ class StreamChatThemeData { MessageThemeData? ownMessageTheme, MessageInputThemeData? messageInputTheme, Widget Function(BuildContext, User)? defaultUserImage, + Widget Function(BuildContext, User)? placeholderUserImage, IconThemeData? primaryIconTheme, List? reactionIcons, GalleryHeaderThemeData? imageHeaderTheme, @@ -68,7 +69,7 @@ class StreamChatThemeData { textTheme ??= isDark ? TextTheme.dark() : TextTheme.light(); colorTheme ??= isDark ? ColorTheme.dark() : ColorTheme.light(); - final defaultData = fromColorAndTextTheme( + final defaultData = StreamChatThemeData.fromColorAndTextTheme( colorTheme, textTheme, ); @@ -81,6 +82,7 @@ class StreamChatThemeData { ownMessageTheme: ownMessageTheme, messageInputTheme: messageInputTheme, defaultUserImage: defaultUserImage, + placeholderUserImage: placeholderUserImage, primaryIconTheme: primaryIconTheme, reactionIcons: reactionIcons, galleryHeaderTheme: imageHeaderTheme, @@ -113,6 +115,7 @@ class StreamChatThemeData { required this.ownMessageTheme, required this.messageInputTheme, required this.defaultUserImage, + this.placeholderUserImage, required this.primaryIconTheme, required this.reactionIcons, required this.galleryHeaderTheme, @@ -135,134 +138,8 @@ class StreamChatThemeData { return defaultTheme.merge(customizedTheme); } - /// The text themes used in the widgets - final TextTheme textTheme; - - /// The color themes used in the widgets - final ColorTheme colorTheme; - - /// Theme of the [ChannelPreview] - final ChannelPreviewThemeData channelPreviewTheme; - - /// Theme of the [ChannelListHeader] - final ChannelListHeaderThemeData channelListHeaderTheme; - - /// Theme of the chat widgets dedicated to a channel header - final ChannelHeaderThemeData channelHeaderTheme; - - /// The default style for [GalleryHeader]s below the overall - /// [StreamChatTheme]. - final GalleryHeaderThemeData galleryHeaderTheme; - - /// The default style for [GalleryFooter]s below the overall - /// [StreamChatTheme]. - final GalleryFooterThemeData galleryFooterTheme; - - /// Theme of the current user messages - final MessageThemeData ownMessageTheme; - - /// Theme of other users messages - final MessageThemeData otherMessageTheme; - - /// Theme dedicated to the [MessageInput] widget - final MessageInputThemeData messageInputTheme; - - /// The widget that will be built when the user image is unavailable - final Widget Function(BuildContext, User) defaultUserImage; - - /// Primary icon theme - final IconThemeData primaryIconTheme; - - /// Assets used for rendering reactions - final List reactionIcons; - - /// Theme configuration for the [MessageListView] widget. - final MessageListViewThemeData messageListViewTheme; - - /// Theme configuration for the [ChannelListView] widget. - final ChannelListViewThemeData channelListViewTheme; - - /// Theme configuration for the [UserListView] widget. - final UserListViewThemeData userListViewTheme; - - /// Theme configuration for the [MessageSearchListView] widget. - final MessageSearchListViewThemeData messageSearchListViewTheme; - - /// Creates a copy of [StreamChatThemeData] with specified attributes - /// overridden. - StreamChatThemeData copyWith({ - TextTheme? textTheme, - ColorTheme? colorTheme, - ChannelPreviewThemeData? channelPreviewTheme, - ChannelHeaderThemeData? channelHeaderTheme, - MessageThemeData? ownMessageTheme, - MessageThemeData? otherMessageTheme, - MessageInputThemeData? messageInputTheme, - Widget Function(BuildContext, User)? defaultUserImage, - IconThemeData? primaryIconTheme, - ChannelListHeaderThemeData? channelListHeaderTheme, - List? reactionIcons, - GalleryHeaderThemeData? galleryHeaderTheme, - GalleryFooterThemeData? galleryFooterTheme, - MessageListViewThemeData? messageListViewTheme, - ChannelListViewThemeData? channelListViewTheme, - UserListViewThemeData? userListViewTheme, - MessageSearchListViewThemeData? messageSearchListViewTheme, - }) => - StreamChatThemeData.raw( - channelListHeaderTheme: - this.channelListHeaderTheme.merge(channelListHeaderTheme), - textTheme: this.textTheme.merge(textTheme), - colorTheme: this.colorTheme.merge(colorTheme), - primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), - defaultUserImage: defaultUserImage ?? this.defaultUserImage, - channelPreviewTheme: - this.channelPreviewTheme.merge(channelPreviewTheme), - channelHeaderTheme: this.channelHeaderTheme.merge(channelHeaderTheme), - ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme), - otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), - messageInputTheme: this.messageInputTheme.merge(messageInputTheme), - reactionIcons: reactionIcons ?? this.reactionIcons, - galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, - galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, - messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, - channelListViewTheme: channelListViewTheme ?? this.channelListViewTheme, - userListViewTheme: userListViewTheme ?? this.userListViewTheme, - messageSearchListViewTheme: - messageSearchListViewTheme ?? this.messageSearchListViewTheme, - ); - - /// Merge themes - StreamChatThemeData merge(StreamChatThemeData? other) { - if (other == null) return this; - return copyWith( - channelListHeaderTheme: - channelListHeaderTheme.merge(other.channelListHeaderTheme), - textTheme: textTheme.merge(other.textTheme), - colorTheme: colorTheme.merge(other.colorTheme), - primaryIconTheme: other.primaryIconTheme, - defaultUserImage: other.defaultUserImage, - channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme), - channelHeaderTheme: channelHeaderTheme.merge(other.channelHeaderTheme), - ownMessageTheme: ownMessageTheme.merge(other.ownMessageTheme), - otherMessageTheme: otherMessageTheme.merge(other.otherMessageTheme), - messageInputTheme: messageInputTheme.merge(other.messageInputTheme), - reactionIcons: other.reactionIcons, - galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme), - galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme), - messageListViewTheme: - messageListViewTheme.merge(other.messageListViewTheme), - channelListViewTheme: - channelListViewTheme.merge(other.channelListViewTheme), - userListViewTheme: userListViewTheme.merge(other.userListViewTheme), - messageSearchListViewTheme: - messageSearchListViewTheme.merge(other.messageSearchListViewTheme), - ); - } - /// Create theme from color and text theme - // ignore: prefer_constructors_over_static_methods - static StreamChatThemeData fromColorAndTextTheme( + factory StreamChatThemeData.fromColorAndTextTheme( ColorTheme colorTheme, TextTheme textTheme, ) { @@ -487,4 +364,135 @@ class StreamChatThemeData { ), ); } + + /// The text themes used in the widgets + final TextTheme textTheme; + + /// The color themes used in the widgets + final ColorTheme colorTheme; + + /// Theme of the [ChannelPreview] + final ChannelPreviewThemeData channelPreviewTheme; + + /// Theme of the [ChannelListHeader] + final ChannelListHeaderThemeData channelListHeaderTheme; + + /// Theme of the chat widgets dedicated to a channel header + final ChannelHeaderThemeData channelHeaderTheme; + + /// The default style for [GalleryHeader]s below the overall + /// [StreamChatTheme]. + final GalleryHeaderThemeData galleryHeaderTheme; + + /// The default style for [GalleryFooter]s below the overall + /// [StreamChatTheme]. + final GalleryFooterThemeData galleryFooterTheme; + + /// Theme of the current user messages + final MessageThemeData ownMessageTheme; + + /// Theme of other users messages + final MessageThemeData otherMessageTheme; + + /// Theme dedicated to the [MessageInput] widget + final MessageInputThemeData messageInputTheme; + + /// The widget that will be built when the user image is unavailable + final Widget Function(BuildContext, User) defaultUserImage; + + /// The widget that will be built when the user image is loading + final Widget Function(BuildContext, User)? placeholderUserImage; + + /// Primary icon theme + final IconThemeData primaryIconTheme; + + /// Assets used for rendering reactions + final List reactionIcons; + + /// Theme configuration for the [MessageListView] widget. + final MessageListViewThemeData messageListViewTheme; + + /// Theme configuration for the [ChannelListView] widget. + final ChannelListViewThemeData channelListViewTheme; + + /// Theme configuration for the [UserListView] widget. + final UserListViewThemeData userListViewTheme; + + /// Theme configuration for the [MessageSearchListView] widget. + final MessageSearchListViewThemeData messageSearchListViewTheme; + + /// Creates a copy of [StreamChatThemeData] with specified attributes + /// overridden. + StreamChatThemeData copyWith({ + TextTheme? textTheme, + ColorTheme? colorTheme, + ChannelPreviewThemeData? channelPreviewTheme, + ChannelHeaderThemeData? channelHeaderTheme, + MessageThemeData? ownMessageTheme, + MessageThemeData? otherMessageTheme, + MessageInputThemeData? messageInputTheme, + Widget Function(BuildContext, User)? defaultUserImage, + Widget Function(BuildContext, User)? placeholderUserImage, + IconThemeData? primaryIconTheme, + ChannelListHeaderThemeData? channelListHeaderTheme, + List? reactionIcons, + GalleryHeaderThemeData? galleryHeaderTheme, + GalleryFooterThemeData? galleryFooterTheme, + MessageListViewThemeData? messageListViewTheme, + ChannelListViewThemeData? channelListViewTheme, + UserListViewThemeData? userListViewTheme, + MessageSearchListViewThemeData? messageSearchListViewTheme, + }) => + StreamChatThemeData.raw( + channelListHeaderTheme: + this.channelListHeaderTheme.merge(channelListHeaderTheme), + textTheme: this.textTheme.merge(textTheme), + colorTheme: this.colorTheme.merge(colorTheme), + primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), + defaultUserImage: defaultUserImage ?? this.defaultUserImage, + placeholderUserImage: placeholderUserImage ?? this.placeholderUserImage, + channelPreviewTheme: + this.channelPreviewTheme.merge(channelPreviewTheme), + channelHeaderTheme: this.channelHeaderTheme.merge(channelHeaderTheme), + ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme), + otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), + messageInputTheme: this.messageInputTheme.merge(messageInputTheme), + reactionIcons: reactionIcons ?? this.reactionIcons, + galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, + galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, + messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, + channelListViewTheme: channelListViewTheme ?? this.channelListViewTheme, + userListViewTheme: userListViewTheme ?? this.userListViewTheme, + messageSearchListViewTheme: + messageSearchListViewTheme ?? this.messageSearchListViewTheme, + ); + + /// Merge themes + StreamChatThemeData merge(StreamChatThemeData? other) { + if (other == null) return this; + return copyWith( + channelListHeaderTheme: + channelListHeaderTheme.merge(other.channelListHeaderTheme), + textTheme: textTheme.merge(other.textTheme), + colorTheme: colorTheme.merge(other.colorTheme), + primaryIconTheme: other.primaryIconTheme, + defaultUserImage: other.defaultUserImage, + placeholderUserImage: other.placeholderUserImage, + channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme), + channelHeaderTheme: channelHeaderTheme.merge(other.channelHeaderTheme), + ownMessageTheme: ownMessageTheme.merge(other.ownMessageTheme), + otherMessageTheme: otherMessageTheme.merge(other.otherMessageTheme), + messageInputTheme: messageInputTheme.merge(other.messageInputTheme), + reactionIcons: other.reactionIcons, + galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme), + galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme), + messageListViewTheme: + messageListViewTheme.merge(other.messageListViewTheme), + channelListViewTheme: + channelListViewTheme.merge(other.channelListViewTheme), + userListViewTheme: userListViewTheme.merge(other.userListViewTheme), + messageSearchListViewTheme: + messageSearchListViewTheme.merge(other.messageSearchListViewTheme), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index 31e07f0a..61627a81 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -19,6 +19,7 @@ class UserAvatar extends StatelessWidget { this.selected = false, this.selectionColor, this.selectionThickness = 4, + this.placeholder, }) : super(key: key); /// User whose avatar is to displayed @@ -54,6 +55,9 @@ class UserAvatar extends StatelessWidget { /// Selection thickness around the avatar final double selectionThickness; + /// The widget that will be built when the user image is loading + final Widget Function(BuildContext, User)? placeholder; + @override Widget build(BuildContext context) { final hasImage = user.extraData.containsKey('image') && @@ -61,6 +65,9 @@ class UserAvatar extends StatelessWidget { user.extraData['image'] != ''; final streamChatTheme = StreamChatTheme.of(context); + final placeholder = + this.placeholder ?? streamChatTheme.placeholderUserImage; + Widget avatar = FittedBox( fit: BoxFit.cover, child: ClipRRect( @@ -69,17 +76,17 @@ class UserAvatar extends StatelessWidget { child: Container( constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - decoration: BoxDecoration( - color: streamChatTheme.colorTheme.accentPrimary, - ), child: hasImage ? CachedNetworkImage( + fit: BoxFit.cover, filterQuality: FilterQuality.high, // ignore: cast_nullable_to_non_nullable imageUrl: user.extraData['image'] as String, - errorWidget: (_, __, ___) => + errorWidget: (context, __, ___) => streamChatTheme.defaultUserImage(context, user), - fit: BoxFit.cover, + placeholder: placeholder != null + ? (context, __) => placeholder(context, user) + : null, ) : streamChatTheme.defaultUserImage(context, user), ),