diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 1fd3ccb6..75b8f64b 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -84,7 +84,7 @@ class _SplitViewState extends State { ); } -class ChannelListPage extends StatelessWidget { +class ChannelListPage extends StatefulWidget { const ChannelListPage({ Key? key, this.onTap, @@ -92,22 +92,26 @@ class ChannelListPage extends StatelessWidget { final void Function(Channel)? onTap; + @override + State createState() => _ChannelListPageState(); +} + +class _ChannelListPageState extends State { + late final _listController = StreamChannelListController( + client: StreamChat.of(context).client, + filter: Filter.in_( + 'members', + [StreamChat.of(context).currentUser!.id], + ), + sort: const [SortOption('last_message_at')], + limit: 20, + ); + @override Widget build(BuildContext context) => Scaffold( - body: ChannelsBloc( - child: ChannelListView( - onChannelTap: onTap != null - ? (channel, _) { - onTap!(channel); - } - : null, - filter: Filter.in_( - 'members', - [StreamChat.of(context).currentUser!.id], - ), - sort: const [SortOption('last_message_at')], - limit: 20, - ), + body: StreamChannelListView( + onChannelTap: widget.onTap, + controller: _listController, ), ); } diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart index 459d8690..82acabf9 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_1.dart @@ -90,18 +90,15 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - appBar: const ChannelHeader(), - body: Column( - children: const [ - Expanded( - child: MessageListView(), - ), - MessageInput(), - ], - ), - ); - } + Widget build(BuildContext context) => Scaffold( + appBar: const ChannelHeader(), + body: Column( + children: const [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ); } diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart index f704abab..9f718b7e 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart @@ -92,26 +92,23 @@ class _ChannelListPageState extends State { ); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - body: RefreshIndicator( - onRefresh: _controller.refresh, - child: StreamChannelListView( - controller: _controller, - onChannelTap: (channel) => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => StreamChannel( - channel: channel, - child: const ChannelPage(), + Widget build(BuildContext context) => Scaffold( + body: RefreshIndicator( + onRefresh: _controller.refresh, + child: StreamChannelListView( + controller: _controller, + onChannelTap: (channel) => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), ), ), ), ), - ), - ); - } + ); } class ChannelPage extends StatelessWidget { @@ -120,18 +117,15 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - appBar: const ChannelHeader(), - body: Column( - children: const [ - Expanded( - child: MessageListView(), - ), - MessageInput(), - ], - ), - ); - } + Widget build(BuildContext context) => Scaffold( + appBar: const ChannelHeader(), + body: Column( + children: const [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ); } 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 538dbd68..e8834eb1 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -68,31 +68,49 @@ class MyApp extends StatelessWidget { } } -class ChannelListPage extends StatelessWidget { +class ChannelListPage extends StatefulWidget { const ChannelListPage({ Key? key, }) : super(key: key); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - body: ChannelsBloc( - child: ChannelListView( - filter: Filter.in_( - 'members', - [StreamChat.of(context).currentUser!.id], - ), - channelPreviewBuilder: _channelPreviewBuilder, - // sort: [SortOption('last_message_at')], - limit: 20, - channelWidget: const ChannelPage(), - ), - ), - ); - } + State createState() => _ChannelListPageState(); +} - Widget _channelPreviewBuilder(BuildContext context, Channel channel) { +class _ChannelListPageState extends State { + late final _listController = StreamChannelListController( + client: StreamChat.of(context).client, + filter: Filter.in_( + 'members', + [StreamChat.of(context).currentUser!.id], + ), + sort: const [SortOption('last_message_at')], + limit: 20, + ); + + @override + Widget build(BuildContext context) => Scaffold( + body: StreamChannelListView( + controller: _listController, + itemBuilder: _channelPreviewBuilder, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ), + ); + }, + ), + ); + + Widget _channelPreviewBuilder( + BuildContext context, + Channel channel, + StreamChannelListTile defaultTile, + ) { final lastMessage = channel.state?.messages.reversed.firstWhereOrNull( (message) => !message.isDeleted, ); @@ -115,13 +133,14 @@ class ChannelListPage extends StatelessWidget { leading: ChannelAvatar( channel: channel, ), - title: ChannelName( + title: StreamChannelName( textStyle: ChannelPreviewTheme.of(context).titleStyle!.copyWith( color: StreamChatTheme.of(context) .colorTheme .textHighEmphasis .withOpacity(opacity), ), + channel: channel, ), subtitle: Text(subtitle), trailing: channel.state!.unreadCount > 0 diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart index 5b80b721..ce461ddc 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -53,38 +53,42 @@ class MyApp extends StatelessWidget { } } -class ChannelListPage extends StatelessWidget { +class ChannelListPage extends StatefulWidget { const ChannelListPage({ Key? key, }) : super(key: key); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - body: StreamChannelListView( - controller: StreamChannelListController( - client: StreamChat.of(context).client, - filter: Filter.in_( - 'members', - [StreamChat.of(context).currentUser!.id], - ), - sort: const [SortOption('last_message_at')], - limit: 20, - ), - onChannelTap: (channel) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: const ChannelPage(), + State createState() => _ChannelListPageState(); +} + +class _ChannelListPageState extends State { + late final _listController = StreamChannelListController( + client: StreamChat.of(context).client, + filter: Filter.in_( + 'members', + [StreamChat.of(context).currentUser!.id], + ), + sort: const [SortOption('last_message_at')], + limit: 20, + ); + + @override + Widget build(BuildContext context) => Scaffold( + body: StreamChannelListView( + controller: _listController, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), ), - ), - ); - }, - ), - ); - } + ); + }, + ), + ); } class ChannelPage extends StatelessWidget { diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart index 86ac2e1f..347ceaf4 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart @@ -59,28 +59,42 @@ class MyApp extends StatelessWidget { } } -class ChannelListPage extends StatelessWidget { +class ChannelListPage extends StatefulWidget { const ChannelListPage({ Key? key, }) : super(key: key); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - body: ChannelsBloc( - child: ChannelListView( - filter: Filter.in_( - 'members', - [StreamChat.of(context).currentUser!.id], - ), - sort: const [SortOption('last_message_at')], - limit: 20, - channelWidget: const ChannelPage(), + State createState() => _ChannelListPageState(); +} + +class _ChannelListPageState extends State { + late final _listController = StreamChannelListController( + client: StreamChat.of(context).client, + filter: Filter.in_( + 'members', + [StreamChat.of(context).currentUser!.id], + ), + sort: const [SortOption('last_message_at')], + limit: 20, + ); + + @override + Widget build(BuildContext context) => Scaffold( + body: StreamChannelListView( + controller: _listController, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ), + ); + }, ), - ), - ); - } + ); } class ChannelPage extends StatelessWidget { diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart index 2555e007..4bb8e928 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart @@ -93,28 +93,41 @@ class MyApp extends StatelessWidget { } } -class ChannelListPage extends StatelessWidget { +class ChannelListPage extends StatefulWidget { const ChannelListPage({ Key? key, }) : super(key: key); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - body: ChannelsBloc( - child: ChannelListView( - filter: Filter.in_( - 'members', - [StreamChat.of(context).currentUser!.id], - ), - sort: const [SortOption('last_message_at')], - limit: 20, - channelWidget: const ChannelPage(), + State createState() => _ChannelListPageState(); +} + +class _ChannelListPageState extends State { + late final _listController = StreamChannelListController( + client: StreamChat.of(context).client, + filter: Filter.in_( + 'members', + [StreamChat.of(context).currentUser!.id], + ), + sort: const [SortOption('last_message_at')], + limit: 20, + ); + @override + Widget build(BuildContext context) => Scaffold( + body: StreamChannelListView( + controller: _listController, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ), + ); + }, ), - ), - ); - } + ); } class ChannelPage extends StatelessWidget { @@ -123,24 +136,21 @@ class ChannelPage extends StatelessWidget { }) : super(key: key); @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return Scaffold( - appBar: const ChannelHeader(), - body: Column( - children: [ - Expanded( - child: MessageListView( - threadBuilder: (_, parentMessage) => ThreadPage( - parent: parentMessage, + Widget build(BuildContext context) => Scaffold( + appBar: const ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + threadBuilder: (_, parentMessage) => ThreadPage( + parent: parentMessage, + ), ), ), - ), - const MessageInput(), - ], - ), - ); - } + const MessageInput(), + ], + ), + ); } class ThreadPage extends StatelessWidget { diff --git a/packages/stream_chat_flutter/lib/src/group_avatar.dart b/packages/stream_chat_flutter/lib/src/group_avatar.dart index d7996c4d..e0e87e23 100644 --- a/packages/stream_chat_flutter/lib/src/group_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/group_avatar.dart @@ -16,6 +16,7 @@ class GroupAvatar extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); + /// The channel of the avatar final Channel? channel; /// List of images to display diff --git a/packages/stream_chat_flutter/lib/src/paged_value_notifier.dart b/packages/stream_chat_flutter/lib/src/paged_value_notifier.dart index 525e663d..9525fea4 100644 --- a/packages/stream_chat_flutter/lib/src/paged_value_notifier.dart +++ b/packages/stream_chat_flutter/lib/src/paged_value_notifier.dart @@ -5,8 +5,10 @@ import 'package:stream_chat/stream_chat.dart' show StreamChatError; part 'paged_value_notifier.freezed.dart'; +/// Default initial page size multiplier. const defaultInitialPagedLimitMultiplier = 3; +/// Value listenable for paged data. typedef PagedValueListenableBuilder = ValueListenableBuilder>; @@ -57,6 +59,7 @@ abstract class PagedValueNotifier final nextPageKey = lastValue.nextPageKey; // resetting the error value = lastValue.copyWith(error: null); + // ignore: null_check_on_nullable_type_parameter return loadMore(nextPageKey!); } @@ -78,10 +81,9 @@ abstract class PagedValueNotifier Future loadMore(Key nextPageKey); } +/// Paged value that can be used with [PagedValueNotifier]. @freezed abstract class PagedValue with _$PagedValue { - const PagedValue._(); - /// Represents the success state of the [PagedValue] // @Assert( // 'nextPageKey != null', @@ -98,6 +100,8 @@ abstract class PagedValue with _$PagedValue { StreamChatError? error, }) = Success; + const PagedValue._(); + /// Represents the loading state of the [PagedValue]. const factory PagedValue.loading() = Loading; diff --git a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_controller.dart b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_controller.dart index 488452bd..e7eb903e 100644 --- a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_controller.dart +++ b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:stream_chat/stream_chat.dart' hide Success; import 'package:stream_chat_flutter/src/paged_value_notifier.dart'; @@ -7,6 +8,8 @@ import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list /// The default channel page limit to load. const defaultChannelPagedLimit = 10; +const _kDefaultBackendPaginationLimit = 30; + /// A controller for a Channel list. /// /// This class lets you perform tasks such as: @@ -102,7 +105,10 @@ class StreamChannelListController extends PagedValueNotifier { @override Future doInitialLoad() async { - final limit = this.limit * defaultInitialPagedLimitMultiplier; + final limit = min( + this.limit * defaultInitialPagedLimitMultiplier, + _kDefaultBackendPaginationLimit, + ); try { await for (final channels in client.queryChannels( filter: filter, diff --git a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_event_handler.dart b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_event_handler.dart index 449dd982..44ace69e 100644 --- a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_event_handler.dart +++ b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_event_handler.dart @@ -146,8 +146,8 @@ class StreamChannelListEventHandler { /// Function which gets called for the event /// [EventType.notificationMessageNew]. /// - /// This event is fired when a new message is created in a channel which we are - /// not currently watching. + /// This event is fired when a new message is created in a channel + /// which we are not currently watching. /// /// By default, this adds the channel and moves it to the top of list. void onNotificationMessageNew( diff --git a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_tile.dart index 3c392a8f..f820e040 100644 --- a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_tile.dart @@ -68,7 +68,8 @@ class StreamChannelListTile extends StatelessWidget { /// {@template flutter.material.ListTile.tileColor} /// Defines the background color of `ListTile`. /// - /// When the value is null, the `tileColor` is set to [ListTileTheme.tileColor] + /// 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; diff --git a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_view.dart index 1527493e..adf69be0 100644 --- a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_view.dart @@ -143,9 +143,9 @@ class StreamChannelListView extends StatefulWidget { /// only scroll the view if it has sufficient content. See [physics]. /// /// Also when true, the scroll view is used for default [ScrollAction]s. If a - /// ScrollAction is not handled by an otherwise focused part of the application, - /// the ScrollAction will be evaluated using this scroll view, for example, - /// when executing [Shortcuts] key events like page up and down. + /// ScrollAction is not handled by an otherwise focused part of the + /// application, the ScrollAction will be evaluated using this scroll view, + /// for example, when executing [Shortcuts] key events like page up and down. /// /// On iOS, this also identifies the scroll view that will scroll to top in /// response to a tap in the status bar. diff --git a/packages/stream_chat_flutter/lib/src/v4/stream_channel_info_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/v4/stream_channel_info_bottom_sheet.dart index 51bda67d..5107131d 100644 --- a/packages/stream_chat_flutter/lib/src/v4/stream_channel_info_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/v4/stream_channel_info_bottom_sheet.dart @@ -229,8 +229,9 @@ const _kDefaultChannelInfoBottomSheetShape = RoundedRectangleBorder( /// The [transitionAnimationController] controls the bottom sheet's entrance and /// exit animations if provided. /// -/// The optional `routeSettings` parameter sets the [RouteSettings] of the modal bottom sheet -/// sheet. This is particularly useful in the case that a user wants to observe +/// The optional `routeSettings` parameter sets the [RouteSettings] +/// of the modal bottom sheet sheet. +/// This is particularly useful in the case that a user wants to observe /// [PopupRoute]s within a [NavigatorObserver]. /// /// Returns a `Future` that resolves to the value (if any) that was passed to diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index fab90d17..7e6dc769 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -57,6 +57,10 @@ export 'src/utils.dart'; // v4 export 'src/v4/channel_list_view/stream_channel_list_controller.dart'; export 'src/v4/channel_list_view/stream_channel_list_event_handler.dart'; +export 'src/v4/channel_list_view/stream_channel_list_loading_tile.dart'; +export 'src/v4/channel_list_view/stream_channel_list_tile.dart'; export 'src/v4/channel_list_view/stream_channel_list_view.dart'; +export 'src/v4/stream_channel_avatar.dart'; export 'src/v4/stream_channel_info_bottom_sheet.dart'; +export 'src/v4/stream_channel_name.dart'; export 'src/visible_footnote.dart';