From 3aaba43e743b5dafd8b836276bb3b8f168dcb813 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 26 Feb 2026 10:02:40 +0100 Subject: [PATCH 01/18] update avatars --- .../lib/src/components/avatar/stream_channel_avatar.dart | 1 + .../lib/src/components/avatar/stream_user_avatar.dart | 4 ++-- sample_app/lib/widgets/location/location_user_marker.dart | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart index b1c4c592e7..e2af189d8e 100644 --- a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart @@ -103,6 +103,7 @@ class StreamChannelAvatar extends StatelessWidget { ) => switch (size) { .lg => StreamAvatarSize.lg, .xl => StreamAvatarSize.xl, + .xxl => StreamAvatarSize.xxl, }; } diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart index 6190e0e815..cccfa2b3d1 100644 --- a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart @@ -135,7 +135,7 @@ class StreamUserAvatar extends StatelessWidget { .xs || .sm => StreamOnlineIndicatorSize.sm, .md => StreamOnlineIndicatorSize.md, .lg => StreamOnlineIndicatorSize.lg, - .xl => StreamOnlineIndicatorSize.xl, + .xl || .xxl => StreamOnlineIndicatorSize.xl, }; } @@ -158,7 +158,7 @@ class _StreamUserAvatarPlaceholder extends StatelessWidget { final userInitials = user.name.initials; if (userInitials != null && userInitials.isNotEmpty) { return switch (size) { - .md || .lg || .xl => Text(userInitials), + .md || .lg || .xl || .xxl => Text(userInitials), .xs || .sm => Text(userInitials.characters.first), }; } diff --git a/sample_app/lib/widgets/location/location_user_marker.dart b/sample_app/lib/widgets/location/location_user_marker.dart index 3c8994ab98..bca88aa83a 100644 --- a/sample_app/lib/widgets/location/location_user_marker.dart +++ b/sample_app/lib/widgets/location/location_user_marker.dart @@ -72,6 +72,6 @@ class LocationUserMarker extends StatelessWidget { .sm => StreamAvatarSize.sm, .md => StreamAvatarSize.md, .lg => StreamAvatarSize.lg, - .xl => StreamAvatarSize.xl, + .xl => StreamAvatarSize.xxl, }; } From aba1f5033bec2dcc531504f5fa8bbace88903117 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 26 Feb 2026 12:21:49 +0100 Subject: [PATCH 02/18] Use channel list item in sdk --- melos.yaml | 5 +- .../lib/src/channel/stream_channel_name.dart | 2 +- .../avatar/stream_channel_avatar.dart | 17 +- .../lib/src/indicators/typing_indicator.dart | 13 +- .../stream_channel_list_tile.dart | 420 +++++++++++------- .../lib/stream_chat_flutter.dart | 2 + packages/stream_chat_flutter/pubspec.yaml | 5 +- sample_app/ios/Flutter/AppFrameworkInfo.plist | 2 - 8 files changed, 293 insertions(+), 173 deletions(-) diff --git a/melos.yaml b/melos.yaml index a3e47fa6be..e77cafaeee 100644 --- a/melos.yaml +++ b/melos.yaml @@ -93,10 +93,7 @@ command: svg_icon_widget: ^0.0.1 # TODO: Replace with hosted version before merging PR stream_core_flutter: - git: - url: https://github.com/GetStream/stream-core-flutter.git - ref: da18aa04ad48d4d5bb429c9e90d9f0253c418fae - path: packages/stream_core_flutter + path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 url_launcher: ^6.3.0 diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart index 7b1d963f9f..12049cf60a 100644 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart @@ -73,7 +73,7 @@ class StreamChannelName extends StatelessWidget { final exceedingMembers = otherMembers.length - currentMembers.length; channelName = '${currentMembers.map((e) => e.user?.name).join(', ')} ' - '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + '${exceedingMembers > 0 ? '+ ${exceedingMembers + 1}' : ''}'; } } diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart index e2af189d8e..b3975aa811 100644 --- a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart @@ -82,13 +82,24 @@ class StreamChannelAvatar extends StatelessWidget { stream: channel.state!.membersStream, initialData: channel.state!.members, builder: (context, members) { - final users = members.map((it) => it.user!); + final users = members.map((it) => it.user!).toList(); final currentUserId = channel.client.state.currentUser?.id; + if (channel.isDistinct && users.length == 2) { + final otherUser = users.firstWhere( + (u) => u.id != currentUserId, + orElse: () => users.first, + ); + return StreamUserAvatar( + user: otherUser, + size: _avatarSizeForAvatarGroupSize(effectiveSize), + showOnlineIndicator: true, + ); + } + return StreamUserAvatarGroup( size: effectiveSize, - // Sort users by current user first. - users: users.sortedBy((it) => it.id == currentUserId ? 0 : 1), + users: users.sortedBy((it) => it.id == currentUserId ? 1 : 0), ); }, ), diff --git a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart index b3cf76d0d4..10a84b2c7f 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart @@ -58,11 +58,6 @@ class StreamTypingIndicator extends StatelessWidget { mainAxisSize: MainAxisSize.min, spacing: 4, children: [ - Lottie.asset( - 'lib/assets/animations/typing_dots.json', - package: 'stream_chat_flutter', - height: 4, - ), Flexible( child: Text( context.translations.userTypingText(users), @@ -70,6 +65,14 @@ class StreamTypingIndicator extends StatelessWidget { style: style, ), ), + Padding( + padding: const EdgeInsets.only(top: 2), + child: Lottie.asset( + 'lib/assets/animations/typing_dots.json', + package: 'stream_chat_flutter', + height: 5, + ), + ), ], ), ) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index 07dd751271..f7fe01b4e3 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -1,7 +1,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat_flutter/src/message_widget/sending_indicator_builder.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/misc/timestamp.dart'; import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; @@ -9,15 +8,19 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that displays a channel preview. /// -/// This widget is intended to be used as a Tile in [StreamChannelListView] +/// This widget is intended to be used as a Tile in [StreamChannelListView]. /// /// It shows the last message of the channel, the last message time, the unread /// message count, the typing indicator, the sending indicator and the channel /// avatar. /// +/// Internally uses [StreamChannelListItem] from the core design system for +/// consistent visual presentation. +/// /// See also: /// * [StreamChannelAvatar] /// * [StreamChannelName] +/// * [StreamChannelListItem] class StreamChannelListTile extends StatelessWidget { /// Creates a new instance of [StreamChannelListTile] widget. StreamChannelListTile({ @@ -25,17 +28,13 @@ class StreamChannelListTile extends StatelessWidget { required this.channel, this.leading, this.title, + this.titleTrailing, this.subtitle, + this.subtitleTrailing, this.trailing, this.onTap, this.onLongPress, - this.tileColor, - this.visualDensity = VisualDensity.compact, - this.contentPadding = const EdgeInsets.symmetric(horizontal: 8), - this.unreadIndicatorBuilder, this.sendingIndicatorBuilder, - this.selected = false, - this.selectedTileColor, }) : assert( channel.state != null, 'Channel ${channel.id} is not initialized', @@ -44,16 +43,35 @@ class StreamChannelListTile extends StatelessWidget { /// The channel to display. final Channel channel; - /// A widget to display before the title. + /// A widget to display as the avatar. + /// + /// Defaults to [StreamChannelAvatar]. final Widget? leading; /// The primary content of the list tile. + /// + /// Defaults to [StreamChannelName]. final Widget? title; + /// An optional widget displayed after the title. + /// + /// Typically used for a mute icon or similar indicator. + final Widget? titleTrailing; + /// Additional content displayed below the title. + /// + /// Defaults to [ChannelListTileSubtitle] which shows typing indicators, + /// draft messages, or the last message preview. final Widget? subtitle; - /// A widget to display at the end of tile. + /// An optional trailing widget in the subtitle row. + /// + /// When not provided, a sending indicator is shown for outgoing messages. + final Widget? subtitleTrailing; + + /// A widget to display as the timestamp. + /// + /// Defaults to [ChannelLastMessageDate]. final Widget? trailing; /// Called when the user taps this list tile. @@ -62,48 +80,12 @@ class StreamChannelListTile extends StatelessWidget { /// Called when the user long-presses on this list tile. final GestureLongPressCallback? onLongPress; - /// {@template flutter.material.ListTile.tileColor} - /// Defines the background color of `ListTile`. - /// - /// When the value is null, - /// the `tileColor` is set to [ListTileTheme.tileColor] - /// if it's not null and to [Colors.transparent] if it's null. - /// {@endtemplate} - final Color? tileColor; - - /// Defines how compact the list tile's layout will be. - /// - /// {@macro flutter.material.themedata.visualDensity} - /// - /// See also: - /// - /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all - /// widgets within a [Theme]. - final VisualDensity visualDensity; - - /// The tile's internal padding. - /// - /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], - /// and [trailing] widgets. - /// - /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. - final EdgeInsetsGeometry contentPadding; - - /// The widget builder for the unread indicator. - final WidgetBuilder? unreadIndicatorBuilder; - /// The widget builder for the sending indicator. /// - /// `Message` is the last message in the channel, Use it to determine the + /// `Message` is the last message in the channel. Use it to determine the /// status using [Message.state]. final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; - /// True if the tile is in a selected state. - final bool selected; - - /// The color of the tile in selected state. - final Color? selectedTileColor; - /// Creates a copy of this tile but with the given fields replaced with /// the new values. StreamChannelListTile copyWith({ @@ -111,150 +93,159 @@ class StreamChannelListTile extends StatelessWidget { Channel? channel, Widget? leading, Widget? title, + Widget? titleTrailing, Widget? subtitle, + Widget? subtitleTrailing, + Widget? trailing, VoidCallback? onTap, VoidCallback? onLongPress, - VisualDensity? visualDensity, - EdgeInsetsGeometry? contentPadding, - bool? selected, Widget Function(BuildContext, Message)? sendingIndicatorBuilder, - Color? tileColor, - Color? selectedTileColor, - WidgetBuilder? unreadIndicatorBuilder, - Widget? trailing, }) { return StreamChannelListTile( key: key ?? this.key, channel: channel ?? this.channel, leading: leading ?? this.leading, title: title ?? this.title, + titleTrailing: titleTrailing ?? this.titleTrailing, subtitle: subtitle ?? this.subtitle, + subtitleTrailing: subtitleTrailing ?? this.subtitleTrailing, + trailing: trailing ?? this.trailing, onTap: onTap ?? this.onTap, onLongPress: onLongPress ?? this.onLongPress, - visualDensity: visualDensity ?? this.visualDensity, - contentPadding: contentPadding ?? this.contentPadding, - sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, - tileColor: tileColor ?? this.tileColor, - trailing: trailing ?? this.trailing, - unreadIndicatorBuilder: unreadIndicatorBuilder ?? this.unreadIndicatorBuilder, - selected: selected ?? this.selected, - selectedTileColor: selectedTileColor ?? this.selectedTileColor, + sendingIndicatorBuilder: + sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, ); } @override Widget build(BuildContext context) { final channelState = channel.state!; - final currentUser = channel.client.state.currentUser!; - - final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final streamChatTheme = StreamChatTheme.of(context); - final streamChat = StreamChat.of(context); + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; - final leading = this.leading ?? StreamChannelAvatar(channel: channel); - - final title = - this.title ?? + final avatar = leading ?? StreamChannelAvatar(channel: channel); + final titleWidget = + title ?? StreamChannelName( channel: channel, - textStyle: channelPreviewTheme.titleStyle, + textStyle: textTheme.headingSm.copyWith(height: 1), ); - - final subtitle = - this.subtitle ?? + final subtitleWidget = + subtitle ?? ChannelListTileSubtitle( channel: channel, - textStyle: channelPreviewTheme.subtitleStyle, + textStyle: textTheme.captionDefault.copyWith( + color: colorScheme.textSecondary, + ), + sendingIndicatorBuilder: sendingIndicatorBuilder, ); - - final trailing = - this.trailing ?? + final timestampWidget = + trailing ?? ChannelLastMessageDate( channel: channel, - textStyle: channelPreviewTheme.lastMessageAtStyle, - formatter: channelPreviewTheme.lastMessageAtFormatter, + textStyle: textTheme.captionDefault.copyWith( + color: colorScheme.textTertiary, + ), ); return BetterStreamBuilder( stream: channel.isMutedStream, initialData: channel.isMuted, - builder: (context, isMuted) => AnimatedOpacity( - opacity: isMuted ? 0.5 : 1, - duration: const Duration(milliseconds: 300), - child: ListTile( - onTap: onTap, - onLongPress: onLongPress, - visualDensity: visualDensity, - contentPadding: contentPadding, - leading: leading, - tileColor: tileColor, - selected: selected, - selectedTileColor: selectedTileColor ?? StreamChatTheme.of(context).colorTheme.borders, - title: Row( - children: [ - Expanded(child: title), - BetterStreamBuilder>( - stream: channelState.membersStream, - initialData: channelState.members, - comparator: const ListEquality().equals, - builder: (context, members) { - if (members.isEmpty) { - return const Empty(); - } - return unreadIndicatorBuilder?.call(context) ?? StreamUnreadIndicator.channels(cid: channel.cid); - }, - ), - ], - ), - subtitle: Row( - children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: subtitle, - ), - ), - BetterStreamBuilder>( - stream: channelState.messagesStream, - initialData: channelState.messages, - comparator: const ListEquality().equals, - builder: (context, messages) { - final lastMessage = messages.lastWhereOrNull( - (m) => !m.shadowed && !m.isDeleted, - ); - - if (lastMessage == null || (lastMessage.user?.id != currentUser.id)) { - return const Empty(); - } - - final hasNonUrlAttachments = lastMessage.attachments.any( - (it) => it.type != AttachmentType.urlPreview, - ); - - return Padding( - padding: const EdgeInsets.only(right: 4), - child: - sendingIndicatorBuilder?.call(context, lastMessage) ?? - SendingIndicatorBuilder( - messageTheme: streamChatTheme.ownMessageTheme, - message: lastMessage, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - channel: channel, - ), - ); - }, - ), - trailing, - ], - ), - ), + builder: (context, isMuted) => BetterStreamBuilder( + stream: channelState.unreadCountStream, + initialData: channelState.unreadCount, + builder: (context, unreadCount) { + final titleTrailing = isMuted + ? Icon( + context.streamIcons.mute, + size: 20, + color: colorScheme.textTertiary, + ) + : null; + return StreamChannelListItem( + avatar: avatar, + title: titleWidget, + titleTrailing: titleTrailing, + subtitle: subtitleWidget, + timestamp: timestampWidget, + unreadCount: unreadCount, + onTap: onTap, + onLongPress: onLongPress, + ); + }, ), ); } } +/// Shows the delivery status icon + "You:" prefix for outgoing messages in +/// the channel list. +/// +/// Unlike [StreamSendingIndicator], this widget does not show a read count +/// number. It only shows: +/// - Clock icon + "You:" (sending) +/// - Single check + "You:" (sent) +/// - Double check grey + "You:" (delivered) +/// - Double check blue + "You:" (read) +class _ChannelListDeliveryStatus extends StatelessWidget { + const _ChannelListDeliveryStatus({ + required this.channel, + required this.message, + }); + + final Channel channel; + final Message message; + + @override + Widget build(BuildContext context) { + final colorTheme = StreamChatTheme.of(context).colorTheme; + final colorScheme = context.streamColorScheme; + + return BetterStreamBuilder>( + stream: channel.state?.readStream, + initialData: channel.state?.read, + builder: (context, data) { + final isRead = data.readsOf(message: message).isNotEmpty; + final isDelivered = data.deliveriesOf(message: message).isNotEmpty; + + final Widget icon; + if (isRead) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.checkAll, + color: colorTheme.accentPrimary, + ); + } else if (isDelivered) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.checkAll, + color: colorScheme.textTertiary, + ); + } else if (message.state.isCompleted) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.check, + color: colorScheme.textTertiary, + ); + } else if (message.state.isOutgoing) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.time, + color: colorScheme.textTertiary, + ); + } else { + return const Empty(); + } + + return Padding( + padding: const EdgeInsetsDirectional.only(end: 4), + child: icon, + ); + }, + ); + } +} + /// A widget that displays the channel last message date. class ChannelLastMessageDate extends StatelessWidget { /// Creates a new instance of the [ChannelLastMessageDate] widget. @@ -292,12 +283,18 @@ class ChannelLastMessageDate extends StatelessWidget { } /// A widget that displays the subtitle for [StreamChannelListTile]. +/// +/// Shows typing indicators, draft messages, or the last message preview. +/// The delivery status prefix (icon + "You:") is only shown when the subtitle +/// displays an actual sent message from the current user (not for drafts or +/// typing indicators). class ChannelListTileSubtitle extends StatelessWidget { /// Creates a new instance of [StreamChannelListTileSubtitle] widget. ChannelListTileSubtitle({ super.key, required this.channel, this.textStyle, + this.sendingIndicatorBuilder, }) : assert( channel.state != null, 'Channel ${channel.id} is not initialized', @@ -309,6 +306,12 @@ class ChannelListTileSubtitle extends StatelessWidget { /// The style of the text displayed final TextStyle? textStyle; + /// The widget builder for the sending indicator. + /// + /// `Message` is the last message in the channel. Use it to determine the + /// status using [Message.state]. + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + @override Widget build(BuildContext context) { if (channel.isMuted) { @@ -330,14 +333,123 @@ class ChannelListTileSubtitle extends StatelessWidget { return StreamTypingIndicator( channel: channel, style: textStyle, - alternativeWidget: ChannelLastMessageText( + alternativeWidget: _ChannelLastMessageWithStatus( channel: channel, textStyle: textStyle, + sendingIndicatorBuilder: sendingIndicatorBuilder, ), ); } } +/// Combines the delivery status prefix with the last message text. +/// +/// Shows the delivery status only when the displayed content is an actual +/// sent message from the current user (not a draft). +class _ChannelLastMessageWithStatus extends StatefulWidget { + const _ChannelLastMessageWithStatus({ + required this.channel, + this.textStyle, + this.sendingIndicatorBuilder, + }); + + final Channel channel; + final TextStyle? textStyle; + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + @override + State<_ChannelLastMessageWithStatus> createState() => + _ChannelLastMessageWithStatusState(); +} + +class _ChannelLastMessageWithStatusState + extends State<_ChannelLastMessageWithStatus> { + Message? _currentLastMessage; + + static bool _defaultLastMessagePredicate(Message message) { + if (message.isShadowed) return false; + if (message.isDeleted) return false; + if (message.isError) return false; + if (message.isEphemeral) return false; + + return true; + } + + @override + Widget build(BuildContext context) { + final channelState = widget.channel.state; + if (channelState == null) return const Empty(); + + final currentUser = widget.channel.client.state.currentUser; + + return BetterStreamBuilder<(Draft?, List)>( + stream: CombineLatestStream.combine2( + channelState.draftStream, + channelState.messagesStream, + (draft, messages) => (draft, messages), + ), + initialData: (channelState.draft, channelState.messages), + builder: (context, data) { + final (draft, messages) = data; + + // If there's a draft, show only the draft preview (no delivery status). + if (draft?.message case final draftMessage?) { + return StreamDraftMessagePreviewText( + draftMessage: draftMessage, + textStyle: widget.textStyle, + ); + } + + // Find the last valid message. + final message = messages.lastWhereOrNull( + _defaultLastMessagePredicate, + ); + final latestLastMessage = [message, _currentLastMessage].latest; + + if (latestLastMessage == null) { + return Text( + context.translations.emptyMessagesText, + style: widget.textStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + + final isOwnMessage = + currentUser != null && + latestLastMessage.user?.id == currentUser.id; + + // Show delivery status prefix only for own messages. + final Widget deliveryPrefix; + if (isOwnMessage) { + deliveryPrefix = + widget.sendingIndicatorBuilder + ?.call(context, latestLastMessage) ?? + _ChannelListDeliveryStatus( + channel: widget.channel, + message: latestLastMessage, + ); + } else { + deliveryPrefix = const Empty(); + } + + return Row( + children: [ + deliveryPrefix, + Flexible( + child: StreamMessagePreviewText( + message: latestLastMessage, + textStyle: widget.textStyle, + channel: channelState.channelState.channel, + ), + ), + ], + ); + }, + ); + } +} + /// A widget that displays the last message of a channel. class ChannelLastMessageText extends StatefulWidget { /// Creates a new instance of [ChannelLastMessageText] widget. diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index edfb37280a..f5af1c44c2 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -5,6 +5,8 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'package:stream_core_flutter/stream_core_flutter.dart' show StreamAvatarSize, + StreamChannelListItem, + StreamChannelListItemProps, StreamTheme, StreamIcons, StreamThemeExtension, diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 0b2afe5d99..1b7f4e818d 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -60,10 +60,7 @@ dependencies: shimmer: ^3.0.0 stream_chat_flutter_core: ^10.0.0-beta.12 stream_core_flutter: - git: - url: https://github.com/GetStream/stream-core-flutter.git - ref: da18aa04ad48d4d5bb429c9e90d9f0253c418fae - path: packages/stream_core_flutter + path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/sample_app/ios/Flutter/AppFrameworkInfo.plist b/sample_app/ios/Flutter/AppFrameworkInfo.plist index d57061dd6b..ab8e063fe8 100644 --- a/sample_app/ios/Flutter/AppFrameworkInfo.plist +++ b/sample_app/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 From bab22cffbf1785e49b3d0a7097a08552d36b5bb3 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 3 Mar 2026 14:03:21 +0100 Subject: [PATCH 03/18] improve muted state --- .../stream_channel_list_tile.dart | 40 +++++-------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index f7fe01b4e3..d003460396 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -112,8 +112,7 @@ class StreamChannelListTile extends StatelessWidget { trailing: trailing ?? this.trailing, onTap: onTap ?? this.onTap, onLongPress: onLongPress ?? this.onLongPress, - sendingIndicatorBuilder: - sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, + sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, ); } @@ -123,6 +122,9 @@ class StreamChannelListTile extends StatelessWidget { final colorScheme = context.streamColorScheme; final textTheme = context.streamTextTheme; + // TODO: Make this configurable + const showMuteIconInTitle = true; + final avatar = leading ?? StreamChannelAvatar(channel: channel); final titleWidget = title ?? @@ -155,7 +157,7 @@ class StreamChannelListTile extends StatelessWidget { stream: channelState.unreadCountStream, initialData: channelState.unreadCount, builder: (context, unreadCount) { - final titleTrailing = isMuted + final muteIcon = isMuted ? Icon( context.streamIcons.mute, size: 20, @@ -165,8 +167,9 @@ class StreamChannelListTile extends StatelessWidget { return StreamChannelListItem( avatar: avatar, title: titleWidget, - titleTrailing: titleTrailing, + titleTrailing: showMuteIconInTitle ? muteIcon : null, subtitle: subtitleWidget, + subtitleTrailing: showMuteIconInTitle ? null : muteIcon, timestamp: timestampWidget, unreadCount: unreadCount, onTap: onTap, @@ -314,22 +317,6 @@ class ChannelListTileSubtitle extends StatelessWidget { @override Widget build(BuildContext context) { - if (channel.isMuted) { - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - const StreamSvgIcon(size: 16, icon: StreamSvgIcons.mute), - Expanded( - child: Text( - ' ${context.translations.channelIsMutedText}', - style: textStyle, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } return StreamTypingIndicator( channel: channel, style: textStyle, @@ -358,12 +345,10 @@ class _ChannelLastMessageWithStatus extends StatefulWidget { final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; @override - State<_ChannelLastMessageWithStatus> createState() => - _ChannelLastMessageWithStatusState(); + State<_ChannelLastMessageWithStatus> createState() => _ChannelLastMessageWithStatusState(); } -class _ChannelLastMessageWithStatusState - extends State<_ChannelLastMessageWithStatus> { +class _ChannelLastMessageWithStatusState extends State<_ChannelLastMessageWithStatus> { Message? _currentLastMessage; static bool _defaultLastMessagePredicate(Message message) { @@ -415,16 +400,13 @@ class _ChannelLastMessageWithStatusState ); } - final isOwnMessage = - currentUser != null && - latestLastMessage.user?.id == currentUser.id; + final isOwnMessage = currentUser != null && latestLastMessage.user?.id == currentUser.id; // Show delivery status prefix only for own messages. final Widget deliveryPrefix; if (isOwnMessage) { deliveryPrefix = - widget.sendingIndicatorBuilder - ?.call(context, latestLastMessage) ?? + widget.sendingIndicatorBuilder?.call(context, latestLastMessage) ?? _ChannelListDeliveryStatus( channel: widget.channel, message: latestLastMessage, From 03a439df51569d2b533d7c3a8fae99f670f8faa4 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 3 Mar 2026 14:31:40 +0100 Subject: [PATCH 04/18] improve channel avatars --- .../lib/src/components/avatar/stream_channel_avatar.dart | 3 ++- .../channel_scroll_view/stream_channel_list_tile.dart | 2 +- packages/stream_chat_flutter/lib/stream_chat_flutter.dart | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart index b3975aa811..6d47fd2dd1 100644 --- a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart @@ -93,7 +93,8 @@ class StreamChannelAvatar extends StatelessWidget { return StreamUserAvatar( user: otherUser, size: _avatarSizeForAvatarGroupSize(effectiveSize), - showOnlineIndicator: true, + // TODO: make this configurable when the online state is shown. + showOnlineIndicator: otherUser.online, ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index d003460396..8771d4a94e 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -125,7 +125,7 @@ class StreamChannelListTile extends StatelessWidget { // TODO: Make this configurable const showMuteIconInTitle = true; - final avatar = leading ?? StreamChannelAvatar(channel: channel); + final avatar = leading ?? StreamChannelAvatar(channel: channel, size: StreamAvatarGroupSize.xl); final titleWidget = title ?? StreamChannelName( diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index f5af1c44c2..f773c9c3da 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -4,6 +4,7 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'package:stream_core_flutter/stream_core_flutter.dart' show + StreamAvatarGroupSize, StreamAvatarSize, StreamChannelListItem, StreamChannelListItemProps, From 5f000897caaa1e44969f7396c059bb5201264c98 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 3 Mar 2026 15:07:38 +0100 Subject: [PATCH 05/18] change to git dependency --- melos.yaml | 5 ++++- packages/stream_chat_flutter/pubspec.yaml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/melos.yaml b/melos.yaml index e77cafaeee..d7a4776499 100644 --- a/melos.yaml +++ b/melos.yaml @@ -93,7 +93,10 @@ command: svg_icon_widget: ^0.0.1 # TODO: Replace with hosted version before merging PR stream_core_flutter: - path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: edc8c2b3f93d41d4a317a5c06190552d9b9c0c9d + path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 url_launcher: ^6.3.0 diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 1b7f4e818d..4fcb0f3dbf 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -60,7 +60,10 @@ dependencies: shimmer: ^3.0.0 stream_chat_flutter_core: ^10.0.0-beta.12 stream_core_flutter: - path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: edc8c2b3f93d41d4a317a5c06190552d9b9c0c9d + path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 thumblr: ^0.0.4 From 774058629a4a2319655f645225aad18f68214a8c Mon Sep 17 00:00:00 2001 From: renefloor <15101411+renefloor@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:11:39 +0000 Subject: [PATCH 06/18] chore: Update Goldens --- .../stream_message_reactions_modal_dark.png | Bin 10510 -> 10048 bytes .../stream_message_reactions_modal_light.png | Bin 11106 -> 10564 bytes ..._message_reactions_modal_reversed_dark.png | Bin 10586 -> 10122 bytes ...message_reactions_modal_reversed_light.png | Bin 11117 -> 10572 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png index 0065e1d2e7c05a3b6664b1727d0a1a9298bfe9f9..845bbf0c5ae8f407f6ff0f81997bba77ee6e62f0 100644 GIT binary patch literal 10048 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 P;L_DJdRVRgH2Qx5Lq?T2 literal 10510 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 bBViYw_rulr&BnYP2>3wsjBi!zIK21|c|U%E diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png index 8be55f6a6e3396eda82066d6f332d08b59b47ada..1a72d6dc1068643d20d7466ff2d4a6c8e17b9b3d 100644 GIT binary patch literal 10564 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png index 10b912349654d9e3a223026c72de10e9ce83be87..6e051952d56081482a993cee0d1a38fc68c34eed 100644 GIT binary patch literal 10122 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

