From 6876cb671c6d8b1b6bb8f7c54b24676c898eccf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Braz=CC=87ewicz?= Date: Fri, 10 Apr 2026 12:51:06 +0200 Subject: [PATCH 01/12] new threads banner redesign --- .../lib/src/localization/translations.dart | 6 + .../stream_thread_list_tile.dart | 129 +++++++++--------- .../stream_thread_list_view.dart | 56 +++++++- .../stream_unread_threads_banner.dart | 126 ++++++++++------- .../stream_thread_list_view_test.dart | 6 + .../example/lib/add_new_lang.dart | 3 + .../lib/src/stream_chat_localizations_ca.dart | 3 + .../lib/src/stream_chat_localizations_de.dart | 3 + .../lib/src/stream_chat_localizations_en.dart | 3 + .../lib/src/stream_chat_localizations_es.dart | 3 + .../lib/src/stream_chat_localizations_fr.dart | 3 + .../lib/src/stream_chat_localizations_hi.dart | 3 + .../lib/src/stream_chat_localizations_it.dart | 3 + .../lib/src/stream_chat_localizations_ja.dart | 3 + .../lib/src/stream_chat_localizations_ko.dart | 3 + .../lib/src/stream_chat_localizations_no.dart | 3 + .../lib/src/stream_chat_localizations_pt.dart | 3 + .../test/translations_test.dart | 1 + sample_app/lib/pages/thread_list_page.dart | 85 +++++------- 19 files changed, 285 insertions(+), 160 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 430341058..f8bf23832 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -504,6 +504,9 @@ abstract class Translations { /// The label for "$count new threads" String newThreadsLabel({required int count}); + /// The label for "Loading..." + String get loadingLabel; + /// The label for "Slide to cancel" String get slideToCancelLabel; @@ -1233,6 +1236,9 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments return '$count new threads'; } + @override + String get loadingLabel => 'Loading...'; + @override String get slideToCancelLabel => 'Slide to cancel'; diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart index 35090db82..b5e00fc32 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.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'; /// {@template streamThreadListTile} /// A widget that displays a thread in a list. @@ -105,75 +106,77 @@ class _DefaultStreamThreadListTile extends StatelessWidget { channel?.formatName(currentUser: currentUser) ?? avatarUser?.name ?? context.translations.noTitleText; final participantUsers = thread.threadParticipants.map((it) => it.user).nonNulls.toList(growable: false); - return Material( - color: effectiveBackgroundColor, - child: InkWell( + return StreamListTileTheme( + data: context.streamListTileTheme.copyWith( + contentPadding: effectivePadding, + backgroundColor: WidgetStatePropertyAll(effectiveBackgroundColor), + ), + child: StreamListTileContainer( + enabled: true, + selected: false, onTap: props.onTap, onLongPress: props.onLongPress, - child: Padding( - padding: effectivePadding, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (avatarUser case final user?) - Padding( - padding: const EdgeInsetsDirectional.only(end: 12), - child: StreamUserAvatar( - user: user, - size: StreamAvatarSize.xl, - ), - ) - else - const Padding( - padding: EdgeInsetsDirectional.only(end: 12), - child: SizedBox.square(dimension: 40), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (avatarUser case final user?) + Padding( + padding: const EdgeInsetsDirectional.only(end: 12), + child: StreamUserAvatar( + user: user, + size: StreamAvatarSize.xl, ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: ThreadTitle( - channelName: channelName, - style: effectiveChannelNameStyle, - ), + ) + else + const Padding( + padding: EdgeInsetsDirectional.only(end: 12), + child: SizedBox.square(dimension: 40), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ThreadTitle( + channelName: channelName, + style: effectiveChannelNameStyle, + ), + ), + if (unreadMessageCount case final count? when count > 0) ...[ + const SizedBox(width: 8), + ThreadUnreadCount( + unreadCount: count, + style: effectiveUnreadCountStyle, + backgroundColor: effectiveUnreadCountBackgroundColor, ), - if (unreadMessageCount case final count? when count > 0) ...[ - const SizedBox(width: 8), - ThreadUnreadCount( - unreadCount: count, - style: effectiveUnreadCountStyle, - backgroundColor: effectiveUnreadCountBackgroundColor, - ), - ], ], - ), - const SizedBox(height: 2), - ThreadRootMessagePreview( - parentMessage: parentMessage, - channel: channel, - language: language, - style: effectiveReplyToMessageStyle, - emptyStyle: effectiveLatestReplyMessageStyle, - ), - const SizedBox(height: 8), - ThreadFooter( - participantUsers: participantUsers, - replyCount: thread.replyCount, - latestActivityAt: latestActivityAt, - replyCountStyle: effectiveReplyCountStyle, - timestampStyle: effectiveTimestampStyle, - timestampFormatter: effectiveTimestampFormatter, - ), - ], - ), + ], + ), + const SizedBox(height: 2), + ThreadRootMessagePreview( + parentMessage: parentMessage, + channel: channel, + language: language, + style: effectiveReplyToMessageStyle, + emptyStyle: effectiveLatestReplyMessageStyle, + ), + const SizedBox(height: 8), + ThreadFooter( + participantUsers: participantUsers, + replyCount: thread.replyCount, + latestActivityAt: latestActivityAt, + replyCountStyle: effectiveReplyCountStyle, + timestampStyle: effectiveTimestampStyle, + timestampFormatter: effectiveTimestampFormatter, + ), + ], ), - ], - ), + ), + ], ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart index 5ccd0826d..81d5668f5 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart @@ -19,12 +19,25 @@ Widget defaultThreadListViewSeparatorBuilder( /// typedef StreamThreadListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; +/// Signature for a builder that creates a custom unread threads banner. +typedef StreamUnreadThreadsBannerBuilder = + Widget Function( + BuildContext context, + Set unreadThreadIds, + ); + /// {@template streamThreadListView} /// A [ListView] that shows a list of [Thread]'s the current user participated -/// in. +/// in, with a built-in unread threads banner. /// /// Uses a [StreamThreadListController] to load threads in paginated form. /// +/// The banner is shown above the list when new unseen threads are available +/// (driven by [StreamThreadListController.unseenThreadIds]). +/// +/// To hide the banner, set [showUnreadThreadsBanner] to `false`. +/// To provide a custom banner, use [unreadThreadsBannerBuilder]. +/// /// Each row is rendered using [StreamThreadListTile], which can be customized /// app-wide through [StreamComponentFactory]. /// @@ -55,6 +68,8 @@ class StreamThreadListView extends StatelessWidget { this.errorBuilder, this.onThreadTap, this.onThreadLongPress, + this.showUnreadThreadsBanner = true, + this.unreadThreadsBannerBuilder, this.loadMoreTriggerIndex = 3, this.scrollDirection = Axis.vertical, this.reverse = false, @@ -98,6 +113,17 @@ class StreamThreadListView extends StatelessWidget { /// Called when the user long-presses on a thread. final void Function(Thread)? onThreadLongPress; + /// Whether to show the built-in unread threads banner above the list. + /// + /// Defaults to `true`. + final bool showUnreadThreadsBanner; + + /// Optional builder for a custom unread threads banner. + /// + /// When provided, this replaces the default [StreamUnreadThreadsBanner]. + /// The builder receives the current set of unseen thread IDs. + final StreamUnreadThreadsBannerBuilder? unreadThreadsBannerBuilder; + /// The index to take into account when triggering [controller.loadMore]. final int loadMoreTriggerIndex; @@ -280,6 +306,34 @@ class StreamThreadListView extends StatelessWidget { @override Widget build(BuildContext context) { + return Column( + children: [ + if (showUnreadThreadsBanner) _buildBanner(), + Expanded(child: _buildList(context)), + ], + ); + } + + Widget _buildBanner() { + return ValueListenableBuilder>( + valueListenable: controller.unseenThreadIds, + builder: (context, unseenThreadIds, _) { + if (unreadThreadsBannerBuilder != null) { + return unreadThreadsBannerBuilder!(context, unseenThreadIds); + } + + return StreamUnreadThreadsBanner( + unreadThreads: unseenThreadIds, + onRefresh: () async { + await controller.refresh(resetValue: false); + controller.clearUnseenThreadIds(); + }, + ); + }, + ); + } + + Widget _buildList(BuildContext context) { return PagedValueListView( scrollDirection: scrollDirection, padding: padding, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart index 8156992d7..cbe897893 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart @@ -1,86 +1,118 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; +/// Callback that performs a refresh and returns a [Future] that completes +/// when the refresh is done. +typedef RefreshCallback = Future Function(); + /// {@template unreadThreadsBanner} /// A widget that shows a banner with the number of unread threads. /// /// This widget can be used to show a banner with the number of unread threads -/// on the top of the [ThreadListView]. +/// on the top of the [StreamThreadListView]. +/// /// {@endtemplate} -class StreamUnreadThreadsBanner extends StatelessWidget { +class StreamUnreadThreadsBanner extends StatefulWidget { /// {@macro unreadThreadsBanner} const StreamUnreadThreadsBanner({ super.key, required this.unreadThreads, - this.onTap, - this.minHeight = 52, - this.margin = const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - this.padding = const EdgeInsets.symmetric(horizontal: 16), + this.onRefresh, + this.margin = EdgeInsets.zero, + this.padding, }); /// The set of all the unread threads. final Set unreadThreads; - /// Optional callback to handle tap events. - final VoidCallback? onTap; - - /// The minimum height of the banner. + /// Called when the user taps the banner. /// - /// Defaults to 52. - final double minHeight; + /// While the returned [Future] is pending, the banner shows a loading + /// spinner instead of the refresh icon and label. + final RefreshCallback? onRefresh; /// The margin applied to the banner. /// - /// Defaults to `EdgeInsets.symmetric(horizontal: 8, vertical: 6)`. + /// Defaults to [EdgeInsets.zero]. final EdgeInsetsGeometry? margin; /// The padding applied to the banner. /// - /// Defaults to `EdgeInsets.symmetric(horizontal: 16)`. - final EdgeInsetsGeometry padding; + /// Defaults to `EdgeInsets.all(spacing.sm)`. + final EdgeInsetsGeometry? padding; @override - Widget build(BuildContext context) { - if (unreadThreads.isEmpty) { - return const Empty(); + State createState() => _StreamUnreadThreadsBannerState(); +} + +class _StreamUnreadThreadsBannerState extends State { + bool _isRefreshing = false; + + Future _handleTap() async { + if (_isRefreshing) return; + + setState(() => _isRefreshing = true); + try { + await widget.onRefresh?.call(); + } finally { + if (mounted) setState(() => _isRefreshing = false); } + } - final theme = StreamChatTheme.of(context); + @override + Widget build(BuildContext context) { + final isVisible = _isRefreshing || widget.unreadThreads.isNotEmpty; + if (!isVisible) return const Empty(); return GestureDetector( - onTap: onTap, + onTap: _isRefreshing ? null : _handleTap, child: Container( - margin: margin, - padding: padding, - constraints: BoxConstraints(minHeight: minHeight), - decoration: BoxDecoration( - color: theme.colorTheme.textHighEmphasis, - borderRadius: BorderRadius.circular(16), + margin: widget.margin, + padding: widget.padding ?? EdgeInsets.all(context.streamSpacing.sm), + color: context.streamColorScheme.backgroundSurface, + child: _isRefreshing ? _buildLoading() : _buildContent(context), + ), + ); + } + + Widget _buildLoading() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + StreamLoadingSpinner( + color: context.streamColorScheme.textSecondary, ), - child: Row( - children: [ - Expanded( - child: Text( - context.translations.newThreadsLabel( - count: unreadThreads.length, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.headline.copyWith( - color: theme.colorTheme.barsBg, - ), - ), - ), - Icon( - context.streamIcons.refresh20, - color: theme.colorTheme.barsBg, - ), - ], + SizedBox(width: context.streamSpacing.xs), + Text( + context.translations.loadingLabel, + style: context.streamTextTheme.metadataEmphasis, ), - ), + ], + ); + } + + Widget _buildContent(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + context.streamIcons.refresh20, + size: 20, + color: context.streamColorScheme.textSecondary, + ), + SizedBox(width: context.streamSpacing.xs), + Text( + context.translations.newThreadsLabel( + count: widget.unreadThreads.length, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: context.streamTextTheme.metadataEmphasis, + ), + ], ); } } diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_thread_list_view_test.dart b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_thread_list_view_test.dart index d387ecbc9..bccb89733 100644 --- a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_thread_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_thread_list_view_test.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -8,6 +9,11 @@ import '../../mocks.dart'; class MockStreamThreadListController extends Mock implements StreamThreadListController { @override PagedValue value = const PagedValue.loading(); + + final _unseenThreadIds = ValueNotifier>(const {}); + + @override + ValueListenable> get unseenThreadIds => _unseenThreadIds; } void main() { diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index 027fa4823..d30643354 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -634,6 +634,9 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { return '$count new threads'; } + @override + String get loadingLabel => 'Loading...'; + @override String get slideToCancelLabel => 'Slide to cancel'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index 928d3a069..bb100652b 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -614,6 +614,9 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { return '$count fils nous'; } + @override + String get loadingLabel => 'Carregant...'; + @override String get slideToCancelLabel => 'Llisca per cancel·lar'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart index 51a6edd03..49e1015a9 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart @@ -612,6 +612,9 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { return '$count neue Threads'; } + @override + String get loadingLabel => 'Wird geladen...'; + @override String get slideToCancelLabel => 'Zum Abbrechen schieben'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index 85cc7f795..ef39cb005 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -612,6 +612,9 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { return '$count new threads'; } + @override + String get loadingLabel => 'Loading...'; + @override String get slideToCancelLabel => 'Slide to cancel'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index 387d7c4cd..b6de8bce3 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -616,6 +616,9 @@ No es posible añadir más de $limit archivos adjuntos return '$count nuevos hilos'; } + @override + String get loadingLabel => 'Cargando...'; + @override String get slideToCancelLabel => 'Desliza para cancelar'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index c67308f15..172c1e757 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -618,6 +618,9 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ return '$count Nouveaux fils'; } + @override + String get loadingLabel => 'Chargement...'; + @override String get slideToCancelLabel => 'Glissez pour annuler'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index 5829f937d..23fb570b8 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -616,6 +616,9 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { return '$count नए थ्रेड्स'; } + @override + String get loadingLabel => 'लोड हो रहा है...'; + @override String get slideToCancelLabel => 'रद्द करने के लिए स्लाइड करें'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index c39086d7e..c2e9a9e33 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -621,6 +621,9 @@ Attenzione: il limite massimo di $limit file è stato superato. return '$count nuovi thread'; } + @override + String get loadingLabel => 'Caricamento...'; + @override String get slideToCancelLabel => 'Scorri per annullare'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart index e889aace5..df56e031e 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart @@ -599,6 +599,9 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { return '$count 件の新しいスレッド'; } + @override + String get loadingLabel => '読み込み中...'; + @override String get slideToCancelLabel => 'スライドでキャンセル'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index bdb83d04c..e9c182032 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -602,6 +602,9 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { return '$count개의 새 스레드'; } + @override + String get loadingLabel => '로딩 중...'; + @override String get slideToCancelLabel => '슬라이드하여 취소'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart index 404657694..4031afd61 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart @@ -600,6 +600,9 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { return '$count nye tråder'; } + @override + String get loadingLabel => 'Laster...'; + @override String get slideToCancelLabel => 'Gli for å avbryte'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart index f8e314915..56f597560 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -615,6 +615,9 @@ Não é possível adicionar mais de $limit arquivos de uma vez return '$count novos tópicos'; } + @override + String get loadingLabel => 'Carregando...'; + @override String get slideToCancelLabel => 'Deslize para cancelar'; diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index f43cf3ddf..7678ff015 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -290,6 +290,7 @@ void main() { expect(localizations.voteCountLabel(count: 3), isNotNull); expect(localizations.repliedToLabel, isNotNull); expect(localizations.newThreadsLabel(count: 3), isNotNull); + expect(localizations.loadingLabel, isNotNull); expect(localizations.slideToCancelLabel, isNotNull); expect(localizations.holdToRecordLabel, isNotNull); expect(localizations.sendAnywayLabel, isNotNull); diff --git a/sample_app/lib/pages/thread_list_page.dart b/sample_app/lib/pages/thread_list_page.dart index 4201b30e2..69e6f2a21 100644 --- a/sample_app/lib/pages/thread_list_page.dart +++ b/sample_app/lib/pages/thread_list_page.dart @@ -25,63 +25,50 @@ class _ThreadListPageState extends State { @override Widget build(BuildContext context) { - return Column( - children: [ - ValueListenableBuilder( - valueListenable: controller.unseenThreadIds, - builder: (_, unreadThreads, __) => StreamUnreadThreadsBanner( - unreadThreads: unreadThreads, - onTap: () => controller.refresh(resetValue: false).then((_) => controller.clearUnseenThreadIds()), - ), - ), - Expanded( - child: StreamThreadListView( - controller: controller, - onThreadTap: (thread) async { - final channelCid = thread.channelCid; + return StreamThreadListView( + controller: controller, + onThreadTap: (thread) async { + final channelCid = thread.channelCid; - final channel = StreamChat.of(context).client.channel( - channelCid.split(':')[0], - id: channelCid.split(':')[1], - ); + final channel = StreamChat.of(context).client.channel( + channelCid.split(':')[0], + id: channelCid.split(':')[1], + ); - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return StreamChannel( - channel: channel, - initialMessageId: thread.draft?.parentId, - child: BetterStreamBuilder( - initialData: thread.parentMessage, - stream: channel.state?.messagesStream - .map( - (messages) => messages.firstWhereOrNull( - (m) => m.id == thread.parentMessage?.id, - ), - ) - .where((msg) => msg != null) - .cast(), - builder: (_, parentMessage) { - return ThreadPage( - parent: parentMessage, - onViewInChannelTap: (message) { - GoRouter.of(context).goNamed( - Routes.CHANNEL_PAGE.name, - pathParameters: Routes.CHANNEL_PAGE.params(channel), - queryParameters: {'mid': message.id}, - ); - }, - ); - }, - ), + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return StreamChannel( + channel: channel, + initialMessageId: thread.draft?.parentId, + child: BetterStreamBuilder( + initialData: thread.parentMessage, + stream: channel.state?.messagesStream + .map( + (messages) => messages.firstWhereOrNull( + (m) => m.id == thread.parentMessage?.id, + ), + ) + .where((msg) => msg != null) + .cast(), + builder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + onViewInChannelTap: (message) { + GoRouter.of(context).goNamed( + Routes.CHANNEL_PAGE.name, + pathParameters: Routes.CHANNEL_PAGE.params(channel), + queryParameters: {'mid': message.id}, + ); + }, ); }, ), ); }, ), - ), - ], + ); + }, ); } } From c210e1b76c88eb6749fd3de54db6553d27a850b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Braz=CC=87ewicz?= Date: Fri, 10 Apr 2026 13:12:13 +0200 Subject: [PATCH 02/12] fix --- .../scroll_view/thread_scroll_view/stream_thread_list_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart index b5e00fc32..e746fc927 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart @@ -107,7 +107,7 @@ class _DefaultStreamThreadListTile extends StatelessWidget { final participantUsers = thread.threadParticipants.map((it) => it.user).nonNulls.toList(growable: false); return StreamListTileTheme( - data: context.streamListTileTheme.copyWith( + data: StreamListTileThemeData( contentPadding: effectivePadding, backgroundColor: WidgetStatePropertyAll(effectiveBackgroundColor), ), From 290d9fe4e5978ddf83b600e44e72e23ab42adf4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Braz=CC=87ewicz?= Date: Fri, 10 Apr 2026 13:52:46 +0200 Subject: [PATCH 03/12] convert to wrapper --- .../stream_thread_list_view.dart | 58 +---------- .../stream_unread_threads_banner.dart | 60 +++++++++--- sample_app/lib/pages/thread_list_page.dart | 96 +++++++++++-------- 3 files changed, 104 insertions(+), 110 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart index 81d5668f5..20ab75076 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart @@ -19,24 +19,14 @@ Widget defaultThreadListViewSeparatorBuilder( /// typedef StreamThreadListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; -/// Signature for a builder that creates a custom unread threads banner. -typedef StreamUnreadThreadsBannerBuilder = - Widget Function( - BuildContext context, - Set unreadThreadIds, - ); - /// {@template streamThreadListView} /// A [ListView] that shows a list of [Thread]'s the current user participated -/// in, with a built-in unread threads banner. +/// in. /// /// Uses a [StreamThreadListController] to load threads in paginated form. /// -/// The banner is shown above the list when new unseen threads are available -/// (driven by [StreamThreadListController.unseenThreadIds]). -/// -/// To hide the banner, set [showUnreadThreadsBanner] to `false`. -/// To provide a custom banner, use [unreadThreadsBannerBuilder]. +/// Wrap with [StreamUnreadThreadsBanner] to show a banner above the list when +/// new unseen threads are available. /// /// Each row is rendered using [StreamThreadListTile], which can be customized /// app-wide through [StreamComponentFactory]. @@ -53,6 +43,7 @@ typedef StreamUnreadThreadsBannerBuilder = /// ``` /// /// See also: +/// * [StreamUnreadThreadsBanner], which wraps this view to show new threads. /// * [StreamMessageWidget], which renders each thread's parent message. /// * [StreamThreadListController] /// {@endtemplate} @@ -68,8 +59,6 @@ class StreamThreadListView extends StatelessWidget { this.errorBuilder, this.onThreadTap, this.onThreadLongPress, - this.showUnreadThreadsBanner = true, - this.unreadThreadsBannerBuilder, this.loadMoreTriggerIndex = 3, this.scrollDirection = Axis.vertical, this.reverse = false, @@ -113,17 +102,6 @@ class StreamThreadListView extends StatelessWidget { /// Called when the user long-presses on a thread. final void Function(Thread)? onThreadLongPress; - /// Whether to show the built-in unread threads banner above the list. - /// - /// Defaults to `true`. - final bool showUnreadThreadsBanner; - - /// Optional builder for a custom unread threads banner. - /// - /// When provided, this replaces the default [StreamUnreadThreadsBanner]. - /// The builder receives the current set of unseen thread IDs. - final StreamUnreadThreadsBannerBuilder? unreadThreadsBannerBuilder; - /// The index to take into account when triggering [controller.loadMore]. final int loadMoreTriggerIndex; @@ -306,34 +284,6 @@ class StreamThreadListView extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - if (showUnreadThreadsBanner) _buildBanner(), - Expanded(child: _buildList(context)), - ], - ); - } - - Widget _buildBanner() { - return ValueListenableBuilder>( - valueListenable: controller.unseenThreadIds, - builder: (context, unseenThreadIds, _) { - if (unreadThreadsBannerBuilder != null) { - return unreadThreadsBannerBuilder!(context, unseenThreadIds); - } - - return StreamUnreadThreadsBanner( - unreadThreads: unseenThreadIds, - onRefresh: () async { - await controller.refresh(resetValue: false); - controller.clearUnseenThreadIds(); - }, - ); - }, - ); - } - - Widget _buildList(BuildContext context) { return PagedValueListView( scrollDirection: scrollDirection, padding: padding, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart index cbe897893..baaa7cc40 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart @@ -3,35 +3,58 @@ import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; -/// Callback that performs a refresh and returns a [Future] that completes -/// when the refresh is done. -typedef RefreshCallback = Future Function(); - /// {@template unreadThreadsBanner} -/// A widget that shows a banner with the number of unread threads. +/// A wrapper widget that displays an unread-threads banner above its [child], +/// similar to how [RefreshIndicator] wraps a scrollable. +/// +/// When [enabled] is `false` (the default), the banner is hidden and only the +/// [child] is rendered. Set [enabled] to `true` and provide [unreadThreads] to +/// show the banner. /// -/// This widget can be used to show a banner with the number of unread threads -/// on the top of the [StreamThreadListView]. +/// Example: /// +/// ```dart +/// StreamUnreadThreadsBanner( +/// enabled: true, +/// unreadThreads: unseenThreadIds, +/// onRefresh: () async { +/// await controller.refresh(resetValue: false); +/// controller.clearUnseenThreadIds(); +/// }, +/// child: StreamThreadListView(controller: controller), +/// ) +/// ``` /// {@endtemplate} class StreamUnreadThreadsBanner extends StatefulWidget { /// {@macro unreadThreadsBanner} const StreamUnreadThreadsBanner({ super.key, - required this.unreadThreads, + required this.child, + this.enabled = false, + this.unreadThreads = const {}, this.onRefresh, this.margin = EdgeInsets.zero, this.padding, }); - /// The set of all the unread threads. + /// The widget below the banner in the tree. + final Widget child; + + /// Whether the banner is enabled. + /// + /// When `false`, the banner is hidden and only [child] is rendered. + /// + /// Defaults to `false`. + final bool enabled; + + /// The set of all the unread thread IDs. final Set unreadThreads; /// Called when the user taps the banner. /// /// While the returned [Future] is pending, the banner shows a loading /// spinner instead of the refresh icon and label. - final RefreshCallback? onRefresh; + final Future Function()? onRefresh; /// The margin applied to the banner. /// @@ -44,7 +67,8 @@ class StreamUnreadThreadsBanner extends StatefulWidget { final EdgeInsetsGeometry? padding; @override - State createState() => _StreamUnreadThreadsBannerState(); + State createState() => + _StreamUnreadThreadsBannerState(); } class _StreamUnreadThreadsBannerState extends State { @@ -63,6 +87,15 @@ class _StreamUnreadThreadsBannerState extends State { @override Widget build(BuildContext context) { + return Column( + children: [ + if (widget.enabled) _buildBanner(context), + Expanded(child: widget.child), + ], + ); + } + + Widget _buildBanner(BuildContext context) { final isVisible = _isRefreshing || widget.unreadThreads.isNotEmpty; if (!isVisible) return const Empty(); @@ -72,15 +105,14 @@ class _StreamUnreadThreadsBannerState extends State { margin: widget.margin, padding: widget.padding ?? EdgeInsets.all(context.streamSpacing.sm), color: context.streamColorScheme.backgroundSurface, - child: _isRefreshing ? _buildLoading() : _buildContent(context), + child: _isRefreshing ? _buildLoading(context) : _buildContent(context), ), ); } - Widget _buildLoading() { + Widget _buildLoading(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ StreamLoadingSpinner( color: context.streamColorScheme.textSecondary, diff --git a/sample_app/lib/pages/thread_list_page.dart b/sample_app/lib/pages/thread_list_page.dart index 69e6f2a21..4ec6d6646 100644 --- a/sample_app/lib/pages/thread_list_page.dart +++ b/sample_app/lib/pages/thread_list_page.dart @@ -25,50 +25,62 @@ class _ThreadListPageState extends State { @override Widget build(BuildContext context) { - return StreamThreadListView( - controller: controller, - onThreadTap: (thread) async { - final channelCid = thread.channelCid; + return ValueListenableBuilder>( + valueListenable: controller.unseenThreadIds, + builder: (context, unseenThreadIds, child) => StreamUnreadThreadsBanner( + enabled: unseenThreadIds.isNotEmpty, + unreadThreads: unseenThreadIds, + onRefresh: () async { + await controller.refresh(resetValue: false); + controller.clearUnseenThreadIds(); + }, + child: child!, + ), + child: StreamThreadListView( + controller: controller, + onThreadTap: (thread) async { + final channelCid = thread.channelCid; - final channel = StreamChat.of(context).client.channel( - channelCid.split(':')[0], - id: channelCid.split(':')[1], - ); + final channel = StreamChat.of(context).client.channel( + channelCid.split(':')[0], + id: channelCid.split(':')[1], + ); - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return StreamChannel( - channel: channel, - initialMessageId: thread.draft?.parentId, - child: BetterStreamBuilder( - initialData: thread.parentMessage, - stream: channel.state?.messagesStream - .map( - (messages) => messages.firstWhereOrNull( - (m) => m.id == thread.parentMessage?.id, - ), - ) - .where((msg) => msg != null) - .cast(), - builder: (_, parentMessage) { - return ThreadPage( - parent: parentMessage, - onViewInChannelTap: (message) { - GoRouter.of(context).goNamed( - Routes.CHANNEL_PAGE.name, - pathParameters: Routes.CHANNEL_PAGE.params(channel), - queryParameters: {'mid': message.id}, - ); - }, - ); - }, - ), - ); - }, - ), - ); - }, + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return StreamChannel( + channel: channel, + initialMessageId: thread.draft?.parentId, + child: BetterStreamBuilder( + initialData: thread.parentMessage, + stream: channel.state?.messagesStream + .map( + (messages) => messages.firstWhereOrNull( + (m) => m.id == thread.parentMessage?.id, + ), + ) + .where((msg) => msg != null) + .cast(), + builder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + onViewInChannelTap: (message) { + GoRouter.of(context).goNamed( + Routes.CHANNEL_PAGE.name, + pathParameters: Routes.CHANNEL_PAGE.params(channel), + queryParameters: {'mid': message.id}, + ); + }, + ); + }, + ), + ); + }, + ), + ); + }, + ), ); } } From 561d80f5b6cd2cc5253be59a1615d2cb8c904677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Braz=CC=87ewicz?= Date: Fri, 10 Apr 2026 14:05:53 +0200 Subject: [PATCH 04/12] tweaks --- .../stream_unread_threads_banner.dart | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart index baaa7cc40..b89c29839 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart @@ -4,12 +4,15 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template unreadThreadsBanner} -/// A wrapper widget that displays an unread-threads banner above its [child], -/// similar to how [RefreshIndicator] wraps a scrollable. +/// A widget that displays an unread-threads banner. +/// +/// When a [child] is provided, the banner appears above it — similar to how +/// [RefreshIndicator] wraps a scrollable. When [child] is omitted, the widget +/// renders only the banner itself. /// /// When [enabled] is `false` (the default), the banner is hidden and only the -/// [child] is rendered. Set [enabled] to `true` and provide [unreadThreads] to -/// show the banner. +/// [child] (if any) is rendered. Set [enabled] to `true` and provide +/// [unreadThreads] to show the banner. /// /// Example: /// @@ -29,7 +32,7 @@ class StreamUnreadThreadsBanner extends StatefulWidget { /// {@macro unreadThreadsBanner} const StreamUnreadThreadsBanner({ super.key, - required this.child, + this.child, this.enabled = false, this.unreadThreads = const {}, this.onRefresh, @@ -38,7 +41,9 @@ class StreamUnreadThreadsBanner extends StatefulWidget { }); /// The widget below the banner in the tree. - final Widget child; + /// + /// When `null`, only the banner is rendered without any wrapped content. + final Widget? child; /// Whether the banner is enabled. /// @@ -67,8 +72,7 @@ class StreamUnreadThreadsBanner extends StatefulWidget { final EdgeInsetsGeometry? padding; @override - State createState() => - _StreamUnreadThreadsBannerState(); + State createState() => _StreamUnreadThreadsBannerState(); } class _StreamUnreadThreadsBannerState extends State { @@ -87,10 +91,15 @@ class _StreamUnreadThreadsBannerState extends State { @override Widget build(BuildContext context) { + final banner = widget.enabled ? _buildBanner(context) : null; + final child = widget.child; + + if (child == null) return banner ?? const Empty(); + return Column( children: [ - if (widget.enabled) _buildBanner(context), - Expanded(child: widget.child), + if (banner != null) banner, + Expanded(child: child), ], ); } From 1280f5144cc766423092f40809d6133edc505bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Braz=CC=87ewicz?= Date: Fri, 10 Apr 2026 14:26:13 +0200 Subject: [PATCH 05/12] fix --- sample_app/lib/pages/thread_list_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample_app/lib/pages/thread_list_page.dart b/sample_app/lib/pages/thread_list_page.dart index 4ec6d6646..ef0bd215a 100644 --- a/sample_app/lib/pages/thread_list_page.dart +++ b/sample_app/lib/pages/thread_list_page.dart @@ -34,7 +34,7 @@ class _ThreadListPageState extends State { await controller.refresh(resetValue: false); controller.clearUnseenThreadIds(); }, - child: child!, + child: child, ), child: StreamThreadListView( controller: controller, From 61b3bbce02e4437bacad9ebc3dbc10165271ec59 Mon Sep 17 00:00:00 2001 From: Brazol <5622717+Brazol@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:30:21 +0000 Subject: [PATCH 06/12] chore: Update Goldens --- .../ci/stream_thread_list_tile_dark.png | Bin 4451 -> 4834 bytes .../ci/stream_unread_threads_banner_dark.png | Bin 1736 -> 878 bytes .../ci/stream_unread_threads_banner_light.png | Bin 1769 -> 949 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png index b48d6c2d6c621355d27ea4116d28d91411eafe46..0e5493023bf526f86142801f2f500be45c54fb69 100644 GIT binary patch literal 4834 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 literal 4451 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 Ci2|4a diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png index 0615b4018d153cd18eaeb9c14453a341631ba027..79f4324099d8fa04840ed29f509dfcadd227604e 100644 GIT binary patch literal 878 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NV3zQ7aSW-L^Y*TFo^YT@!$VFl$Z-kYd@gL4I3v0`G=? z4I8c~mP3aW=N(z5$l)UQ?c|Mp#{c&H^H@GBepmRe&BB7885V?01W}#{sUZk`o`V>RPqnu?{0+#Kv7ppH|%OU)-dm zq`;6J;OgwXaaBWGhprAm5X;!cc3roV;TS9`2j5iw0T!2z$OJ0Jcc(aN?Qio?r z0i`q?gq4B%TQj17Vha>}yE+;eM3!lS)G?VVDlstythfYHz^ZKt;_L_lad-ogK&lnJ zx`C=qJUl_F+cZ^y3NARPf^3q?x(pIyyPX13_s~ofXj4X`mGY;Bs~(j!0{!CPeMNNQ z;X3<1@eZI7TRdb1cWzDqsZG5GR=cniWK2}nRgf`ayE{%iKB!Q4=J{4Pkm$P-Oa6j6KPWuYG^cPE$8D?JMN`b$sYe%e42$ z2inhl!oSAj_LbRS!24RcBIrf!iL9uwc5!gShpFo+VRF-bw)FD4Uv-Z_P ze2>a1X%ZW21!3Ew{9cRcG}p{|SR*6wd+A5J?<+P#;)xgQa;&Vt@m$bh16Ce0;NGAd z6s?y}7_cN&V!&^oqBy)aH4j0MJKiv0K`NRH^5sPaEJ;OkK|U&=nVG@l3AqE60?Id^ zY|*Xblv4Ejm`gv+H{ipy*k^?1tINJ=l;T-D-i@?@?j@~PV#AN$*Is66jG?F4)~=)d zBOi6%`c$kK^lP)c`KX$i#*lm#HPP z41r*ezS9}!&1aS{n_}%iRB|DXqwl$^z~j{NIb_vdFv&9z>w&{TX>l?xTr#zBWdsD*hZND0kYK3jY%tl1E zwS6-9;&!CPEO4}Rt)UV3it6)dD+dWgiE*m-{;V_PJuT`LAkvNMis^d@XBl5$cg>R> zJZNfRX{oq%t6NP#*8bLYgfoabM}kl&Oh``VkpRwe4`ZL_zL^cqKOV8$Sn6=^t50mOL#l`9vfRLEY`Iax!On@ z+bT?-;dYQ+wH0Fk$4c5jnZI%pV(xyfoto zOwLC6d$EmGU{G-I+lro^pB-WZbHqbgK#8lpzO#eHs+pJR)#`!#bIVV7=>mJyKKjrS zgWDxG)&@MyK`p0rB$}82VIgh52w>(qnJ72emo8m`WM-%13;v{kos34k%zpQ3m&N(@ z>8L#czf#`Xn~(Z7GqfJHF(nF*9uVl7Vj5}|{Ns<7v)_b6cS`9M8fLG3+$=$6|3DH% zNXwm^oo(3GAWHUJHK8$$O-N{|Ob3X&-+VK>^k|4~JS{Sh%u{VY2Ik{^`zFi_?rLi)R}B}V;W3d6NCGg$#6vW{=90u|NO>e2wIs=GVlZ6?x|jH$7U zWG}BmMg!8d1y7ujFkFVKCmkeO!7{F8{uG zF1-9Pmqv|@jI7ZuK3cR~l9v}hJwA{z`Z`xr4j{jo{ZD;FM8ry8-@%OP#~S|8<}xyw z%$*wJgsh#`>n8vN?(Xj7-FjA2C7Xb^7XQxef3oC^k6`0}$31aJQWAr8 zDN5bjT2vTRZr)w_QdE`pzU#X)p1Y`Cyuo8};a8m26wn`54q(8gtb?<}dNy+9OC$-= z(G=d`^N}6dYmJQ{Z1tpcKY#;bo@i9-JcFu;+AF9O0Dsh3s3X2wO!yyynzrDvU%{h& Re%k>Vfqo2H4K@1be*xkQ;0yo& diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png index b4d213990907c84d7464ed3291dce75694a53819..caa9fb17941f80895c9730c78dd086c5a203d7fc 100644 GIT binary patch literal 949 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NV2<~6aSW-L^Y+fhtdLBJ;}64AMIVa@8?RBw+GM_gL(E8vV`Xj&>w2e_ zuWUuEW(TcrzTLLfQ?#kkcOx5j>hu)nE`i)T$xAh|ea_7;PTN=ftn!!rp1bRRJg^Q9 z4HacDdG&tn+8>W!UXF;3?%q0g?ycYL-rgTGZ2H*G&p*EO=g+V_o3+K-KycZPX+b{Q z;=n%@ufue5@{Zm9`?sRFA|m_$^M7?U78V_!rKLY!{!}z)f5pbF(VJOV7N|B~)L8e$ zLWzm#!Xh>0qR034?l6$pQkESH6xg&(>C@p~RXX?f$NRXjup}_e>Nru~-tOw^;=rJ` zBwtWapn>yMFi7UmFCjrj#u-g^E-aPT(~oyh(Abz!NEof$}{vaidM zs{j97KpN-G3;}6uR}%zjT)7QosqHsSrNwL4R&3sWJp&~992lB0(*4PG#m4_`-R!Kt zw|Dhcdo7?z2F%ae4(~9NJAUZ&^p9EP+b4E#a2)6XTKo9dTL;3)2hWW&oE7KIjwhAkBx&Vw$Ft>8S4lRo%DD2}2 zk&aP2T8&U3)nWEyNm^|pZ9wEQFgr-^8o$*U{^?+RWsdxhU%Q5eLn5-J(F9{-W1SQ% z`1{oQ_Tqr$`KZ~J^e=_C224w?-ksetJ|1*-*6qG3;q=VZAsYfyDjgybL6s-W?a{+w zH8eo^(=J}@o=vCt9n$87e;Z6n> zjRw8`=KqBbo=CW+2uq@n0g>p>N25?&;JTXxoisM~@h}*GK5yxUNIj_Akf#(2Pplac zV0QRtBDt8$6vQDt@P#?z(9*QJEHlG?M9SmZuS%)ut5TAMH)L0JhvlndyTf1cUolYJpOc{1$al#(IEiF~+J#A{bp#1Pb zPpyGWZ{?eIQPU=7*aiZJIC;M@fzUrOA%0mN3b>DcDGyAnIovyL;&>{=MzN?iy<3{d zj`en7){7f|AH3J*xaq`|=h`xvEV?ZRZ@n=~WT*+mjU_XgYKHaMFCw0Wurqw;-_P&Z zNkaP+5=jI|)rG z>RZ2swo@_nUe}L2XAV}RIh_lYyKvkf-j@ElynQ@`doI<1c=Z}*0zjs4Fu!mH_1t_zD~ZD1VqVmd!g z16D?%j%~%JBq^0F$JU>BA@Yr`N#7P(oza{H5;O1U_avX92 z#~?my*Cv_FN4S^M&zGVA<$cq|ZzW@9HG42LYkeK3)P<_nwzim~h-!XDUb)3rWJ%!H zIlW4Pwkau$hK7clB9SB_8?Hq;J&mA!wY_+X<-B5BvF*siiPy~;(Wg&K>4+Xq5ca0L zwIck!7;zb%tiJM)%EZyfsLHz#HOqv!#a`kkYrMKp``=b4yG$Bass;N}bd`fL9oOa17E6%*fe z-9qQ&f5`0Vk?t%*yK5cqU=Os;S1%<4uC^-q`}Xhv*g5m>yUPyUO&n`kE8iy{fMwf< zSG!UG*8u)7t0-g$83gXJ4kY5R84-Z3kd&~CTv%O*?Eg=C7eAfBzg^)X{1L!LbM>K0 IC}9`=1*(7Y$^ZZW From 429d0916bda69c0d24c4528ae53f1a1ebaca2ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Braz=CC=87ewicz?= Date: Fri, 10 Apr 2026 15:16:24 +0200 Subject: [PATCH 07/12] migration --- migrations/v10-migration.md | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/migrations/v10-migration.md b/migrations/v10-migration.md index 895dec3e2..c9600a3b5 100644 --- a/migrations/v10-migration.md +++ b/migrations/v10-migration.md @@ -29,6 +29,7 @@ This guide covers all breaking changes in **Stream Chat Flutter SDK v10.0.0**. W - [MessageState](#messagestate) - [File Upload](#file-upload) - [AttachmentFileUploader](#attachmentfileuploader) +- [Unread Threads Banner](#unread-threads-banner) - [Appendix: Beta Release Timeline](#appendix-beta-release-timeline) - [Migration Checklist](#migration-checklist) @@ -60,6 +61,7 @@ Each breaking change section includes an **"Introduced in"** tag so you can quic | [**Message UI**](#message-ui) | New `onAttachmentTap` signature with fallback support, generic `StreamMessageAction` | | [**Message State**](#message-state--deletion) | `MessageDeleteScope` replaces `bool hard`, delete-for-me support | | [**File Upload**](#file-upload) | Four new abstract methods on `AttachmentFileUploader` | +| [**Unread Threads Banner**](#unread-threads-banner) | Wrapper pattern with `child`, `enabled`, `onRefresh`; removed `onTap`, `minHeight` | --- @@ -991,6 +993,58 @@ class CustomAttachmentFileUploader implements AttachmentFileUploader { --- +## Unread Threads Banner + +#### Key Changes: + +- `StreamUnreadThreadsBanner` is now a **wrapper widget** instead of a standalone banner placed in a `Column`. +- New `child` parameter — wrap your `StreamThreadListView` instead of placing the banner as a sibling. +- `onTap` (`VoidCallback?`) replaced by `onRefresh` (`Future Function()?`) — shows a loading spinner while the future is pending. +- `minHeight` parameter **removed**. +- `margin` default changed from `EdgeInsets.symmetric(horizontal: 8, vertical: 6)` to `EdgeInsets.zero`. +- `padding` default changed from `EdgeInsets.symmetric(horizontal: 16)` to `EdgeInsets.all(spacing.sm)`. + +#### Migration Steps: + +**Before:** +```dart +Column( + children: [ + ValueListenableBuilder( + valueListenable: controller.unseenThreadIds, + builder: (_, unreadThreads, __) => StreamUnreadThreadsBanner( + unreadThreads: unreadThreads, + onTap: () => controller + .refresh(resetValue: false) + .then((_) => controller.clearUnseenThreadIds()), + ), + ), + Expanded( + child: StreamThreadListView(controller: controller), + ), + ], +); +``` + +**After:** +```dart +ValueListenableBuilder>( + valueListenable: controller.unseenThreadIds, + builder: (context, unseenThreadIds, child) => StreamUnreadThreadsBanner( + enabled: unseenThreadIds.isNotEmpty, + unreadThreads: unseenThreadIds, + onRefresh: () async { + await controller.refresh(resetValue: false); + controller.clearUnseenThreadIds(); + }, + child: child!, + ), + child: StreamThreadListView(controller: controller), +); +``` + +--- + ## Appendix: Beta Release Timeline This appendix provides a chronological reference of breaking changes by beta version for users upgrading from specific pre-release versions. @@ -1036,6 +1090,12 @@ This appendix provides a chronological reference of breaking changes by beta ver ## Migration Checklist +### Unread Threads Banner +- [ ] Replace `Column` + `Expanded` layout with `StreamUnreadThreadsBanner(child: StreamThreadListView(...))` +- [ ] Replace `onTap` with `onRefresh` (returns `Future`) +- [ ] Add `enabled: true` to show the banner (defaults to hidden) +- [ ] Remove `minHeight` parameter if used + ### For v10.0.0-beta.12: - [ ] Replace `ArgumentError('The size of the attachment is...')` with `AttachmentTooLargeError` (provides `fileSize` and `maxSize` properties) - [ ] Replace `ArgumentError('The maximum number of attachments is...')` with `AttachmentLimitReachedError` (provides `maxCount` property) From b0bc6feb4fb863f140c52309be1ac0eeea37c024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Braz=CC=87ewicz?= Date: Fri, 10 Apr 2026 15:29:07 +0200 Subject: [PATCH 08/12] fix goldens --- .../stream_unread_threads_banner_test.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart index 4c1a8e7a8..120bb8c4d 100644 --- a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart +++ b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart @@ -10,7 +10,10 @@ void main() { constraints: const BoxConstraints.tightFor(width: 400, height: 100), builder: () => _wrapWithMaterialApp( brightness: brightness, - const StreamUnreadThreadsBanner(unreadThreads: {'id1', 'id2', 'id3'}), + const StreamUnreadThreadsBanner( + enabled: true, + unreadThreads: {'id1', 'id2', 'id3'}, + ), ), ); } From 70dfca2e5a5b49e3af1d54727f7dd4c65acddb5c Mon Sep 17 00:00:00 2001 From: Brazol <5622717+Brazol@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:33:10 +0000 Subject: [PATCH 09/12] chore: Update Goldens --- .../ci/stream_unread_threads_banner_dark.png | Bin 878 -> 1055 bytes .../ci/stream_unread_threads_banner_light.png | Bin 949 -> 1076 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png index 79f4324099d8fa04840ed29f509dfcadd227604e..147f39710d467a15b16b23de8061ca1e7645083c 100644 GIT binary patch literal 1055 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NU_R;T;uumf=j~nF{OD4V0}m&+@Z||bmacqxDJ9W0YoXiQfP}P_Ei7kh z7~NKRDHu8mN^>h48+_yx5SG2AsW8bTNOJ*C%aVwRLbI0e3XAG{KCa%e?fu@$d4E09 zKlvX&{MX;*NF77Ks&iLD_SjiV$LHrB{dCUn;m^}+!)&S_xzz03DOvv}{BwGhmeM`> z??Mc|oA>UQFE1~De0Ao`8#P}vuF2c!nEXF|FV4r`@7UBZ?P=v@N%=m|$Y7 z#KdIalIoU{T5evZ{iS?b0|QTFq+sRKCnqD;1-P)VB(R+7@K~KBC@9b%m>C5UXqhD< z$jB(MahZ#Y1H-WeoFFN~mDM1ro>wv;sm;6GfKplu`an`$E24o?3!Gkobgh`N3#3bH z1s_;yRTW6;&?=Cw6+2debY(5*0!htU0kUg>+bXcGnX5p$vQ}_`rKHM1Qpb)-PVC&; z`|n3w3i|?)Z0+(#x4+-(_0PZG7rlS2=)C<__VVjvcL;>%)yQnV`_92Q_Envu9a9G>{lDM)#SxJF`_nU>1c6rj*fA5!q zd?J;%tJrGS-nf$Kbsf83zyA38TiL>&hn0W|k2|cs+n2XqJbk*@0eY}oG(RH*R;!dSzHbchzqh&J zV{s$Hgh#$53_&pp3`>AUXaeQC8W<)3Lj(u+l#2^62pC#-Ixu*E90;^fgppCfl_uON a_KYiS3bh2C)~^BPa0X9TKbLh*2~7Yp*^3JR literal 878 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NV3zQ7aSW-L^Y*TFo^YT@!$VFl$Z-kYd@gL4I3v0`G=? z4I8c~mP3aW=N(z5$l)UQ?c|Mp#{c&H^H@GBepmRe&BB7885V?01W}#{sUZk`o`V>RPqnu?{0+#Kv7ppH|%OU)-dm zq`;6J;OgwXaaBWGhprAm5X;!cc3roV;TS9`2j5iw0T!2z$OJ0Jcc(aN?Qio?r z0i`q?gq4B%TQj17Vha>}yE+;eM3!lS)G?VVDlstythfYHz^ZKt;_L_lad-ogK&lnJ zx`C=qJUl_F+cZ^y3NARPf^3q?x(pIyyPX13_s~ofXj4X`mGY;Bs~(j!0{!CPeMNNQ z;X3<1@eZI7TRdb1cWzDqsZG5GR=cniWK2}nRgf`ayE{%iKB!Q4=J{4Pkm$PV7}++;uumf=k4A7*>_4M4t!J(({@*K*L%6uyZ9@=K-A^QVyha@IbA$> z?jN%rtGA=#mS^_XLI_w@x|9)s~8VB{N1%pEIxLZ$b3EBqe06rfBz$`?sw$q=Jdy3v#%!=RaI$N z+pTr(27rKK;nw3j#5*50^pr{w;e+^?qlckPs{x2ch6&%fU%etv%c;jOnW zl${k4Xi%IvYi{pi(Y}rb2A+!w%0}hyVr=Y9O}E7UR#jkdHe1nQQ~o+^i3@5L^}H!orepWR(y|L9><%Q0mx{Hy|lPtzwYWnNw0g zDUAjrH=q>Pq8yMElV&hbYC*y&kS>h{Mj%~Wt6qSmw2MGe54ym*8kIo0ycT7Eq=Ym< zb}dNh0_$3+1k&Y|bq6dZo$jJ$C4Dz8DV>39N%42Tn$Q2L3;#Yl`{Ubpzx$uXqt;e@ zzOu67>#O4*oi^`WKhybr+`dOlj2b&n9`iBro7a=PdiBP8Z;w^YpMD~0yY$|-Z)e0r z_T8NRd~w+N_m;{E45EfBI(%xk%eeb(|K0-(tdJ89N}pa_+^3`jlrvt{!NK9c+N-3* z#1s&C7!;TxHy(G~*(>~--;5pPj}<#W_S}A(v94C${?2~+{ki$yP4`Cwee-}}#nsmr zbGAReaVzVO#R8x~2N(laUw@qOduk&nnE56|?Ug%wxp}9{p4OuW_LhEp`{j-J{Ccws zu?=%=fBanjt8Uu^$B z-CO049y}V2<~6aSW-L^Y+fhtdLBJ;}64AMIVa@8?RBw+GM_gL(E8vV`Xj&>w2e_ zuWUuEW(TcrzTLLfQ?#kkcOx5j>hu)nE`i)T$xAh|ea_7;PTN=ftn!!rp1bRRJg^Q9 z4HacDdG&tn+8>W!UXF;3?%q0g?ycYL-rgTGZ2H*G&p*EO=g+V_o3+K-KycZPX+b{Q z;=n%@ufue5@{Zm9`?sRFA|m_$^M7?U78V_!rKLY!{!}z)f5pbF(VJOV7N|B~)L8e$ zLWzm#!Xh>0qR034?l6$pQkESH6xg&(>C@p~RXX?f$NRXjup}_e>Nru~-tOw^;=rJ` zBwtWapn>yMFi7UmFCjrj#u-g^E-aPT(~oyh(Abz!NEof$}{vaidM zs{j97KpN-G3;}6uR}%zjT)7QosqHsSrNwL4R&3sWJp&~992lB0(*4PG#m4_`-R!Kt zw|Dhcdo7?z2F%ae4(~9NJAUZ&^p9EP+b4E#a2)6XTKo9dT Date: Tue, 21 Apr 2026 11:00:11 +0200 Subject: [PATCH 10/12] fix --- .../thread_scroll_view/stream_unread_threads_banner.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart index b89c29839..1890b2240 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart @@ -140,7 +140,7 @@ class _StreamUnreadThreadsBannerState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( - context.streamIcons.refresh20, + context.streamIcons.refresh, size: 20, color: context.streamColorScheme.textSecondary, ), From 1941e7933b18e20f1c584332a7ef5dd62d44d3ac Mon Sep 17 00:00:00 2001 From: Brazol <5622717+Brazol@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:41:30 +0000 Subject: [PATCH 11/12] chore: Update Goldens --- .../ci/stream_thread_list_tile_dark.png | Bin 4834 -> 5002 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png index 0e5493023bf526f86142801f2f500be45c54fb69..59cb9f68461f337bdc6cd2cbd24875261f7481bb 100644 GIT binary patch literal 5002 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 From e54f645f1f23bf588128f47f68a199aeab65ccf9 Mon Sep 17 00:00:00 2001 From: Brazol <5622717+Brazol@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:56:30 +0000 Subject: [PATCH 12/12] chore: Update Goldens --- .../ci/stream_thread_list_tile_dark.png | Bin 5002 -> 5010 bytes .../ci/stream_unread_threads_banner_dark.png | Bin 1055 -> 1056 bytes .../ci/stream_unread_threads_banner_light.png | Bin 1076 -> 1077 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png index 59cb9f68461f337bdc6cd2cbd24875261f7481bb..f3d87824a55435ae9ff964df5ef704235f723d39 100644 GIT binary patch literal 5010 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 P6#%NL`KbJ%dGP-L>%up6 literal 5002 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 literal 1055 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NU_R;T;uumf=j~nF{OD4V0}m&+@Z||bmacqxDJ9W0YoXiQfP}P_Ei7kh z7~NKRDHu8mN^>h48+_yx5SG2AsW8bTNOJ*C%aVwRLbI0e3XAG{KCa%e?fu@$d4E09 zKlvX&{MX;*NF77Ks&iLD_SjiV$LHrB{dCUn;m^}+!)&S_xzz03DOvv}{BwGhmeM`> z??Mc|oA>UQFE1~De0Ao`8#P}vuF2c!nEXF|FV4r`@7UBZ?P=v@N%=m|$Y7 z#KdIalIoU{T5evZ{iS?b0|QTFq+sRKCnqD;1-P)VB(R+7@K~KBC@9b%m>C5UXqhD< z$jB(MahZ#Y1H-WeoFFN~mDM1ro>wv;sm;6GfKplu`an`$E24o?3!Gkobgh`N3#3bH z1s_;yRTW6;&?=Cw6+2debY(5*0!htU0kUg>+bXcGnX5p$vQ}_`rKHM1Qpb)-PVC&; z`|n3w3i|?)Z0+(#x4+-(_0PZG7rlS2=)C<__VVjvcL;>%)yQnV`_92Q_Envu9a9G>{lDM)#SxJF`_nU>1c6rj*fA5!q zd?J;%tJrGS-nf$Kbsf83zyA38TiL>&hn0W|k2|cs+n2XqJbk*@0eY}oG(RH*R;!dSzHbchzqh&J zV{s$Hgh#$53_&pp3`>AUXaeQC8W<)3Lj(u+l#2^62pC#-Ixu*E90;^fgppCfl_uON a_KYiS3bh2C)~^BPa0X9TKbLh*2~7Yp*^3JR diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png index e0ae64d3fb385af02b542652cc509d7e78bdaabd..3224ca61033b29779afc4b4ca59e3ffb5b593598 100644 GIT binary patch literal 1077 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCr*NV7~9^;uumf=k48%*|DV}2R;g~o;u-#vi{5L>1tp31)^@N>xMATZC%tn z_b+=4*R&=lwY57mk6w&4FBez$-Vh+w%&D6p+AJ|!EkMZXMe(MH+*#%R@2lV6n|(gr ze){HG%hP{*zgF#yV+dGvKP>$5>u&MKmyaHeh>6#?`G0IGb{eSx;kUVEU6QH z{;v-l0)iFO88T*_oH_Hw92?2Yr%!L(wo`I{UcUIO_?vfn?BCz-Z#K_A*8Tk4(Ssj9 zHol#us=&Z(WN9qiE-fR-$SASENZF|RTaJyrrR9d$ZXtmNMs01S^LKX$7JvRcwX>sv zfyYr&@Mm`Q?jS)yfd<9QC>Iu%gpOGvAibNGxwtqmxGmrWNeQm321&KN0vo$=mm5$@ zYe64Ks%u3wP-=nGE0C@gGj@S=X|3P`ORcH`NgY}R(zRm8Dv++M1zjMiSt~$xEpS@} z)-`h#NLSVhF0hnTDM;#=o8-ieUwhZaC8aZHrTqQ2_Q$us-ydy$ef{xn`)l0i=g*(r zZSFs(BYb_0jDCq#?OBowQD1^D>~QY<+|pltE)|$ zp7iYLWW#$|Z!MJ-7H&%NUR`1tPbkH63PE&O>`NlAg>_koJlqI2`i z`_`;G;;-roG~rw4$1rWa@_0>MB_^f~{XN^GE?S13Q+*U4G3C)(?)>S$_UiBdvFFNr z#{OO3PoMvI_Nn*BlUuVlZrK=E@$b)_7#rgWXBk#(S-E(#;s1y3AJ4tK{4r>EY{kFT z*5!|GHs-J~m#x_Qz5m{gyT6WITM@p1>0hUZioT#Y#{s)TG?XC&}hCqY*Es$Fe%##xb2E+pW|Ghx<7q-{C z1AU{B^6Ev-o^J<5ff75uy_xgfVTJnp$OA8SIxu*ARI_5x)DvI`0vaL0$f)276a$)# wgPXeM`s<(KVhpVyt+V7}++;uumf=k4A7*>_4M4t!J(({@*K*L%6uyZ9@=K-A^QVyha@IbA$> z?jN%rtGA=#mS^_XLI_w@x|9)s~8VB{N1%pEIxLZ$b3EBqe06rfBz$`?sw$q=Jdy3v#%!=RaI$N z+pTr(27rKK;nw3j#5*50^pr{w;e+^?qlckPs{x2ch6&%fU%etv%c;jOnW zl${k4Xi%IvYi{pi(Y}rb2A+!w%0}hyVr=Y9O}E7UR#jkdHe1nQQ~o+^i3@5L^}H!orepWR(y|L9><%Q0mx{Hy|lPtzwYWnNw0g zDUAjrH=q>Pq8yMElV&hbYC*y&kS>h{Mj%~Wt6qSmw2MGe54ym*8kIo0ycT7Eq=Ym< zb}dNh0_$3+1k&Y|bq6dZo$jJ$C4Dz8DV>39N%42Tn$Q2L3;#Yl`{Ubpzx$uXqt;e@ zzOu67>#O4*oi^`WKhybr+`dOlj2b&n9`iBro7a=PdiBP8Z;w^YpMD~0yY$|-Z)e0r z_T8NRd~w+N_m;{E45EfBI(%xk%eeb(|K0-(tdJ89N}pa_+^3`jlrvt{!NK9c+N-3* z#1s&C7!;TxHy(G~*(>~--;5pPj}<#W_S}A(v94C${?2~+{ki$yP4`Cwee-}}#nsmr zbGAReaVzVO#R8x~2N(laUw@qOduk&nnE56|?Ug%wxp}9{p4OuW_LhEp`{j-J{Ccws zu?=%=fBanjt8Uu^$B z-CO049y}