added stream message button, general refactor

This commit is contained in:
Deven Joshi
2021-11-02 22:13:51 +05:30
parent 72e2c4d27e
commit 82109e5e4c
5 changed files with 164 additions and 106 deletions
@@ -79,6 +79,11 @@ typedef ActionButtonBuilder = Widget Function(
IconButton defaultActionButton, IconButton defaultActionButton,
); );
typedef AttachmentsPickerBuilder = Widget Function(
BuildContext context,
List<Attachment> attachments,
);
/// Location for actions on the [MessageInput] /// Location for actions on the [MessageInput]
enum ActionsLocation { enum ActionsLocation {
/// Align to left /// Align to left
@@ -204,6 +209,7 @@ class MessageInput extends StatefulWidget {
this.commandButtonBuilder, this.commandButtonBuilder,
this.customOverlays = const [], this.customOverlays = const [],
this.mentionAllAppUsers = false, this.mentionAllAppUsers = false,
this.attachmentsPickerBuilder,
}) : assert( }) : assert(
initialMessage == null || editMessage == null, initialMessage == null || editMessage == null,
"Can't provide both `initialMessage` and `editMessage`", "Can't provide both `initialMessage` and `editMessage`",
@@ -322,6 +328,9 @@ class MessageInput extends StatefulWidget {
/// Defaults to false. /// Defaults to false.
final bool mentionAllAppUsers; final bool mentionAllAppUsers;
/// Builds bottom sheet when attachment picker is opened.
final AttachmentsPickerBuilder? attachmentsPickerBuilder;
@override @override
MessageInputState createState() => MessageInputState(); MessageInputState createState() => MessageInputState();
@@ -524,7 +533,7 @@ class MessageInputState extends State<MessageInput> {
widget.actionsLocation == ActionsLocation.right) widget.actionsLocation == ActionsLocation.right)
_buildExpandActionsButton(context), _buildExpandActionsButton(context),
if (widget.sendButtonLocation == SendButtonLocation.outside) if (widget.sendButtonLocation == SendButtonLocation.outside)
_animateSendButton(context), _buildSendButton(context),
], ],
); );
@@ -588,27 +597,15 @@ class MessageInputState extends State<MessageInput> {
], ],
); );
Widget _animateSendButton(BuildContext context) { Widget _buildSendButton(BuildContext context) => StreamMessageSendButton(
late Widget sendButton; onSendMessage: sendMessage,
if (_timeOut > 0) { timeOut: _timeOut,
sendButton = _CountdownButton(count: _timeOut); isIdle:
} else if (!_messageIsPresent && !_messageIsPresent && messageInputController.attachments.isEmpty,
messageInputController.attachments.isEmpty) { isEditEnabled: widget.editMessage != null,
sendButton = widget.idleSendButton ?? _buildIdleSendButton(context); idleSendButton: widget.idleSendButton,
} else { activeSendButton: widget.activeSendButton,
sendButton = widget.activeSendButton != null );
? InkWell(
onTap: sendMessage,
child: widget.activeSendButton,
)
: _buildSendButton(context);
}
return AnimatedSwitcher(
duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!,
child: sendButton,
);
}
Widget _buildExpandActionsButton(BuildContext context) { Widget _buildExpandActionsButton(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
@@ -817,7 +814,7 @@ class MessageInputState extends State<MessageInput> {
widget.actionsLocation == ActionsLocation.rightInside) widget.actionsLocation == ActionsLocation.rightInside)
_buildExpandActionsButton(context), _buildExpandActionsButton(context),
if (widget.sendButtonLocation == SendButtonLocation.inside) if (widget.sendButtonLocation == SendButtonLocation.inside)
_animateSendButton(context), _buildSendButton(context),
], ],
), ),
).merge(passedDecoration); ).merge(passedDecoration);
@@ -993,6 +990,13 @@ class MessageInputState extends State<MessageInput> {
} }
} }
if (_openFilePickerSection && widget.attachmentsPickerBuilder != null) {
return widget.attachmentsPickerBuilder!(
context,
messageInputController.attachments,
);
}
return AnimatedContainer( return AnimatedContainer(
duration: _openFilePickerSection duration: _openFilePickerSection
? const Duration(milliseconds: 300) ? const Duration(milliseconds: 300)
@@ -1736,49 +1740,6 @@ class MessageInputState extends State<MessageInput> {
}); });
} }
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 /// Sends the current message
Future<void> sendMessage() async { Future<void> sendMessage() async {
var text = messageInputController.text.trim(); 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'),
),
),
),
);
}
@@ -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'),
),
),
),
);
}
@@ -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';
}
}
}
@@ -28,6 +28,8 @@ export 'src/message_search_item.dart';
export 'src/message_search_list_view.dart'; export 'src/message_search_list_view.dart';
export 'src/message_text.dart'; export 'src/message_text.dart';
export 'src/message_widget.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/mip/stream_message_text_field.dart';
export 'src/option_list_tile.dart'; export 'src/option_list_tile.dart';
export 'src/reaction_icon.dart'; export 'src/reaction_icon.dart';
@@ -1,10 +1,10 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
/// Controller for storing and mutating a [Message] value.
class MessageInputController extends ValueNotifier<Message> { class MessageInputController extends ValueNotifier<Message> {
/// Creates a controller for an editable text field. /// Creates a controller for an editable text field.
/// ///
@@ -17,7 +17,8 @@ class MessageInputController extends ValueNotifier<Message> {
factory MessageInputController.fromText(String? text) => factory MessageInputController.fromText(String? text) =>
MessageInputController._(Message(text: 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( factory MessageInputController.fromAttachments(
List<Attachment> attachments, List<Attachment> attachments,
) => ) =>
@@ -55,23 +56,17 @@ class MessageInputController extends ValueNotifier<Message> {
} }
/// ///
get baseOffset { int get baseOffset => textEditingController.selection.baseOffset;
return textEditingController.selection.baseOffset;
}
/// ///
get selectionStart { int get selectionStart => textEditingController.selection.start;
return textEditingController.selection.start;
}
set showInChannel(bool newValue) { set showInChannel(bool newValue) {
value = value.copyWith(showInChannel: newValue); value = value.copyWith(showInChannel: newValue);
} }
/// ///
bool get showInChannel { bool get showInChannel => value.showInChannel ?? false;
return value.showInChannel ?? false;
}
/// ///
List<Attachment> get attachments => value.attachments; List<Attachment> get attachments => value.attachments;
@@ -142,8 +137,9 @@ class MessageInputController extends ValueNotifier<Message> {
/// After calling this function, [text], [attachments] and [mentionedUsers] /// After calling this function, [text], [attachments] and [mentionedUsers]
/// all will be empty. /// all will be empty.
/// ///
/// Calling this will notify all the listeners of this [MessageInputController] /// Calling this will notify all the listeners of this
/// that they need to update (it calls [notifyListeners]). For this reason, /// [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 /// this method should only be called between frames, e.g. in response to user
/// actions, not during the build, layout, or paint phases. /// actions, not during the build, layout, or paint phases.
void clear() { void clear() {