mv=pr$?ul@mD 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 U!MZyD2m%@0Hn~-K^WpRV0_nDc<^TWy diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png index 7fd124e7d98360b4200d4e6afd6fda2c90e712b0..3bec5c392b58e8a4eab25c6b7caf407785ea73d9 100644 GIT binary patch literal 10572 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 V@7R8^#tZ^JIu8uAP?}F;{s$8H$W;IU literal 11117 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 From 934da8cb22a51f386c37f3b1db110f559ef0d66d Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 10:37:30 +0100 Subject: [PATCH 07/18] Update channel name and list tile --- melos.yaml | 2 +- .../lib/src/channel/channel_list_header.dart | 4 +-- .../lib/src/channel/stream_channel_name.dart | 29 ++++++++++------ .../stream_channel_list_tile.dart | 33 +++++-------------- packages/stream_chat_flutter/pubspec.yaml | 2 +- 5 files changed, 32 insertions(+), 38 deletions(-) diff --git a/melos.yaml b/melos.yaml index 65aa486691..987c75b196 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 490feb000a1b4a2f6baa56621dc8941363195742 + ref: 6eb0c43ca646fce23c6ca49df8ec26a2f6bdd132 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index 19d0e06f7e..87a03735a4 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -172,10 +172,10 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi ConnectionStatus.disconnected => Colors.grey, }; - return StreamSvgIcon( + return Icon( + context.streamIcons.pencil, size: 24, color: color, - icon: StreamSvgIcons.penWrite, ); }, ), diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart index 12049cf60a..4dc226be84 100644 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart @@ -59,21 +59,15 @@ class StreamChannelName extends StatelessWidget { } } else { final maxWidth = constraints.maxWidth; - final maxChars = maxWidth / (textStyle?.fontSize ?? 1); - var currentChars = 0; + channelName = ''; final currentMembers = []; otherMembers.forEach((element) { - final newLength = currentChars + (element.user?.name.length ?? 0); - if (newLength < maxChars) { - currentChars = newLength; + final newTitle = _getChannelName(currentMembers: [...currentMembers, element], members: members); + if (_calculateTextSize(newTitle).width < maxWidth) { currentMembers.add(element); + channelName = newTitle; } }); - - final exceedingMembers = otherMembers.length - currentMembers.length; - channelName = - '${currentMembers.map((e) => e.user?.name).join(', ')} ' - '${exceedingMembers > 0 ? '+ ${exceedingMembers + 1}' : ''}'; } } @@ -84,4 +78,19 @@ class StreamChannelName extends StatelessWidget { ); }, ); + + String _getChannelName({required List currentMembers, required List members}) { + final exceedingMembers = members.length - currentMembers.length; + return '${currentMembers.map((e) => e.user?.name).join(', ')} ' + '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } + + Size _calculateTextSize(String text) { + final textPainter = TextPainter( + text: TextSpan(text: text, style: textStyle), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(minWidth: 0, maxWidth: double.infinity); + return textPainter.size; + } } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index 8771d4a94e..9573253dac 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -35,6 +35,7 @@ class StreamChannelListTile extends StatelessWidget { this.onTap, this.onLongPress, this.sendingIndicatorBuilder, + this.selected = false, }) : assert( channel.state != null, 'Channel ${channel.id} is not initialized', @@ -86,6 +87,9 @@ class StreamChannelListTile extends StatelessWidget { /// status using [Message.state]. final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + /// True if the tile is in a selected state. + final bool selected; + /// Creates a copy of this tile but with the given fields replaced with /// the new values. StreamChannelListTile copyWith({ @@ -100,6 +104,7 @@ class StreamChannelListTile extends StatelessWidget { VoidCallback? onTap, VoidCallback? onLongPress, Widget Function(BuildContext, Message)? sendingIndicatorBuilder, + bool? selected, }) { return StreamChannelListTile( key: key ?? this.key, @@ -113,18 +118,15 @@ class StreamChannelListTile extends StatelessWidget { onTap: onTap ?? this.onTap, onLongPress: onLongPress ?? this.onLongPress, sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, + selected: selected ?? this.selected, ); } @override Widget build(BuildContext context) { final channelState = channel.state!; - final colorScheme = context.streamColorScheme; final textTheme = context.streamTextTheme; - // TODO: Make this configurable - const showMuteIconInTitle = true; - final avatar = leading ?? StreamChannelAvatar(channel: channel, size: StreamAvatarGroupSize.xl); final titleWidget = title ?? @@ -136,19 +138,9 @@ class StreamChannelListTile extends StatelessWidget { subtitle ?? ChannelListTileSubtitle( channel: channel, - textStyle: textTheme.captionDefault.copyWith( - color: colorScheme.textSecondary, - ), sendingIndicatorBuilder: sendingIndicatorBuilder, ); - final timestampWidget = - trailing ?? - ChannelLastMessageDate( - channel: channel, - textStyle: textTheme.captionDefault.copyWith( - color: colorScheme.textTertiary, - ), - ); + final timestampWidget = trailing ?? ChannelLastMessageDate(channel: channel); return BetterStreamBuilder( stream: channel.isMutedStream, @@ -157,23 +149,16 @@ class StreamChannelListTile extends StatelessWidget { stream: channelState.unreadCountStream, initialData: channelState.unreadCount, builder: (context, unreadCount) { - final muteIcon = isMuted - ? Icon( - context.streamIcons.mute, - size: 20, - color: colorScheme.textTertiary, - ) - : null; return StreamChannelListItem( avatar: avatar, title: titleWidget, - titleTrailing: showMuteIconInTitle ? muteIcon : null, subtitle: subtitleWidget, - subtitleTrailing: showMuteIconInTitle ? null : muteIcon, timestamp: timestampWidget, unreadCount: unreadCount, + isMuted: isMuted, onTap: onTap, onLongPress: onLongPress, + selected: selected, ); }, ), diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f36439211b..05cd557089 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 490feb000a1b4a2f6baa56621dc8941363195742 + ref: 6eb0c43ca646fce23c6ca49df8ec26a2f6bdd132 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 From 576ce4dddee345527fcae5fc85f535657486c9a5 Mon Sep 17 00:00:00 2001 From: renefloor <15101411+renefloor@users.noreply.github.com> Date: Wed, 4 Mar 2026 09:41:40 +0000 Subject: [PATCH 08/18] chore: Update Goldens --- .../stream_message_reactions_modal_dark.png | Bin 10048 -> 8707 bytes .../stream_message_reactions_modal_light.png | Bin 10564 -> 10915 bytes ..._message_reactions_modal_reversed_dark.png | Bin 10122 -> 8753 bytes ...message_reactions_modal_reversed_light.png | Bin 10572 -> 10801 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png index 845bbf0c5ae8f407f6ff0f81997bba77ee6e62f0..ed7e11f4e68b1f28e889d399037384bb8fe97d97 100644 GIT binary patch literal 8707 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 literal 10048 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 P;L_DJdRVRgH2Qx5Lq?T2 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png index 1a72d6dc1068643d20d7466ff2d4a6c8e17b9b3d..4f907ddec5b236625847de4cfd23371855e53838 100644 GIT binary patch literal 10915 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 Lp+@C>rV@`-T~17B~UkK7U%K2N?tHnly7sD^N$ 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 Ds+TP+ literal 10122 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

