From 1e9a5b6c1e8fa6d7919e9dc0177045d383830110 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 29 Apr 2022 14:28:50 +0530 Subject: [PATCH 1/6] feat(core, ui): add grid views, minor refactoring Signed-off-by: xsahil03x --- .../example/lib/tutorial_part_4.dart | 2 +- .../example/lib/tutorial_part_6.dart | 2 +- .../lib/src/message_actions_modal.dart | 2 +- .../stream_channel_list_loading_tile.dart | 96 ----- .../stream_attachment_picker.dart | 6 +- .../message_input/stream_message_input.dart | 16 +- .../stream_message_text_field.dart | 10 +- .../stream_channel_grid_tile.dart | 91 ++++ .../stream_channel_grid_view.dart | 399 ++++++++++++++++++ .../stream_channel_list_loading_tile.dart | 96 +++++ .../stream_channel_list_tile.dart | 1 - .../stream_channel_list_view.dart | 141 ++----- .../stream_message_search_list_tile.dart | 6 +- .../stream_message_search_list_view.dart | 180 ++------ .../stream_scroll_view_empty_widget.dart | 61 +++ .../stream_scroll_view_error_widget.dart | 92 ++++ ...am_scroll_view_indexed_widget_builder.dart | 15 + .../stream_scroll_view_load_more_error.dart | 110 +++++ ...tream_scroll_view_load_more_indicator.dart | 25 ++ .../stream_scroll_view_loading_widget.dart | 24 ++ .../stream_user_grid_tile.dart | 102 +++++ .../stream_user_grid_view.dart | 395 +++++++++++++++++ .../stream_user_list_tile.dart | 0 .../stream_user_list_view.dart | 173 ++------ ...ream_list_view_indexed_widget_builder.dart | 9 - .../lib/stream_chat_flutter.dart | 27 +- ...t => stream_message_input_controller.dart} | 50 +-- .../lib/stream_chat_flutter_core.dart | 2 +- 28 files changed, 1588 insertions(+), 545 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_loading_tile.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart rename packages/stream_chat_flutter/lib/src/v4/{channel_list_view => scroll_view/channel_scroll_view}/stream_channel_list_tile.dart (99%) rename packages/stream_chat_flutter/lib/src/v4/{channel_list_view => scroll_view/channel_scroll_view}/stream_channel_list_view.dart (80%) rename packages/stream_chat_flutter/lib/src/v4/{message_search_list_view => scroll_view/message_search_scroll_view}/stream_message_search_list_tile.dart (95%) rename packages/stream_chat_flutter/lib/src/v4/{message_search_list_view => scroll_view/message_search_scroll_view}/stream_message_search_list_view.dart (74%) create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_empty_widget.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_error_widget.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_error.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_loading_widget.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_tile.dart create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart rename packages/stream_chat_flutter/lib/src/v4/{user_list_view => scroll_view/user_scroll_view}/stream_user_list_tile.dart (100%) rename packages/stream_chat_flutter/lib/src/v4/{user_list_view => scroll_view/user_scroll_view}/stream_user_list_view.dart (75%) delete mode 100644 packages/stream_chat_flutter/lib/src/v4/stream_list_view_indexed_widget_builder.dart rename packages/stream_chat_flutter_core/lib/src/{message_input_controller.dart => stream_message_input_controller.dart} (85%) 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 209af7ea..376b0ba8 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -145,7 +145,7 @@ class ThreadPage extends StatelessWidget { ), ), StreamMessageInput( - messageInputController: MessageInputController( + messageInputController: StreamMessageInputController( message: Message(parentId: parent!.id), ), ), 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 a872fb6e..e9c0168f 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart @@ -184,7 +184,7 @@ class ThreadPage extends StatelessWidget { ), ), StreamMessageInput( - messageInputController: MessageInputController( + messageInputController: StreamMessageInputController( message: Message(parentId: parent!.id), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart index 056e84fc..ffd8f386 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -639,7 +639,7 @@ class _StreamMessageActionsModalState extends State { widget.editMessageInputBuilder!(context, widget.message) else StreamMessageInput( - messageInputController: MessageInputController( + messageInputController: StreamMessageInputController( message: widget.message, ), preMessageSending: (m) { diff --git a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_loading_tile.dart b/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_loading_tile.dart deleted file mode 100644 index 69a6cf6e..00000000 --- a/packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_loading_tile.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:shimmer/shimmer.dart'; -import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; - -/// A shimmering list item which shows a loading effect. -/// -/// This is used by [StreamChannelListView] to show a loading effect while -/// the list is being loaded. -class StreamChannelListLoadingTile extends StatelessWidget { - /// Creates a new instance of [StreamChannelListLoadingTile] widget. - const StreamChannelListLoadingTile({ - Key? key, - this.visualDensity = VisualDensity.standard, - this.contentPadding = const EdgeInsets.symmetric(horizontal: 8), - }) : super(key: key); - - /// 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; - - @override - Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; - - final leading = Container( - height: 49, - width: 49, - decoration: BoxDecoration( - color: colorTheme.barsBg, - shape: BoxShape.circle, - ), - ); - - final title = Container( - height: 16, - width: 66, - decoration: BoxDecoration( - color: colorTheme.barsBg, - borderRadius: BorderRadius.circular(8), - ), - ); - - final subtitle = Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Container( - height: 16, - decoration: BoxDecoration( - color: colorTheme.barsBg, - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - const SizedBox(width: 8), - Container( - height: 16, - width: 50, - decoration: BoxDecoration( - color: colorTheme.barsBg, - borderRadius: BorderRadius.circular(8), - ), - ), - ], - ); - - return Shimmer.fromColors( - baseColor: colorTheme.disabled, - highlightColor: colorTheme.inputBg, - child: ListTile( - leading: leading, - title: title, - subtitle: subtitle, - visualDensity: visualDensity, - contentPadding: contentPadding, - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/v4/message_input/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/v4/message_input/stream_attachment_picker.dart index cb3859be..33ba2a25 100644 --- a/packages/stream_chat_flutter/lib/src/v4/message_input/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/v4/message_input/stream_attachment_picker.dart @@ -47,8 +47,8 @@ class StreamAttachmentPicker extends StatefulWidget { /// The picker size in height. final double pickerSize; - /// The [MessageInputController] linked to this picker. - final MessageInputController messageInputController; + /// The [StreamMessageInputController] linked to this picker. + final StreamMessageInputController messageInputController; /// The limit of attachments that can be picked. final int attachmentLimit; @@ -77,7 +77,7 @@ class StreamAttachmentPicker extends StatefulWidget { /// properties. StreamAttachmentPicker copyWith({ Key? key, - MessageInputController? messageInputController, + StreamMessageInputController? messageInputController, FilePickerCallback? onFilePicked, bool? isOpen, double? pickerSize, diff --git a/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_input.dart index 9c33d83c..a16cd49e 100644 --- a/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_input.dart @@ -76,16 +76,16 @@ typedef ActionButtonBuilder = Widget Function( ); /// Widget builder for widgets that may require data from the -/// [MessageInputController]. +/// [StreamMessageInputController]. typedef MessageRelatedBuilder = Widget Function( BuildContext context, - MessageInputController messageInputController, + StreamMessageInputController messageInputController, ); /// Widget builder for a custom attachment picker. typedef AttachmentsPickerBuilder = Widget Function( BuildContext context, - MessageInputController messageInputController, + StreamMessageInputController messageInputController, StreamAttachmentPicker defaultPicker, ); @@ -246,7 +246,7 @@ class StreamMessageInput extends StatefulWidget { final bool hideSendAsDm; /// The text controller of the TextField. - final MessageInputController? messageInputController; + final StreamMessageInputController? messageInputController; /// List of action widgets. final List actions; @@ -375,14 +375,14 @@ class StreamMessageInputState extends State bool get _disableEmojiSuggestionsOverlay => widget.disableEmojiSuggestionsOverlay ?? false; - RestorableMessageInputController? _controller; + StreamRestorableMessageInputController? _controller; - MessageInputController get _effectiveController => + StreamMessageInputController get _effectiveController => widget.messageInputController ?? _controller!.value; void _createLocalController([Message? message]) { assert(_controller == null, ''); - _controller = RestorableMessageInputController(message: message); + _controller = StreamRestorableMessageInputController(message: message); } void _registerController() { @@ -502,7 +502,7 @@ class StreamMessageInputState extends State ), ); } - return MessageValueListenableBuilder( + return StreamMessageValueListenableBuilder( valueListenable: _effectiveController, builder: (context, value, _) { Widget child = DecoratedBox( diff --git a/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_text_field.dart b/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_text_field.dart index 8e4a34eb..d6a6879a 100644 --- a/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_text_field.dart +++ b/packages/stream_chat_flutter/lib/src/v4/message_input/stream_message_text_field.dart @@ -181,8 +181,8 @@ class StreamMessageTextField extends StatefulWidget { /// Controls the message being edited. /// - /// If null, this widget will create its own [MessageInputController]. - final MessageInputController? controller; + /// If null, this widget will create its own [StreamMessageInputController]. + final StreamMessageInputController? controller; /// Defines the keyboard focus for this widget. /// @@ -656,9 +656,9 @@ class StreamMessageTextField extends StatefulWidget { class _StreamMessageTextFieldState extends State with RestorationMixin { - RestorableMessageInputController? _controller; + StreamRestorableMessageInputController? _controller; - MessageInputController get _effectiveController => + StreamMessageInputController get _effectiveController => widget.controller ?? _controller!.value; @override @@ -671,7 +671,7 @@ class _StreamMessageTextFieldState extends State void _createLocalController([Message? message]) { assert(_controller == null, ''); - _controller = RestorableMessageInputController(message: message); + _controller = StreamRestorableMessageInputController(message: message); } @override diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart new file mode 100644 index 00000000..b8e2cf24 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// A widget that displays a user. +/// +/// This widget is intended to be used as a Tile in +/// [StreamChannelGridView]. +/// +/// It shows the user's avatar and name. +/// +/// See also: +/// * [StreamChannelGridView] +/// * [StreamUserAvatar] +class StreamChannelGridTile extends StatelessWidget { + /// Creates a new instance of [StreamChannelGridTile] widget. + const StreamChannelGridTile({ + Key? key, + required this.channel, + this.child, + this.footer, + this.onTap, + this.onLongPress, + }) : super(key: key); + + /// The channel to display. + final Channel channel; + + /// The widget to display in the body of the tile. + final Widget? child; + + /// The widget to display in the footer of the tile. + final Widget? footer; + + /// Called when the user taps this grid tile. + final GestureTapCallback? onTap; + + /// Called when the user long-presses on this grid tile. + final GestureLongPressCallback? onLongPress; + + /// Creates a copy of this tile but with the given fields replaced with + /// the new values. + StreamChannelGridTile copyWith({ + Key? key, + Channel? channel, + Widget? child, + Widget? footer, + GestureTapCallback? onTap, + GestureLongPressCallback? onLongPress, + }) => + StreamChannelGridTile( + key: key ?? this.key, + channel: channel ?? this.channel, + footer: footer ?? this.footer, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + child: child ?? this.child, + ); + + @override + Widget build(BuildContext context) { + final channelPreviewTheme = StreamChannelPreviewTheme.of(context); + + final child = this.child ?? + StreamChannelAvatar( + channel: channel, + borderRadius: BorderRadius.circular(32), + constraints: const BoxConstraints.tightFor( + height: 64, + width: 64, + ), + ); + + final footer = this.footer ?? + StreamChannelName( + channel: channel, + textStyle: channelPreviewTheme.titleStyle, + ); + + return InkWell( + onTap: onTap, + onLongPress: onLongPress, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + child, + footer, + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart new file mode 100644 index 00000000..af5e03ce --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart @@ -0,0 +1,399 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_loading_widget.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// Default grid delegate for [StreamChannelGridView]. +const defaultChannelGridViewDelegate = + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); + +/// Signature for the item builder that creates the children of the +/// [StreamChannelGridView]. +typedef StreamChannelGridViewIndexedWidgetBuilder + = StreamScrollViewIndexedWidgetBuilder; + +/// A [GridView] that shows a grid of [User]s, +/// it uses [StreamChannelGridTile] as a default item. +/// +/// Example: +/// +/// ```dart +/// StreamChannelGridView( +/// controller: controller, +/// onChannelTap: (channel) { +/// // Handle channel tap event +/// }, +/// onChannelLongPress: (channel) { +/// // Handle channel long press event +/// }, +/// ) +/// ``` +/// +/// See also: +/// * [StreamChannelGridTile] +/// * [StreamChannelListController] +class StreamChannelGridView extends StatelessWidget { + /// Creates a new instance of [StreamChannelGridView]. + const StreamChannelGridView({ + Key? key, + required this.controller, + this.gridDelegate = defaultChannelGridViewDelegate, + this.itemBuilder, + this.emptyBuilder, + this.loadMoreErrorBuilder, + this.loadMoreIndicatorBuilder, + this.loadingBuilder, + this.errorBuilder, + this.onChannelTap, + this.onChannelLongPress, + this.loadMoreTriggerIndex = 3, + this.scrollDirection = Axis.vertical, + this.reverse = false, + this.scrollController, + this.primary, + this.physics, + this.shrinkWrap = false, + this.padding, + this.addAutomaticKeepAlives = true, + this.addRepaintBoundaries = true, + this.addSemanticIndexes = true, + this.cacheExtent, + this.semanticChildCount, + this.dragStartBehavior = DragStartBehavior.start, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.restorationId, + this.clipBehavior = Clip.hardEdge, + }) : super(key: key); + + /// The [StreamUserListController] used to control the grid of users. + final StreamChannelListController controller; + + /// A delegate that controls the layout of the children within + /// the [PagedValueGridView]. + final SliverGridDelegate gridDelegate; + + /// A builder that is called to build items in the [PagedValueGridView]. + /// + /// The `value` parameter is the [V] at this position in the grid. + final StreamChannelGridViewIndexedWidgetBuilder? itemBuilder; + + /// A builder that is called to build the empty state of the grid. + final WidgetBuilder? emptyBuilder; + + /// A builder that is called to build the load more error state of the grid. + final PagedValueScrollViewLoadMoreErrorBuilder? loadMoreErrorBuilder; + + /// A builder that is called to build the load more indicator of the grid. + final WidgetBuilder? loadMoreIndicatorBuilder; + + /// A builder that is called to build the loading state of the grid. + final WidgetBuilder? loadingBuilder; + + /// A builder that is called to build the error state of the grid. + final Widget Function(BuildContext, StreamChatError)? errorBuilder; + + /// Called when the user taps this grid tile. + final void Function(Channel)? onChannelTap; + + /// Called when the user long-presses on this grid tile. + final void Function(Channel)? onChannelLongPress; + + /// The index to take into account when triggering [controller.loadMore]. + final int loadMoreTriggerIndex; + + /// {@template flutter.widgets.scroll_view.scrollDirection} + /// The axis along which the scroll view scrolls. + /// + /// Defaults to [Axis.vertical]. + /// {@endtemplate} + final Axis scrollDirection; + + /// {@template flutter.widgets.scroll_view.reverse} + /// Whether the scroll view scrolls in the reading direction. + /// + /// For example, if the reading direction is left-to-right and + /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from + /// left to right when [reverse] is false and from right to left when + /// [reverse] is true. + /// + /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view + /// scrolls from top to bottom when [reverse] is false and from bottom to top + /// when [reverse] is true. + /// + /// Defaults to false. + /// {@endtemplate} + final bool reverse; + + /// {@template flutter.widgets.scroll_view.controller} + /// An object that can be used to control the position to which this scroll + /// view is scrolled. + /// + /// Must be null if [primary] is true. + /// + /// A [ScrollController] serves several purposes. It can be used to control + /// the initial scroll position (see [ScrollController.initialScrollOffset]). + /// It can be used to control whether the scroll view should automatically + /// save and restore its scroll position in the [PageStorage] (see + /// [ScrollController.keepScrollOffset]). It can be used to read the current + /// scroll position (see [ScrollController.offset]), or change it (see + /// [ScrollController.animateTo]). + /// {@endtemplate} + final ScrollController? scrollController; + + /// {@template flutter.widgets.scroll_view.primary} + /// Whether this is the primary scroll view associated with the parent + /// [PrimaryScrollController]. + /// + /// When this is true, the scroll view is scrollable even if it does not have + /// sufficient content to actually scroll. Otherwise, by default the user can + /// 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. + /// + /// On iOS, this also identifies the scroll view that will scroll to top in + /// response to a tap in the status bar. + /// {@endtemplate} + /// + /// Defaults to true when [scrollDirection] is [Axis.vertical] and + /// [controller] is null. + final bool? primary; + + /// {@template flutter.widgets.scroll_view.physics} + /// How the scroll view should respond to user input. + /// + /// For example, determines how the scroll view continues to animate after the + /// user stops dragging the scroll view. + /// + /// Defaults to matching platform conventions. Furthermore, if [primary] is + /// false, then the user cannot scroll if there is insufficient content to + /// scroll, while if [primary] is true, they can always attempt to scroll. + /// + /// To force the scroll view to always be scrollable even if there is + /// insufficient content, as if [primary] was true but without necessarily + /// setting it to true, provide an [AlwaysScrollableScrollPhysics] physics + /// object, as in: + /// + /// ```dart + /// physics: const AlwaysScrollableScrollPhysics(), + /// ``` + /// + /// To force the scroll view to use the default platform conventions and not + /// be scrollable if there is insufficient content, regardless of the value of + /// [primary], provide an explicit [ScrollPhysics] object, as in: + /// + /// ```dart + /// physics: const ScrollPhysics(), + /// ``` + /// + /// The physics can be changed dynamically (by providing a new object in a + /// subsequent build), but new physics will only take effect if the _class_ of + /// the provided object changes. Merely constructing a new instance with a + /// different configuration is insufficient to cause the physics to be + /// reapplied. (This is because the final object used is generated + /// dynamically, which can be relatively expensive, and it would be + /// inefficient to speculatively create this object each frame to see if the + /// physics should be updated.) + /// {@endtemplate} + /// + /// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the + /// [ScrollPhysics] provided by that behavior will take precedence after + /// [physics]. + final ScrollPhysics? physics; + + /// {@template flutter.widgets.scroll_view.shrinkWrap} + /// Whether the extent of the scroll view in the [scrollDirection] should be + /// determined by the contents being viewed. + /// + /// If the scroll view does not shrink wrap, then the scroll view will expand + /// to the maximum allowed size in the [scrollDirection]. If the scroll view + /// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must + /// be true. + /// + /// Shrink wrapping the content of the scroll view is significantly more + /// expensive than expanding to the maximum allowed size because the content + /// can expand and contract during scrolling, which means the size of the + /// scroll view needs to be recomputed whenever the scroll position changes. + /// + /// Defaults to false. + /// {@endtemplate} + final bool shrinkWrap; + + /// The amount of space by which to inset the children. + final EdgeInsetsGeometry? padding; + + /// Whether to wrap each child in an [AutomaticKeepAlive]. + /// + /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive] + /// widgets so that children can use [KeepAliveNotification]s to preserve + /// their state when they would otherwise be garbage collected off-screen. + /// + /// This feature (and [addRepaintBoundaries]) must be disabled if the children + /// are going to manually maintain their [KeepAlive] state. It may also be + /// more efficient to disable this feature if it is known ahead of time that + /// none of the children will ever try to keep themselves alive. + /// + /// Defaults to true. + final bool addAutomaticKeepAlives; + + /// Whether to wrap each child in a [RepaintBoundary]. + /// + /// Typically, children in a scrolling container are wrapped in repaint + /// boundaries so that they do not need to be repainted as the list scrolls. + /// If the children are easy to repaint (e.g., solid color blocks or a short + /// snippet of text), it might be more efficient to not add a repaint boundary + /// and simply repaint the children during scrolling. + /// + /// Defaults to true. + final bool addRepaintBoundaries; + + /// Whether to wrap each child in an [IndexedSemantics]. + /// + /// Typically, children in a scrolling container must be annotated with a + /// semantic index in order to generate the correct accessibility + /// announcements. This should only be set to false if the indexes have + /// already been provided by an [IndexedSemantics] widget. + /// + /// Defaults to true. + /// + /// See also: + /// + /// * [IndexedSemantics], for an explanation of how to manually + /// provide semantic indexes. + final bool addSemanticIndexes; + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + final double? cacheExtent; + + /// The number of children that will contribute semantic information. + /// + /// Some subtypes of [ScrollView] can infer this value automatically. For + /// example [ListView] will use the number of widgets in the child list, + /// while the [ListView.separated] constructor will use half that amount. + /// + /// For [CustomScrollView] and other types which do not receive a builder + /// or list of widgets, the child count must be explicitly provided. If the + /// number is unknown or unbounded this should be left unset or set to null. + /// + /// See also: + /// + /// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. + final int? semanticChildCount; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior} + /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will + /// dismiss the keyboard automatically. + /// {@endtemplate} + final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; + + /// {@macro flutter.widgets.scrollable.restorationId} + final String? restorationId; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + @override + Widget build(BuildContext context) { + return PagedValueGridView( + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + scrollController: scrollController, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + cacheExtent: cacheExtent, + semanticChildCount: semanticChildCount, + dragStartBehavior: dragStartBehavior, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + clipBehavior: clipBehavior, + gridDelegate: gridDelegate, + itemBuilder: (context, channels, index) { + final channel = channels[index]; + final onTap = onChannelTap; + final onLongPress = onChannelLongPress; + + final streamChannelGridTile = StreamChannelGridTile( + channel: channel, + onTap: onTap == null ? null : () => onTap(channel), + onLongPress: onLongPress == null ? null : () => onLongPress(channel), + ); + + return itemBuilder?.call( + context, + channels, + index, + streamChannelGridTile, + ) ?? + streamChannelGridTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon.message( + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.letsStartChattingLabel, + style: chatThemeData.textTheme.headline, + ), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => + StreamScrollViewLoadMoreError.grid( + onTap: controller.retry, + error: Text( + context.translations.loadingChannelsError, + textAlign: TextAlign.center, + ), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), + ), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingChannelsError), + onRetryPressed: controller.refresh, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart new file mode 100644 index 00000000..e3f7f374 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart @@ -0,0 +1,96 @@ +// import 'package:flutter/material.dart'; +// import 'package:shimmer/shimmer.dart'; +// import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +// +// /// A shimmering list item which shows a loading effect. +// /// +// /// This is used by [StreamChannelListView] to show a loading effect while +// /// the list is being loaded. +// class StreamChannelListLoadingTile extends StatelessWidget { +// /// Creates a new instance of [StreamChannelListLoadingTile] widget. +// const StreamChannelListLoadingTile({ +// Key? key, +// this.visualDensity = VisualDensity.standard, +// this.contentPadding = const EdgeInsets.symmetric(horizontal: 8), +// }) : super(key: key); +// +// /// 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], [errorTitle], [subtitle], +// /// and [trailing] widgets. +// /// +// /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. +// final EdgeInsetsGeometry contentPadding; +// +// @override +// Widget build(BuildContext context) { +// final colorTheme = StreamChatTheme.of(context).colorTheme; +// +// final leading = Container( +// height: 49, +// width: 49, +// decoration: BoxDecoration( +// color: colorTheme.barsBg, +// shape: BoxShape.circle, +// ), +// ); +// +// final title = Container( +// height: 16, +// width: 66, +// decoration: BoxDecoration( +// color: colorTheme.barsBg, +// borderRadius: BorderRadius.circular(8), +// ), +// ); +// +// final subtitle = Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Expanded( +// child: Align( +// alignment: Alignment.centerLeft, +// child: Container( +// height: 16, +// decoration: BoxDecoration( +// color: colorTheme.barsBg, +// borderRadius: BorderRadius.circular(8), +// ), +// ), +// ), +// ), +// const SizedBox(width: 8), +// Container( +// height: 16, +// width: 50, +// decoration: BoxDecoration( +// color: colorTheme.barsBg, +// borderRadius: BorderRadius.circular(8), +// ), +// ), +// ], +// ); +// +// return Shimmer.fromColors( +// baseColor: colorTheme.disabled, +// highlightColor: colorTheme.inputBg, +// child: ListTile( +// leading: leading, +// title: title, +// subtitle: subtitle, +// visualDensity: visualDensity, +// contentPadding: contentPadding, +// ), +// ); +// } +// } 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/scroll_view/channel_scroll_view/stream_channel_list_tile.dart similarity index 99% rename from packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_tile.dart rename to packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index 8c5acea6..8a180fca 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/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:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/src/v4/stream_message_preview_text.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that displays a channel preview. 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/scroll_view/channel_scroll_view/stream_channel_list_view.dart similarity index 80% rename from packages/stream_chat_flutter/lib/src/v4/channel_list_view/stream_channel_list_view.dart rename to packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart index 1e76a25b..0feb84c6 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/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -3,9 +3,14 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_loading_tile.dart'; -import 'package:stream_chat_flutter/src/v4/channel_list_view/stream_channel_list_tile.dart'; -import 'package:stream_chat_flutter/src/v4/stream_list_view_indexed_widget_builder.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/channel_scroll_view/stream_channel_list_tile.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart'; + +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_loading_widget.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Default separator builder for [StreamChannelListView]. @@ -19,7 +24,7 @@ Widget defaultChannelListViewSeparatorBuilder( /// Signature for the item builder that creates the children of the /// [StreamChannelListView]. typedef StreamChannelListViewIndexedWidgetBuilder - = StreamListViewIndexedWidgetBuilder; + = StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [Channel]s, /// it uses [StreamChannelListTile] as a default item. @@ -328,96 +333,52 @@ class StreamChannelListView extends StatelessWidget { ) ?? streamChannelListTile; }, - emptyBuilder: (context) => - emptyBuilder?.call(context) ?? - const Center( - child: Padding( - padding: EdgeInsets.all(8), - child: StreamChannelListEmptyWidget(), - ), - ), + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon.message( + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.letsStartChattingLabel, + style: chatThemeData.textTheme.headline, + ), + ), + ), + ); + }, loadMoreErrorBuilder: (context, error) => - StreamChannelListLoadMoreError(onTap: controller.retry), + StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingChannelsError), + ), loadMoreIndicatorBuilder: (context) => const Center( child: Padding( padding: EdgeInsets.all(16), - child: StreamChannelListLoadMoreIndicator(), + child: StreamScrollViewLoadMoreIndicator(), ), ), loadingBuilder: (context) => loadingBuilder?.call(context) ?? - ListView.separated( - padding: padding, - physics: physics, - reverse: reverse, - itemCount: 25, - separatorBuilder: (_, __) => const StreamChannelListSeparator(), - itemBuilder: (_, __) => const StreamChannelListLoadingTile(), + const Center( + child: StreamScrollViewLoadingWidget(), ), errorBuilder: (context, error) => errorBuilder?.call(context, error) ?? Center( - child: StreamChannelListErrorWidget( - onPressed: controller.refresh, + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingChannelsError), + onRetryPressed: controller.refresh, ), ), ); } -/// A [StreamChannelListTile] that can be used in a [ListView] to show a -/// loading tile while waiting for the [StreamChannelListController] to load -/// more channels. -class StreamChannelListLoadMoreIndicator extends StatelessWidget { - /// Creates a new instance of [StreamChannelListLoadMoreIndicator]. - const StreamChannelListLoadMoreIndicator({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => const SizedBox( - height: 16, - width: 16, - child: CircularProgressIndicator.adaptive(), - ); -} - -/// A [StreamChannelListTile] that is used to display the error indicator when -/// loading more channels fails. -class StreamChannelListLoadMoreError extends StatelessWidget { - /// Creates a new instance of [StreamChannelListLoadMoreError]. - const StreamChannelListLoadMoreError({ - Key? key, - this.onTap, - }) : super(key: key); - - /// The callback to invoke when the user taps on the error indicator. - final GestureTapCallback? onTap; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - return InkWell( - onTap: onTap, - child: Container( - color: theme.colorTheme.textLowEmphasis.withOpacity(0.9), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - context.translations.loadingChannelsError, - style: theme.textTheme.body.copyWith( - color: Colors.white, - ), - ), - StreamSvgIcon.retry(color: Colors.white), - ], - ), - ), - ), - ); - } -} - /// A widget that is used to display a separator between /// [StreamChannelListTile] items. class StreamChannelListSeparator extends StatelessWidget { @@ -471,29 +432,3 @@ class StreamChannelListErrorWidget extends StatelessWidget { ], ); } - -/// A widget that is used to display an empty state when -/// [StreamChannelListController] loads zero channels. -class StreamChannelListEmptyWidget extends StatelessWidget { - /// Creates a new instance of [StreamChannelListEmptyWidget] widget. - const StreamChannelListEmptyWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final chatThemeData = StreamChatTheme.of(context); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StreamSvgIcon.message( - size: 148, - color: chatThemeData.colorTheme.disabled, - ), - const SizedBox(height: 28), - Text( - context.translations.letsStartChattingLabel, - style: chatThemeData.textTheme.headline, - ), - ], - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/v4/message_search_list_view/stream_message_search_list_tile.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart similarity index 95% rename from packages/stream_chat_flutter/lib/src/v4/message_search_list_view/stream_message_search_list_tile.dart rename to packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart index f67597f0..c303add0 100644 --- a/packages/stream_chat_flutter/lib/src/v4/message_search_list_view/stream_message_search_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/src/v4/stream_message_preview_text.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that displays a message search item. @@ -158,15 +157,19 @@ class StreamMessageSearchListTile extends StatelessWidget { } } +/// A widget that displays the title of a [StreamMessageSearchListTile]. class MessageSearchListTileTitle extends StatelessWidget { + /// Creates a new [MessageSearchListTileTitle] instance. const MessageSearchListTileTitle({ Key? key, required this.messageResponse, this.textStyle, }) : super(key: key); + /// The message response for the tile. final GetMessageResponse messageResponse; + /// The style to use for the title. final TextStyle? textStyle; @override @@ -200,6 +203,7 @@ class MessageSearchListTileTitle extends StatelessWidget { } } +/// A widget which shows formatted created date of the passed [message]. class MessageSearchTileMessageDate extends StatelessWidget { /// Creates a new instance of [MessageSearchTileMessageDate]. const MessageSearchTileMessageDate({ diff --git a/packages/stream_chat_flutter/lib/src/v4/message_search_list_view/stream_message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart similarity index 74% rename from packages/stream_chat_flutter/lib/src/v4/message_search_list_view/stream_message_search_list_view.dart rename to packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart index aef76b10..e6b526d7 100644 --- a/packages/stream_chat_flutter/lib/src/v4/message_search_list_view/stream_message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart @@ -1,6 +1,11 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_loading_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default separator builder for [StreamMessageSearchListView]. @@ -14,7 +19,7 @@ Widget defaultMessageSearchListViewSeparatorBuilder( /// Signature for the item builder that creates the children of the /// [StreamMessageSearchListView]. typedef StreamMessageSearchListViewIndexedWidgetBuilder - = StreamListViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [GetMessageResponse]s, @@ -312,7 +317,7 @@ class StreamMessageSearchListView extends StatelessWidget { final onTap = onMessageTap; final onLongPress = onMessageLongPress; - final streamUserListTile = StreamMessageSearchListTile( + final streamMessageSearchListTile = StreamMessageSearchListTile( messageResponse: messageResponse, onTap: onTap == null ? null : () => onTap(messageResponse), onLongPress: @@ -323,101 +328,56 @@ class StreamMessageSearchListView extends StatelessWidget { context, messageResponses, index, - streamUserListTile, + streamMessageSearchListTile, ) ?? - streamUserListTile; + streamMessageSearchListTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon.message( + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.emptyMessagesText, + style: chatThemeData.textTheme.headline, + ), + ), + ), + ); }, - emptyBuilder: (context) => - emptyBuilder?.call(context) ?? - const Center( - child: Padding( - padding: EdgeInsets.all(8), - child: StreamMessageSearchListEmptyWidget(), - ), - ), loadMoreErrorBuilder: (context, error) => - StreamMessageSearchListLoadMoreError(onTap: controller.retry), + StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingMessagesError), + ), loadMoreIndicatorBuilder: (context) => const Center( child: Padding( padding: EdgeInsets.all(16), - child: StreamMessageSearchListLoadMoreIndicator(), + child: StreamScrollViewLoadMoreIndicator(), ), ), loadingBuilder: (context) => loadingBuilder?.call(context) ?? - ListView.separated( - padding: padding, - physics: physics, - reverse: reverse, - itemCount: 25, - separatorBuilder: (_, __) => - const StreamMessageSearchListSeparator(), - itemBuilder: (_, __) => const StreamChannelListLoadingTile(), + const Center( + child: StreamScrollViewLoadingWidget(), ), errorBuilder: (context, error) => errorBuilder?.call(context, error) ?? Center( - child: StreamMessageSearchListErrorWidget( - onPressed: controller.refresh, + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingMessagesError), + onRetryPressed: controller.refresh, ), ), ); } -/// A [StreamMessageSearchListTile] that can be used in a [ListView] to show a -/// loading tile while waiting for the [StreamMessageSearchListController] to -/// load more messages. -class StreamMessageSearchListLoadMoreIndicator extends StatelessWidget { - /// Creates a new instance of [StreamMessageSearchListLoadMoreIndicator]. - const StreamMessageSearchListLoadMoreIndicator({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => const SizedBox( - height: 16, - width: 16, - child: CircularProgressIndicator.adaptive(), - ); -} - -/// A [StreamMessageSearchListTile] that is used to display the error indicator -/// when loading more messages fails. -class StreamMessageSearchListLoadMoreError extends StatelessWidget { - /// Creates a new instance of [StreamMessageSearchListLoadMoreError]. - const StreamMessageSearchListLoadMoreError({ - Key? key, - this.onTap, - }) : super(key: key); - - /// The callback to invoke when the user taps on the error indicator. - final GestureTapCallback? onTap; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - return InkWell( - onTap: onTap, - child: Container( - color: theme.colorTheme.textLowEmphasis.withOpacity(0.9), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - context.translations.loadingChannelsError, - style: theme.textTheme.body.copyWith( - color: Colors.white, - ), - ), - StreamSvgIcon.retry(color: Colors.white), - ], - ), - ), - ), - ); - } -} - /// A widget that is used to display a separator between /// [StreamMessageSearchListTile] items. class StreamMessageSearchListSeparator extends StatelessWidget { @@ -433,67 +393,3 @@ class StreamMessageSearchListSeparator extends StatelessWidget { ); } } - -/// A widget that is used to display an error screen -/// when [StreamMessageSearchListController] fails to load initial messages. -class StreamMessageSearchListErrorWidget extends StatelessWidget { - /// Creates a new instance of [StreamMessageSearchListErrorWidget] widget. - const StreamMessageSearchListErrorWidget({ - Key? key, - this.onPressed, - }) : super(key: key); - - /// The callback to invoke when the user taps on the retry button. - final VoidCallback? onPressed; - - @override - Widget build(BuildContext context) => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - const WidgetSpan( - child: Padding( - padding: EdgeInsets.only(right: 2), - child: Icon(Icons.error_outline), - ), - ), - TextSpan(text: context.translations.loadingChannelsError), - ], - ), - style: Theme.of(context).textTheme.headline6, - ), - TextButton( - onPressed: onPressed, - child: Text(context.translations.retryLabel), - ), - ], - ); -} - -/// A widget that is used to display an empty state when -/// [StreamMessageSearchListController] loads zero messages. -class StreamMessageSearchListEmptyWidget extends StatelessWidget { - /// Creates a new instance of [StreamMessageSearchListEmptyWidget] widget. - const StreamMessageSearchListEmptyWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final chatThemeData = StreamChatTheme.of(context); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StreamSvgIcon.message( - size: 148, - color: chatThemeData.colorTheme.disabled, - ), - const SizedBox(height: 28), - Text( - context.translations.letsStartChattingLabel, - style: chatThemeData.textTheme.headline, - ), - ], - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_empty_widget.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_empty_widget.dart new file mode 100644 index 00000000..531eac79 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_empty_widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; + +/// A widget that shows an empty view when the [StreamScrollView] loads +/// empty data. +class StreamScrollViewEmptyWidget extends StatelessWidget { + /// Creates a new instance of the [StreamScrollViewEmptyWidget]. + const StreamScrollViewEmptyWidget({ + Key? key, + required this.emptyIcon, + required this.emptyTitle, + this.emptyTitleStyle, + this.mainAxisSize = MainAxisSize.max, + this.mainAxisAlignment = MainAxisAlignment.center, + this.crossAxisAlignment = CrossAxisAlignment.center, + }) : super(key: key); + + /// The title of the empty view. + final Widget emptyTitle; + + /// The style of the title. + final TextStyle? emptyTitleStyle; + + /// The icon of the empty view. + final Widget emptyIcon; + + /// The main axis size of the empty view. + final MainAxisSize mainAxisSize; + + /// The main axis alignment of the empty view. + final MainAxisAlignment mainAxisAlignment; + + /// The cross axis alignment of the empty view. + final CrossAxisAlignment crossAxisAlignment; + + @override + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + + final emptyIcon = AnimatedSwitcher( + duration: kThemeChangeDuration, + child: this.emptyIcon, + ); + + final emptyTitleText = AnimatedDefaultTextStyle( + style: emptyTitleStyle ?? chatThemeData.textTheme.headline, + duration: kThemeChangeDuration, + child: emptyTitle, + ); + + return Column( + mainAxisSize: mainAxisSize, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, + children: [ + emptyIcon, + emptyTitleText, + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_error_widget.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_error_widget.dart new file mode 100644 index 00000000..9d6af29c --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_error_widget.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; + +/// A widget that is displayed when a [StreamScrollView] encounters an error +/// while loading the initial items. +class StreamScrollViewErrorWidget extends StatelessWidget { + /// Creates a new instance of the [StreamScrollViewErrorWidget]. + const StreamScrollViewErrorWidget({ + Key? key, + this.errorTitle, + this.errorTitleStyle, + this.errorIcon, + this.retryButtonText, + this.retryButtonTextStyle, + required this.onRetryPressed, + this.mainAxisSize = MainAxisSize.max, + this.mainAxisAlignment = MainAxisAlignment.center, + this.crossAxisAlignment = CrossAxisAlignment.center, + }) : super(key: key); + + /// The title of the error. + final Widget? errorTitle; + + /// The style of the title. + final TextStyle? errorTitleStyle; + + /// The icon to display when the list shows error. + final Widget? errorIcon; + + /// The text to display in the retry button. + final Widget? retryButtonText; + + /// The style of the retryButtonText. + final TextStyle? retryButtonTextStyle; + + /// The callback to invoke when the user taps on the retry button. + final VoidCallback onRetryPressed; + + /// The main axis size of the error view. + final MainAxisSize mainAxisSize; + + /// The main axis alignment of the error view. + final MainAxisAlignment mainAxisAlignment; + + /// The cross axis alignment of the error view. + final CrossAxisAlignment crossAxisAlignment; + + @override + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + + final errorIcon = AnimatedSwitcher( + duration: kThemeChangeDuration, + child: this.errorIcon ?? + Icon( + Icons.error_outline_rounded, + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + ); + + final titleText = AnimatedDefaultTextStyle( + style: errorTitleStyle ?? chatThemeData.textTheme.headline, + duration: kThemeChangeDuration, + child: errorTitle ?? const SizedBox(), + ); + + final retryButtonText = AnimatedDefaultTextStyle( + style: errorTitleStyle ?? + chatThemeData.textTheme.headline.copyWith( + color: Colors.white, + ), + duration: kThemeChangeDuration, + child: this.retryButtonText ?? Text(context.translations.retryLabel), + ); + + return Column( + mainAxisSize: mainAxisSize, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, + children: [ + errorIcon, + titleText, + ElevatedButton( + onPressed: onRetryPressed, + child: retryButtonText, + ), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart new file mode 100644 index 00000000..0305cd1f --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +/// Signature for a function that creates a widget for a given index, e.g., in a +/// list, grid. +/// +/// Used by [StreamChannelListView], [StreamMessageSearchListView] +/// and [StreamUserListView]. +typedef StreamScrollViewIndexedWidgetBuilder + = Widget Function( + BuildContext context, + List items, + int index, + WidgetType defaultWidget, +); diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_error.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_error.dart new file mode 100644 index 00000000..03575b1f --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_error.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; + +/// A tile that is used to display the error indicator when +/// loading more items fails. +class StreamScrollViewLoadMoreError extends StatelessWidget { + /// Creates a new instance of [StreamScrollViewLoadMoreError.list]. + const StreamScrollViewLoadMoreError.list({ + Key? key, + this.error, + this.errorStyle, + this.errorIcon, + this.backgroundColor, + required this.onTap, + this.padding = const EdgeInsets.all(16), + this.mainAxisSize = MainAxisSize.max, + this.mainAxisAlignment = MainAxisAlignment.spaceBetween, + this.crossAxisAlignment = CrossAxisAlignment.center, + }) : _isList = true, + super(key: key); + + /// Creates a new instance of [StreamScrollViewLoadMoreError.grid]. + const StreamScrollViewLoadMoreError.grid({ + Key? key, + this.error, + this.errorStyle, + this.errorIcon, + this.backgroundColor, + required this.onTap, + this.padding = const EdgeInsets.all(16), + this.mainAxisSize = MainAxisSize.max, + this.mainAxisAlignment = MainAxisAlignment.spaceEvenly, + this.crossAxisAlignment = CrossAxisAlignment.center, + }) : _isList = false, + super(key: key); + + /// The error message to display. + final Widget? error; + + /// The style of the error message. + final TextStyle? errorStyle; + + /// The icon to display next to the message. + final Widget? errorIcon; + + /// The background color of the error message. + final Color? backgroundColor; + + /// The callback to invoke when the user taps on the error indicator. + final GestureTapCallback onTap; + + /// The amount of space by which to inset the child. + final EdgeInsetsGeometry padding; + + /// The main axis size of the error view. + final MainAxisSize mainAxisSize; + + /// The main axis alignment of the error view. + final MainAxisAlignment mainAxisAlignment; + + /// The cross axis alignment of the error view. + final CrossAxisAlignment crossAxisAlignment; + + final bool _isList; + + @override + Widget build(BuildContext context) { + final theme = StreamChatTheme.of(context); + + final errorText = AnimatedDefaultTextStyle( + style: errorStyle ?? theme.textTheme.body.copyWith(color: Colors.white), + duration: kThemeChangeDuration, + child: error ?? const SizedBox(), + ); + + final errorIcon = AnimatedSwitcher( + duration: kThemeChangeDuration, + child: this.errorIcon ?? StreamSvgIcon.retry(color: Colors.white), + ); + + final backgroundColor = this.backgroundColor ?? + theme.colorTheme.textLowEmphasis.withOpacity(0.9); + + final children = [errorText, errorIcon]; + + return InkWell( + onTap: onTap, + child: Container( + color: backgroundColor, + child: Padding( + padding: padding, + child: _isList + ? Row( + mainAxisSize: mainAxisSize, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, + children: children, + ) + : Column( + mainAxisSize: mainAxisSize, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, + children: children, + ), + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart new file mode 100644 index 00000000..93bf8d06 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +/// A widget that shows a loading indicator when the user is near the bottom of +/// the list. +class StreamScrollViewLoadMoreIndicator extends StatelessWidget { + /// Creates a new instance of [StreamScrollViewLoadMoreIndicator]. + const StreamScrollViewLoadMoreIndicator({ + Key? key, + this.height = 16, + this.width = 16, + }) : super(key: key); + + /// The height of the indicator. + final double height; + + /// The width of the indicator. + final double width; + + @override + Widget build(BuildContext context) => SizedBox( + height: height, + width: width, + child: const CircularProgressIndicator.adaptive(), + ); +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_loading_widget.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_loading_widget.dart new file mode 100644 index 00000000..ae47b152 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/stream_scroll_view_loading_widget.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +/// A widget that is displayed while the [StreamScrollView] is loading. +class StreamScrollViewLoadingWidget extends StatelessWidget { + /// Creates a new instance of [StreamScrollViewLoadingWidget]. + const StreamScrollViewLoadingWidget({ + Key? key, + this.height = 42, + this.width = 42, + }) : super(key: key); + + /// The height of the indicator. + final double height; + + /// The width of the indicator. + final double width; + + @override + Widget build(BuildContext context) => SizedBox( + height: height, + width: width, + child: const CircularProgressIndicator.adaptive(), + ); +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_tile.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_tile.dart new file mode 100644 index 00000000..6f8743eb --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_tile.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// A widget that displays a user. +/// +/// This widget is intended to be used as a Tile in [StreamUserGridView] +/// +/// It shows the user's avatar and name. +/// +/// See also: +/// * [StreamUserGridView] +/// * [StreamUserAvatar] +class StreamUserGridTile extends StatelessWidget { + /// Creates a new instance of [StreamUserGridTile] widget. + const StreamUserGridTile({ + Key? key, + required this.user, + this.child, + this.footer, + this.onTap, + this.onLongPress, + }) : super(key: key); + + /// The user to display. + final User user; + + /// The widget to display in the body of the tile. + final Widget? child; + + /// The widget to display in the footer of the tile. + final Widget? footer; + + /// Called when the user taps this grid tile. + final GestureTapCallback? onTap; + + /// Called when the user long-presses on this grid tile. + final GestureLongPressCallback? onLongPress; + + /// Creates a copy of this tile but with the given fields replaced with + /// the new values. + StreamUserGridTile copyWith({ + Key? key, + User? user, + Widget? child, + Widget? footer, + GestureTapCallback? onTap, + GestureLongPressCallback? onLongPress, + }) => + StreamUserGridTile( + key: key ?? this.key, + user: user ?? this.user, + footer: footer ?? this.footer, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + child: child ?? this.child, + ); + + @override + Widget build(BuildContext context) { + final child = this.child ?? + StreamUserAvatar( + user: user, + borderRadius: BorderRadius.circular(32), + constraints: const BoxConstraints.tightFor( + height: 64, + width: 64, + ), + onlineIndicatorConstraints: const BoxConstraints.tightFor( + height: 12, + width: 12, + ), + ); + + final footer = this.footer ?? + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + user.name, + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ); + + return InkWell( + onTap: onTap, + onLongPress: onLongPress, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + child, + const SizedBox(height: 4), + footer, + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart new file mode 100644 index 00000000..24765a6a --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart @@ -0,0 +1,395 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_loading_widget.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Default grid delegate for [StreamUserGridView]. +const defaultUserGridViewDelegate = + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); + +/// Signature for the item builder that creates the children of the +/// [StreamUserGridView]. +typedef StreamUserGridViewIndexedWidgetBuilder + = StreamScrollViewIndexedWidgetBuilder; + +/// A [GridView] that shows a grid of [User]s, +/// it uses [StreamUserGridTile] as a default item. +/// +/// Example: +/// +/// ```dart +/// StreamUserGridView( +/// controller: controller, +/// onUserTap: (user) { +/// // Handle user tap event +/// }, +/// onUserLongPress: (user) { +/// // Handle user long press event +/// }, +/// ) +/// ``` +/// +/// See also: +/// * [StreamUserListTile] +/// * [StreamUserListController] +class StreamUserGridView extends StatelessWidget { + /// Creates a new instance of [StreamUserGridView]. + const StreamUserGridView({ + Key? key, + required this.controller, + this.gridDelegate = defaultUserGridViewDelegate, + this.itemBuilder, + this.emptyBuilder, + this.loadMoreErrorBuilder, + this.loadMoreIndicatorBuilder, + this.loadingBuilder, + this.errorBuilder, + this.onUserTap, + this.onUserLongPress, + this.loadMoreTriggerIndex = 3, + this.scrollDirection = Axis.vertical, + this.reverse = false, + this.scrollController, + this.primary, + this.physics, + this.shrinkWrap = false, + this.padding, + this.addAutomaticKeepAlives = true, + this.addRepaintBoundaries = true, + this.addSemanticIndexes = true, + this.cacheExtent, + this.semanticChildCount, + this.dragStartBehavior = DragStartBehavior.start, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.restorationId, + this.clipBehavior = Clip.hardEdge, + }) : super(key: key); + + /// The [StreamUserListController] used to control the grid of users. + final StreamUserListController controller; + + /// A delegate that controls the layout of the children within + /// the [PagedValueGridView]. + final SliverGridDelegate gridDelegate; + + /// A builder that is called to build items in the [PagedValueGridView]. + /// + /// The `value` parameter is the [V] at this position in the grid. + final StreamUserGridViewIndexedWidgetBuilder? itemBuilder; + + /// A builder that is called to build the empty state of the grid. + final WidgetBuilder? emptyBuilder; + + /// A builder that is called to build the load more error state of the grid. + final PagedValueScrollViewLoadMoreErrorBuilder? loadMoreErrorBuilder; + + /// A builder that is called to build the load more indicator of the grid. + final WidgetBuilder? loadMoreIndicatorBuilder; + + /// A builder that is called to build the loading state of the grid. + final WidgetBuilder? loadingBuilder; + + /// A builder that is called to build the error state of the grid. + final Widget Function(BuildContext, StreamChatError)? errorBuilder; + + /// Called when the user taps this grid tile. + final void Function(User)? onUserTap; + + /// Called when the user long-presses on this grid tile. + final void Function(User)? onUserLongPress; + + /// The index to take into account when triggering [controller.loadMore]. + final int loadMoreTriggerIndex; + + /// {@template flutter.widgets.scroll_view.scrollDirection} + /// The axis along which the scroll view scrolls. + /// + /// Defaults to [Axis.vertical]. + /// {@endtemplate} + final Axis scrollDirection; + + /// {@template flutter.widgets.scroll_view.reverse} + /// Whether the scroll view scrolls in the reading direction. + /// + /// For example, if the reading direction is left-to-right and + /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from + /// left to right when [reverse] is false and from right to left when + /// [reverse] is true. + /// + /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view + /// scrolls from top to bottom when [reverse] is false and from bottom to top + /// when [reverse] is true. + /// + /// Defaults to false. + /// {@endtemplate} + final bool reverse; + + /// {@template flutter.widgets.scroll_view.controller} + /// An object that can be used to control the position to which this scroll + /// view is scrolled. + /// + /// Must be null if [primary] is true. + /// + /// A [ScrollController] serves several purposes. It can be used to control + /// the initial scroll position (see [ScrollController.initialScrollOffset]). + /// It can be used to control whether the scroll view should automatically + /// save and restore its scroll position in the [PageStorage] (see + /// [ScrollController.keepScrollOffset]). It can be used to read the current + /// scroll position (see [ScrollController.offset]), or change it (see + /// [ScrollController.animateTo]). + /// {@endtemplate} + final ScrollController? scrollController; + + /// {@template flutter.widgets.scroll_view.primary} + /// Whether this is the primary scroll view associated with the parent + /// [PrimaryScrollController]. + /// + /// When this is true, the scroll view is scrollable even if it does not have + /// sufficient content to actually scroll. Otherwise, by default the user can + /// 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. + /// + /// On iOS, this also identifies the scroll view that will scroll to top in + /// response to a tap in the status bar. + /// {@endtemplate} + /// + /// Defaults to true when [scrollDirection] is [Axis.vertical] and + /// [controller] is null. + final bool? primary; + + /// {@template flutter.widgets.scroll_view.physics} + /// How the scroll view should respond to user input. + /// + /// For example, determines how the scroll view continues to animate after the + /// user stops dragging the scroll view. + /// + /// Defaults to matching platform conventions. Furthermore, if [primary] is + /// false, then the user cannot scroll if there is insufficient content to + /// scroll, while if [primary] is true, they can always attempt to scroll. + /// + /// To force the scroll view to always be scrollable even if there is + /// insufficient content, as if [primary] was true but without necessarily + /// setting it to true, provide an [AlwaysScrollableScrollPhysics] physics + /// object, as in: + /// + /// ```dart + /// physics: const AlwaysScrollableScrollPhysics(), + /// ``` + /// + /// To force the scroll view to use the default platform conventions and not + /// be scrollable if there is insufficient content, regardless of the value of + /// [primary], provide an explicit [ScrollPhysics] object, as in: + /// + /// ```dart + /// physics: const ScrollPhysics(), + /// ``` + /// + /// The physics can be changed dynamically (by providing a new object in a + /// subsequent build), but new physics will only take effect if the _class_ of + /// the provided object changes. Merely constructing a new instance with a + /// different configuration is insufficient to cause the physics to be + /// reapplied. (This is because the final object used is generated + /// dynamically, which can be relatively expensive, and it would be + /// inefficient to speculatively create this object each frame to see if the + /// physics should be updated.) + /// {@endtemplate} + /// + /// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the + /// [ScrollPhysics] provided by that behavior will take precedence after + /// [physics]. + final ScrollPhysics? physics; + + /// {@template flutter.widgets.scroll_view.shrinkWrap} + /// Whether the extent of the scroll view in the [scrollDirection] should be + /// determined by the contents being viewed. + /// + /// If the scroll view does not shrink wrap, then the scroll view will expand + /// to the maximum allowed size in the [scrollDirection]. If the scroll view + /// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must + /// be true. + /// + /// Shrink wrapping the content of the scroll view is significantly more + /// expensive than expanding to the maximum allowed size because the content + /// can expand and contract during scrolling, which means the size of the + /// scroll view needs to be recomputed whenever the scroll position changes. + /// + /// Defaults to false. + /// {@endtemplate} + final bool shrinkWrap; + + /// The amount of space by which to inset the children. + final EdgeInsetsGeometry? padding; + + /// Whether to wrap each child in an [AutomaticKeepAlive]. + /// + /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive] + /// widgets so that children can use [KeepAliveNotification]s to preserve + /// their state when they would otherwise be garbage collected off-screen. + /// + /// This feature (and [addRepaintBoundaries]) must be disabled if the children + /// are going to manually maintain their [KeepAlive] state. It may also be + /// more efficient to disable this feature if it is known ahead of time that + /// none of the children will ever try to keep themselves alive. + /// + /// Defaults to true. + final bool addAutomaticKeepAlives; + + /// Whether to wrap each child in a [RepaintBoundary]. + /// + /// Typically, children in a scrolling container are wrapped in repaint + /// boundaries so that they do not need to be repainted as the list scrolls. + /// If the children are easy to repaint (e.g., solid color blocks or a short + /// snippet of text), it might be more efficient to not add a repaint boundary + /// and simply repaint the children during scrolling. + /// + /// Defaults to true. + final bool addRepaintBoundaries; + + /// Whether to wrap each child in an [IndexedSemantics]. + /// + /// Typically, children in a scrolling container must be annotated with a + /// semantic index in order to generate the correct accessibility + /// announcements. This should only be set to false if the indexes have + /// already been provided by an [IndexedSemantics] widget. + /// + /// Defaults to true. + /// + /// See also: + /// + /// * [IndexedSemantics], for an explanation of how to manually + /// provide semantic indexes. + final bool addSemanticIndexes; + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + final double? cacheExtent; + + /// The number of children that will contribute semantic information. + /// + /// Some subtypes of [ScrollView] can infer this value automatically. For + /// example [ListView] will use the number of widgets in the child list, + /// while the [ListView.separated] constructor will use half that amount. + /// + /// For [CustomScrollView] and other types which do not receive a builder + /// or list of widgets, the child count must be explicitly provided. If the + /// number is unknown or unbounded this should be left unset or set to null. + /// + /// See also: + /// + /// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. + final int? semanticChildCount; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior} + /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will + /// dismiss the keyboard automatically. + /// {@endtemplate} + final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; + + /// {@macro flutter.widgets.scrollable.restorationId} + final String? restorationId; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + @override + Widget build(BuildContext context) { + return PagedValueGridView( + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + scrollController: scrollController, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + cacheExtent: cacheExtent, + semanticChildCount: semanticChildCount, + dragStartBehavior: dragStartBehavior, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + clipBehavior: clipBehavior, + gridDelegate: gridDelegate, + itemBuilder: (context, users, index) { + final user = users[index]; + final onTap = onUserTap; + final onLongPress = onUserLongPress; + + final streamUserGridTile = StreamUserGridTile( + user: user, + onTap: onTap == null ? null : () => onTap(user), + onLongPress: onLongPress == null ? null : () => onLongPress(user), + ); + + return itemBuilder?.call( + context, + users, + index, + streamUserGridTile, + ) ?? + streamUserGridTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon.user( + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noUsersLabel, + style: chatThemeData.textTheme.headline, + ), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => + StreamScrollViewLoadMoreError.grid( + onTap: controller.retry, + error: Text( + context.translations.loadingUsersError, + textAlign: TextAlign.center, + ), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), + ), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingUsersError), + onRetryPressed: controller.refresh, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/v4/user_list_view/stream_user_list_tile.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_tile.dart similarity index 100% rename from packages/stream_chat_flutter/lib/src/v4/user_list_view/stream_user_list_tile.dart rename to packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_tile.dart diff --git a/packages/stream_chat_flutter/lib/src/v4/user_list_view/stream_user_list_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_view.dart similarity index 75% rename from packages/stream_chat_flutter/lib/src/v4/user_list_view/stream_user_list_view.dart rename to packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_view.dart index 45fd37bb..1bca6e84 100644 --- a/packages/stream_chat_flutter/lib/src/v4/user_list_view/stream_user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_view.dart @@ -1,6 +1,11 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_loading_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default separator builder for [StreamUserListView]. @@ -14,7 +19,7 @@ Widget defaultUserListViewSeparatorBuilder( /// Signature for the item builder that creates the children of the /// [StreamUserListView]. typedef StreamUserListViewIndexedWidgetBuilder - = StreamListViewIndexedWidgetBuilder; + = StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [User]s, /// it uses [StreamUserListTile] as a default item. @@ -322,96 +327,52 @@ class StreamUserListView extends StatelessWidget { ) ?? streamUserListTile; }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon.user( + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noUsersLabel, + style: chatThemeData.textTheme.headline, + ), + ), + ), + ); + }, loadMoreErrorBuilder: (context, error) => - StreamUserListLoadMoreError(onTap: controller.retry), + StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingUsersError), + ), loadMoreIndicatorBuilder: (context) => const Center( child: Padding( padding: EdgeInsets.all(16), - child: StreamUserListLoadMoreIndicator(), + child: StreamScrollViewLoadMoreIndicator(), ), ), - emptyBuilder: (context) => - emptyBuilder?.call(context) ?? - const Center( - child: Padding( - padding: EdgeInsets.all(8), - child: StreamUserListEmptyWidget(), - ), - ), loadingBuilder: (context) => loadingBuilder?.call(context) ?? - ListView.separated( - padding: padding, - physics: physics, - reverse: reverse, - itemCount: 25, - separatorBuilder: (_, __) => const StreamUserListSeparator(), - itemBuilder: (_, __) => const StreamChannelListLoadingTile(), + const Center( + child: StreamScrollViewLoadingWidget(), ), errorBuilder: (context, error) => errorBuilder?.call(context, error) ?? Center( - child: StreamUserListErrorWidget( - onPressed: controller.refresh, + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingUsersError), + onRetryPressed: controller.refresh, ), ), ); } -/// A [StreamUserListTile] that can be used in a [ListView] to show a -/// loading tile while waiting for the [StreamUserListController] to load -/// more channels. -class StreamUserListLoadMoreIndicator extends StatelessWidget { - /// Creates a new instance of [StreamUserListLoadMoreIndicator]. - const StreamUserListLoadMoreIndicator({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => const SizedBox( - height: 16, - width: 16, - child: CircularProgressIndicator.adaptive(), - ); -} - -/// A [StreamUserListTile] that is used to display the error indicator when -/// loading more users fails. -class StreamUserListLoadMoreError extends StatelessWidget { - /// Creates a new instance of [StreamUserListLoadMoreError]. - const StreamUserListLoadMoreError({ - Key? key, - this.onTap, - }) : super(key: key); - - /// The callback to invoke when the user taps on the error indicator. - final GestureTapCallback? onTap; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - return InkWell( - onTap: onTap, - child: Container( - color: theme.colorTheme.textLowEmphasis.withOpacity(0.9), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - context.translations.loadingChannelsError, - style: theme.textTheme.body.copyWith( - color: Colors.white, - ), - ), - StreamSvgIcon.retry(color: Colors.white), - ], - ), - ), - ), - ); - } -} - /// A widget that is used to display a separator between /// [StreamUserListTile] items. class StreamUserListSeparator extends StatelessWidget { @@ -427,67 +388,3 @@ class StreamUserListSeparator extends StatelessWidget { ); } } - -/// A widget that is used to display an error screen -/// when [StreamUserListController] fails to load initial users. -class StreamUserListErrorWidget extends StatelessWidget { - /// Creates a new instance of [StreamUserListErrorWidget] widget. - const StreamUserListErrorWidget({ - Key? key, - this.onPressed, - }) : super(key: key); - - /// The callback to invoke when the user taps on the retry button. - final VoidCallback? onPressed; - - @override - Widget build(BuildContext context) => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - const WidgetSpan( - child: Padding( - padding: EdgeInsets.only(right: 2), - child: Icon(Icons.error_outline), - ), - ), - TextSpan(text: context.translations.loadingChannelsError), - ], - ), - style: Theme.of(context).textTheme.headline6, - ), - TextButton( - onPressed: onPressed, - child: Text(context.translations.retryLabel), - ), - ], - ); -} - -/// A widget that is used to display an empty state when -/// [StreamUserListController] loads zero users. -class StreamUserListEmptyWidget extends StatelessWidget { - /// Creates a new instance of [StreamUserListEmptyWidget] widget. - const StreamUserListEmptyWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final chatThemeData = StreamChatTheme.of(context); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StreamSvgIcon.message( - size: 148, - color: chatThemeData.colorTheme.disabled, - ), - const SizedBox(height: 28), - Text( - context.translations.letsStartChattingLabel, - style: chatThemeData.textTheme.headline, - ), - ], - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/v4/stream_list_view_indexed_widget_builder.dart b/packages/stream_chat_flutter/lib/src/v4/stream_list_view_indexed_widget_builder.dart deleted file mode 100644 index e235e36a..00000000 --- a/packages/stream_chat_flutter/lib/src/v4/stream_list_view_indexed_widget_builder.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/material.dart'; - -typedef StreamListViewIndexedWidgetBuilder - = Widget Function( - BuildContext context, - List items, - int index, - WidgetType defaultWidget, -); diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 0c2fc44b..5178fc01 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -48,23 +48,30 @@ export 'src/user_item.dart'; export 'src/user_list_view.dart'; export 'src/user_mention_tile.dart'; export 'src/utils.dart'; - -// v4 -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/message_input/countdown_button.dart'; export 'src/v4/message_input/stream_attachment_picker.dart'; export 'src/v4/message_input/stream_message_input.dart'; export 'src/v4/message_input/stream_message_send_button.dart'; export 'src/v4/message_input/stream_message_text_field.dart'; -export 'src/v4/message_search_list_view/stream_message_search_list_tile.dart'; -export 'src/v4/message_search_list_view/stream_message_search_list_view.dart'; +export 'src/v4/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; +export 'src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart'; + +// v4 +export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart'; +export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_tile.dart'; +export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart'; + +export 'src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart'; +export 'src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart'; +export 'src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart'; +export 'src/v4/scroll_view/user_scroll_view/stream_user_grid_tile.dart'; +export 'src/v4/scroll_view/user_scroll_view/stream_user_grid_tile.dart'; +export 'src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart'; +export 'src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart'; +export 'src/v4/scroll_view/user_scroll_view/stream_user_list_tile.dart'; +export 'src/v4/scroll_view/user_scroll_view/stream_user_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/v4/stream_list_view_indexed_widget_builder.dart'; export 'src/v4/stream_message_preview_text.dart'; -export 'src/v4/user_list_view/stream_user_list_tile.dart'; -export 'src/v4/user_list_view/stream_user_list_view.dart'; export 'src/visible_footnote.dart'; diff --git a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart similarity index 85% rename from packages/stream_chat_flutter_core/lib/src/message_input_controller.dart rename to packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart index 65c6d8d8..e5dbc898 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart @@ -8,46 +8,46 @@ import 'package:stream_chat_flutter_core/src/message_text_field_controller.dart' /// A value listenable builder related to a [Message]. /// -/// Pass in a [MessageInputController] as the `valueListenable`. -typedef MessageValueListenableBuilder = ValueListenableBuilder; +/// Pass in a [StreamMessageInputController] as the `valueListenable`. +typedef StreamMessageValueListenableBuilder = ValueListenableBuilder; /// Controller for storing and mutating a [Message] value. -class MessageInputController extends ValueNotifier { +class StreamMessageInputController extends ValueNotifier { /// Creates a controller for an editable text field. /// /// This constructor treats a null [message] argument as if it were the empty /// message. - factory MessageInputController({ + factory StreamMessageInputController({ Message? message, Map? textPatternStyle, }) => - MessageInputController._( + StreamMessageInputController._( initialMessage: message ?? Message(), textPatternStyle: textPatternStyle, ); /// Creates a controller for an editable text field from an initial [text]. - factory MessageInputController.fromText( + factory StreamMessageInputController.fromText( String? text, { Map? textPatternStyle, }) => - MessageInputController._( + StreamMessageInputController._( initialMessage: Message(text: text), textPatternStyle: textPatternStyle, ); /// Creates a controller for an editable text field from initial /// [attachments]. - factory MessageInputController.fromAttachments( + factory StreamMessageInputController.fromAttachments( List attachments, { Map? textPatternStyle, }) => - MessageInputController._( + StreamMessageInputController._( initialMessage: Message(attachments: attachments), textPatternStyle: textPatternStyle, ); - MessageInputController._({ + StreamMessageInputController._({ required Message initialMessage, Map? textPatternStyle, }) : _textEditingController = MessageTextFieldController.fromValue( @@ -245,7 +245,7 @@ class MessageInputController extends ValueNotifier { /// will all be empty. /// /// Calling this will notify all the listeners of this - /// [MessageInputController] that they need to update + /// [StreamMessageInputController] that they need to update /// (calls [notifyListeners]). For this reason, /// this method should only be called between frames, e.g. in response to user /// actions, not during the build, layout, or paint phases. @@ -272,36 +272,36 @@ class MessageInputController extends ValueNotifier { } /// A [RestorableProperty] that knows how to store and restore a -/// [MessageInputController]. +/// [StreamMessageInputController]. /// -/// The [MessageInputController] is accessible via the [value] getter. During -/// state restoration, the property will restore [MessageInputController.value] +/// The [StreamMessageInputController] is accessible via the [value] getter. During +/// state restoration, the property will restore [StreamMessageInputController.value] /// to the value it had when the restoration data it is getting restored from /// was collected. -class RestorableMessageInputController - extends RestorableChangeNotifier { - /// Creates a [RestorableMessageInputController]. +class StreamRestorableMessageInputController + extends RestorableChangeNotifier { + /// Creates a [StreamRestorableMessageInputController]. /// /// This constructor creates a default [Message] when no `message` argument /// is supplied. - RestorableMessageInputController({Message? message}) + StreamRestorableMessageInputController({Message? message}) : _initialValue = message ?? Message(); - /// Creates a [RestorableMessageInputController] from an initial + /// Creates a [StreamRestorableMessageInputController] from an initial /// [text] value. - factory RestorableMessageInputController.fromText(String? text) => - RestorableMessageInputController(message: Message(text: text)); + factory StreamRestorableMessageInputController.fromText(String? text) => + StreamRestorableMessageInputController(message: Message(text: text)); final Message _initialValue; @override - MessageInputController createDefaultValue() => - MessageInputController(message: _initialValue); + StreamMessageInputController createDefaultValue() => + StreamMessageInputController(message: _initialValue); @override - MessageInputController fromPrimitives(Object? data) { + StreamMessageInputController fromPrimitives(Object? data) { final message = Message.fromJson(json.decode(data! as String)); - return MessageInputController(message: message); + return StreamMessageInputController(message: message); } @override diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index cddae515..a5004f32 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -7,7 +7,6 @@ export 'src/better_stream_builder.dart'; export 'src/channel_list_core.dart' hide ChannelListCoreState; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; -export 'src/message_input_controller.dart'; export 'src/message_list_core.dart' hide MessageListCoreState; export 'src/message_search_bloc.dart'; export 'src/message_search_list_core.dart' hide MessageSearchListCoreState; @@ -18,6 +17,7 @@ export 'src/stream_channel.dart'; export 'src/stream_channel_list_controller.dart'; export 'src/stream_channel_list_event_handler.dart'; export 'src/stream_chat_core.dart'; +export 'src/stream_message_input_controller.dart'; export 'src/stream_message_search_list_controller.dart'; export 'src/stream_user_list_controller.dart'; export 'src/typedef.dart'; From 2142577a7d949f5d49e7c2cf08c0c6b3a90be87a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 29 Apr 2022 14:33:25 +0530 Subject: [PATCH 2/6] chore(core): remove stream_channel_list_loading_tile.dart Signed-off-by: xsahil03x --- .../stream_channel_list_loading_tile.dart | 96 ------------------- .../lib/stream_chat_flutter.dart | 1 - 2 files changed, 97 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart deleted file mode 100644 index e3f7f374..00000000 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart +++ /dev/null @@ -1,96 +0,0 @@ -// import 'package:flutter/material.dart'; -// import 'package:shimmer/shimmer.dart'; -// import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; -// -// /// A shimmering list item which shows a loading effect. -// /// -// /// This is used by [StreamChannelListView] to show a loading effect while -// /// the list is being loaded. -// class StreamChannelListLoadingTile extends StatelessWidget { -// /// Creates a new instance of [StreamChannelListLoadingTile] widget. -// const StreamChannelListLoadingTile({ -// Key? key, -// this.visualDensity = VisualDensity.standard, -// this.contentPadding = const EdgeInsets.symmetric(horizontal: 8), -// }) : super(key: key); -// -// /// 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], [errorTitle], [subtitle], -// /// and [trailing] widgets. -// /// -// /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. -// final EdgeInsetsGeometry contentPadding; -// -// @override -// Widget build(BuildContext context) { -// final colorTheme = StreamChatTheme.of(context).colorTheme; -// -// final leading = Container( -// height: 49, -// width: 49, -// decoration: BoxDecoration( -// color: colorTheme.barsBg, -// shape: BoxShape.circle, -// ), -// ); -// -// final title = Container( -// height: 16, -// width: 66, -// decoration: BoxDecoration( -// color: colorTheme.barsBg, -// borderRadius: BorderRadius.circular(8), -// ), -// ); -// -// final subtitle = Row( -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// Expanded( -// child: Align( -// alignment: Alignment.centerLeft, -// child: Container( -// height: 16, -// decoration: BoxDecoration( -// color: colorTheme.barsBg, -// borderRadius: BorderRadius.circular(8), -// ), -// ), -// ), -// ), -// const SizedBox(width: 8), -// Container( -// height: 16, -// width: 50, -// decoration: BoxDecoration( -// color: colorTheme.barsBg, -// borderRadius: BorderRadius.circular(8), -// ), -// ), -// ], -// ); -// -// return Shimmer.fromColors( -// baseColor: colorTheme.disabled, -// highlightColor: colorTheme.inputBg, -// child: ListTile( -// leading: leading, -// title: title, -// subtitle: subtitle, -// visualDensity: visualDensity, -// contentPadding: contentPadding, -// ), -// ); -// } -// } diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 5178fc01..c9a0d359 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -57,7 +57,6 @@ export 'src/v4/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; export 'src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart'; // v4 -export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_loading_tile.dart'; export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_tile.dart'; export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart'; From 5e965746297074a3c07c995b226c20893368d665 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 29 Apr 2022 17:56:32 +0530 Subject: [PATCH 3/6] feat(ui): add stream_message_search_grid_view.dart Signed-off-by: xsahil03x --- .../stream_message_search_grid_view.dart | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart new file mode 100644 index 00000000..27af9937 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart @@ -0,0 +1,366 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; + +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_loading_widget.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Default grid delegate for [StreamMessageSearchGridView]. +const defaultMessageSearchGridViewDelegate = + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); + +/// Signature for the item builder that creates the children of the +/// [StreamMessageSearchGridView]. +typedef StreamMessageSearchGridViewIndexedWidgetBuilder + = PagedValueScrollViewIndexedWidgetBuilder; + +/// A [GridView] that shows a grid of [GetMessageResponse]s, +/// it uses [StreamMessageSearchGridTile] as a default item. +/// +/// Example: +/// +/// ```dart +/// StreamMessageSearchGridView( +/// controller: controller, +/// onMessageTap: (user) { +/// // Handle user tap event +/// }, +/// onMessageLongPress: (user) { +/// // Handle user long press event +/// }, +/// ) +/// ``` +/// +/// See also: +/// * [StreamUserListTile] +/// * [StreamUserListController] +class StreamMessageSearchGridView extends StatelessWidget { + /// Creates a new instance of [StreamMessageSearchGridView]. + const StreamMessageSearchGridView({ + Key? key, + required this.controller, + required this.itemBuilder, + this.gridDelegate = defaultMessageSearchGridViewDelegate, + this.emptyBuilder, + this.loadMoreErrorBuilder, + this.loadMoreIndicatorBuilder, + this.loadingBuilder, + this.errorBuilder, + this.loadMoreTriggerIndex = 3, + this.scrollDirection = Axis.vertical, + this.reverse = false, + this.scrollController, + this.primary, + this.physics, + this.shrinkWrap = false, + this.padding, + this.addAutomaticKeepAlives = true, + this.addRepaintBoundaries = true, + this.addSemanticIndexes = true, + this.cacheExtent, + this.semanticChildCount, + this.dragStartBehavior = DragStartBehavior.start, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.restorationId, + this.clipBehavior = Clip.hardEdge, + }) : super(key: key); + + /// The [StreamUserListController] used to control the grid of users. + final StreamMessageSearchListController controller; + + /// A delegate that controls the layout of the children within + /// the [PagedValueGridView]. + final SliverGridDelegate gridDelegate; + + /// A builder that is called to build items in the [PagedValueGridView]. + /// + /// The `value` parameter is the [V] at this position in the grid. + final StreamMessageSearchGridViewIndexedWidgetBuilder itemBuilder; + + /// A builder that is called to build the empty state of the grid. + final WidgetBuilder? emptyBuilder; + + /// A builder that is called to build the load more error state of the grid. + final PagedValueScrollViewLoadMoreErrorBuilder? loadMoreErrorBuilder; + + /// A builder that is called to build the load more indicator of the grid. + final WidgetBuilder? loadMoreIndicatorBuilder; + + /// A builder that is called to build the loading state of the grid. + final WidgetBuilder? loadingBuilder; + + /// A builder that is called to build the error state of the grid. + final Widget Function(BuildContext, StreamChatError)? errorBuilder; + + /// The index to take into account when triggering [controller.loadMore]. + final int loadMoreTriggerIndex; + + /// {@template flutter.widgets.scroll_view.scrollDirection} + /// The axis along which the scroll view scrolls. + /// + /// Defaults to [Axis.vertical]. + /// {@endtemplate} + final Axis scrollDirection; + + /// {@template flutter.widgets.scroll_view.reverse} + /// Whether the scroll view scrolls in the reading direction. + /// + /// For example, if the reading direction is left-to-right and + /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from + /// left to right when [reverse] is false and from right to left when + /// [reverse] is true. + /// + /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view + /// scrolls from top to bottom when [reverse] is false and from bottom to top + /// when [reverse] is true. + /// + /// Defaults to false. + /// {@endtemplate} + final bool reverse; + + /// {@template flutter.widgets.scroll_view.controller} + /// An object that can be used to control the position to which this scroll + /// view is scrolled. + /// + /// Must be null if [primary] is true. + /// + /// A [ScrollController] serves several purposes. It can be used to control + /// the initial scroll position (see [ScrollController.initialScrollOffset]). + /// It can be used to control whether the scroll view should automatically + /// save and restore its scroll position in the [PageStorage] (see + /// [ScrollController.keepScrollOffset]). It can be used to read the current + /// scroll position (see [ScrollController.offset]), or change it (see + /// [ScrollController.animateTo]). + /// {@endtemplate} + final ScrollController? scrollController; + + /// {@template flutter.widgets.scroll_view.primary} + /// Whether this is the primary scroll view associated with the parent + /// [PrimaryScrollController]. + /// + /// When this is true, the scroll view is scrollable even if it does not have + /// sufficient content to actually scroll. Otherwise, by default the user can + /// 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. + /// + /// On iOS, this also identifies the scroll view that will scroll to top in + /// response to a tap in the status bar. + /// {@endtemplate} + /// + /// Defaults to true when [scrollDirection] is [Axis.vertical] and + /// [controller] is null. + final bool? primary; + + /// {@template flutter.widgets.scroll_view.physics} + /// How the scroll view should respond to user input. + /// + /// For example, determines how the scroll view continues to animate after the + /// user stops dragging the scroll view. + /// + /// Defaults to matching platform conventions. Furthermore, if [primary] is + /// false, then the user cannot scroll if there is insufficient content to + /// scroll, while if [primary] is true, they can always attempt to scroll. + /// + /// To force the scroll view to always be scrollable even if there is + /// insufficient content, as if [primary] was true but without necessarily + /// setting it to true, provide an [AlwaysScrollableScrollPhysics] physics + /// object, as in: + /// + /// ```dart + /// physics: const AlwaysScrollableScrollPhysics(), + /// ``` + /// + /// To force the scroll view to use the default platform conventions and not + /// be scrollable if there is insufficient content, regardless of the value of + /// [primary], provide an explicit [ScrollPhysics] object, as in: + /// + /// ```dart + /// physics: const ScrollPhysics(), + /// ``` + /// + /// The physics can be changed dynamically (by providing a new object in a + /// subsequent build), but new physics will only take effect if the _class_ of + /// the provided object changes. Merely constructing a new instance with a + /// different configuration is insufficient to cause the physics to be + /// reapplied. (This is because the final object used is generated + /// dynamically, which can be relatively expensive, and it would be + /// inefficient to speculatively create this object each frame to see if the + /// physics should be updated.) + /// {@endtemplate} + /// + /// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the + /// [ScrollPhysics] provided by that behavior will take precedence after + /// [physics]. + final ScrollPhysics? physics; + + /// {@template flutter.widgets.scroll_view.shrinkWrap} + /// Whether the extent of the scroll view in the [scrollDirection] should be + /// determined by the contents being viewed. + /// + /// If the scroll view does not shrink wrap, then the scroll view will expand + /// to the maximum allowed size in the [scrollDirection]. If the scroll view + /// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must + /// be true. + /// + /// Shrink wrapping the content of the scroll view is significantly more + /// expensive than expanding to the maximum allowed size because the content + /// can expand and contract during scrolling, which means the size of the + /// scroll view needs to be recomputed whenever the scroll position changes. + /// + /// Defaults to false. + /// {@endtemplate} + final bool shrinkWrap; + + /// The amount of space by which to inset the children. + final EdgeInsetsGeometry? padding; + + /// Whether to wrap each child in an [AutomaticKeepAlive]. + /// + /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive] + /// widgets so that children can use [KeepAliveNotification]s to preserve + /// their state when they would otherwise be garbage collected off-screen. + /// + /// This feature (and [addRepaintBoundaries]) must be disabled if the children + /// are going to manually maintain their [KeepAlive] state. It may also be + /// more efficient to disable this feature if it is known ahead of time that + /// none of the children will ever try to keep themselves alive. + /// + /// Defaults to true. + final bool addAutomaticKeepAlives; + + /// Whether to wrap each child in a [RepaintBoundary]. + /// + /// Typically, children in a scrolling container are wrapped in repaint + /// boundaries so that they do not need to be repainted as the list scrolls. + /// If the children are easy to repaint (e.g., solid color blocks or a short + /// snippet of text), it might be more efficient to not add a repaint boundary + /// and simply repaint the children during scrolling. + /// + /// Defaults to true. + final bool addRepaintBoundaries; + + /// Whether to wrap each child in an [IndexedSemantics]. + /// + /// Typically, children in a scrolling container must be annotated with a + /// semantic index in order to generate the correct accessibility + /// announcements. This should only be set to false if the indexes have + /// already been provided by an [IndexedSemantics] widget. + /// + /// Defaults to true. + /// + /// See also: + /// + /// * [IndexedSemantics], for an explanation of how to manually + /// provide semantic indexes. + final bool addSemanticIndexes; + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + final double? cacheExtent; + + /// The number of children that will contribute semantic information. + /// + /// Some subtypes of [ScrollView] can infer this value automatically. For + /// example [ListView] will use the number of widgets in the child list, + /// while the [ListView.separated] constructor will use half that amount. + /// + /// For [CustomScrollView] and other types which do not receive a builder + /// or list of widgets, the child count must be explicitly provided. If the + /// number is unknown or unbounded this should be left unset or set to null. + /// + /// See also: + /// + /// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. + final int? semanticChildCount; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior} + /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will + /// dismiss the keyboard automatically. + /// {@endtemplate} + final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; + + /// {@macro flutter.widgets.scrollable.restorationId} + final String? restorationId; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + @override + Widget build(BuildContext context) { + return PagedValueGridView( + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + scrollController: scrollController, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + cacheExtent: cacheExtent, + semanticChildCount: semanticChildCount, + dragStartBehavior: dragStartBehavior, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + clipBehavior: clipBehavior, + gridDelegate: gridDelegate, + itemBuilder: itemBuilder, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon.message( + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.emptyMessagesText, + style: chatThemeData.textTheme.headline, + ), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => + StreamScrollViewLoadMoreError.grid( + onTap: controller.retry, + error: Text(context.translations.loadingMessagesError), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), + ), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + onRetryPressed: controller.refresh, + ), + ), + ); + } +} From 7589e39323b1cb01cf4af54661bda8f9be3f6e68 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 29 Apr 2022 17:57:15 +0530 Subject: [PATCH 4/6] chore(ui): export StreamMessageSearchGridView Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/stream_chat_flutter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index c9a0d359..736b9dce 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -59,7 +59,7 @@ export 'src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart'; // v4 export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_tile.dart'; export 'src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart'; - +export 'src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart'; export 'src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart'; export 'src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart'; export 'src/v4/scroll_view/stream_scroll_view_indexed_widget_builder.dart'; From 264253feaec5132ae61d891682f5016b25be688d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 29 Apr 2022 18:00:34 +0530 Subject: [PATCH 5/6] chore(ui): update doc Signed-off-by: xsahil03x --- .../stream_message_search_grid_view.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart index 27af9937..502b6f54 100644 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart @@ -26,11 +26,8 @@ typedef StreamMessageSearchGridViewIndexedWidgetBuilder /// ```dart /// StreamMessageSearchGridView( /// controller: controller, -/// onMessageTap: (user) { -/// // Handle user tap event -/// }, -/// onMessageLongPress: (user) { -/// // Handle user long press event +/// itemBuilder: (context, messageResponses, index) { +/// return GridTile(message: messageResponses[index]); /// }, /// ) /// ``` From 51a3711613179e6f6e312b1a39a31bcefcedf821 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 29 Apr 2022 18:14:01 +0530 Subject: [PATCH 6/6] chore(ui): remove generics from grid views, update docs Signed-off-by: xsahil03x --- .../channel_scroll_view/stream_channel_grid_view.dart | 4 ++-- .../channel_scroll_view/stream_channel_list_view.dart | 4 ---- .../stream_message_search_grid_view.dart | 4 ++-- .../stream_message_search_list_view.dart | 4 ---- .../scroll_view/user_scroll_view/stream_user_grid_view.dart | 4 +--- .../scroll_view/user_scroll_view/stream_user_list_view.dart | 4 ---- 6 files changed, 5 insertions(+), 19 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart index af5e03ce..27b71979 100644 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_grid_view.dart @@ -41,7 +41,7 @@ typedef StreamChannelGridViewIndexedWidgetBuilder /// See also: /// * [StreamChannelGridTile] /// * [StreamChannelListController] -class StreamChannelGridView extends StatelessWidget { +class StreamChannelGridView extends StatelessWidget { /// Creates a new instance of [StreamChannelGridView]. const StreamChannelGridView({ Key? key, @@ -83,7 +83,7 @@ class StreamChannelGridView extends StatelessWidget { /// A builder that is called to build items in the [PagedValueGridView]. /// - /// The `value` parameter is the [V] at this position in the grid. + /// The `value` parameter is the [Channel] at this position in the grid. final StreamChannelGridViewIndexedWidgetBuilder? itemBuilder; /// A builder that is called to build the empty state of the grid. diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart index 0feb84c6..23753644 100644 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -83,10 +83,6 @@ class StreamChannelListView extends StatelessWidget { final StreamChannelListController controller; /// A builder that is called to build items in the [ListView]. - /// - /// The `channel` parameter is the [Channel] at this position in the list - /// and the `defaultWidget` is the default widget used - /// i.e: [StreamChannelListTile]. final StreamChannelListViewIndexedWidgetBuilder? itemBuilder; /// A builder that is called to build the list separator. diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart index 502b6f54..142d4a7a 100644 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart @@ -35,7 +35,7 @@ typedef StreamMessageSearchGridViewIndexedWidgetBuilder /// See also: /// * [StreamUserListTile] /// * [StreamUserListController] -class StreamMessageSearchGridView extends StatelessWidget { +class StreamMessageSearchGridView extends StatelessWidget { /// Creates a new instance of [StreamMessageSearchGridView]. const StreamMessageSearchGridView({ Key? key, @@ -75,7 +75,7 @@ class StreamMessageSearchGridView extends StatelessWidget { /// A builder that is called to build items in the [PagedValueGridView]. /// - /// The `value` parameter is the [V] at this position in the grid. + /// The `value` parameter is the [GetMessageBuilder] at this position in the grid. final StreamMessageSearchGridViewIndexedWidgetBuilder itemBuilder; /// A builder that is called to build the empty state of the grid. diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart index e6b526d7..3b23694b 100644 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart @@ -80,10 +80,6 @@ class StreamMessageSearchListView extends StatelessWidget { final StreamMessageSearchListController controller; /// A builder that is called to build items in the [ListView]. - /// - /// The `messageResponse` parameter is the [GetMessageResponse] at this - /// position in the list and the `defaultWidget` is the default widget used - /// i.e: [StreamMessageSearchListTile]. final StreamMessageSearchListViewIndexedWidgetBuilder? itemBuilder; /// A builder that is called to build the list separator. diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart index 24765a6a..3b900971 100644 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_grid_view.dart @@ -37,7 +37,7 @@ typedef StreamUserGridViewIndexedWidgetBuilder /// See also: /// * [StreamUserListTile] /// * [StreamUserListController] -class StreamUserGridView extends StatelessWidget { +class StreamUserGridView extends StatelessWidget { /// Creates a new instance of [StreamUserGridView]. const StreamUserGridView({ Key? key, @@ -78,8 +78,6 @@ class StreamUserGridView extends StatelessWidget { final SliverGridDelegate gridDelegate; /// A builder that is called to build items in the [PagedValueGridView]. - /// - /// The `value` parameter is the [V] at this position in the grid. final StreamUserGridViewIndexedWidgetBuilder? itemBuilder; /// A builder that is called to build the empty state of the grid. diff --git a/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_view.dart b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_view.dart index 1bca6e84..92915811 100644 --- a/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/v4/scroll_view/user_scroll_view/stream_user_list_view.dart @@ -78,10 +78,6 @@ class StreamUserListView extends StatelessWidget { final StreamUserListController controller; /// A builder that is called to build items in the [ListView]. - /// - /// The `user` parameter is the [User] at this position in the list - /// and the `defaultWidget` is the default widget used - /// i.e: [StreamUserListTile]. final StreamUserListViewIndexedWidgetBuilder? itemBuilder; /// A builder that is called to build the list separator.