From 82109e5e4cc7a544d802723bf696fada4c7a8045 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 2 Nov 2021 22:13:51 +0530 Subject: [PATCH] added stream message button, general refactor --- .../lib/src/message_input.dart | 120 ++++-------------- .../lib/src/mip/countdown_button.dart | 29 +++++ .../src/mip/stream_message_send_button.dart | 97 ++++++++++++++ .../lib/stream_chat_flutter.dart | 2 + .../lib/src/message_input_controller.dart | 22 ++-- 5 files changed, 164 insertions(+), 106 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/mip/countdown_button.dart diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index e1ab4b90..596e61c4 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -79,6 +79,11 @@ typedef ActionButtonBuilder = Widget Function( IconButton defaultActionButton, ); +typedef AttachmentsPickerBuilder = Widget Function( + BuildContext context, + List attachments, +); + /// Location for actions on the [MessageInput] enum ActionsLocation { /// Align to left @@ -204,6 +209,7 @@ class MessageInput extends StatefulWidget { this.commandButtonBuilder, this.customOverlays = const [], this.mentionAllAppUsers = false, + this.attachmentsPickerBuilder, }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", @@ -322,6 +328,9 @@ class MessageInput extends StatefulWidget { /// Defaults to false. final bool mentionAllAppUsers; + /// Builds bottom sheet when attachment picker is opened. + final AttachmentsPickerBuilder? attachmentsPickerBuilder; + @override MessageInputState createState() => MessageInputState(); @@ -524,7 +533,7 @@ class MessageInputState extends State { widget.actionsLocation == ActionsLocation.right) _buildExpandActionsButton(context), if (widget.sendButtonLocation == SendButtonLocation.outside) - _animateSendButton(context), + _buildSendButton(context), ], ); @@ -588,27 +597,15 @@ class MessageInputState extends State { ], ); - Widget _animateSendButton(BuildContext context) { - late Widget sendButton; - if (_timeOut > 0) { - sendButton = _CountdownButton(count: _timeOut); - } else if (!_messageIsPresent && - messageInputController.attachments.isEmpty) { - sendButton = widget.idleSendButton ?? _buildIdleSendButton(context); - } else { - sendButton = widget.activeSendButton != null - ? InkWell( - onTap: sendMessage, - child: widget.activeSendButton, - ) - : _buildSendButton(context); - } - - return AnimatedSwitcher( - duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!, - child: sendButton, - ); - } + Widget _buildSendButton(BuildContext context) => StreamMessageSendButton( + onSendMessage: sendMessage, + timeOut: _timeOut, + isIdle: + !_messageIsPresent && messageInputController.attachments.isEmpty, + isEditEnabled: widget.editMessage != null, + idleSendButton: widget.idleSendButton, + activeSendButton: widget.activeSendButton, + ); Widget _buildExpandActionsButton(BuildContext context) { final channel = StreamChannel.of(context).channel; @@ -817,7 +814,7 @@ class MessageInputState extends State { widget.actionsLocation == ActionsLocation.rightInside) _buildExpandActionsButton(context), if (widget.sendButtonLocation == SendButtonLocation.inside) - _animateSendButton(context), + _buildSendButton(context), ], ), ).merge(passedDecoration); @@ -993,6 +990,13 @@ class MessageInputState extends State { } } + if (_openFilePickerSection && widget.attachmentsPickerBuilder != null) { + return widget.attachmentsPickerBuilder!( + context, + messageInputController.attachments, + ); + } + return AnimatedContainer( duration: _openFilePickerSection ? const Duration(milliseconds: 300) @@ -1736,49 +1740,6 @@ class MessageInputState extends State { }); } - Widget _buildIdleSendButton(BuildContext context) => Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - assetName: _getIdleSendIcon(), - color: _messageInputTheme.sendButtonIdleColor, - ), - ); - - Widget _buildSendButton(BuildContext context) => Padding( - padding: const EdgeInsets.all(8), - child: IconButton( - onPressed: sendMessage, - padding: const EdgeInsets.all(0), - splashRadius: 24, - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - icon: StreamSvgIcon( - assetName: _getSendIcon(), - color: _messageInputTheme.sendButtonColor, - ), - ), - ); - - String _getIdleSendIcon() { - if (_commandEnabled) { - return 'Icon_search.svg'; - } else { - return 'Icon_circle_right.svg'; - } - } - - String _getSendIcon() { - if (widget.editMessage != null) { - return 'Icon_circle_up.svg'; - } else if (_commandEnabled) { - return 'Icon_search.svg'; - } else { - return 'Icon_circle_up.svg'; - } - } - /// Sends the current message Future sendMessage() async { var text = messageInputController.text.trim(); @@ -2080,30 +2041,3 @@ class _PickerWidgetState extends State<_PickerWidget> { ); } } - -class _CountdownButton extends StatelessWidget { - const _CountdownButton({ - Key? key, - required this.count, - }) : super(key: key); - - final int count; - - @override - Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.all(8), - child: DecoratedBox( - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.disabled, - shape: BoxShape.circle, - ), - child: SizedBox( - height: 24, - width: 24, - child: Center( - child: Text('$count'), - ), - ), - ), - ); -} diff --git a/packages/stream_chat_flutter/lib/src/mip/countdown_button.dart b/packages/stream_chat_flutter/lib/src/mip/countdown_button.dart new file mode 100644 index 00000000..e3d8bd32 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/mip/countdown_button.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class CountdownButton extends StatelessWidget { + const CountdownButton({ + Key? key, + required this.count, + }) : super(key: key); + + final int count; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.all(8), + child: DecoratedBox( + decoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.disabled, + shape: BoxShape.circle, + ), + child: SizedBox( + height: 24, + width: 24, + child: Center( + child: Text('$count'), + ), + ), + ), + ); +} diff --git a/packages/stream_chat_flutter/lib/src/mip/stream_message_send_button.dart b/packages/stream_chat_flutter/lib/src/mip/stream_message_send_button.dart index 8b137891..5443fb18 100644 --- a/packages/stream_chat_flutter/lib/src/mip/stream_message_send_button.dart +++ b/packages/stream_chat_flutter/lib/src/mip/stream_message_send_button.dart @@ -1 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +class StreamMessageSendButton extends StatelessWidget { + final int timeOut; + final bool isIdle; + final bool isCommandEnabled; + final bool isEditEnabled; + final Widget? idleSendButton; + final Widget? activeSendButton; + final VoidCallback onSendMessage; + + const StreamMessageSendButton({ + Key? key, + this.timeOut = 0, + this.isIdle = true, + this.isCommandEnabled = false, + this.isEditEnabled = false, + this.idleSendButton, + this.activeSendButton, + required this.onSendMessage, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var _streamChatTheme = StreamChatTheme.of(context); + + late Widget sendButton; + if (timeOut > 0) { + sendButton = CountdownButton(count: timeOut); + } else if (isIdle) { + sendButton = idleSendButton ?? _buildIdleSendButton(context); + } else { + sendButton = activeSendButton != null + ? InkWell( + onTap: onSendMessage, + child: activeSendButton, + ) + : _buildSendButton(context); + } + + return AnimatedSwitcher( + duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!, + child: sendButton, + ); + } + + Widget _buildIdleSendButton(BuildContext context) { + var _messageInputTheme = MessageInputTheme.of(context); + + return Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon( + assetName: _getIdleSendIcon(), + color: _messageInputTheme.sendButtonIdleColor, + ), + ); + } + + Widget _buildSendButton(BuildContext context) { + var _messageInputTheme = MessageInputTheme.of(context); + + return Padding( + padding: const EdgeInsets.all(8), + child: IconButton( + onPressed: onSendMessage, + padding: const EdgeInsets.all(0), + splashRadius: 24, + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + icon: StreamSvgIcon( + assetName: _getSendIcon(), + color: _messageInputTheme.sendButtonColor, + ), + ), + ); + } + + String _getIdleSendIcon() { + if (isCommandEnabled) { + return 'Icon_search.svg'; + } else { + return 'Icon_circle_right.svg'; + } + } + + String _getSendIcon() { + if (isEditEnabled) { + return 'Icon_circle_up.svg'; + } else if (isCommandEnabled) { + return 'Icon_search.svg'; + } else { + return 'Icon_circle_up.svg'; + } + } +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 948f0261..f58b7439 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -28,6 +28,8 @@ export 'src/message_search_item.dart'; export 'src/message_search_list_view.dart'; export 'src/message_text.dart'; export 'src/message_widget.dart'; +export 'src/mip/countdown_button.dart'; +export 'src/mip/stream_message_send_button.dart'; export 'src/mip/stream_message_text_field.dart'; export 'src/option_list_tile.dart'; export 'src/reaction_icon.dart'; diff --git a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart index 3f60efaf..3339bc5b 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart @@ -1,10 +1,10 @@ -import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_chat/stream_chat.dart'; +/// Controller for storing and mutating a [Message] value. class MessageInputController extends ValueNotifier { /// Creates a controller for an editable text field. /// @@ -17,7 +17,8 @@ class MessageInputController extends ValueNotifier { factory MessageInputController.fromText(String? text) => MessageInputController._(Message(text: text)); - /// Creates a controller for an editable text field from an initial [attachments]. + /// Creates a controller for an editable text field from an initial + /// [attachments]. factory MessageInputController.fromAttachments( List attachments, ) => @@ -55,23 +56,17 @@ class MessageInputController extends ValueNotifier { } /// - get baseOffset { - return textEditingController.selection.baseOffset; - } + int get baseOffset => textEditingController.selection.baseOffset; /// - get selectionStart { - return textEditingController.selection.start; - } + int get selectionStart => textEditingController.selection.start; set showInChannel(bool newValue) { value = value.copyWith(showInChannel: newValue); } /// - bool get showInChannel { - return value.showInChannel ?? false; - } + bool get showInChannel => value.showInChannel ?? false; /// List get attachments => value.attachments; @@ -142,8 +137,9 @@ class MessageInputController extends ValueNotifier { /// After calling this function, [text], [attachments] and [mentionedUsers] /// all will be empty. /// - /// Calling this will notify all the listeners of this [MessageInputController] - /// that they need to update (it calls [notifyListeners]). For this reason, + /// Calling this will notify all the listeners of this + /// [MessageInputController] that they need to update + /// (it 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. void clear() {