mv=pr$?ul@mD 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 literal 10572 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 V@7R8^#tZ^JIu8uAP?}F;{s$8H$W;IU From 81f2c565f321fbe7fe32340ef4ae3d8b7118d7d7 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 10:48:16 +0100 Subject: [PATCH 09/18] remove unused properties --- .../stream_channel_list_tile.dart | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index 9573253dac..49da4b0ae4 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -28,9 +28,7 @@ class StreamChannelListTile extends StatelessWidget { required this.channel, this.leading, this.title, - this.titleTrailing, this.subtitle, - this.subtitleTrailing, this.trailing, this.onTap, this.onLongPress, @@ -54,22 +52,12 @@ class StreamChannelListTile extends StatelessWidget { /// Defaults to [StreamChannelName]. final Widget? title; - /// An optional widget displayed after the title. - /// - /// Typically used for a mute icon or similar indicator. - final Widget? titleTrailing; - /// Additional content displayed below the title. /// /// Defaults to [ChannelListTileSubtitle] which shows typing indicators, /// draft messages, or the last message preview. final Widget? subtitle; - /// An optional trailing widget in the subtitle row. - /// - /// When not provided, a sending indicator is shown for outgoing messages. - final Widget? subtitleTrailing; - /// A widget to display as the timestamp. /// /// Defaults to [ChannelLastMessageDate]. @@ -97,9 +85,7 @@ class StreamChannelListTile extends StatelessWidget { Channel? channel, Widget? leading, Widget? title, - Widget? titleTrailing, Widget? subtitle, - Widget? subtitleTrailing, Widget? trailing, VoidCallback? onTap, VoidCallback? onLongPress, @@ -111,9 +97,7 @@ class StreamChannelListTile extends StatelessWidget { channel: channel ?? this.channel, leading: leading ?? this.leading, title: title ?? this.title, - titleTrailing: titleTrailing ?? this.titleTrailing, subtitle: subtitle ?? this.subtitle, - subtitleTrailing: subtitleTrailing ?? this.subtitleTrailing, trailing: trailing ?? this.trailing, onTap: onTap ?? this.onTap, onLongPress: onLongPress ?? this.onLongPress, From df851a0801070b02950900dc78953ae77bb89567 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 11:00:56 +0100 Subject: [PATCH 10/18] revert change on location marker --- sample_app/lib/widgets/location/location_user_marker.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample_app/lib/widgets/location/location_user_marker.dart b/sample_app/lib/widgets/location/location_user_marker.dart index bca88aa83a..3c8994ab98 100644 --- a/sample_app/lib/widgets/location/location_user_marker.dart +++ b/sample_app/lib/widgets/location/location_user_marker.dart @@ -72,6 +72,6 @@ class LocationUserMarker extends StatelessWidget { .sm => StreamAvatarSize.sm, .md => StreamAvatarSize.md, .lg => StreamAvatarSize.lg, - .xl => StreamAvatarSize.xxl, + .xl => StreamAvatarSize.xl, }; } From 80d80eba9f21804f7e40c6e9224b878aa6a61a4f Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 14:41:22 +0100 Subject: [PATCH 11/18] rename listitem and move items from core --- .../example/lib/tutorial_part_3.dart | 4 +- ...ile.dart => stream_channel_list_item.dart} | 265 +++++++++++++++++- .../stream_channel_list_view.dart | 10 +- .../theme/stream_channel_list_item_theme.dart | 138 +++++++++ ...tream_channel_list_item_theme.g.theme.dart | 127 +++++++++ .../lib/src/theme/stream_chat_theme.dart | 11 + .../lib/stream_chat_flutter.dart | 2 +- 7 files changed, 540 insertions(+), 17 deletions(-) rename packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/{stream_channel_list_tile.dart => stream_channel_list_item.dart} (64%) create mode 100644 packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart create mode 100644 packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart 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 bc57390a29..ce718b6018 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -17,7 +17,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// We're passing a custom widget /// to [StreamChannelListView.itemBuilder]; -/// this will override the default [StreamChannelListTile] and allows you +/// this will override the default [StreamChannelListItem] and allows you /// to create one yourself. /// /// There are a couple interesting things we do in this widget: @@ -116,7 +116,7 @@ class _ChannelListPageState extends State { BuildContext context, List channels, int index, - StreamChannelListTile defaultTile, + StreamChannelListItem defaultTile, ) { final channel = channels[index]; final lastMessage = channel.state?.messages.reversed.firstWhereOrNull( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart similarity index 64% rename from packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart rename to packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart index 49da4b0ae4..3cd940b176 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -5,6 +5,7 @@ import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/misc/timestamp.dart'; import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// A widget that displays a channel preview. /// @@ -14,16 +15,15 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// message count, the typing indicator, the sending indicator and the channel /// avatar. /// -/// Internally uses [StreamChannelListItem] from the core design system for +/// Internally uses [StreamListTileContainer] from the core design system for /// consistent visual presentation. /// /// See also: /// * [StreamChannelAvatar] /// * [StreamChannelName] -/// * [StreamChannelListItem] -class StreamChannelListTile extends StatelessWidget { - /// Creates a new instance of [StreamChannelListTile] widget. - StreamChannelListTile({ +class StreamChannelListItem extends StatelessWidget { + /// Creates a new instance of [StreamChannelListItem] widget. + StreamChannelListItem({ super.key, required this.channel, this.leading, @@ -80,7 +80,7 @@ class StreamChannelListTile extends StatelessWidget { /// Creates a copy of this tile but with the given fields replaced with /// the new values. - StreamChannelListTile copyWith({ + StreamChannelListItem copyWith({ Key? key, Channel? channel, Widget? leading, @@ -92,7 +92,7 @@ class StreamChannelListTile extends StatelessWidget { Widget Function(BuildContext, Message)? sendingIndicatorBuilder, bool? selected, }) { - return StreamChannelListTile( + return StreamChannelListItem( key: key ?? this.key, channel: channel ?? this.channel, leading: leading ?? this.leading, @@ -133,7 +133,7 @@ class StreamChannelListTile extends StatelessWidget { stream: channelState.unreadCountStream, initialData: channelState.unreadCount, builder: (context, unreadCount) { - return StreamChannelListItem( + return _StreamChannelListTile( avatar: avatar, title: titleWidget, subtitle: subtitleWidget, @@ -150,6 +150,253 @@ class StreamChannelListTile extends StatelessWidget { } } +class _StreamChannelListTile extends StatelessWidget { + const _StreamChannelListTile({ + required this.avatar, + required this.title, + this.subtitle, + this.timestamp, + this.unreadCount = 0, + this.isMuted = false, + this.onTap, + this.onLongPress, + this.selected = false, + }); + + /// The avatar widget displayed at the leading edge. + /// + /// Typically a [StreamAvatar], [StreamAvatarGroup], or an avatar wrapped + /// in a [StreamOnlineIndicator]. + final Widget avatar; + + /// The channel title widget. + /// + /// Typically a [Text] widget with the channel name. The default text style + /// is provided by the theme's title style via [DefaultTextStyle]. + final Widget title; + + /// The message preview widget displayed below the title. + /// + /// Typically a [Text] widget with the last message, but can be any widget + /// for richer content (e.g., icons, read receipts, sender prefix). + final Widget? subtitle; + + /// The timestamp widget displayed in the trailing section of the title row. + /// + /// Typically a [Text] widget with a formatted date string. The default text + /// style is provided by the theme's timestamp style via [DefaultTextStyle]. + final Widget? timestamp; + + /// The number of unread messages. + /// + /// When greater than zero, a [StreamBadgeNotification] is displayed. + final int unreadCount; + + /// Whether the channel is muted. + /// + /// When true, a mute icon is displayed in the title or subtitle. + final bool isMuted; + + /// Called when the list item is tapped. + final VoidCallback? onTap; + + /// Called when the list item is long-pressed. + final VoidCallback? onLongPress; + + /// Whether the list item is in a selected state. + final bool selected; + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final channelListItemTheme = context.streamChannelListItemTheme; + final defaults = _StreamChannelListItemThemeDefaults(context); + + final effectiveTitleStyle = channelListItemTheme.titleStyle ?? defaults.titleStyle; + final effectiveSubtitleStyle = channelListItemTheme.subtitleStyle ?? defaults.subtitleStyle; + final effectiveTimestampStyle = channelListItemTheme.timestampStyle ?? defaults.timestampStyle; + final effectiveMuteIconPosition = channelListItemTheme.muteIconPosition ?? defaults.muteIconPosition; + + final muteIcon = isMuted + ? Icon( + context.streamIcons.mute, + size: 20, + color: context.streamColorScheme.textTertiary, + ) + : null; + + final hasMuteIconInSubtitle = effectiveMuteIconPosition == MuteIconPosition.subtitle && isMuted; + + return StreamListTileTheme( + data: context.streamListTileTheme.copyWith( + contentPadding: EdgeInsets.all(spacing.md - 4), + backgroundColor: channelListItemTheme.backgroundColor, + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: Material( + type: MaterialType.transparency, + child: StreamListTileContainer( + enabled: true, + selected: selected, + onTap: onTap, + onLongPress: onLongPress, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: spacing.md, + children: [ + avatar, + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(vertical: spacing.xxxs), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: spacing.xxs, + children: [ + _TitleRow( + title: title, + titleTrailing: effectiveMuteIconPosition == MuteIconPosition.title ? muteIcon : null, + timestamp: timestamp, + unreadCount: unreadCount, + titleStyle: effectiveTitleStyle, + timestampStyle: effectiveTimestampStyle, + spacing: spacing, + ), + if (subtitle != null || hasMuteIconInSubtitle) + _SubtitleRow( + subtitle: subtitle, + subtitleTrailing: effectiveMuteIconPosition == MuteIconPosition.subtitle ? muteIcon : null, + subtitleStyle: effectiveSubtitleStyle, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _TitleRow extends StatelessWidget { + const _TitleRow({ + required this.title, + this.titleTrailing, + this.timestamp, + required this.unreadCount, + required this.titleStyle, + required this.timestampStyle, + required this.spacing, + }); + + final Widget title; + final Widget? titleTrailing; + final Widget? timestamp; + final int unreadCount; + final TextStyle titleStyle; + final TextStyle timestampStyle; + final StreamSpacing spacing; + + @override + Widget build(BuildContext context) { + return Row( + spacing: spacing.md, + children: [ + Expanded( + child: Row( + spacing: spacing.xxs, + children: [ + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: StreamBadgeNotificationSize.sm.value), + child: Align( + alignment: AlignmentDirectional.centerStart, + widthFactor: 1, + child: DefaultTextStyle.merge( + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: title, + ), + ), + ), + ), + ?titleTrailing, + ], + ), + ), + if (timestamp != null || unreadCount > 0) + Row( + mainAxisSize: MainAxisSize.min, + spacing: spacing.xs, + children: [ + if (timestamp case final timestamp?) + DefaultTextStyle.merge( + style: timestampStyle, + child: timestamp, + ), + if (unreadCount > 0) StreamBadgeNotification(label: '$unreadCount'), + ], + ), + ], + ); + } +} + +class _SubtitleRow extends StatelessWidget { + const _SubtitleRow({ + required this.subtitle, + this.subtitleTrailing, + required this.subtitleStyle, + }); + + final Widget? subtitle; + final Widget? subtitleTrailing; + final TextStyle subtitleStyle; + + @override + Widget build(BuildContext context) { + return DefaultTextStyle( + style: subtitleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: Row( + children: [ + Expanded(child: subtitle ?? const SizedBox.shrink()), + ?subtitleTrailing, + ], + ), + ); + } +} + +class _StreamChannelListItemThemeDefaults extends StreamChannelListItemThemeData { + _StreamChannelListItemThemeDefaults(this._context); + + final BuildContext _context; + + late final _colorScheme = _context.streamColorScheme; + late final _textTheme = _context.streamTextTheme; + + @override + TextStyle get titleStyle => _textTheme.headingSm.copyWith(color: _colorScheme.textPrimary); + + @override + TextStyle get subtitleStyle => _textTheme.captionDefault.copyWith(color: _colorScheme.textSecondary); + + @override + TextStyle get timestampStyle => _textTheme.captionDefault.copyWith(color: _colorScheme.textTertiary); + + @override + Color get borderColor => _colorScheme.borderSubtle; + + @override + MuteIconPosition get muteIconPosition => MuteIconPosition.title; +} + /// Shows the delivery status icon + "You:" prefix for outgoing messages in /// the channel list. /// @@ -254,7 +501,7 @@ class ChannelLastMessageDate extends StatelessWidget { } } -/// A widget that displays the subtitle for [StreamChannelListTile]. +/// A widget that displays the subtitle for [StreamChannelListItem]. /// /// Shows typing indicators, draft messages, or the last message preview. /// The delivery status prefix (icon + "You:") is only shown when the subtitle diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart index a2c3be15a3..1d697dfd6a 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -16,10 +16,10 @@ Widget defaultChannelListViewSeparatorBuilder( /// Signature for the item builder that creates the children of the /// [StreamChannelListView]. typedef StreamChannelListViewIndexedWidgetBuilder = - StreamScrollViewIndexedWidgetBuilder; + StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [Channel]s, -/// it uses [StreamChannelListTile] as a default item. +/// it uses [StreamChannelListItem] as a default item. /// /// This is the new version of [StreamChannelListView] that uses /// [StreamChannelListController]. @@ -39,7 +39,7 @@ typedef StreamChannelListViewIndexedWidgetBuilder = /// ``` /// /// See also: -/// * [StreamChannelListTile] +/// * [StreamChannelListItem] /// * [StreamChannelListController] class StreamChannelListView extends StatelessWidget { /// Creates a new instance of [StreamChannelListView]. @@ -303,7 +303,7 @@ class StreamChannelListView extends StatelessWidget { final onTap = onChannelTap; final onLongPress = onChannelLongPress; - final streamChannelListTile = StreamChannelListTile( + final streamChannelListTile = StreamChannelListItem( channel: channel, onTap: onTap == null ? null : () => onTap(channel), onLongPress: onLongPress == null ? null : () => onLongPress(channel), @@ -365,7 +365,7 @@ class StreamChannelListView extends StatelessWidget { } /// A widget that is used to display a separator between -/// [StreamChannelListTile] items. +/// [StreamChannelListItem] items. class StreamChannelListSeparator extends StatelessWidget { /// Creates a new instance of [StreamChannelListSeparator]. const StreamChannelListSeparator({super.key}); diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart new file mode 100644 index 0000000000..1979dccb12 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart @@ -0,0 +1,138 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart'; + +part 'stream_channel_list_item_theme.g.theme.dart'; + +/// Applies a channel list item theme to descendant +/// [StreamChannelListItem] widgets. +/// +/// Wrap a subtree with [StreamChannelListItemTheme] to override styling. +/// Access the merged theme using [BuildContext.streamChannelListItemTheme]. +/// +/// {@tool snippet} +/// +/// Override channel list item colors for a specific section: +/// +/// ```dart +/// StreamChannelListItemTheme( +/// data: StreamChannelListItemThemeData( +/// backgroundColor: Colors.grey.shade50, +/// ), +/// child: StreamChannelListItem( +/// avatar: StreamAvatar(...), +/// title: 'General', +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamChannelListItemThemeData], which describes the theme. +/// * [StreamChannelListItem], the widget affected by this theme. +class StreamChannelListItemTheme extends InheritedTheme { + /// Creates a channel list item theme that controls descendant widgets. + const StreamChannelListItemTheme({ + super.key, + required this.data, + required super.child, + }); + + /// The channel list item theme data for descendant widgets. + final StreamChannelListItemThemeData data; + + /// Returns the [StreamChannelListItemThemeData] merged from local and + /// global themes. + /// + /// Local values from the nearest [StreamChannelListItemTheme] ancestor + /// take precedence over global values from [StreamTheme.of]. + static StreamChannelListItemThemeData of(BuildContext context) { + final localTheme = context.dependOnInheritedWidgetOfExactType(); + return StreamChatTheme.of(context).channelListItemTheme.merge(localTheme?.data); + } + + @override + Widget wrap(BuildContext context, Widget child) { + return StreamChannelListItemTheme(data: data, child: child); + } + + @override + bool updateShouldNotify(StreamChannelListItemTheme oldWidget) => data != oldWidget.data; +} + +/// Theme data for customizing [StreamChannelListItem] widgets. +/// +/// {@tool snippet} +/// +/// Customize channel list item appearance globally: +/// +/// ```dart +/// StreamTheme( +/// channelListItemTheme: StreamChannelListItemThemeData( +/// backgroundColor: Colors.white, +/// borderColor: Colors.grey.shade200, +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamChannelListItem], the widget that uses this theme data. +/// * [StreamChannelListItemTheme], for overriding theme in a widget subtree. +@themeGen +@immutable +class StreamChannelListItemThemeData with _$StreamChannelListItemThemeData { + /// Creates a channel list item theme with optional style overrides. + const StreamChannelListItemThemeData({ + this.titleStyle, + this.subtitleStyle, + this.timestampStyle, + this.backgroundColor, + this.borderColor, + this.muteIconPosition, + }); + + /// The text style for the channel title. + /// + /// Falls back to [StreamTextTheme.headingSm] with [StreamColorScheme.textPrimary]. + final TextStyle? titleStyle; + + /// The text style for the message preview subtitle. + /// + /// Falls back to [StreamTextTheme.captionDefault] with [StreamColorScheme.textSecondary]. + final TextStyle? subtitleStyle; + + /// The text style for the timestamp. + /// + /// Falls back to [StreamTextTheme.captionDefault] with [StreamColorScheme.textTertiary]. + final TextStyle? timestampStyle; + + /// Defines the default background color of the tile. + /// + /// This color is resolved from [WidgetState]s. + final WidgetStateProperty? backgroundColor; + + /// The bottom border color of the list item. + /// + /// Falls back to [StreamColorScheme.borderSubtle]. + final Color? borderColor; + + /// The position of the mute icon. + /// + /// Falls back to [MuteIconPosition.title]. + final MuteIconPosition? muteIconPosition; + + /// Linearly interpolate between two [StreamChannelListItemThemeData] objects. + static StreamChannelListItemThemeData? lerp( + StreamChannelListItemThemeData? a, + StreamChannelListItemThemeData? b, + double t, + ) => _$StreamChannelListItemThemeData.lerp(a, b, t); +} + +enum MuteIconPosition { + title, + subtitle, +} diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart new file mode 100644 index 0000000000..bb1baafe13 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart @@ -0,0 +1,127 @@ +// dart format width=80 +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_element + +part of 'stream_channel_list_item_theme.dart'; + +// ************************************************************************** +// ThemeGenGenerator +// ************************************************************************** + +mixin _$StreamChannelListItemThemeData { + bool get canMerge => true; + + static StreamChannelListItemThemeData? lerp( + StreamChannelListItemThemeData? a, + StreamChannelListItemThemeData? b, + double t, + ) { + if (identical(a, b)) { + return a; + } + + if (a == null) { + return t == 1.0 ? b : null; + } + + if (b == null) { + return t == 0.0 ? a : null; + } + + return StreamChannelListItemThemeData( + titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), + subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t), + timestampStyle: TextStyle.lerp(a.timestampStyle, b.timestampStyle, t), + backgroundColor: WidgetStateProperty.lerp( + a.backgroundColor, + b.backgroundColor, + t, + Color.lerp, + ), + borderColor: Color.lerp(a.borderColor, b.borderColor, t), + muteIconPosition: t < 0.5 ? a.muteIconPosition : b.muteIconPosition, + ); + } + + StreamChannelListItemThemeData copyWith({ + TextStyle? titleStyle, + TextStyle? subtitleStyle, + TextStyle? timestampStyle, + WidgetStateProperty? backgroundColor, + Color? borderColor, + MuteIconPosition? muteIconPosition, + }) { + final _this = (this as StreamChannelListItemThemeData); + + return StreamChannelListItemThemeData( + titleStyle: titleStyle ?? _this.titleStyle, + subtitleStyle: subtitleStyle ?? _this.subtitleStyle, + timestampStyle: timestampStyle ?? _this.timestampStyle, + backgroundColor: backgroundColor ?? _this.backgroundColor, + borderColor: borderColor ?? _this.borderColor, + muteIconPosition: muteIconPosition ?? _this.muteIconPosition, + ); + } + + StreamChannelListItemThemeData merge(StreamChannelListItemThemeData? other) { + final _this = (this as StreamChannelListItemThemeData); + + if (other == null || identical(_this, other)) { + return _this; + } + + if (!other.canMerge) { + return other; + } + + return copyWith( + titleStyle: _this.titleStyle?.merge(other.titleStyle) ?? other.titleStyle, + subtitleStyle: + _this.subtitleStyle?.merge(other.subtitleStyle) ?? + other.subtitleStyle, + timestampStyle: + _this.timestampStyle?.merge(other.timestampStyle) ?? + other.timestampStyle, + backgroundColor: other.backgroundColor, + borderColor: other.borderColor, + muteIconPosition: other.muteIconPosition, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + final _this = (this as StreamChannelListItemThemeData); + final _other = (other as StreamChannelListItemThemeData); + + return _other.titleStyle == _this.titleStyle && + _other.subtitleStyle == _this.subtitleStyle && + _other.timestampStyle == _this.timestampStyle && + _other.backgroundColor == _this.backgroundColor && + _other.borderColor == _this.borderColor && + _other.muteIconPosition == _this.muteIconPosition; + } + + @override + int get hashCode { + final _this = (this as StreamChannelListItemThemeData); + + return Object.hash( + runtimeType, + _this.titleStyle, + _this.subtitleStyle, + _this.timestampStyle, + _this.backgroundColor, + _this.borderColor, + _this.muteIconPosition, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 572dd3c3f2..17560167b6 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -1,6 +1,7 @@ // ignore_for_file: deprecated_member_use_from_same_package import 'package:flutter/material.dart' hide TextTheme; +import 'package:stream_chat_flutter/src/theme/stream_channel_list_item_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamChatTheme} @@ -63,6 +64,7 @@ class StreamChatThemeData { StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, + StreamChannelListItemThemeData? channelListItemTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; textTheme ??= StreamTextTheme(brightness: brightness); @@ -95,6 +97,7 @@ class StreamChatThemeData { threadListTileTheme: threadListTileTheme, draftListTileTheme: draftListTileTheme, voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme, + channelListItemTheme: channelListItemTheme, ); return defaultData.merge(customizedData); @@ -129,6 +132,7 @@ class StreamChatThemeData { required this.threadListTileTheme, required this.draftListTileTheme, required this.voiceRecordingAttachmentTheme, + required this.channelListItemTheme, }); /// Creates a theme from a Material [Theme] @@ -525,6 +529,7 @@ class StreamChatThemeData { ), ), voiceRecordingAttachmentTheme: const StreamVoiceRecordingAttachmentThemeData(), + channelListItemTheme: const StreamChannelListItemThemeData(), ); } @@ -590,6 +595,9 @@ class StreamChatThemeData { /// Theme configuration for the [StreamVoiceRecordingAttachment] widget. final StreamVoiceRecordingAttachmentThemeData voiceRecordingAttachmentTheme; + /// Theme configuration for the [StreamChannelListItem] widget. + final StreamChannelListItemThemeData channelListItemTheme; + /// Theme configuration for the [StreamDraftListTile] widget. final StreamDraftListTileThemeData draftListTileTheme; @@ -628,6 +636,7 @@ class StreamChatThemeData { StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, + StreamChannelListItemThemeData? channelListItemTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: this.channelListHeaderTheme.merge(channelListHeaderTheme), textTheme: this.textTheme.merge(textTheme), @@ -650,6 +659,7 @@ class StreamChatThemeData { threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, draftListTileTheme: draftListTileTheme ?? this.draftListTileTheme, voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme ?? this.voiceRecordingAttachmentTheme, + channelListItemTheme: channelListItemTheme ?? this.channelListItemTheme, ); /// Merge themes @@ -677,6 +687,7 @@ class StreamChatThemeData { threadListTileTheme: threadListTileTheme.merge(other.threadListTileTheme), draftListTileTheme: draftListTileTheme.merge(other.draftListTileTheme), voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme.merge(other.voiceRecordingAttachmentTheme), + channelListItemTheme: channelListItemTheme.merge(other.channelListItemTheme), ); } } diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 92712d94cf..70d48ce787 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -137,7 +137,7 @@ export 'src/reactions/picker/reaction_picker.dart'; export 'src/reactions/user_reactions.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart'; -export 'src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart'; +export 'src/scroll_view/channel_scroll_view/stream_channel_list_item.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_list_view.dart'; export 'src/scroll_view/draft_scroll_view/stream_draft_list_tile.dart'; export 'src/scroll_view/draft_scroll_view/stream_draft_list_view.dart'; From 4dc4af8523c2fa37724247e663aab4731a6f89bf Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 16:11:29 +0100 Subject: [PATCH 12/18] Add factory builder --- melos.yaml | 5 +- .../stream_chat_component_builders.dart | 2 + .../stream_channel_list_item.dart | 116 ++++++++++++++++-- packages/stream_chat_flutter/pubspec.yaml | 6 +- 4 files changed, 108 insertions(+), 21 deletions(-) diff --git a/melos.yaml b/melos.yaml index 4894e2382f..e77cafaeee 100644 --- a/melos.yaml +++ b/melos.yaml @@ -93,10 +93,7 @@ command: svg_icon_widget: ^0.0.1 # TODO: Replace with hosted version before merging PR stream_core_flutter: - git: - url: https://github.com/GetStream/stream-core-flutter.git - ref: 07f2357a4f1266784c26fde3430f0bdff43bbee8 - path: packages/stream_core_flutter + path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 url_launcher: ^6.3.0 diff --git a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart index 3d925516de..f047aa14f3 100644 --- a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart +++ b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart @@ -2,6 +2,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Builds the list of component builders for the stream chat components. Iterable> streamChatComponentBuilders({ + StreamComponentBuilder? channelListItem, StreamComponentBuilder? messageComposer, StreamComponentBuilder? messageComposerLeading, StreamComponentBuilder? messageComposerTrailing, @@ -11,6 +12,7 @@ Iterable> streamChatComponentBuilders({ StreamComponentBuilder? messageComposerInputTrailing, }) { final builders = [ + if (channelListItem != null) StreamComponentBuilderExtension(builder: channelListItem), if (messageComposer != null) StreamComponentBuilderExtension(builder: messageComposer), if (messageComposerLeading != null) StreamComponentBuilderExtension(builder: messageComposerLeading), if (messageComposerTrailing != null) StreamComponentBuilderExtension(builder: messageComposerTrailing), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart index 3cd940b176..8e3c2f8873 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -108,27 +108,117 @@ class StreamChannelListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final channelState = channel.state!; + final builder = StreamComponentFactory.of(context).extension(); + if (builder != null) return builder(context, _props); + return _DefaultStreamChannelListItem(props: _props); + } + + StreamChannelListItemProps get _props => StreamChannelListItemProps( + channel: channel, + leading: leading, + title: title, + subtitle: subtitle, + trailing: trailing, + onTap: onTap, + onLongPress: onLongPress, + sendingIndicatorBuilder: sendingIndicatorBuilder, + selected: selected, + ); +} + +/// Properties for configuring a [StreamChannelListItem]. +/// +/// This class holds all the configuration options for a channel list item, +/// allowing them to be passed through the [StreamComponentFactory]. +/// +/// See also: +/// +/// * [StreamChannelListItem], which uses these properties. +/// * [DefaultStreamChannelListItem], the default implementation. +class StreamChannelListItemProps { + /// Creates properties for a channel list item. + const StreamChannelListItemProps({ + required this.channel, + this.leading, + this.title, + this.subtitle, + this.trailing, + this.onTap, + this.onLongPress, + this.sendingIndicatorBuilder, + this.selected = false, + }); + + /// The channel to display. + final Channel channel; + + /// A widget to display as the avatar. + /// + /// Defaults to [StreamChannelAvatar]. + final Widget? leading; + + /// The primary content of the list tile. + /// + /// Defaults to [StreamChannelName]. + final Widget? title; + + /// Additional content displayed below the title. + /// + /// Defaults to [ChannelListTileSubtitle] which shows typing indicators, + /// draft messages, or the last message preview. + final Widget? subtitle; + + /// A widget to display as the timestamp. + /// + /// Defaults to [ChannelLastMessageDate]. + final Widget? trailing; + + /// Called when the user taps this list tile. + final GestureTapCallback? onTap; + + /// Called when the user long-presses on this list tile. + final GestureLongPressCallback? onLongPress; + + /// The widget builder for the sending indicator. + /// + /// `Message` is the last message in the channel. Use it to determine the + /// status using [Message.state]. + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + /// True if the tile is in a selected state. + final bool selected; +} + +class _DefaultStreamChannelListItem extends StatelessWidget { + const _DefaultStreamChannelListItem({ + required this.props, + }); + + final StreamChannelListItemProps props; + + @override + Widget build(BuildContext context) { + final channelState = props.channel.state!; final textTheme = context.streamTextTheme; - final avatar = leading ?? StreamChannelAvatar(channel: channel, size: StreamAvatarGroupSize.xl); + final avatar = props.leading ?? StreamChannelAvatar(channel: props.channel, size: StreamAvatarGroupSize.xl); final titleWidget = - title ?? + props.title ?? StreamChannelName( - channel: channel, + channel: props.channel, textStyle: textTheme.headingSm.copyWith(height: 1), ); final subtitleWidget = - subtitle ?? + props.subtitle ?? ChannelListTileSubtitle( - channel: channel, - sendingIndicatorBuilder: sendingIndicatorBuilder, + channel: props.channel, + sendingIndicatorBuilder: props.sendingIndicatorBuilder, ); - final timestampWidget = trailing ?? ChannelLastMessageDate(channel: channel); + final timestampWidget = props.trailing ?? ChannelLastMessageDate(channel: props.channel); return BetterStreamBuilder( - stream: channel.isMutedStream, - initialData: channel.isMuted, + stream: props.channel.isMutedStream, + initialData: props.channel.isMuted, builder: (context, isMuted) => BetterStreamBuilder( stream: channelState.unreadCountStream, initialData: channelState.unreadCount, @@ -140,9 +230,9 @@ class StreamChannelListItem extends StatelessWidget { timestamp: timestampWidget, unreadCount: unreadCount, isMuted: isMuted, - onTap: onTap, - onLongPress: onLongPress, - selected: selected, + onTap: props.onTap, + onLongPress: props.onLongPress, + selected: props.selected, ); }, ), diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 43741c9ee8..d9637ac182 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -60,10 +60,7 @@ dependencies: shimmer: ^3.0.0 stream_chat_flutter_core: ^10.0.0-beta.12 stream_core_flutter: - git: - url: https://github.com/GetStream/stream-core-flutter.git - ref: 07f2357a4f1266784c26fde3430f0bdff43bbee8 - path: packages/stream_core_flutter + path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 thumblr: ^0.0.4 @@ -80,6 +77,7 @@ dev_dependencies: path: ^1.8.3 path_provider_platform_interface: ^2.0.0 plugin_platform_interface: ^2.0.0 + theme_extensions_builder_annotation: ^7.1.0 flutter: assets: From cfbf64162b8f3b8599607f34595374e605bb97b4 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 16:26:50 +0100 Subject: [PATCH 13/18] move props construction --- .../stream_channel_list_item.dart | 104 +++++------------- 1 file changed, 26 insertions(+), 78 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart index 8e3c2f8873..e6ff2a1922 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -25,105 +25,48 @@ class StreamChannelListItem extends StatelessWidget { /// Creates a new instance of [StreamChannelListItem] widget. StreamChannelListItem({ super.key, - required this.channel, - this.leading, - this.title, - this.subtitle, - this.trailing, - this.onTap, - this.onLongPress, - this.sendingIndicatorBuilder, - this.selected = false, + required Channel channel, + GestureTapCallback? onTap, + GestureLongPressCallback? onLongPress, + bool selected = false, }) : assert( channel.state != null, 'Channel ${channel.id} is not initialized', + ), + props = .new( + channel: channel, + onTap: onTap, + onLongPress: onLongPress, + selected: selected, ); - /// The channel to display. - final Channel channel; - - /// A widget to display as the avatar. - /// - /// Defaults to [StreamChannelAvatar]. - final Widget? leading; - - /// The primary content of the list tile. - /// - /// Defaults to [StreamChannelName]. - final Widget? title; - - /// Additional content displayed below the title. - /// - /// Defaults to [ChannelListTileSubtitle] which shows typing indicators, - /// draft messages, or the last message preview. - final Widget? subtitle; - - /// A widget to display as the timestamp. - /// - /// Defaults to [ChannelLastMessageDate]. - final Widget? trailing; - - /// Called when the user taps this list tile. - final GestureTapCallback? onTap; - - /// Called when the user long-presses on this list tile. - final GestureLongPressCallback? onLongPress; - - /// The widget builder for the sending indicator. - /// - /// `Message` is the last message in the channel. Use it to determine the - /// status using [Message.state]. - final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; - - /// True if the tile is in a selected state. - final bool selected; + /// The properties for the channel list item. + final StreamChannelListItemProps props; /// Creates a copy of this tile but with the given fields replaced with /// the new values. StreamChannelListItem copyWith({ Key? key, Channel? channel, - Widget? leading, - Widget? title, - Widget? subtitle, - Widget? trailing, VoidCallback? onTap, VoidCallback? onLongPress, - Widget Function(BuildContext, Message)? sendingIndicatorBuilder, bool? selected, }) { return StreamChannelListItem( key: key ?? this.key, - channel: channel ?? this.channel, - leading: leading ?? this.leading, - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - trailing: trailing ?? this.trailing, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, - selected: selected ?? this.selected, + channel: channel ?? props.channel, + onTap: onTap ?? props.onTap, + onLongPress: onLongPress ?? props.onLongPress, + selected: selected ?? props.selected, ); } @override Widget build(BuildContext context) { final builder = StreamComponentFactory.of(context).extension(); - if (builder != null) return builder(context, _props); - return _DefaultStreamChannelListItem(props: _props); + if (builder != null) return builder(context, props); + return _DefaultStreamChannelListItem(props: props); } - - StreamChannelListItemProps get _props => StreamChannelListItemProps( - channel: channel, - leading: leading, - title: title, - subtitle: subtitle, - trailing: trailing, - onTap: onTap, - onLongPress: onLongPress, - sendingIndicatorBuilder: sendingIndicatorBuilder, - selected: selected, - ); } /// Properties for configuring a [StreamChannelListItem]. @@ -223,7 +166,7 @@ class _DefaultStreamChannelListItem extends StatelessWidget { stream: channelState.unreadCountStream, initialData: channelState.unreadCount, builder: (context, unreadCount) { - return _StreamChannelListTile( + return StreamChannelListTile( avatar: avatar, title: titleWidget, subtitle: subtitleWidget, @@ -240,8 +183,13 @@ class _DefaultStreamChannelListItem extends StatelessWidget { } } -class _StreamChannelListTile extends StatelessWidget { - const _StreamChannelListTile({ +/// A widget that displays a channel list tile. +/// It's the basic component for [StreamChannelListItem] without any of the logic. +/// It can be used to fully customize the list tile data being shown. +class StreamChannelListTile extends StatelessWidget { + /// Creates a new instance of [StreamChannelListTile] widget. + const StreamChannelListTile({ + super.key, required this.avatar, required this.title, this.subtitle, From 6b4860023848b618fc9067029ff0b91d297f1d8f Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 16:49:37 +0100 Subject: [PATCH 14/18] update theming --- melos.yaml | 5 ++++- .../channel_scroll_view/stream_channel_list_item.dart | 2 +- packages/stream_chat_flutter/lib/src/theme/themes.dart | 1 + packages/stream_chat_flutter/pubspec.yaml | 7 +++++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/melos.yaml b/melos.yaml index e77cafaeee..5eb60b46ea 100644 --- a/melos.yaml +++ b/melos.yaml @@ -93,7 +93,10 @@ command: svg_icon_widget: ^0.0.1 # TODO: Replace with hosted version before merging PR stream_core_flutter: - path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: 77f66669da436947e7c146f90b64b825351cd6ef + path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 url_launcher: ^6.3.0 diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart index e6ff2a1922..43187228be 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -247,7 +247,7 @@ class StreamChannelListTile extends StatelessWidget { @override Widget build(BuildContext context) { final spacing = context.streamSpacing; - final channelListItemTheme = context.streamChannelListItemTheme; + final channelListItemTheme = StreamChannelListItemTheme.of(context); final defaults = _StreamChannelListItemThemeDefaults(context); final effectiveTitleStyle = channelListItemTheme.titleStyle ?? defaults.titleStyle; diff --git a/packages/stream_chat_flutter/lib/src/theme/themes.dart b/packages/stream_chat_flutter/lib/src/theme/themes.dart index 8f1558bdc3..7bcf5a75e6 100644 --- a/packages/stream_chat_flutter/lib/src/theme/themes.dart +++ b/packages/stream_chat_flutter/lib/src/theme/themes.dart @@ -15,6 +15,7 @@ export 'poll_interactor_theme.dart'; export 'poll_option_votes_dialog_theme.dart'; export 'poll_options_dialog_theme.dart'; export 'poll_results_dialog_theme.dart'; +export 'stream_channel_list_item_theme.dart'; export 'text_theme.dart'; export 'thread_list_tile_theme.dart'; export 'voice_recording_attachment_theme.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index d9637ac182..53af9ee6aa 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -60,9 +60,13 @@ dependencies: shimmer: ^3.0.0 stream_chat_flutter_core: ^10.0.0-beta.12 stream_core_flutter: - path: /Users/renefloor/Documents/github/stream-core-flutter/packages/stream_core_flutter + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: 77f66669da436947e7c146f90b64b825351cd6ef + path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 + theme_extensions_builder_annotation: ^7.1.0 thumblr: ^0.0.4 url_launcher: ^6.3.0 video_player: ^2.8.7 @@ -77,7 +81,6 @@ dev_dependencies: path: ^1.8.3 path_provider_platform_interface: ^2.0.0 plugin_platform_interface: ^2.0.0 - theme_extensions_builder_annotation: ^7.1.0 flutter: assets: From a05d2e4c156b494d8dc7e490ec815095433924d4 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 5 Mar 2026 10:23:12 +0100 Subject: [PATCH 15/18] Add migration docs --- migrations/redesign/README.md | 78 +++++ migrations/redesign/channel_list_item.md | 270 ++++++++++++++++++ .../lib/src/theme/channel_preview_theme.dart | 3 + 3 files changed, 351 insertions(+) create mode 100644 migrations/redesign/channel_list_item.md diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md index 3f84bfa4ef..97ed8364da 100644 --- a/migrations/redesign/README.md +++ b/migrations/redesign/README.md @@ -39,11 +39,89 @@ MaterialApp( You can also use the convenience factories `StreamTheme.light()` or `StreamTheme.dark()` as a starting point. + +## Component factories + +In the redesigned components we don't use builders in the constructors anymore, but have a centralized component factory. +The component factory contains product agnotic component builders, such as the button and the avatar, and also product specific component builders, such as the channel list item. +You can supply your component factory at any point in the widget tree, but you would usually wrap your full app around it. + +An example of a component factory with custom buttons and a custom channel list item: + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return StreamComponentFactory( + builders: StreamComponentBuilders( + button: (context, props) => switch (props.type) { + StreamButtonType.solid => ElevatedButton( + onPressed: props.onTap, + child: Text(props.label ?? ''), + ), + StreamButtonType.outline => OutlinedButton(onPressed: props.onTap, child: Text(props.label ?? '')), + StreamButtonType.ghost => TextButton(onPressed: props.onTap, child: Text(props.label ?? '')), + }, + extensions: streamChatComponentBuilders( + channelListItem: (context, props) => StreamChannelListTile( + title: Text(props.channel.name ?? ''), + avatar: StreamChannelAvatar(channel: props.channel), + onTap: props.onTap, + onLongPress: props.onLongPress, + selected: props.selected, + ), + ), + ), + child: ... + ); + } +} +``` + +You should make the builder themselves as simple as possible by extracting this into separate widgets, such as this: + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return StreamComponentFactory( + builders: StreamComponentBuilders( + button: (context, props) => MyCustomButton(props: props), + ), + child: ... + ); + } +} + +class MyCustomButton extends StatelessWidget { + const MyCustomButton({super.key, required this.props}); + + final StreamButtonProps props; + + @override + Widget build(BuildContext context) { + return switch (props.type) { + StreamButtonType.solid => ElevatedButton( + onPressed: props.onTap, + child: Text(props.label ?? ''), + ), + StreamButtonType.outline => OutlinedButton(onPressed: props.onTap, child: Text(props.label ?? '')), + StreamButtonType.ghost => TextButton(onPressed: props.onTap, child: Text(props.label ?? '')), + }; + } +} +``` + ## Components | Component | Migration Guide | |-----------|-----------------| | Stream Avatar | [stream_avatar.md](stream_avatar.md) | +| Channel List Item | [channel_list_item.md](channel_list_item.md) | | Message Actions | [message_actions.md](message_actions.md) | | Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | diff --git a/migrations/redesign/channel_list_item.md b/migrations/redesign/channel_list_item.md new file mode 100644 index 0000000000..30d236b624 --- /dev/null +++ b/migrations/redesign/channel_list_item.md @@ -0,0 +1,270 @@ +# Channel List Item Migration Guide + +This guide covers the migration for the redesigned channel list item components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamChannelListTile → StreamChannelListItem](#streamchannellisttile--streamchannellistitem) +- [Customizing Slots](#customizing-slots) +- [Low-level Presentational Component](#low-level-presentational-component) +- [Theme Migration](#theme-migration) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Old | New | +|-----|-----| +| `StreamChannelListTile` | `StreamChannelListItem` | +| Constructor props: `leading`, `title`, `subtitle`, `trailing` | `StreamChannelListItemProps` (via `StreamComponentFactory`) | +| `tileColor`, `visualDensity`, `contentPadding` | Removed — use `StreamChannelListItemThemeData` | +| `selectedTileColor` | Removed — use `StreamChannelListItemThemeData.backgroundColor` | +| `unreadIndicatorBuilder` | Removed | +| `StreamChannelPreviewThemeData` | `StreamChannelListItemThemeData` | +| `StreamChannelPreviewTheme.of(context)` | `StreamChannelListItemTheme.of(context)` | +| `StreamChatThemeData.channelPreviewTheme` | `StreamChatThemeData.channelListItemTheme` | + +--- + +## StreamChannelListTile → StreamChannelListItem + +The old `StreamChannelListTile` accepted all slot widgets directly in its constructor. The new `StreamChannelListItem` takes only the essential interaction properties. Slot customization is now done via `StreamChannelListItemProps` and the `StreamComponentFactory`. + +### Breaking Changes + +- `leading`, `title`, `subtitle`, `trailing` removed from constructor +- `tileColor` removed — use `StreamChannelListItemThemeData.backgroundColor` +- `visualDensity` removed +- `contentPadding` removed +- `selectedTileColor` removed +- `unreadIndicatorBuilder` removed +- `sendingIndicatorBuilder` removed from constructor — pass via `StreamChannelListItemProps` + +### Migration + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + onTap: () => openChannel(channel), + onLongPress: () => showOptions(channel), + tileColor: Colors.white, + selectedTileColor: Colors.blue.shade50, + selected: isSelected, + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + leading: StreamChannelAvatar(channel: channel), + title: StreamChannelName(channel: channel), + subtitle: ChannelListTileSubtitle(channel: channel), + trailing: ChannelLastMessageDate(channel: channel), +) +``` + +**After:** +```dart +StreamChannelListItem( + channel: channel, + onTap: () => openChannel(channel), + onLongPress: () => showOptions(channel), + selected: isSelected, +) +``` + +--- + +## Customizing Slots + +To customize the slot widgets (avatar, title, subtitle, timestamp), provide a custom builder via `StreamComponentFactory`: + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + leading: MyCustomAvatar(channel: channel), + subtitle: MyCustomSubtitle(channel: channel), +) +``` + +**After:** +```dart +StreamComponentFactory( + builders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + channelListItem: (context, props) => StreamChannelListTile( + avatar: StreamChannelAvatar(channel: props.channel), + title: Text(props.channel.name ?? ''), + subtitle: Text(props.channel.lastMessageAt?.toString() ?? ''), + ), + ), + ), + child: ..., +) +``` + +--- + +## Low-level Presentational Component + +The new `StreamChannelListTile` is a low-level component that renders pre-resolved data without any channel-specific logic. Use this when you want to display a channel-shaped list item with fully controlled content (e.g., in a skeleton loader or a custom list): + +```dart +StreamChannelListTile( + avatar: StreamChannelAvatar(channel: channel), + title: Text('General'), + subtitle: Text('Last message preview'), + timestamp: Text('9:41 AM'), + unreadCount: 3, + isMuted: false, + onTap: () {}, +) +``` + +> **Note:** This widget does not subscribe to any streams — all values must be provided explicitly. + +--- + +## Theme Migration + +`StreamChannelPreviewThemeData` has been replaced by `StreamChannelListItemThemeData`. + +### Property Mapping + +| Old (`StreamChannelPreviewThemeData`) | New (`StreamChannelListItemThemeData`) | +|---------------------------------------|----------------------------------------| +| `titleStyle` | `titleStyle` | +| `subtitleStyle` | `subtitleStyle` | +| `lastMessageAtStyle` | `timestampStyle` | +| `avatarTheme` | Removed — use `StreamAvatarThemeData` directly | +| `unreadCounterColor` | Removed — use `StreamBadgeNotificationThemeData` | +| `indicatorIconSize` | Removed | +| `lastMessageAtFormatter` | Removed from theme — pass to `ChannelLastMessageDate(formatter: ...)` | + +### New Properties + +| Property | Type | Description | +|----------|------|-------------| +| `backgroundColor` | `WidgetStateProperty?` | Background color resolved per state (default, hover, pressed, selected) | +| `borderColor` | `Color?` | Bottom border color | +| `muteIconPosition` | `MuteIconPosition?` | Whether the mute icon appears in `title` or `subtitle` row | + +### Global Theme Migration + +**Before:** +```dart +StreamChatTheme( + data: StreamChatThemeData( + channelPreviewTheme: StreamChannelPreviewThemeData( + titleStyle: TextStyle(fontWeight: FontWeight.bold), + subtitleStyle: TextStyle(color: Colors.grey), + lastMessageAtStyle: TextStyle(fontSize: 12), + unreadCounterColor: Colors.red, + ), + ), + child: ..., +) +``` + +**After:** +```dart +StreamChatTheme( + data: StreamChatThemeData( + channelListItemTheme: StreamChannelListItemThemeData( + titleStyle: TextStyle(fontWeight: FontWeight.bold), + subtitleStyle: TextStyle(color: Colors.grey), + timestampStyle: TextStyle(fontSize: 12), + // unreadCounterColor → customize via StreamBadgeNotificationThemeData + ), + ), + child: ..., +) +``` + +### Subtree Theme Override + +**Before:** +```dart +StreamChannelPreviewTheme( + data: StreamChannelPreviewThemeData( + titleStyle: TextStyle(color: Colors.blue), + ), + child: StreamChannelListView(...), +) +``` + +**After:** +```dart +StreamChannelListItemTheme( + data: StreamChannelListItemThemeData( + titleStyle: TextStyle(color: Colors.blue), + ), + child: StreamChannelListView(...), +) +``` + +### Background and Selected Color + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + tileColor: Colors.white, + selectedTileColor: Colors.blue.shade50, + selected: isSelected, +) +``` + +**After:** +```dart +StreamChannelListItemTheme( + data: StreamChannelListItemThemeData( + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) return Colors.blue.shade50; + return Colors.white; + }), + ), + child: StreamChannelListItem( + channel: channel, + selected: isSelected, + ), +) +``` + +### Custom Timestamp Formatter + +**Before:** +```dart +StreamChannelPreviewThemeData( + lastMessageAtFormatter: (context, date) { + return Jiffy.parseFromDateTime(date).format('d MMMM'); + }, +) +``` + +**After:** +```dart +// Pass directly to the widget — no longer a theme property +ChannelLastMessageDate( + channel: channel, + formatter: (context, date) { + return Jiffy.parseFromDateTime(date).format('d MMMM'); + }, +) +``` + +--- + +## Migration Checklist + +- [ ] Replace `StreamChannelListTile` with `StreamChannelListItem` +- [ ] Remove `tileColor`, `visualDensity`, `contentPadding`, `selectedTileColor`, `unreadIndicatorBuilder` parameters +- [ ] Move slot customization (`leading`, `title`, `subtitle`, `trailing`) to `StreamComponentFactory` +- [ ] Replace `StreamChannelPreviewTheme` with `StreamChannelListItemTheme` +- [ ] Replace `StreamChatThemeData.channelPreviewTheme` with `StreamChatThemeData.channelListItemTheme` +- [ ] Rename `lastMessageAtStyle` to `timestampStyle` +- [ ] Move `lastMessageAtFormatter` from theme to `ChannelLastMessageDate(formatter: ...)` +- [ ] Replace `tileColor`/`selectedTileColor` with `StreamChannelListItemThemeData.backgroundColor` using `WidgetStateProperty` +- [ ] Replace `unreadCounterColor` with `StreamBadgeNotificationThemeData` +- [ ] Replace `avatarTheme` in `StreamChannelPreviewThemeData` with `StreamAvatarThemeData` on the avatar widget directly diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart index 89019f322a..88ea65595e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart @@ -11,6 +11,9 @@ import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; /// /// * [StreamChannelPreviewThemeData], which is used to configure this theme. /// {@endtemplate} +/// +/// This is deprecated, but currently still used by `StreamChannelInfoBottomSheet`. +@Deprecated('Use StreamChannelListItemTheme instead.') class StreamChannelPreviewTheme extends InheritedTheme { /// Creates a [StreamChannelPreviewTheme]. /// From fd420d7c359594b4061d607d46633e8c02704a90 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 5 Mar 2026 10:25:37 +0100 Subject: [PATCH 16/18] Fix analysis issues --- .../lib/src/theme/stream_channel_list_item_theme.dart | 6 ++++++ packages/stream_chat_flutter/lib/stream_chat_flutter.dart | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart index 1979dccb12..d56174f744 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart @@ -132,7 +132,13 @@ class StreamChannelListItemThemeData with _$StreamChannelListItemThemeData { ) => _$StreamChannelListItemThemeData.lerp(a, b, t); } +/// The position of the mute icon. +/// By default the mute icon will be shown directly next to the title. +/// When choosing for subtitle, the mute icon will be shown at the end of the list item. enum MuteIconPosition { + /// Top row of the list item, next to the title. title, + + /// Bottom row, at the end of the list item. subtitle, } diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 6c9cbbba12..6ff3c40507 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -8,8 +8,6 @@ export 'package:stream_core_flutter/stream_core_flutter.dart' StreamAvatarSize, StreamAudioWaveformSlider, StreamAudioWaveform, - StreamChannelListItem, - StreamChannelListItemProps, StreamTheme, StreamIcons, StreamThemeExtension, From 596f1301367f1b4cc43d16fda1ad3bdeee2cf938 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 5 Mar 2026 10:40:21 +0100 Subject: [PATCH 17/18] Simplify the builder extensions --- .../message_composer_extensions.dart | 37 ------------------- .../message_composer_input_header.dart | 6 ++- .../message_composer_input_leading.dart | 6 ++- .../message_composer_input_trailing.dart | 6 ++- .../message_composer_leading.dart | 6 ++- .../message_composer_trailing.dart | 6 ++- .../stream_chat_message_composer.dart | 3 +- .../stream_chat_component_builders.dart | 7 ++++ .../stream_channel_list_item.dart | 5 +-- .../lib/src/theme/stream_chat_theme.dart | 1 - 10 files changed, 30 insertions(+), 53 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart deleted file mode 100644 index cb070faf71..0000000000 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// Helper extensions for internal use only. -extension MessageComposerBuilderExtensions on BuildContext { - StreamComponentBuilders get _factory => StreamComponentFactory.of( - this, - ); - - /// The builder for the message composer component. - StreamComponentBuilder? get messageComposerBuilder => - _factory.extension(); - - /// The builder for the message composer leading component. - StreamComponentBuilder? get messageComposerLeadingBuilder => - _factory.extension(); - - /// The builder for the message composer trailing component. - StreamComponentBuilder? get messageComposerTrailingBuilder => - _factory.extension(); - - /// The builder for the message composer input component. - StreamComponentBuilder? get messageComposerInputBuilder => - _factory.extension(); - - /// The builder for the message composer input leading component. - StreamComponentBuilder? get messageComposerInputLeadingBuilder => - _factory.extension(); - - /// The builder for the message composer input header component. - StreamComponentBuilder? get messageComposerInputHeaderBuilder => - _factory.extension(); - - /// The builder for the message composer input trailing component. - StreamComponentBuilder? get messageComposerInputTrailingBuilder => - _factory.extension(); -} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index 9000377d6d..1c46f99045 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -1,7 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -17,7 +16,10 @@ class StreamMessageComposerInputHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerInputHeaderBuilder?.call(context, MessageComposerInputHeaderProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputHeaderProps.from(props), + ) ?? _DefaultStreamMessageComposerInputHeader(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart index c959a44c0e..2399796aa3 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart @@ -1,5 +1,4 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that shows the input leading of the message composer. @@ -14,7 +13,10 @@ class StreamMessageComposerInputLeading extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerInputLeadingBuilder?.call(context, MessageComposerInputLeadingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputLeadingProps.from(props), + ) ?? const SizedBox.shrink(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index 01763810ec..61190893bf 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -17,7 +16,10 @@ class StreamMessageComposerInputTrailing extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerInputTrailingBuilder?.call(context, MessageComposerInputTrailingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputTrailingProps.from(props), + ) ?? DefaultStreamMessageComposerInputTrailing(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index 13b66ecb2f..eb877c7e7d 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -16,7 +15,10 @@ class StreamMessageComposerLeading extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerLeadingBuilder?.call(context, MessageComposerLeadingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerLeadingProps.from(props), + ) ?? _DefaultStreamMessageComposerLeading(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart index c60a315a0e..f5399ebc34 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart @@ -1,5 +1,4 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that shows the trailing of the message composer. @@ -15,7 +14,10 @@ class StreamMessageComposerTrailing extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerTrailingBuilder?.call(context, MessageComposerTrailingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerTrailingProps.from(props), + ) ?? const SizedBox.shrink(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index 7110cf02bf..e410a6b8ad 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_header.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_leading.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_trailing.dart'; @@ -93,7 +92,7 @@ class _StreamChatMessageComposerState extends State { @override Widget build(BuildContext context) { - if (context.messageComposerBuilder?.call(context, widget.props) case final messageComposer?) { + if (context.chatComponentBuilder()?.call(context, widget.props) case final messageComposer?) { return messageComposer; } diff --git a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart index f047aa14f3..199ceb9fa2 100644 --- a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart +++ b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart @@ -1,3 +1,4 @@ +import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Builds the list of component builders for the stream chat components. @@ -24,3 +25,9 @@ Iterable> streamChatComponentBuilders({ return builders; } + +/// Helper extensions for the factory builders. +extension StreamChatComponentBuildersExtension on BuildContext { + /// The builder for the given component type. + StreamComponentBuilder? chatComponentBuilder() => StreamComponentFactory.of(this).extension(); +} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart index 43187228be..f29bacaf0f 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -63,9 +63,8 @@ class StreamChannelListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final builder = StreamComponentFactory.of(context).extension(); - if (builder != null) return builder(context, props); - return _DefaultStreamChannelListItem(props: props); + final builder = context.chatComponentBuilder(); + return builder?.call(context, props) ?? _DefaultStreamChannelListItem(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 17560167b6..fbef12f1b2 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -1,7 +1,6 @@ // ignore_for_file: deprecated_member_use_from_same_package import 'package:flutter/material.dart' hide TextTheme; -import 'package:stream_chat_flutter/src/theme/stream_channel_list_item_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamChatTheme} From 748f7b2c50f836b01f87c4aa72c8db070d695b13 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 5 Mar 2026 10:54:59 +0100 Subject: [PATCH 18/18] Fix test --- packages/stream_chat_flutter/example/lib/tutorial_part_3.dart | 2 +- .../test/src/channel/channel_image_test.dart | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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 ce718b6018..d6cbd0d3a2 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -142,7 +142,7 @@ class _ChannelListPageState extends State { channel: channel, ), title: StreamChannelName( - textStyle: StreamChannelPreviewTheme.of(context).titleStyle!.copyWith( + textStyle: StreamChannelListItemTheme.of(context).titleStyle!.copyWith( color: StreamChatTheme.of(context).colorTheme.textHighEmphasis // ignore: deprecated_member_use .withOpacity(opacity), diff --git a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart index 44921dc7c8..a1e4d22ff2 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart @@ -60,6 +60,7 @@ void main() { when(() => channel.client).thenReturn(client); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); + when(() => channel.isDistinct).thenReturn(true); when(() => channel.imageStream).thenAnswer((i) => Stream.value(null)); when(() => channel.image).thenReturn(null); when(() => channelState.membersStream).thenAnswer( @@ -137,6 +138,7 @@ void main() { when(() => clientState.currentUser).thenReturn(currentUser); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); + when(() => channel.isDistinct).thenReturn(false); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); when(() => channel.imageStream).thenAnswer((i) => Stream.value(null));