diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 9ac07033..21737814 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -83,121 +83,117 @@ class ChannelImage extends StatelessWidget { Widget build(BuildContext context) { final streamChat = StreamChat.of(context); final channel = this.channel ?? StreamChannel.of(context).channel; + + assert(channel.state != null, 'Channel ${channel.id} is not initialized'); + + final chatThemeData = StreamChatTheme.of(context); + final colorTheme = chatThemeData.colorTheme; + final previewTheme = chatThemeData.channelPreviewTheme.avatarTheme; + return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, data) { - String? image; - final chatThemeData = StreamChatTheme.of(context); - if (data.containsKey('image') == true) { - image = data['image']; - } else if (channel.state?.members.length == 2) { - final otherMember = channel.state?.members - .firstWhere((member) => member.user?.id != streamChat.user?.id); - return BetterStreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => - users[otherMember?.userId] ?? otherMember!.user!) - .distinct(), - initialData: otherMember!.user, - builder: (context, user) => UserAvatar( - borderRadius: borderRadius ?? - chatThemeData - .channelPreviewTheme.avatarTheme?.borderRadius, - user: user ?? otherMember.user!, - constraints: constraints ?? - chatThemeData - .channelPreviewTheme.avatarTheme?.constraints, - onTap: onTap != null ? (_) => onTap!() : null, - selected: selected, - selectionColor: selectionColor ?? - chatThemeData.colorTheme.accentPrimary, - selectionThickness: selectionThickness, - )); - } else { - final images = channel.state?.members - .where((member) => - member.user?.id != streamChat.user?.id && - member.user?.extraData['image'] != null) - .take(4) - // ignore: cast_nullable_to_non_nullable - .map((e) => e.user?.extraData['image'] as String) - .toList(); - return GroupImage( - images: images ?? [], - borderRadius: borderRadius ?? - chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, - constraints: constraints ?? - chatThemeData.channelPreviewTheme.avatarTheme?.constraints, - onTap: onTap, - selected: selected, - selectionColor: - selectionColor ?? chatThemeData.colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - } + builder: (context, extraData) { + final channelImage = extraData['image']; - Widget child = ClipRRect( - borderRadius: borderRadius ?? - chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, - child: Container( - constraints: constraints ?? - chatThemeData.channelPreviewTheme.avatarTheme?.constraints, - decoration: BoxDecoration( - color: chatThemeData.colorTheme.accentPrimary, - ), - child: Stack( - alignment: Alignment.center, - fit: StackFit.expand, - children: [ - if (image != null) - CachedNetworkImage( - imageUrl: image, - errorWidget: (_, __, ___) => Center( - child: Text( - data.containsKey('name') ? data['name'][0] : '', - style: TextStyle( - color: chatThemeData.colorTheme.barsBg, - fontWeight: FontWeight.bold, - ), + if (channelImage != null) { + Widget child = ClipRRect( + borderRadius: borderRadius ?? previewTheme?.borderRadius, + child: Container( + constraints: constraints ?? previewTheme?.constraints, + decoration: BoxDecoration(color: colorTheme.accentPrimary), + child: InkWell( + onTap: onTap, + child: CachedNetworkImage( + imageUrl: channelImage, + errorWidget: (_, __, ___) => Center( + child: Text( + extraData['name']?[0] ?? '', + style: TextStyle( + color: colorTheme.barsBg, + fontWeight: FontWeight.bold, ), ), - fit: BoxFit.cover, - ) - else - chatThemeData.defaultChannelImage( - context, - channel, - ), - Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, ), + fit: BoxFit.cover, ), - ], - ), - ), - ); - if (selected) { - child = ClipRRect( - key: const Key('selectedImage'), - borderRadius: (borderRadius ?? - chatThemeData.ownMessageTheme.avatarTheme?.borderRadius ?? - BorderRadius.zero) + - BorderRadius.circular(selectionThickness), - child: Container( - constraints: constraints ?? - chatThemeData.ownMessageTheme.avatarTheme?.constraints, - color: selectionColor ?? chatThemeData.colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: child, ), ), ); + + if (selected) { + child = ClipRRect( + key: const Key('selectedImage'), + borderRadius: BorderRadius.circular(selectionThickness) + + (borderRadius ?? + previewTheme?.borderRadius ?? + BorderRadius.zero), + child: Container( + constraints: constraints ?? previewTheme?.constraints, + color: selectionColor ?? colorTheme.accentPrimary, + child: Padding( + padding: EdgeInsets.all(selectionThickness), + child: child, + ), + ), + ); + } + return child; } - return child; + + final currentUser = streamChat.user!; + final otherMembers = channel.state!.members + .where((it) => it.userId != currentUser.id) + .toList(growable: false); + + // our own space, no other members + if (otherMembers.isEmpty) { + return BetterStreamBuilder( + stream: streamChat.client.state.userStream.map((it) => it!), + initialData: currentUser, + builder: (context, user) => UserAvatar( + borderRadius: borderRadius ?? previewTheme?.borderRadius, + user: user, + constraints: constraints ?? previewTheme?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, + selected: selected, + selectionColor: selectionColor ?? colorTheme.accentPrimary, + selectionThickness: selectionThickness, + ), + ); + } + + // 1-1 Conversation + if (otherMembers.length == 1) { + final member = otherMembers.first; + final user = member.user!; + return BetterStreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => users[member.userId ?? user.id] ?? user) + .distinct(), + initialData: user, + builder: (context, user) => UserAvatar( + borderRadius: borderRadius ?? previewTheme?.borderRadius, + user: user, + constraints: constraints ?? previewTheme?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, + selected: selected, + selectionColor: selectionColor ?? colorTheme.accentPrimary, + selectionThickness: selectionThickness, + ), + ); + } + + // Group conversation + return GroupImage( + members: otherMembers, + borderRadius: borderRadius ?? previewTheme?.borderRadius, + constraints: constraints ?? previewTheme?.constraints, + onTap: onTap, + selected: selected, + selectionColor: selectionColor ?? colorTheme.accentPrimary, + selectionThickness: selectionThickness, + ); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index 3513bda2..62b8b7b5 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -7,7 +6,7 @@ class GroupImage extends StatelessWidget { /// Constructor for creating a [GroupImage] const GroupImage({ Key? key, - required this.images, + required this.members, this.constraints, this.onTap, this.borderRadius, @@ -17,7 +16,7 @@ class GroupImage extends StatelessWidget { }) : super(key: key); /// List of images to display - final List images; + final List members; /// Constraints on the widget final BoxConstraints? constraints; @@ -39,20 +38,22 @@ class GroupImage extends StatelessWidget { @override Widget build(BuildContext context) { - Widget? avatar; - final streamChatTheme = StreamChatTheme.of(context); + final streamChat = StreamChat.of(context); + final channel = StreamChannel.of(context).channel; - avatar = GestureDetector( + assert(channel.state != null, 'Channel ${channel.id} is not initialized'); + + final streamChatTheme = StreamChatTheme.of(context); + final colorTheme = streamChatTheme.colorTheme; + final previewTheme = streamChatTheme.channelPreviewTheme.avatarTheme; + + Widget avatar = GestureDetector( onTap: onTap, child: ClipRRect( - borderRadius: borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, + borderRadius: borderRadius ?? previewTheme?.borderRadius, child: Container( - constraints: constraints ?? - streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - decoration: BoxDecoration( - color: streamChatTheme.colorTheme.accentPrimary, - ), + constraints: constraints ?? previewTheme?.constraints, + decoration: BoxDecoration(color: colorTheme.accentPrimary), child: Flex( direction: Axis.vertical, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -62,48 +63,62 @@ class GroupImage extends StatelessWidget { child: Flex( direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch, - children: images - .take(2) - .map((url) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: CachedNetworkImage( - imageUrl: url, - fit: BoxFit.cover, - ), - ), + children: members.take(2).map((member) { + final user = member.user!; + return Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => + users[member.userId ?? user.id] ?? user) + .distinct(), + initialData: user, + builder: (context, user) => UserAvatar( + user: user, + borderRadius: BorderRadius.zero, ), - )) - .toList(), + ), + ), + ), + ); + }).toList(), ), ), - if (images.length > 2) + if (members.length > 2) Flexible( fit: FlexFit.tight, child: Flex( direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.stretch, - children: images - .skip(2) - .map((url) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: CachedNetworkImage( - imageUrl: url, - fit: BoxFit.cover, - ), - ), + children: members.skip(2).take(2).map((member) { + final user = member.user!; + return Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: streamChat.client.state.usersStream + .map((users) => + users[member.userId ?? user.id] ?? user) + .distinct(), + initialData: user, + builder: (context, user) => UserAvatar( + user: user, + borderRadius: BorderRadius.zero, ), - )) - .toList(), + ), + ), + ), + ); + }).toList(), ), ), ], @@ -114,14 +129,11 @@ class GroupImage extends StatelessWidget { if (selected) { avatar = ClipRRect( - borderRadius: (borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? - BorderRadius.zero) + - BorderRadius.circular(selectionThickness), + borderRadius: BorderRadius.circular(selectionThickness) + + (borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero), child: Container( - color: selectionColor ?? streamChatTheme.colorTheme.accentPrimary, - height: 64, - width: 64, + constraints: constraints ?? previewTheme?.constraints, + color: selectionColor ?? colorTheme.accentPrimary, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, 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 72b04a80..6956ff3d 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -103,7 +103,6 @@ class StreamChatThemeData { required this.otherMessageTheme, required this.ownMessageTheme, required this.messageInputTheme, - required this.defaultChannelImage, required this.defaultUserImage, required this.primaryIconTheme, required this.reactionIcons, @@ -145,9 +144,6 @@ class StreamChatThemeData { /// Theme dedicated to the [MessageInput] widget final MessageInputTheme messageInputTheme; - /// The widget that will be built when the channel image is unavailable - final Widget Function(BuildContext, Channel) defaultChannelImage; - /// The widget that will be built when the user image is unavailable final Widget Function(BuildContext, User) defaultUserImage; @@ -179,7 +175,6 @@ class StreamChatThemeData { textTheme: this.textTheme.merge(textTheme), colorTheme: this.colorTheme.merge(colorTheme), primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), - defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage, channelPreviewTheme: this.channelPreviewTheme.merge(channelPreviewTheme), @@ -199,7 +194,6 @@ class StreamChatThemeData { textTheme: textTheme.merge(other.textTheme), colorTheme: colorTheme.merge(other.colorTheme), primaryIconTheme: other.primaryIconTheme, - defaultChannelImage: other.defaultChannelImage, defaultUserImage: other.defaultUserImage, channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme), channelTheme: channelTheme.merge(other.channelTheme), @@ -223,7 +217,6 @@ class StreamChatThemeData { textTheme: textTheme, colorTheme: colorTheme, primaryIconTheme: iconTheme, - defaultChannelImage: (context, channel) => const SizedBox(), defaultUserImage: (context, user) => Center( child: CachedNetworkImage( filterQuality: FilterQuality.high,