diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 5289ec30..341d481b 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -2,21 +2,35 @@ ✅ Added -- [#516](https://github.com/GetStream/stream-chat-flutter/issues/516): Added `StreamChatThemeData.placeholderUserImage` for - building a widget when the `UserAvatar` image is loading +- [#516](https://github.com/GetStream/stream-chat-flutter/issues/516): + Added `StreamChatThemeData.placeholderUserImage` for building a widget when the `UserAvatar` image + is loading - Added a `backgroundColor` property to the following widgets: - - `ChannelHeader` - - `ChannelListHeader` - - `GalleryHeader` - - `GalleryFooter` - - `ThreadHeader` + - `ChannelHeader` + - `ChannelListHeader` + - `GalleryHeader` + - `GalleryFooter` + - `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 -Theming has been upgraded! Most theme classes now have `InheritedTheme` classes -associated with them, and have been upgraded with some goodies like `lerp` functions. -Here's the full naming breakdown: +Theming has been upgraded! Most theme classes now have `InheritedTheme` classes associated with +them, and have been upgraded with some goodies like `lerp` functions. Here's the full naming +breakdown: + * `AvatarTheme` is now `AvatarThemeData` * `ChannelHeaderTheme` is now `ChannelHeaderThemeData` * `ChannelListHeaderTheme` is now `ChannelListHeaderThemeData` @@ -32,8 +46,9 @@ Here's the full naming breakdown: 🐞 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 - Updated core dependency @@ -50,7 +65,8 @@ Here's the full naming breakdown: 🔄 Changed - `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 @@ -64,17 +80,17 @@ Here's the full naming breakdown: - Renamed `ChannelImage` to `ChannelAvatar` - Updated `StreamChatThemeData.reactionIcons` to accept custom builder - Renamed `ColorTheme` properties to reflect the purpose of the colors - - `ColorTheme.black` -> `ColorTheme.textHighEmphasis` - - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` - - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` - - `ColorTheme.greyWhisper` -> `ColorTheme.borders` - - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` - - `ColorTheme.whiteSnow` -> `ColorTheme.appBg` - - `ColorTheme.white` -> `ColorTheme.barsBg` - - `ColorTheme.blueAlice` -> `ColorTheme.linkBg` - - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` - - `ColorTheme.accentRed` -> `ColorTheme.accentError` - - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` + - `ColorTheme.black` -> `ColorTheme.textHighEmphasis` + - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` + - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` + - `ColorTheme.greyWhisper` -> `ColorTheme.borders` + - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` + - `ColorTheme.whiteSnow` -> `ColorTheme.appBg` + - `ColorTheme.white` -> `ColorTheme.barsBg` + - `ColorTheme.blueAlice` -> `ColorTheme.linkBg` + - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` + - `ColorTheme.accentRed` -> `ColorTheme.accentError` + - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` - `ChannelListCore` options property is removed in favor of individual properties - `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 @@ -103,7 +119,8 @@ You can call `.copyWith` to customize just a subset of properties - Added video compress options (frame and quality) to `MessageInput` - 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` - Added pinMessage ui support - Added `MessageListView.threadSeparatorBuilder` property @@ -112,10 +129,12 @@ You can call `.copyWith` to customize just a subset of properties 🐞 Fixed -- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing - message -- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case -- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload +- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text + box when editing message +- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator + 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 - Fix `MessageInput` overflow when there are no actions - 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` - Renamed `ColorTheme` properties to reflect the purpose of the colors - - `ColorTheme.black` -> `ColorTheme.textHighEmphasis` - - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` - - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` - - `ColorTheme.greyWhisper` -> `ColorTheme.borders` - - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` - - `ColorTheme.whiteSnow` -> `ColorTheme.appBg` - - `ColorTheme.white` -> `ColorTheme.barsBg` - - `ColorTheme.blueAlice` -> `ColorTheme.linkBg` - - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` - - `ColorTheme.accentRed` -> `ColorTheme.accentError` - - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` + - `ColorTheme.black` -> `ColorTheme.textHighEmphasis` + - `ColorTheme.grey` -> `ColorTheme.textLowEmphasis` + - `ColorTheme.greyGainsboro` -> `ColorTheme.disabled` + - `ColorTheme.greyWhisper` -> `ColorTheme.borders` + - `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg` + - `ColorTheme.whiteSnow` -> `ColorTheme.appBg` + - `ColorTheme.white` -> `ColorTheme.barsBg` + - `ColorTheme.blueAlice` -> `ColorTheme.linkBg` + - `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary` + - `ColorTheme.accentRed` -> `ColorTheme.accentError` + - `ColorTheme.accentGreen` -> `ColorTheme.accentInfo` ✅ Added @@ -162,21 +181,24 @@ typedef MessageBuilder = Widget Function( ); ``` -the last parameter is the default `MessageWidget` -You can call `.copyWith` to customize just a subset of properties +> **_NOTE:_** The last parameter is the default `MessageWidget` +You can call `.copyWith` to customize just a subset of properties. ✅ Added - 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` 🐞 Fixed -- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing - message -- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case -- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload +- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text + box when editing message +- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator + 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 ## 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 - Remove notification badge logic - 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` - Added `MessageListView.onAttachmentTap` callback - Fixed message newline issue @@ -303,7 +326,8 @@ You can call `.copyWith` to customize just a subset of properties - Improved api documentation - Updated `stream_chat` dependency to `^1.0.0-beta` - 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 @@ -320,8 +344,8 @@ You can call `.copyWith` to customize just a subset of properties ## 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 - arrives +- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the + list when a new message arrives ## 0.2.20+1 @@ -355,7 +379,8 @@ You can call `.copyWith` to customize just a subset of properties ## 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 ## 0.2.15 @@ -540,10 +565,11 @@ You can call `.copyWith` to customize just a subset of properties ## 0.2.1-alpha+1 -- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have the `StreamChat` widget - as ancestor in every route. Now the recommended way to add `StreamChat` to your app is using the `builder` property of - your `MaterialApp` widget. Otherwise you can use it in 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 +- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have + the `StreamChat` widget as ancestor in every route. Now the recommended way to add `StreamChat` to + your app is using the `builder` property of your `MaterialApp` widget. Otherwise you can use it in + 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. ```dart @@ -645,8 +671,8 @@ Widget build(BuildContext context) { - 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 - keyboard) +- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will + even close the keyboard) The property showVideoFullScreen was added mainly because of this issue brianegan/chewie#261 diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index b975941e..19bf4523 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -46,9 +46,9 @@ extension PlatformFileX on PlatformFile { ); } -/// +/// Extension on [InputDecoration] extension InputDecorationX on InputDecoration { - /// + /// Merges this [InputDecoration] with the [other] InputDecoration merge(InputDecoration? other) { if (other == null) return this; return copyWith( @@ -123,3 +123,50 @@ extension FlipBorder on BorderRadius { bottomRight: bottomLeft) : 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, + ); +} diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 9431cd22..4765bcc5 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -50,6 +50,15 @@ typedef MentionTileBuilder = Widget Function( 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] enum ActionsLocation { /// Align to left @@ -164,6 +173,8 @@ class MessageInput extends StatefulWidget { this.compressedVideoQuality = VideoQuality.DefaultQuality, this.compressedVideoFrameRate = 30, this.onError, + this.attachmentButtonBuilder, + this.commandButtonBuilder, }) : super(key: key); /// Message to edit @@ -247,6 +258,18 @@ class MessageInput extends StatefulWidget { /// A callback for error reporting 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 MessageInputState createState() => MessageInputState(); @@ -403,11 +426,11 @@ class MessageInputState extends State { children: [ if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left) - _buildExpandActionsButton(), + _buildExpandActionsButton(context), _buildTextInput(context), if (!_commandEnabled && widget.actionsLocation == ActionsLocation.right) - _buildExpandActionsButton(), + _buildExpandActionsButton(context), if (widget.sendButtonLocation == SendButtonLocation.outside) _animateSendButton(context), ], @@ -490,7 +513,7 @@ class MessageInputState extends State { ); } - Widget _buildExpandActionsButton() { + Widget _buildExpandActionsButton(BuildContext context) { final channel = StreamChannel.of(context).channel; return Padding( padding: const EdgeInsets.symmetric(horizontal: 8), @@ -528,12 +551,13 @@ class MessageInputState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - if (!widget.disableAttachments) _buildAttachmentButton(), + if (!widget.disableAttachments) + _buildAttachmentButton(context), if (widget.showCommandsButton && widget.editMessage == null && channel.state != null && channel.config?.commands.isNotEmpty == true) - _buildCommandButton(), + _buildCommandButton(context), ...widget.actions ?? [], ].insertBetween(const SizedBox(width: 8)), ), @@ -669,9 +693,7 @@ class MessageInputState extends State { : (widget.actionsLocation == ActionsLocation.leftInside ? Row( mainAxisSize: MainAxisSize.min, - children: [ - _buildExpandActionsButton(), - ], + children: [_buildExpandActionsButton(context)], ) : null), suffixIconConstraints: const BoxConstraints.tightFor(height: 40), @@ -697,7 +719,7 @@ class MessageInputState extends State { ), if (!_commandEnabled && widget.actionsLocation == ActionsLocation.rightInside) - _buildExpandActionsButton(), + _buildExpandActionsButton(context), if (widget.sendButtonLocation == SendButtonLocation.inside) _animateSendButton(context), ], @@ -1681,10 +1703,9 @@ class MessageInputState extends State { } } - Widget _buildCommandButton() { + Widget _buildCommandButton(BuildContext context) { final s = textEditingController.text.trim(); - - return IconButton( + final defaultButton = IconButton( icon: StreamSvgIcon.lightning( color: s.isNotEmpty ? _streamChatTheme.colorTheme.disabled @@ -1722,38 +1743,46 @@ class MessageInputState extends State { } }, ); + + return widget.commandButtonBuilder?.call(context, defaultButton) ?? + defaultButton; } - Widget _buildAttachmentButton() => IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? _messageInputTheme.actionButtonColor - : _messageInputTheme.actionButtonIdleColor, - ), - padding: const EdgeInsets.all(0), - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; + Widget _buildAttachmentButton(BuildContext context) { + final defaultButton = IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? _messageInputTheme.actionButtonColor + : _messageInputTheme.actionButtonIdleColor, + ), + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; - if (_openFilePickerSection) { - setState(() { - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, - ); + if (_openFilePickerSection) { + setState(() { + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, + ); + + return widget.attachmentButtonBuilder?.call(context, defaultButton) ?? + defaultButton; + } /// Show the attachment modal, making the user choose where to /// pick a media from diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 89c6450c..42188fa0 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -12,6 +12,7 @@ export 'src/channel_preview.dart'; export 'src/connection_status_builder.dart'; export 'src/date_divider.dart'; export 'src/deleted_message.dart'; +export 'src/extension.dart' show IconButtonX; export 'src/full_screen_media.dart'; export 'src/gallery_footer.dart'; export 'src/gallery_header.dart';