diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c4888be2..d43e774c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -12,12 +12,12 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; -import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.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/user_avatar.dart'; +import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; @@ -35,6 +35,8 @@ typedef AttachmentThumbnailBuilder = Widget Function( enum ActionsLocation { left, right, + leftInside, + rightInside, } enum DefaultAttachmentTypes { @@ -122,6 +124,7 @@ class MessageInput extends StatefulWidget { this.hideSendAsDm = false, this.idleSendButton, this.activeSendButton, + this.showCommandsButton = true, }) : super(key: key); /// Message to edit @@ -149,6 +152,9 @@ class MessageInput extends StatefulWidget { /// If true the attachments button will not be displayed final bool disableAttachments; + /// Use this property to hide/show the commands button + final bool showCommandsButton; + /// Hide send as dm checkbox final bool hideSendAsDm; @@ -336,12 +342,11 @@ class MessageInputState extends State { direction: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (!_commandEnabled) _buildExpandActionsButton(), - if (widget.actionsLocation == ActionsLocation.left) - ...widget.actions ?? [], + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left) + _buildExpandActionsButton(), _buildTextInput(context), - if (widget.actionsLocation == ActionsLocation.right) - ...widget.actions ?? [], + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.right) + _buildExpandActionsButton(), if (widget.sendButtonLocation == SendButtonLocation.outside) _animateSendButton(context), ], @@ -421,18 +426,15 @@ class MessageInputState extends State { child: widget.activeSendButton, ) : _buildSendButton(context); - return Padding( - padding: const EdgeInsets.all(8.0), - child: AnimatedCrossFade( - crossFadeState: (_messageIsPresent || _attachments.isNotEmpty) - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: sendButton, - secondChild: widget.idleSendButton ?? _buildIdleSendButton(context), - duration: - StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration, - alignment: Alignment.center, - ), + return AnimatedCrossFade( + crossFadeState: (_messageIsPresent || _attachments.isNotEmpty) + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: sendButton, + secondChild: widget.idleSendButton ?? _buildIdleSendButton(context), + duration: + StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration, + alignment: Alignment.center, ); } @@ -445,8 +447,15 @@ class MessageInputState extends State { : CrossFadeState.showSecond, firstChild: IconButton( onPressed: () => setState(() => _actionsShrunk = false), - icon: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + icon: Transform.rotate( + alignment: Alignment.center, + angle: (widget.actionsLocation == ActionsLocation.right || + widget.actionsLocation == ActionsLocation.rightInside) + ? pi + : 0, + child: StreamSvgIcon.emptyCircleLeft( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), ), padding: const EdgeInsets.all(0), constraints: BoxConstraints.tightFor( @@ -457,10 +466,12 @@ class MessageInputState extends State { ), secondChild: FittedBox( child: Row( + crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + children: [ if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.editMessage == null && + if (widget.showCommandsButton && + widget.editMessage == null && StreamChannel.of(context) .channel ?.config @@ -468,6 +479,7 @@ class MessageInputState extends State { ?.isNotEmpty == true) _buildCommandButton(), + ...widget.actions ?? [], ].insertBetween(const SizedBox(width: 8)), ), ), @@ -565,51 +577,75 @@ class MessageInputState extends State { ), ), contentPadding: const EdgeInsets.fromLTRB(16, 12, 13, 11), - prefixIconConstraints: BoxConstraints.tight(Size(78, 24)), - suffixIconConstraints: BoxConstraints.tight(Size(40, 40)), prefixIcon: _commandEnabled - ? Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: theme.colorTheme.accentBlue, - ), - margin: const EdgeInsets.only(right: 4, left: 8), - alignment: Alignment.center, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - StreamSvgIcon.lightning( - color: Colors.white, - size: 16.0, - ), - Text( - _chosenCommand?.name?.toUpperCase() ?? '', - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + constraints: BoxConstraints.tight(Size(64, 24)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: theme.colorTheme.accentBlue, + ), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + StreamSvgIcon.lightning( color: Colors.white, + size: 16.0, ), + Text( + _chosenCommand?.name?.toUpperCase() ?? '', + style: StreamChatTheme.of(context) + .textTheme + .footnoteBold + .copyWith( + color: Colors.white, + ), + ), + ], + ), ), - ], - ), + ), + ], ) - : null, + : (widget.actionsLocation == ActionsLocation.leftInside + ? Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildExpandActionsButton(), + ], + ) + : null), + suffixIconConstraints: BoxConstraints.tightFor(height: 40), + prefixIconConstraints: BoxConstraints.tightFor(height: 40), suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, children: [ if (_commandEnabled) - IconButton( - icon: StreamSvgIcon.closeSmall(), - splashRadius: 24, - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: IconButton( + icon: StreamSvgIcon.closeSmall(), + splashRadius: 24, + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + onPressed: () { + setState(() => _commandEnabled = false); + }, ), - onPressed: () { - setState(() => _commandEnabled = false); - }, ), + if (!_commandEnabled && + widget.actionsLocation == ActionsLocation.rightInside) + _buildExpandActionsButton(), if (widget.sendButtonLocation == SendButtonLocation.inside) _animateSendButton(context), ], @@ -619,7 +655,13 @@ class MessageInputState extends State { Timer _debounce; + String _previousValue; void _onChanged(BuildContext context, String s) { + if (s == _previousValue) { + return; + } + _previousValue = s; + if (_debounce?.isActive == true) _debounce.cancel(); _debounce = Timer( const Duration(milliseconds: 350), @@ -631,7 +673,11 @@ class MessageInputState extends State { setState(() { _messageIsPresent = s.trim().isNotEmpty; - _actionsShrunk = s.trim().isNotEmpty; + _actionsShrunk = s.trim().isNotEmpty && + ((widget.actions?.length ?? 0) + + (widget.showCommandsButton ? 1 : 0) + + (widget.disableAttachments ? 0 : 1) > + 1); }); _commandsOverlay?.remove(); @@ -1684,13 +1730,19 @@ class MessageInputState extends State { } Widget _buildCommandButton() { + final s = textEditingController.text.trim(); + return IconButton( icon: StreamSvgIcon.lightning( - color: _commandsOverlay != null - ? StreamChatTheme.of(context).messageInputTheme.actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor, + color: s.isNotEmpty + ? StreamChatTheme.of(context).colorTheme.greyGainsboro + : (_commandsOverlay != null + ? StreamChatTheme.of(context) + .messageInputTheme + .actionButtonColor + : StreamChatTheme.of(context) + .messageInputTheme + .actionButtonIdleColor), ), padding: const EdgeInsets.all(0), constraints: BoxConstraints.tightFor( @@ -1711,7 +1763,9 @@ class MessageInputState extends State { if (_commandsOverlay == null) { setState(() { _commandsOverlay = _buildCommandsOverlayEntry(); - Overlay.of(context).insert(_commandsOverlay); + if (_commandsOverlay != null) { + Overlay.of(context).insert(_commandsOverlay); + } }); } else { setState(() { @@ -1965,24 +2019,31 @@ class MessageInputState extends State { } Widget _buildIdleSendButton(BuildContext context) { - return StreamSvgIcon( - assetName: _getIdleSendIcon(), - color: StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor, + return Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon( + assetName: _getIdleSendIcon(), + color: + StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor, + ), ); } Widget _buildSendButton(BuildContext context) { - return IconButton( - onPressed: sendMessage, - padding: const EdgeInsets.all(0), - splashRadius: 24, - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - icon: StreamSvgIcon( - assetName: _getSendIcon(), - color: StreamChatTheme.of(context).messageInputTheme.sendButtonColor, + return Padding( + padding: const EdgeInsets.all(8.0), + child: IconButton( + onPressed: sendMessage, + padding: const EdgeInsets.all(0), + splashRadius: 24, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + icon: StreamSvgIcon( + assetName: _getSendIcon(), + color: StreamChatTheme.of(context).messageInputTheme.sendButtonColor, + ), ), ); }