added stream message button, general refactor
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user