Merge pull request #1109 from GetStream/feat/grid-views
feat(core, ui): add grid views, minor refactoring
This commit is contained in:
@@ -145,7 +145,7 @@ class ThreadPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
StreamMessageInput(
|
||||
messageInputController: MessageInputController(
|
||||
messageInputController: StreamMessageInputController(
|
||||
message: Message(parentId: parent!.id),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -184,7 +184,7 @@ class ThreadPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
StreamMessageInput(
|
||||
messageInputController: MessageInputController(
|
||||
messageInputController: StreamMessageInputController(
|
||||
message: Message(parentId: parent!.id),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -639,7 +639,7 @@ class _StreamMessageActionsModalState extends State<StreamMessageActionsModal> {
|
||||
widget.editMessageInputBuilder!(context, widget.message)
|
||||
else
|
||||
StreamMessageInput(
|
||||
messageInputController: MessageInputController(
|
||||
messageInputController: StreamMessageInputController(
|
||||
message: widget.message,
|
||||
),
|
||||
preMessageSending: (m) {
|
||||
|
||||
-96
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Widget> actions;
|
||||
@@ -375,14 +375,14 @@ class StreamMessageInputState extends State<StreamMessageInput>
|
||||
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<StreamMessageInput>
|
||||
),
|
||||
);
|
||||
}
|
||||
return MessageValueListenableBuilder(
|
||||
return StreamMessageValueListenableBuilder(
|
||||
valueListenable: _effectiveController,
|
||||
builder: (context, value, _) {
|
||||
Widget child = DecoratedBox(
|
||||
|
||||
+5
-5
@@ -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<StreamMessageTextField>
|
||||
with RestorationMixin<StreamMessageTextField> {
|
||||
RestorableMessageInputController? _controller;
|
||||
StreamRestorableMessageInputController? _controller;
|
||||
|
||||
MessageInputController get _effectiveController =>
|
||||
StreamMessageInputController get _effectiveController =>
|
||||
widget.controller ?? _controller!.value;
|
||||
|
||||
@override
|
||||
@@ -671,7 +671,7 @@ class _StreamMessageTextFieldState extends State<StreamMessageTextField>
|
||||
|
||||
void _createLocalController([Message? message]) {
|
||||
assert(_controller == null, '');
|
||||
_controller = RestorableMessageInputController(message: message);
|
||||
_controller = StreamRestorableMessageInputController(message: message);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
+91
@@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+399
@@ -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<Channel, StreamChannelGridTile>;
|
||||
|
||||
/// 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 [Channel] 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<int, Channel>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-1
@@ -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.
|
||||
+38
-107
@@ -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<Channel, StreamChannelListTile>;
|
||||
= StreamScrollViewIndexedWidgetBuilder<Channel, StreamChannelListTile>;
|
||||
|
||||
/// A [ListView] that shows a list of [Channel]s,
|
||||
/// it uses [StreamChannelListTile] as a default item.
|
||||
@@ -78,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.
|
||||
@@ -328,96 +329,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 +428,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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
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<GetMessageResponse>;
|
||||
|
||||
/// A [GridView] that shows a grid of [GetMessageResponse]s,
|
||||
/// it uses [StreamMessageSearchGridTile] as a default item.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```dart
|
||||
/// StreamMessageSearchGridView(
|
||||
/// controller: controller,
|
||||
/// itemBuilder: (context, messageResponses, index) {
|
||||
/// return GridTile(message: messageResponses[index]);
|
||||
/// },
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// 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 [GetMessageBuilder] 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<String, GetMessageResponse>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -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({
|
||||
+38
-146
@@ -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<GetMessageResponse,
|
||||
= StreamScrollViewIndexedWidgetBuilder<GetMessageResponse,
|
||||
StreamMessageSearchListTile>;
|
||||
|
||||
/// A [ListView] that shows a list of [GetMessageResponse]s,
|
||||
@@ -75,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.
|
||||
@@ -312,7 +313,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 +324,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 +389,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: <Widget>[
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+61
@@ -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,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+92
@@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
@@ -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<ItemType,
|
||||
WidgetType extends Widget>
|
||||
= Widget Function(
|
||||
BuildContext context,
|
||||
List<ItemType> items,
|
||||
int index,
|
||||
WidgetType defaultWidget,
|
||||
);
|
||||
+110
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+25
@@ -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(),
|
||||
);
|
||||
}
|
||||
+24
@@ -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(),
|
||||
);
|
||||
}
|
||||
+102
@@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
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<User, StreamUserGridTile>;
|
||||
|
||||
/// 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].
|
||||
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<int, User>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
-142
@@ -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<User, StreamUserListTile>;
|
||||
= StreamScrollViewIndexedWidgetBuilder<User, StreamUserListTile>;
|
||||
|
||||
/// A [ListView] that shows a list of [User]s,
|
||||
/// it uses [StreamUserListTile] as a default item.
|
||||
@@ -73,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.
|
||||
@@ -322,96 +323,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 +384,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: <Widget>[
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef StreamListViewIndexedWidgetBuilder<ItemType, WidgetType extends Widget>
|
||||
= Widget Function(
|
||||
BuildContext context,
|
||||
List<ItemType> items,
|
||||
int index,
|
||||
WidgetType defaultWidget,
|
||||
);
|
||||
@@ -48,23 +48,29 @@ 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_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';
|
||||
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';
|
||||
|
||||
+25
-25
@@ -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<Message>;
|
||||
/// Pass in a [StreamMessageInputController] as the `valueListenable`.
|
||||
typedef StreamMessageValueListenableBuilder = ValueListenableBuilder<Message>;
|
||||
|
||||
/// Controller for storing and mutating a [Message] value.
|
||||
class MessageInputController extends ValueNotifier<Message> {
|
||||
class StreamMessageInputController extends ValueNotifier<Message> {
|
||||
/// 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<RegExp, TextStyleBuilder>? 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<RegExp, TextStyleBuilder>? 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<Attachment> attachments, {
|
||||
Map<RegExp, TextStyleBuilder>? textPatternStyle,
|
||||
}) =>
|
||||
MessageInputController._(
|
||||
StreamMessageInputController._(
|
||||
initialMessage: Message(attachments: attachments),
|
||||
textPatternStyle: textPatternStyle,
|
||||
);
|
||||
|
||||
MessageInputController._({
|
||||
StreamMessageInputController._({
|
||||
required Message initialMessage,
|
||||
Map<RegExp, TextStyleBuilder>? textPatternStyle,
|
||||
}) : _textEditingController = MessageTextFieldController.fromValue(
|
||||
@@ -245,7 +245,7 @@ class MessageInputController extends ValueNotifier<Message> {
|
||||
/// 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<Message> {
|
||||
}
|
||||
|
||||
/// 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<MessageInputController> {
|
||||
/// Creates a [RestorableMessageInputController].
|
||||
class StreamRestorableMessageInputController
|
||||
extends RestorableChangeNotifier<StreamMessageInputController> {
|
||||
/// 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
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user