import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /*Navigator.push( context, MaterialPageRoute( builder: (context) { return StreamChannel( child: widget.channelWidget, channel: client, ); }, ), ); */ /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) /// /// It shows the current [Channel] preview. /// /// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates. /// /// Usually you don't use this widget as it's the default channel preview used by [ChannelListView]. /// /// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. class MyChannelPreview extends StatelessWidget { /// Function called when tapping this widget final void Function(Channel)? onTap; /// Function called when long pressing this widget final void Function(Channel)? onLongPress; /// Channel displayed final Channel channel; /// The function called when the image is tapped final VoidCallback? onImageTap; final String? heroTag; MyChannelPreview({ required this.channel, Key? key, this.onTap, this.onLongPress, this.onImageTap, this.heroTag, }) : super(key: key); @override Widget build(BuildContext context) { return StreamBuilder( stream: channel.isMutedStream, initialData: channel.isMuted, builder: (context, snapshot) { return Opacity( opacity: snapshot.data! ? 0.5 : 1, child: ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 8, ), onTap: () { if (onTap != null) { onTap!(channel); } }, onLongPress: () { if (onLongPress != null) { onLongPress!(channel); } }, leading: Material( child: Hero( tag: heroTag!, child: StreamChannel( channel: channel, child: ChannelAvatar( onTap: onImageTap, ), ), ), ), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: ChannelName( textStyle: StreamChatTheme.of(context) .channelPreviewTheme .titleStyle, ), ), StreamBuilder>( stream: channel.state!.membersStream, initialData: channel.state!.members, builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data!.isEmpty || !snapshot.data!.any((Member e) => e.user!.id == channel.client.state.currentUser!.id)) { return SizedBox(); } return ChannelUnreadIndicator( channel: channel, ); }), ], ), subtitle: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible(child: _buildSubtitle(context)), Builder( builder: (context) { final lastMessage = channel.state!.messages.lastWhereOrNull( (m) => !m.isDeleted && m.shadowed != true, ); if (lastMessage?.user?.id == StreamChat.of(context).currentUser!.id) { return Padding( padding: const EdgeInsets.only(right: 4.0), child: SendingIndicator( message: lastMessage!, size: StreamChatTheme.of(context) .channelPreviewTheme .indicatorIconSize, isMessageRead: channel.state!.read .where((element) => element.user.id != channel.client.state.currentUser!.id) .where((element) => element.lastRead .isAfter(lastMessage.createdAt)) .isNotEmpty == true, ), ); } return SizedBox(); }, ), _buildDate(context), ], ), ), ); }); } Widget _buildDate(BuildContext context) { return StreamBuilder( stream: channel.lastMessageAtStream, initialData: channel.lastMessageAt, builder: (context, snapshot) { if (!snapshot.hasData) { return SizedBox(); } final lastMessageAt = snapshot.data!.toLocal(); String stringDate; final now = DateTime.now(); final startOfDay = DateTime(now.year, now.month, now.day); if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.millisecondsSinceEpoch) { stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm'); } else if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) { stringDate = 'Yesterday'; } else if (startOfDay.difference(lastMessageAt).inDays < 7) { stringDate = Jiffy(lastMessageAt.toLocal()).EEEE; } else { stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy'); } return Text( stringDate, style: StreamChatTheme.of(context) .channelPreviewTheme .lastMessageAtStyle, ); }, ); } Widget _buildSubtitle(BuildContext context) { if (channel.isMuted) { return Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ StreamSvgIcon.mute( size: 16, ), Text( ' Channel is muted', style: StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .copyWith( color: StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .color, ), ), ], ); } return TypingIndicator( channel: channel, alternativeWidget: _buildLastMessage(context), style: StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .copyWith( color: StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .color, ), ); } Widget _buildLastMessage(BuildContext context) { return StreamBuilder?>( stream: channel.state!.messagesStream, initialData: channel.state!.messages, builder: (context, snapshot) { final lastMessage = snapshot.data ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); if (lastMessage == null) { return SizedBox(); } var text = lastMessage.text; final parts = [ ...lastMessage.attachments.map((e) { if (e.type == 'image') { return '📷'; } else if (e.type == 'video') { return '🎬'; } else if (e.type == 'giphy') { return '[GIF]'; } return e == lastMessage.attachments.last ? (e.title ?? 'File') : '${e.title ?? 'File'} , '; }), lastMessage.text ?? '', ]; text = parts.join(' '); return Text.rich( _getDisplayText( text, lastMessage.mentionedUsers, lastMessage.attachments, StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .copyWith( color: StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .color, fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal), StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .copyWith( color: StreamChatTheme.of(context) .channelPreviewTheme .subtitleStyle! .color, fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal, fontWeight: FontWeight.bold), ), maxLines: 1, overflow: TextOverflow.ellipsis, ); }, ); } TextSpan _getDisplayText( String text, List mentions, List attachments, TextStyle normalTextStyle, TextStyle mentionsTextStyle) { final textList = text.split(' '); final resList = []; for (final e in textList) { if (mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) { resList.add(TextSpan( text: '$e ', style: mentionsTextStyle, )); } else if (attachments.isNotEmpty && attachments .where((e) => e.title != null) .any((element) => element.title == e)) { resList.add(TextSpan( text: '$e ', style: normalTextStyle.copyWith(fontStyle: FontStyle.italic), )); } else { resList.add(TextSpan( text: e == textList.last ? '$e' : '$e ', style: normalTextStyle, )); } } return TextSpan(children: resList); } } class ChannelUnreadIndicator extends StatelessWidget { const ChannelUnreadIndicator({ Key? key, required this.channel, }) : super(key: key); final Channel channel; @override Widget build(BuildContext context) { return StreamBuilder( stream: channel.state!.unreadCountStream, initialData: channel.state!.unreadCount, builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data == 0) { return SizedBox(); } return Material( borderRadius: BorderRadius.circular(8), color: StreamChatTheme.of(context) .channelPreviewTheme .unreadCounterColor, child: Padding( padding: const EdgeInsets.only( left: 5.0, right: 5.0, top: 2, bottom: 1, ), child: Center( child: Text( '${snapshot.data! > 99 ? '99+' : snapshot.data}', style: TextStyle( fontSize: 11, color: Colors.white, ), ), ), ), ); }, ); } }