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,
);
typedef AttachmentsPickerBuilder = Widget Function(
BuildContext context,
List<Attachment> 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<MessageInput> {
widget.actionsLocation == ActionsLocation.right)
_buildExpandActionsButton(context),
if (widget.sendButtonLocation == SendButtonLocation.outside)
_animateSendButton(context),
_buildSendButton(context),
],
);
@@ -588,27 +597,15 @@ class MessageInputState extends State<MessageInput> {
],
);
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<MessageInput> {
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<MessageInput> {
}
}
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<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
Future<void> 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'),
),
),
),
);
}
@@ -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_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';
@@ -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<Message> {
/// Creates a controller for an editable text field.
///
@@ -17,7 +17,8 @@ class MessageInputController extends ValueNotifier<Message> {
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<Attachment> attachments,
) =>
@@ -55,23 +56,17 @@ class MessageInputController extends ValueNotifier<Message> {
}
///
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<Attachment> get attachments => value.attachments;
@@ -142,8 +137,9 @@ class MessageInputController extends ValueNotifier<Message> {
/// 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() {