Merge pull request #616 from GetStream/feat/message-input-action-builders

feat(ui): add support for `attachmentButtonBuilder`, `commandButtonBuilder`
This commit is contained in:
Sahil Kumar
2021-08-13 15:01:55 +05:30
committed by GitHub
4 changed files with 205 additions and 102 deletions
+84 -58
View File
@@ -2,21 +2,35 @@
✅ Added ✅ Added
- [#516](https://github.com/GetStream/stream-chat-flutter/issues/516): Added `StreamChatThemeData.placeholderUserImage` for - [#516](https://github.com/GetStream/stream-chat-flutter/issues/516):
building a widget when the `UserAvatar` image is loading Added `StreamChatThemeData.placeholderUserImage` for building a widget when the `UserAvatar` image
is loading
- Added a `backgroundColor` property to the following widgets: - Added a `backgroundColor` property to the following widgets:
- `ChannelHeader` - `ChannelHeader`
- `ChannelListHeader` - `ChannelListHeader`
- `GalleryHeader` - `GalleryHeader`
- `GalleryFooter` - `GalleryFooter`
- `ThreadHeader` - `ThreadHeader`
- Added `MessageInput.attachmentButtonBuilder` and `MessageInput.commandButtonBuilder` for more
customizations.
```dart
typedef ActionButtonBuilder = Widget Function(
BuildContext context,
IconButton defaultActionButton,
);
```
> **_NOTE:_** The last parameter is the default `ActionButton`
You can call `.copyWith` to customize just a subset of properties.
🔄 Changed 🔄 Changed
Theming has been upgraded! Most theme classes now have `InheritedTheme` classes Theming has been upgraded! Most theme classes now have `InheritedTheme` classes associated with
associated with them, and have been upgraded with some goodies like `lerp` functions. them, and have been upgraded with some goodies like `lerp` functions. Here's the full naming
Here's the full naming breakdown: breakdown:
* `AvatarTheme` is now `AvatarThemeData` * `AvatarTheme` is now `AvatarThemeData`
* `ChannelHeaderTheme` is now `ChannelHeaderThemeData` * `ChannelHeaderTheme` is now `ChannelHeaderThemeData`
* `ChannelListHeaderTheme` is now `ChannelListHeaderThemeData` * `ChannelListHeaderTheme` is now `ChannelListHeaderThemeData`
@@ -32,7 +46,8 @@ Here's the full naming breakdown:
🐞 Fixed 🐞 Fixed
- [#590](https://github.com/GetStream/stream-chat-flutter/issues/590): livestream use case, no members when sending message - [#590](https://github.com/GetStream/stream-chat-flutter/issues/590): livestream use case, no
members when sending message
## 2.1.1 ## 2.1.1
@@ -50,7 +65,8 @@ Here's the full naming breakdown:
🔄 Changed 🔄 Changed
- `StreamChat.of(context).user` is now deprecated in favor of `StreamChat.of(context).currentUser`. - `StreamChat.of(context).user` is now deprecated in favor of `StreamChat.of(context).currentUser`.
- `StreamChat.of(context).userStream` is now deprecated in favor of `StreamChat.of(context).currentUserStream`. - `StreamChat.of(context).userStream` is now deprecated in favor
of `StreamChat.of(context).currentUserStream`.
🐞 Fixed 🐞 Fixed
@@ -64,17 +80,17 @@ Here's the full naming breakdown:
- Renamed `ChannelImage` to `ChannelAvatar` - Renamed `ChannelImage` to `ChannelAvatar`
- Updated `StreamChatThemeData.reactionIcons` to accept custom builder - Updated `StreamChatThemeData.reactionIcons` to accept custom builder
- Renamed `ColorTheme` properties to reflect the purpose of the colors - Renamed `ColorTheme` properties to reflect the purpose of the colors
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis` - `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
- `ColorTheme.greyWhisper` -> `ColorTheme.borders` - `ColorTheme.greyWhisper` -> `ColorTheme.borders`
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg` - `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
- `ColorTheme.white` -> `ColorTheme.barsBg` - `ColorTheme.white` -> `ColorTheme.barsBg`
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg` - `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
- `ColorTheme.accentRed` -> `ColorTheme.accentError` - `ColorTheme.accentRed` -> `ColorTheme.accentError`
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
- `ChannelListCore` options property is removed in favor of individual properties - `ChannelListCore` options property is removed in favor of individual properties
- `options.state` -> bool state - `options.state` -> bool state
@@ -95,7 +111,7 @@ typedef MessageBuilder = Widget Function(
); );
``` ```
the last parameter is the default `MessageWidget` > **_NOTE:_** the last parameter is the default `MessageWidget`
You can call `.copyWith` to customize just a subset of properties You can call `.copyWith` to customize just a subset of properties
@@ -103,7 +119,8 @@ You can call `.copyWith` to customize just a subset of properties
- Added video compress options (frame and quality) to `MessageInput` - Added video compress options (frame and quality) to `MessageInput`
- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads - TypingIndicator now has a property called `parentId` to show typing indicator specific to threads
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer - [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView
header/footer
- `MessageWidget` accepts a `userAvatarBuilder` - `MessageWidget` accepts a `userAvatarBuilder`
- Added pinMessage ui support - Added pinMessage ui support
- Added `MessageListView.threadSeparatorBuilder` property - Added `MessageListView.threadSeparatorBuilder` property
@@ -112,10 +129,12 @@ You can call `.copyWith` to customize just a subset of properties
🐞 Fixed 🐞 Fixed
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing - [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text
message box when editing message
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case - Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload use case
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without
a reload
- `MessageListView` not rendering if the user is not a member of the channel - `MessageListView` not rendering if the user is not a member of the channel
- Fix `MessageInput` overflow when there are no actions - Fix `MessageInput` overflow when there are no actions
- Minor fixes and improvements - Minor fixes and improvements
@@ -125,17 +144,17 @@ You can call `.copyWith` to customize just a subset of properties
🛑️ Breaking Changes from `2.0.0-nullsafety.8` 🛑️ Breaking Changes from `2.0.0-nullsafety.8`
- Renamed `ColorTheme` properties to reflect the purpose of the colors - Renamed `ColorTheme` properties to reflect the purpose of the colors
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis` - `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
- `ColorTheme.greyWhisper` -> `ColorTheme.borders` - `ColorTheme.greyWhisper` -> `ColorTheme.borders`
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg` - `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
- `ColorTheme.white` -> `ColorTheme.barsBg` - `ColorTheme.white` -> `ColorTheme.barsBg`
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg` - `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
- `ColorTheme.accentRed` -> `ColorTheme.accentError` - `ColorTheme.accentRed` -> `ColorTheme.accentError`
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
✅ Added ✅ Added
@@ -162,21 +181,24 @@ typedef MessageBuilder = Widget Function(
); );
``` ```
the last parameter is the default `MessageWidget` > **_NOTE:_** The last parameter is the default `MessageWidget`
You can call `.copyWith` to customize just a subset of properties You can call `.copyWith` to customize just a subset of properties.
✅ Added ✅ Added
- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads - TypingIndicator now has a property called `parentId` to show typing indicator specific to threads
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer - [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView
header/footer
- `MessageWidget` accepts a `userAvatarBuilder` - `MessageWidget` accepts a `userAvatarBuilder`
🐞 Fixed 🐞 Fixed
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing - [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text
message box when editing message
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case - Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload use case
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without
a reload
- `MessageListView` not rendering if the user is not a member of the channel - `MessageListView` not rendering if the user is not a member of the channel
## 2.0.0-nullsafety.7 ## 2.0.0-nullsafety.7
@@ -246,7 +268,8 @@ You can call `.copyWith` to customize just a subset of properties
- Show error messages as system and keep them in the message input - Show error messages as system and keep them in the message input
- Remove notification badge logic - Remove notification badge logic
- Use shimmer while loading images - Use shimmer while loading images
- Polished `StreamChatTheme` adding more options and a new `MessageInputTheme` dedicated to `MessageInput` - Polished `StreamChatTheme` adding more options and a new `MessageInputTheme` dedicated
to `MessageInput`
- Add possibility to specify custom message actions using `MessageWidget.customActions` - Add possibility to specify custom message actions using `MessageWidget.customActions`
- Added `MessageListView.onAttachmentTap` callback - Added `MessageListView.onAttachmentTap` callback
- Fixed message newline issue - Fixed message newline issue
@@ -303,7 +326,8 @@ You can call `.copyWith` to customize just a subset of properties
- Improved api documentation - Improved api documentation
- Updated `stream_chat` dependency to `^1.0.0-beta` - Updated `stream_chat` dependency to `^1.0.0-beta`
- Extracted sample app into dedicated [repo](https://github.com/GetStream/flutter-samples) - Extracted sample app into dedicated [repo](https://github.com/GetStream/flutter-samples)
- Reimplemented existing widgets using [stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core) - Reimplemented existing widgets
using [stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core)
## 0.2.21 ## 0.2.21
@@ -320,8 +344,8 @@ You can call `.copyWith` to customize just a subset of properties
## 0.2.20+2 ## 0.2.20+2
- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the list when a new message - Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the
arrives list when a new message arrives
## 0.2.20+1 ## 0.2.20+1
@@ -355,7 +379,8 @@ You can call `.copyWith` to customize just a subset of properties
## 0.2.16 ## 0.2.16
- Do not wrap channel preview builder. Users will have to implement they're custom onTap/onLongPress implementation - Do not wrap channel preview builder. Users will have to implement they're custom onTap/onLongPress
implementation
- Make public autofocus field of the TextField of message_input - Make public autofocus field of the TextField of message_input
## 0.2.15 ## 0.2.15
@@ -540,10 +565,11 @@ You can call `.copyWith` to customize just a subset of properties
## 0.2.1-alpha+1 ## 0.2.1-alpha+1
- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have the `StreamChat` widget - Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have
as ancestor in every route. Now the recommended way to add `StreamChat` to your app is using the `builder` property of the `StreamChat` widget as ancestor in every route. Now the recommended way to add `StreamChat` to
your `MaterialApp` widget. Otherwise you can use it in the usual way, but you need to add a `StreamChat` widget to your app is using the `builder` property of your `MaterialApp` widget. Otherwise you can use it in
every route of your app. Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more the usual way, but you need to add a `StreamChat` widget to every route of your app.
Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more
information. information.
```dart ```dart
@@ -645,8 +671,8 @@ Widget build(BuildContext context) {
- Add gesture (vertical drag down) to close the keyboard - Add gesture (vertical drag down) to close the keyboard
- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will even close the - Add keyboard type parameters (set it to TextInputType.text to show the submit button that will
keyboard) even close the keyboard)
The property showVideoFullScreen was added mainly because of this issue brianegan/chewie#261 The property showVideoFullScreen was added mainly because of this issue brianegan/chewie#261
@@ -46,9 +46,9 @@ extension PlatformFileX on PlatformFile {
); );
} }
/// /// Extension on [InputDecoration]
extension InputDecorationX on InputDecoration { extension InputDecorationX on InputDecoration {
/// /// Merges this [InputDecoration] with the [other]
InputDecoration merge(InputDecoration? other) { InputDecoration merge(InputDecoration? other) {
if (other == null) return this; if (other == null) return this;
return copyWith( return copyWith(
@@ -123,3 +123,50 @@ extension FlipBorder on BorderRadius {
bottomRight: bottomLeft) bottomRight: bottomLeft)
: this; : this;
} }
/// Extension on [IconButton]
extension IconButtonX on IconButton {
/// Creates a copy of [IconButton] with specified attributes overridden.
IconButton copyWith({
double? iconSize,
VisualDensity? visualDensity,
EdgeInsetsGeometry? padding,
AlignmentGeometry? alignment,
double? splashRadius,
Color? color,
Color? focusColor,
Color? hoverColor,
Color? highlightColor,
Color? splashColor,
Color? disabledColor,
void Function()? onPressed,
MouseCursor? mouseCursor,
FocusNode? focusNode,
bool? autofocus,
String? tooltip,
bool? enableFeedback,
BoxConstraints? constraints,
Widget? icon,
}) =>
IconButton(
iconSize: iconSize ?? this.iconSize,
visualDensity: visualDensity ?? this.visualDensity,
padding: padding ?? this.padding,
alignment: alignment ?? this.alignment,
splashRadius: splashRadius ?? this.splashRadius,
color: color ?? this.color,
focusColor: focusColor ?? this.focusColor,
hoverColor: hoverColor ?? this.hoverColor,
highlightColor: highlightColor ?? this.highlightColor,
splashColor: splashColor ?? this.splashColor,
disabledColor: disabledColor ?? this.disabledColor,
onPressed: onPressed ?? this.onPressed,
mouseCursor: mouseCursor ?? this.mouseCursor,
focusNode: focusNode ?? this.focusNode,
autofocus: autofocus ?? this.autofocus,
tooltip: tooltip ?? this.tooltip,
enableFeedback: enableFeedback ?? this.enableFeedback,
constraints: constraints ?? this.constraints,
icon: icon ?? this.icon,
);
}
@@ -50,6 +50,15 @@ typedef MentionTileBuilder = Widget Function(
Member member, Member member,
); );
/// Widget builder for action button.
///
/// [defaultActionButton] is the default [IconButton] configuration,
/// use [defaultActionButton.copyWith] to easily customize it.
typedef ActionButtonBuilder = Widget Function(
BuildContext context,
IconButton defaultActionButton,
);
/// Location for actions on the [MessageInput] /// Location for actions on the [MessageInput]
enum ActionsLocation { enum ActionsLocation {
/// Align to left /// Align to left
@@ -164,6 +173,8 @@ class MessageInput extends StatefulWidget {
this.compressedVideoQuality = VideoQuality.DefaultQuality, this.compressedVideoQuality = VideoQuality.DefaultQuality,
this.compressedVideoFrameRate = 30, this.compressedVideoFrameRate = 30,
this.onError, this.onError,
this.attachmentButtonBuilder,
this.commandButtonBuilder,
}) : super(key: key); }) : super(key: key);
/// Message to edit /// Message to edit
@@ -247,6 +258,18 @@ class MessageInput extends StatefulWidget {
/// A callback for error reporting /// A callback for error reporting
final ErrorListener? onError; final ErrorListener? onError;
/// Builder for customizing the attachment button.
///
/// The builder contains the default [IconButton] that can be customized by
/// calling `.copyWith`.
final ActionButtonBuilder? attachmentButtonBuilder;
/// Builder for customizing the command button.
///
/// The builder contains the default [IconButton] that can be customized by
/// calling `.copyWith`.
final ActionButtonBuilder? commandButtonBuilder;
@override @override
MessageInputState createState() => MessageInputState(); MessageInputState createState() => MessageInputState();
@@ -403,11 +426,11 @@ class MessageInputState extends State<MessageInput> {
children: <Widget>[ children: <Widget>[
if (!_commandEnabled && if (!_commandEnabled &&
widget.actionsLocation == ActionsLocation.left) widget.actionsLocation == ActionsLocation.left)
_buildExpandActionsButton(), _buildExpandActionsButton(context),
_buildTextInput(context), _buildTextInput(context),
if (!_commandEnabled && if (!_commandEnabled &&
widget.actionsLocation == ActionsLocation.right) widget.actionsLocation == ActionsLocation.right)
_buildExpandActionsButton(), _buildExpandActionsButton(context),
if (widget.sendButtonLocation == SendButtonLocation.outside) if (widget.sendButtonLocation == SendButtonLocation.outside)
_animateSendButton(context), _animateSendButton(context),
], ],
@@ -490,7 +513,7 @@ class MessageInputState extends State<MessageInput> {
); );
} }
Widget _buildExpandActionsButton() { Widget _buildExpandActionsButton(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
@@ -528,12 +551,13 @@ class MessageInputState extends State<MessageInput> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[ children: <Widget>[
if (!widget.disableAttachments) _buildAttachmentButton(), if (!widget.disableAttachments)
_buildAttachmentButton(context),
if (widget.showCommandsButton && if (widget.showCommandsButton &&
widget.editMessage == null && widget.editMessage == null &&
channel.state != null && channel.state != null &&
channel.config?.commands.isNotEmpty == true) channel.config?.commands.isNotEmpty == true)
_buildCommandButton(), _buildCommandButton(context),
...widget.actions ?? [], ...widget.actions ?? [],
].insertBetween(const SizedBox(width: 8)), ].insertBetween(const SizedBox(width: 8)),
), ),
@@ -669,9 +693,7 @@ class MessageInputState extends State<MessageInput> {
: (widget.actionsLocation == ActionsLocation.leftInside : (widget.actionsLocation == ActionsLocation.leftInside
? Row( ? Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [_buildExpandActionsButton(context)],
_buildExpandActionsButton(),
],
) )
: null), : null),
suffixIconConstraints: const BoxConstraints.tightFor(height: 40), suffixIconConstraints: const BoxConstraints.tightFor(height: 40),
@@ -697,7 +719,7 @@ class MessageInputState extends State<MessageInput> {
), ),
if (!_commandEnabled && if (!_commandEnabled &&
widget.actionsLocation == ActionsLocation.rightInside) widget.actionsLocation == ActionsLocation.rightInside)
_buildExpandActionsButton(), _buildExpandActionsButton(context),
if (widget.sendButtonLocation == SendButtonLocation.inside) if (widget.sendButtonLocation == SendButtonLocation.inside)
_animateSendButton(context), _animateSendButton(context),
], ],
@@ -1681,10 +1703,9 @@ class MessageInputState extends State<MessageInput> {
} }
} }
Widget _buildCommandButton() { Widget _buildCommandButton(BuildContext context) {
final s = textEditingController.text.trim(); final s = textEditingController.text.trim();
final defaultButton = IconButton(
return IconButton(
icon: StreamSvgIcon.lightning( icon: StreamSvgIcon.lightning(
color: s.isNotEmpty color: s.isNotEmpty
? _streamChatTheme.colorTheme.disabled ? _streamChatTheme.colorTheme.disabled
@@ -1722,38 +1743,46 @@ class MessageInputState extends State<MessageInput> {
} }
}, },
); );
return widget.commandButtonBuilder?.call(context, defaultButton) ??
defaultButton;
} }
Widget _buildAttachmentButton() => IconButton( Widget _buildAttachmentButton(BuildContext context) {
icon: StreamSvgIcon.attach( final defaultButton = IconButton(
color: _openFilePickerSection icon: StreamSvgIcon.attach(
? _messageInputTheme.actionButtonColor color: _openFilePickerSection
: _messageInputTheme.actionButtonIdleColor, ? _messageInputTheme.actionButtonColor
), : _messageInputTheme.actionButtonIdleColor,
padding: const EdgeInsets.all(0), ),
constraints: const BoxConstraints.tightFor( padding: const EdgeInsets.all(0),
height: 24, constraints: const BoxConstraints.tightFor(
width: 24, height: 24,
), width: 24,
splashRadius: 24, ),
onPressed: () async { splashRadius: 24,
_emojiOverlay?.remove(); onPressed: () async {
_emojiOverlay = null; _emojiOverlay?.remove();
_commandsOverlay?.remove(); _emojiOverlay = null;
_commandsOverlay = null; _commandsOverlay?.remove();
_mentionsOverlay?.remove(); _commandsOverlay = null;
_mentionsOverlay = null; _mentionsOverlay?.remove();
_mentionsOverlay = null;
if (_openFilePickerSection) { if (_openFilePickerSection) {
setState(() { setState(() {
_openFilePickerSection = false; _openFilePickerSection = false;
_filePickerSize = _kMinMediaPickerSize; _filePickerSize = _kMinMediaPickerSize;
}); });
} else { } else {
showAttachmentModal(); showAttachmentModal();
} }
}, },
); );
return widget.attachmentButtonBuilder?.call(context, defaultButton) ??
defaultButton;
}
/// Show the attachment modal, making the user choose where to /// Show the attachment modal, making the user choose where to
/// pick a media from /// pick a media from
@@ -12,6 +12,7 @@ export 'src/channel_preview.dart';
export 'src/connection_status_builder.dart'; export 'src/connection_status_builder.dart';
export 'src/date_divider.dart'; export 'src/date_divider.dart';
export 'src/deleted_message.dart'; export 'src/deleted_message.dart';
export 'src/extension.dart' show IconButtonX;
export 'src/full_screen_media.dart'; export 'src/full_screen_media.dart';
export 'src/gallery_footer.dart'; export 'src/gallery_footer.dart';
export 'src/gallery_header.dart'; export 'src/gallery_header.dart';