diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index 99e9ceab..78081c85 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -114,7 +114,7 @@ class ChannelListPage extends StatelessWidget { ), ); }, - leading: ChannelImage( + leading: ChannelAvatar( channel: channel, ), title: ChannelName( diff --git a/packages/stream_chat_flutter/lib/src/channel_avatar.dart b/packages/stream_chat_flutter/lib/src/channel_avatar.dart new file mode 100644 index 00000000..4ab6ba31 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/channel_avatar.dart @@ -0,0 +1,202 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/group_avatar.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image.png) +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image_paint.png) +/// +/// It shows the current [Channel] image. +/// +/// ```dart +/// class MyApp extends StatelessWidget { +/// final StreamChatClient client; +/// final Channel channel; +/// +/// MyApp(this.client, this.channel); +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// debugShowCheckedModeBanner: false, +/// home: StreamChat( +/// client: client, +/// child: StreamChannel( +/// channel: channel, +/// child: Center( +/// child: ChannelImage( +/// channel: channel, +/// ), +/// ), +/// ), +/// ), +/// ); +/// } +/// } +/// ``` +/// +/// The widget uses a [StreamBuilder] to render the channel information +/// image as soon as it updates. +/// +/// By default the widget radius size is 40x40 pixels. +/// Set the property [constraints] to set a custom dimension. +/// +/// The widget renders the ui based on the first ancestor of type +/// [StreamChatTheme]. +/// Modify it to change the widget appearance. +class ChannelAvatar extends StatelessWidget { + /// Instantiate a new ChannelImage + const ChannelAvatar({ + Key? key, + this.channel, + this.constraints, + this.onTap, + this.borderRadius, + this.selected = false, + this.selectionColor, + this.selectionThickness = 4, + }) : super(key: key); + + /// [BorderRadius] to display the widget + final BorderRadius? borderRadius; + + /// The channel to show the image of + final Channel? channel; + + /// The diameter of the image + final BoxConstraints? constraints; + + /// The function called when the image is tapped + final VoidCallback? onTap; + + /// If image is selected + final bool selected; + + /// Selection color for image + final Color? selectionColor; + + /// Thickness of selection image + final double selectionThickness; + + @override + 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, extraData) { + final channelImage = extraData['image']; + + 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, + ), + ), + ), + ); + + 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; + } + + 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; + return BetterStreamBuilder( + stream: channel.state!.membersStream.map( + (members) => members.firstWhere( + (it) => it.userId == member.userId, + orElse: () => member, + ), + ), + initialData: member, + builder: (context, member) => UserAvatar( + borderRadius: borderRadius ?? previewTheme?.borderRadius, + user: member.user!, + constraints: constraints ?? previewTheme?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, + selected: selected, + selectionColor: selectionColor ?? colorTheme.accentPrimary, + selectionThickness: selectionThickness, + ), + ); + } + + // Group conversation + return GroupAvatar( + 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/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 16c1f1b7..8f586516 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -98,7 +98,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { final Widget? leading; /// AppBar actions - /// By default it shows the [ChannelImage] + /// By default it shows the [ChannelAvatar] final List? actions; @override @@ -147,7 +147,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { Padding( padding: const EdgeInsets.only(right: 10), child: Center( - child: ChannelImage( + child: ChannelAvatar( borderRadius: chatThemeData.channelTheme .channelHeaderTheme.avatarTheme?.borderRadius, constraints: chatThemeData.channelTheme diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart deleted file mode 100644 index 9ac07033..00000000 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ /dev/null @@ -1,204 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/group_image.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image_paint.png) -/// -/// It shows the current [Channel] image. -/// -/// ```dart -/// class MyApp extends StatelessWidget { -/// final StreamChatClient client; -/// final Channel channel; -/// -/// MyApp(this.client, this.channel); -/// -/// @override -/// Widget build(BuildContext context) { -/// return MaterialApp( -/// debugShowCheckedModeBanner: false, -/// home: StreamChat( -/// client: client, -/// child: StreamChannel( -/// channel: channel, -/// child: Center( -/// child: ChannelImage( -/// channel: channel, -/// ), -/// ), -/// ), -/// ), -/// ); -/// } -/// } -/// ``` -/// -/// The widget uses a [StreamBuilder] to render the channel information -/// image as soon as it updates. -/// -/// By default the widget radius size is 40x40 pixels. -/// Set the property [constraints] to set a custom dimension. -/// -/// The widget renders the ui based on the first ancestor of type -/// [StreamChatTheme]. -/// Modify it to change the widget appearance. -class ChannelImage extends StatelessWidget { - /// Instantiate a new ChannelImage - const ChannelImage({ - Key? key, - this.channel, - this.constraints, - this.onTap, - this.borderRadius, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - }) : super(key: key); - - /// [BorderRadius] to display the widget - final BorderRadius? borderRadius; - - /// The channel to show the image of - final Channel? channel; - - /// The diameter of the image - final BoxConstraints? constraints; - - /// The function called when the image is tapped - final VoidCallback? onTap; - - /// If image is selected - final bool selected; - - /// Selection color for image - final Color? selectionColor; - - /// Thickness of selection image - final double selectionThickness; - - @override - Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); - final channel = this.channel ?? StreamChannel.of(context).channel; - 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, - ); - } - - 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, - ), - ), - ), - fit: BoxFit.cover, - ) - else - chatThemeData.defaultChannelImage( - context, - channel, - ), - Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - ), - ), - ], - ), - ), - ); - 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, - ), - ), - ); - } - return child; - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 06f1aee9..83f196df 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -620,7 +620,7 @@ class _ChannelListViewState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - ChannelImage( + ChannelAvatar( channel: channel, borderRadius: BorderRadius.circular(32), selected: selected, diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 231b5f94..bf615248 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -55,7 +55,7 @@ class ChannelPreview extends StatelessWidget { final Widget? subtitle; /// Widget rendering the leading element, by default - /// it shows the [ChannelImage] + /// it shows the [ChannelAvatar] final Widget? leading; /// Widget rendering the trailing element, @@ -81,20 +81,9 @@ class ChannelPreview extends StatelessWidget { contentPadding: const EdgeInsets.symmetric( horizontal: 8, ), - onTap: () { - if (onTap != null) { - onTap!(channel); - } - }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(channel); - } - }, - leading: leading ?? - ChannelImage( - onTap: onImageTap, - ), + onTap: () => onTap?.call(channel), + onLongPress: () => onLongPress?.call(channel), + leading: leading ?? ChannelAvatar(onTap: onImageTap), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/packages/stream_chat_flutter/lib/src/group_avatar.dart b/packages/stream_chat_flutter/lib/src/group_avatar.dart new file mode 100644 index 00000000..4a16279e --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/group_avatar.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Widget for constructing a group of images +class GroupAvatar extends StatelessWidget { + /// Constructor for creating a [GroupAvatar] + const GroupAvatar({ + Key? key, + required this.members, + this.constraints, + this.onTap, + this.borderRadius, + this.selected = false, + this.selectionColor, + this.selectionThickness = 4, + }) : super(key: key); + + /// List of images to display + final List members; + + /// Constraints on the widget + final BoxConstraints? constraints; + + /// Callback when widget is tapped + final VoidCallback? onTap; + + /// Highlights if selected + final bool selected; + + /// [BorderRadius] to pass to the widget + final BorderRadius? borderRadius; + + /// Color of selection if selected + final Color? selectionColor; + + /// Thickness with which color of selection is shown + final double selectionThickness; + + @override + Widget build(BuildContext context) { + final channel = StreamChannel.of(context).channel; + + 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 ?? previewTheme?.borderRadius, + child: Container( + constraints: constraints ?? previewTheme?.constraints, + decoration: BoxDecoration(color: colorTheme.accentPrimary), + child: Flex( + direction: Axis.vertical, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible( + fit: FlexFit.tight, + child: Flex( + direction: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: members + .take(2) + .map( + (member) => Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: channel.state!.membersStream.map( + (members) => members.firstWhere( + (it) => it.userId == member.userId, + orElse: () => member, + ), + ), + initialData: member, + builder: (context, member) => UserAvatar( + user: member.user!, + borderRadius: BorderRadius.zero, + ), + ), + ), + ), + ), + ) + .toList(), + ), + ), + if (members.length > 2) + Flexible( + fit: FlexFit.tight, + child: Flex( + direction: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: members + .skip(2) + .take(2) + .map( + (member) => Flexible( + fit: FlexFit.tight, + child: FittedBox( + fit: BoxFit.cover, + clipBehavior: Clip.antiAlias, + child: Transform.scale( + scale: 1.2, + child: BetterStreamBuilder( + stream: channel.state!.membersStream.map( + (members) => members.firstWhere( + (it) => it.userId == member.userId, + orElse: () => member, + ), + ), + initialData: member, + builder: (context, member) => UserAvatar( + user: member.user!, + borderRadius: BorderRadius.zero, + ), + ), + ), + ), + ), + ) + .toList(), + ), + ), + ], + ), + ), + ), + ); + + if (selected) { + avatar = ClipRRect( + 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: avatar, + ), + ), + ); + } + + return avatar; + } +} diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart deleted file mode 100644 index 3513bda2..00000000 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ /dev/null @@ -1,135 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// Widget for constructing a group of images -class GroupImage extends StatelessWidget { - /// Constructor for creating a [GroupImage] - const GroupImage({ - Key? key, - required this.images, - this.constraints, - this.onTap, - this.borderRadius, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - }) : super(key: key); - - /// List of images to display - final List images; - - /// Constraints on the widget - final BoxConstraints? constraints; - - /// Callback when widget is tapped - final VoidCallback? onTap; - - /// Highlights if selected - final bool selected; - - /// [BorderRadius] to pass to the widget - final BorderRadius? borderRadius; - - /// Color of selection if selected - final Color? selectionColor; - - /// Thickness with which color of selection is shown - final double selectionThickness; - - @override - Widget build(BuildContext context) { - Widget? avatar; - final streamChatTheme = StreamChatTheme.of(context); - - avatar = GestureDetector( - onTap: onTap, - child: ClipRRect( - borderRadius: borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, - child: Container( - constraints: constraints ?? - streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - decoration: BoxDecoration( - color: streamChatTheme.colorTheme.accentPrimary, - ), - child: Flex( - direction: Axis.vertical, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Flexible( - fit: FlexFit.tight, - 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, - ), - ), - ), - )) - .toList(), - ), - ), - if (images.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, - ), - ), - ), - )) - .toList(), - ), - ), - ], - ), - ), - ), - ); - - if (selected) { - avatar = ClipRRect( - borderRadius: (borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? - BorderRadius.zero) + - BorderRadius.circular(selectionThickness), - child: Container( - color: selectionColor ?? streamChatTheme.colorTheme.accentPrimary, - height: 64, - width: 64, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: avatar, - ), - ), - ); - } - - return avatar; - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 9dca6c6b..5a4f6822 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1859,11 +1859,11 @@ class MessageInputState extends State { } if (camera) { - PickedFile? pickedFile; + XFile? pickedFile; if (fileType == DefaultAttachmentTypes.image) { - pickedFile = await _imagePicker.getImage(source: ImageSource.camera); + pickedFile = await _imagePicker.pickImage(source: ImageSource.camera); } else if (fileType == DefaultAttachmentTypes.video) { - pickedFile = await _imagePicker.getVideo(source: ImageSource.camera); + pickedFile = await _imagePicker.pickVideo(source: ImageSource.camera); } if (pickedFile == null) { return; 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 498b429e..c62e92ec 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -55,7 +55,6 @@ class StreamChatThemeData { MessageTheme? otherMessageTheme, MessageTheme? ownMessageTheme, MessageInputTheme? messageInputTheme, - Widget Function(BuildContext, Channel)? defaultChannelImage, Widget Function(BuildContext, User)? defaultUserImage, IconThemeData? primaryIconTheme, List? reactionIcons, @@ -79,7 +78,6 @@ class StreamChatThemeData { otherMessageTheme: otherMessageTheme, ownMessageTheme: ownMessageTheme, messageInputTheme: messageInputTheme, - defaultChannelImage: defaultChannelImage, defaultUserImage: defaultUserImage, primaryIconTheme: primaryIconTheme, reactionIcons: reactionIcons, @@ -108,7 +106,6 @@ class StreamChatThemeData { required this.otherMessageTheme, required this.ownMessageTheme, required this.messageInputTheme, - required this.defaultChannelImage, required this.defaultUserImage, required this.primaryIconTheme, required this.reactionIcons, @@ -160,9 +157,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; @@ -182,7 +176,6 @@ class StreamChatThemeData { MessageTheme? ownMessageTheme, MessageTheme? otherMessageTheme, MessageInputTheme? messageInputTheme, - Widget Function(BuildContext, Channel)? defaultChannelImage, Widget Function(BuildContext, User)? defaultUserImage, IconThemeData? primaryIconTheme, ChannelListHeaderTheme? channelListHeaderTheme, @@ -196,7 +189,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), @@ -218,7 +210,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), @@ -278,7 +269,6 @@ class StreamChatThemeData { textTheme: textTheme, colorTheme: colorTheme, primaryIconTheme: iconTheme, - defaultChannelImage: (context, channel) => const SizedBox(), defaultUserImage: (context, user) => Center( child: CachedNetworkImage( filterQuality: FilterQuality.high, diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 45c0f32e..39aabb85 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -2,8 +2,8 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'src/attachment/attachment.dart'; export 'src/back_button.dart'; +export 'src/channel_avatar.dart'; export 'src/channel_header.dart'; -export 'src/channel_image.dart'; export 'src/channel_list_header.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 70a02f56..ce7ba0e5 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: flutter_svg: ^0.22.0 http_parser: ^4.0.0 image_gallery_saver: ^1.6.9 - image_picker: ^0.8.0 + image_picker: ^0.8.2 jiffy: ^4.1.0 lottie: ^1.0.1 meta: ^1.3.0 diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 87f3d028..211a7132 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -14,10 +14,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -63,7 +65,7 @@ void main() { )); expect(find.text('test'), findsOneWidget); - expect(find.byType(ChannelImage), findsOneWidget); + expect(find.byType(ChannelAvatar), findsOneWidget); expect(find.byType(StreamBackButton), findsOneWidget); expect(find.byType(ChannelInfo), findsOneWidget); }, @@ -76,10 +78,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -141,10 +145,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -207,10 +213,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -263,7 +271,7 @@ void main() { expect(find.text('test'), findsNothing); expect(find.byType(StreamBackButton), findsNothing); - expect(find.byType(ChannelImage), findsNothing); + expect(find.byType(ChannelAvatar), findsNothing); expect(find.byType(ChannelInfo), findsNothing); expect(find.text('leading'), findsOneWidget); expect(find.text('title'), findsOneWidget); @@ -281,10 +289,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -346,10 +356,12 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -403,7 +415,7 @@ void main() { )); await tester.tap(find.byType(StreamBackButton)); - await tester.tap(find.byType(ChannelImage)); + await tester.tap(find.byType(ChannelAvatar)); await tester.tap(find.byType(ChannelName)); expect(backPressed, true); diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index b2eb725f..7c620774 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/src/group_image.dart'; +import 'package:stream_chat_flutter/src/group_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -35,7 +35,7 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage(), + body: ChannelAvatar(), ), ), ), @@ -113,7 +113,7 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage(), + body: ChannelAvatar(), ), ), ), @@ -132,9 +132,10 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final currentUser = OwnUser(id: 'user-id'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(currentUser); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ @@ -143,36 +144,7 @@ void main() { when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ - Member( - userId: 'user-id', - user: User( - id: 'user-id', - extraData: const { - 'image': 'testimage1', - }, - ), - ), - Member( - userId: 'user-id2', - user: User( - id: 'user-id2', - extraData: const { - 'image': 'testimage2', - }, - ), - ), - Member( - userId: 'user-id3', - user: User( - id: 'user-id3', - extraData: const { - 'image': 'testimage3', - }, - ), - ), - ])); - when(() => channelState.members).thenReturn([ + final members = [ Member( userId: 'user-id', user: User( @@ -200,7 +172,10 @@ void main() { }, ), ), - ]); + ]; + when(() => channelState.members).thenReturn(members); + when(() => channelState.membersStream) + .thenAnswer((_) => Stream.value(members)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -208,17 +183,18 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage(), + body: ChannelAvatar(), ), ), ), )); - final image = tester.widget(find.byType(GroupImage)); - expect(image.images, [ - 'testimage2', - 'testimage3', - ]); + final image = tester.widget(find.byType(GroupAvatar)); + final otherMembers = members.where((it) => it.userId != currentUser.id); + expect( + image.members.map((it) => it.user?.id), + otherMembers.map((it) => it.user?.id), + ); }, ); @@ -249,7 +225,7 @@ void main() { child: StreamChannel( channel: channel, child: const Scaffold( - body: ChannelImage( + body: ChannelAvatar( selected: true, ), ), diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index baa82614..6add0c60 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -13,11 +13,13 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); + final user = OwnUser(id: 'user-id'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); when(() => channel.cid).thenReturn('cid'); when(() => client.state).thenReturn(clientState); - when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.user).thenReturn(user); + when(() => clientState.userStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -78,7 +80,7 @@ void main() { expect(find.text('test name'), findsOneWidget); expect(find.text('1'), findsOneWidget); expect(find.text('hello'), findsOneWidget); - expect(find.byType(ChannelImage), findsOneWidget); + expect(find.byType(ChannelAvatar), findsOneWidget); }, ); }