refactor(ui): improve message input controller
This commit is contained in:
@@ -58,7 +58,7 @@ class Message extends Equatable {
|
|||||||
this.ownReactions,
|
this.ownReactions,
|
||||||
this.parentId,
|
this.parentId,
|
||||||
this.quotedMessage,
|
this.quotedMessage,
|
||||||
this.quotedMessageId,
|
String? quotedMessageId,
|
||||||
this.replyCount = 0,
|
this.replyCount = 0,
|
||||||
this.threadParticipants,
|
this.threadParticipants,
|
||||||
this.showInChannel,
|
this.showInChannel,
|
||||||
@@ -72,16 +72,19 @@ class Message extends Equatable {
|
|||||||
this.pinnedBy,
|
this.pinnedBy,
|
||||||
this.extraData = const {},
|
this.extraData = const {},
|
||||||
this.deletedAt,
|
this.deletedAt,
|
||||||
this.status = MessageSendingStatus.sent,
|
this.status = MessageSendingStatus.sending,
|
||||||
this.i18n,
|
this.i18n,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
pinExpires = pinExpires?.toUtc(),
|
pinExpires = pinExpires?.toUtc(),
|
||||||
createdAt = createdAt ?? DateTime.now(),
|
_createdAt = createdAt,
|
||||||
updatedAt = updatedAt ?? DateTime.now();
|
_updatedAt = updatedAt,
|
||||||
|
_quotedMessageId = quotedMessageId;
|
||||||
|
|
||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
||||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||||
|
).copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The message ID. This is either created by Stream or set client side when
|
/// The message ID. This is either created by Stream or set client side when
|
||||||
@@ -134,8 +137,10 @@ class Message extends Equatable {
|
|||||||
@JsonKey(toJson: Serializer.readOnly)
|
@JsonKey(toJson: Serializer.readOnly)
|
||||||
final Message? quotedMessage;
|
final Message? quotedMessage;
|
||||||
|
|
||||||
|
final String? _quotedMessageId;
|
||||||
|
|
||||||
/// The ID of the quoted message, if the message is a quoted reply.
|
/// The ID of the quoted message, if the message is a quoted reply.
|
||||||
final String? quotedMessageId;
|
String? get quotedMessageId => _quotedMessageId ?? quotedMessage?.id;
|
||||||
|
|
||||||
/// Reserved field indicating the number of replies for this message.
|
/// Reserved field indicating the number of replies for this message.
|
||||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||||
@@ -162,13 +167,17 @@ class Message extends Equatable {
|
|||||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||||
final String? command;
|
final String? command;
|
||||||
|
|
||||||
|
final DateTime? _createdAt;
|
||||||
|
|
||||||
/// Reserved field indicating when the message was created.
|
/// Reserved field indicating when the message was created.
|
||||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||||
final DateTime createdAt;
|
DateTime get createdAt => _createdAt ?? DateTime.now();
|
||||||
|
|
||||||
|
final DateTime? _updatedAt;
|
||||||
|
|
||||||
/// Reserved field indicating when the message was updated last time.
|
/// Reserved field indicating when the message was updated last time.
|
||||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||||
final DateTime updatedAt;
|
DateTime get updatedAt => _updatedAt ?? DateTime.now();
|
||||||
|
|
||||||
/// User who sent the message
|
/// User who sent the message
|
||||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||||
@@ -301,17 +310,17 @@ class Message extends Equatable {
|
|||||||
ownReactions: ownReactions ?? this.ownReactions,
|
ownReactions: ownReactions ?? this.ownReactions,
|
||||||
parentId: parentId ?? this.parentId,
|
parentId: parentId ?? this.parentId,
|
||||||
quotedMessage: quotedMessage ?? this.quotedMessage,
|
quotedMessage: quotedMessage ?? this.quotedMessage,
|
||||||
quotedMessageId: quotedMessageId ?? this.quotedMessageId,
|
quotedMessageId: quotedMessageId ?? _quotedMessageId,
|
||||||
replyCount: replyCount ?? this.replyCount,
|
replyCount: replyCount ?? this.replyCount,
|
||||||
threadParticipants: threadParticipants ?? this.threadParticipants,
|
threadParticipants: threadParticipants ?? this.threadParticipants,
|
||||||
showInChannel: showInChannel ?? this.showInChannel,
|
showInChannel: showInChannel ?? this.showInChannel,
|
||||||
command: command ?? this.command,
|
command: command ?? this.command,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? _createdAt,
|
||||||
silent: silent ?? this.silent,
|
silent: silent ?? this.silent,
|
||||||
extraData: extraData ?? this.extraData,
|
extraData: extraData ?? this.extraData,
|
||||||
user: user ?? this.user,
|
user: user ?? this.user,
|
||||||
shadowed: shadowed ?? this.shadowed,
|
shadowed: shadowed ?? this.shadowed,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? _updatedAt,
|
||||||
deletedAt: deletedAt ?? this.deletedAt,
|
deletedAt: deletedAt ?? this.deletedAt,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
pinned: pinned ?? this.pinned,
|
pinned: pinned ?? this.pinned,
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ class ThreadPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
MessageInput(
|
MessageInput(
|
||||||
parentMessage: parent,
|
messageInputController: MessageInputController(
|
||||||
|
message: Message(parentId: parent!.id),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -166,7 +166,9 @@ class ThreadPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
MessageInput(
|
MessageInput(
|
||||||
parentMessage: parent,
|
messageInputController: MessageInputController(
|
||||||
|
message: Message(parentId: parent!.id),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,9 +27,12 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.3
|
cupertino_icons: ^1.0.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_flutter: ^2.2.1
|
stream_chat_flutter:
|
||||||
stream_chat_localizations: ^1.1.0
|
path: ../
|
||||||
stream_chat_persistence: ^2.2.0
|
stream_chat_localizations:
|
||||||
|
path: ../../stream_chat_localizations
|
||||||
|
stream_chat_persistence:
|
||||||
|
path: ../../stream_chat_persistence
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:stream_chat_flutter/src/connection_status_builder.dart';
|
import 'package:stream_chat_flutter/src/connection_status_builder.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_input.dart';
|
import 'package:stream_chat_flutter/src/message_input/message_input.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_list_view.dart';
|
import 'package:stream_chat_flutter/src/message_list_view.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_search_list_view.dart';
|
import 'package:stream_chat_flutter/src/message_search_list_view.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
|
||||||
|
|||||||
@@ -610,7 +610,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
widget.editMessageInputBuilder!(context, widget.message)
|
widget.editMessageInputBuilder!(context, widget.message)
|
||||||
else
|
else
|
||||||
MessageInput(
|
MessageInput(
|
||||||
editMessage: widget.message,
|
messageInputController: MessageInputController(
|
||||||
|
message: widget.message,
|
||||||
|
),
|
||||||
preMessageSending: (m) {
|
preMessageSending: (m) {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
+110
-192
@@ -184,12 +184,9 @@ class MessageInput extends StatefulWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
this.onMessageSent,
|
this.onMessageSent,
|
||||||
this.preMessageSending,
|
this.preMessageSending,
|
||||||
this.parentMessage,
|
|
||||||
this.editMessage,
|
|
||||||
this.maxHeight = 150,
|
this.maxHeight = 150,
|
||||||
this.keyboardType = TextInputType.multiline,
|
this.keyboardType = TextInputType.multiline,
|
||||||
this.disableAttachments = false,
|
this.disableAttachments = false,
|
||||||
this.initialMessage,
|
|
||||||
this.messageInputController,
|
this.messageInputController,
|
||||||
this.actions = const [],
|
this.actions = const [],
|
||||||
this.actionsLocation = ActionsLocation.left,
|
this.actionsLocation = ActionsLocation.left,
|
||||||
@@ -219,18 +216,11 @@ class MessageInput extends StatefulWidget {
|
|||||||
this.attachmentsPickerBuilder,
|
this.attachmentsPickerBuilder,
|
||||||
this.sendButtonBuilder,
|
this.sendButtonBuilder,
|
||||||
this.shouldKeepFocusAfterMessage,
|
this.shouldKeepFocusAfterMessage,
|
||||||
}) : assert(
|
}) : super(key: key);
|
||||||
initialMessage == null || editMessage == null,
|
|
||||||
"Can't provide both `initialMessage` and `editMessage`",
|
|
||||||
),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// List of options for showing overlays
|
/// List of options for showing overlays
|
||||||
final List<OverlayOptions> customOverlays;
|
final List<OverlayOptions> customOverlays;
|
||||||
|
|
||||||
/// Message to edit
|
|
||||||
final Message? editMessage;
|
|
||||||
|
|
||||||
/// Video quality to use when compressing the videos
|
/// Video quality to use when compressing the videos
|
||||||
final VideoQuality compressedVideoQuality;
|
final VideoQuality compressedVideoQuality;
|
||||||
|
|
||||||
@@ -242,9 +232,6 @@ class MessageInput extends StatefulWidget {
|
|||||||
/// do not set it if you're using our default CDN
|
/// do not set it if you're using our default CDN
|
||||||
final int maxAttachmentSize;
|
final int maxAttachmentSize;
|
||||||
|
|
||||||
/// Message to start with
|
|
||||||
final Message? initialMessage;
|
|
||||||
|
|
||||||
/// Function called after sending the message
|
/// Function called after sending the message
|
||||||
final void Function(Message)? onMessageSent;
|
final void Function(Message)? onMessageSent;
|
||||||
|
|
||||||
@@ -252,9 +239,6 @@ class MessageInput extends StatefulWidget {
|
|||||||
/// Use this to transform the message
|
/// Use this to transform the message
|
||||||
final FutureOr<Message> Function(Message)? preMessageSending;
|
final FutureOr<Message> Function(Message)? preMessageSending;
|
||||||
|
|
||||||
/// Parent message in case of a thread
|
|
||||||
final Message? parentMessage;
|
|
||||||
|
|
||||||
/// Maximum Height for the TextField to grow before it starts scrolling
|
/// Maximum Height for the TextField to grow before it starts scrolling
|
||||||
final double maxHeight;
|
final double maxHeight;
|
||||||
|
|
||||||
@@ -367,11 +351,10 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
final _imagePicker = ImagePicker();
|
final _imagePicker = ImagePicker();
|
||||||
late final _focusNode = widget.focusNode ?? FocusNode();
|
late final _focusNode = widget.focusNode ?? FocusNode();
|
||||||
bool _inputEnabled = true;
|
bool _inputEnabled = true;
|
||||||
bool _commandEnabled = false;
|
bool get _commandEnabled => messageInputController.value.command != null;
|
||||||
bool _showCommandsOverlay = false;
|
bool _showCommandsOverlay = false;
|
||||||
bool _showMentionsOverlay = false;
|
bool _showMentionsOverlay = false;
|
||||||
|
|
||||||
Command? _chosenCommand;
|
|
||||||
bool _actionsShrunk = false;
|
bool _actionsShrunk = false;
|
||||||
bool _openFilePickerSection = false;
|
bool _openFilePickerSection = false;
|
||||||
|
|
||||||
@@ -382,16 +365,17 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
late StreamChatThemeData _streamChatTheme;
|
late StreamChatThemeData _streamChatTheme;
|
||||||
late MessageInputThemeData _messageInputTheme;
|
late MessageInputThemeData _messageInputTheme;
|
||||||
|
|
||||||
bool get _hasQuotedMessage => widget.quotedMessage != null;
|
bool get _hasQuotedMessage =>
|
||||||
|
messageInputController.value.quotedMessage != null;
|
||||||
|
|
||||||
bool get _messageIsPresent => messageInputController.text.trim().isNotEmpty;
|
bool get _messageIsPresent => messageInputController.text.trim().isNotEmpty;
|
||||||
|
|
||||||
|
bool get _isEditing =>
|
||||||
|
messageInputController.value.status != MessageSendingStatus.sending;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (widget.editMessage != null || widget.initialMessage != null) {
|
|
||||||
_parseExistingMessage(widget.editMessage ?? widget.initialMessage!);
|
|
||||||
}
|
|
||||||
messageInputController.textEditingController
|
messageInputController.textEditingController
|
||||||
.addListener(_onChangedDebounced);
|
.addListener(_onChangedDebounced);
|
||||||
_focusNode.addListener(_focusNodeListener);
|
_focusNode.addListener(_focusNodeListener);
|
||||||
@@ -437,70 +421,74 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget child = ValueListenableBuilder<Message>(
|
Widget child = ValueListenableBuilder<Message>(
|
||||||
valueListenable: messageInputController,
|
valueListenable: messageInputController,
|
||||||
builder: (context, value, wid) => DecoratedBox(
|
builder: (context, value, wid) {
|
||||||
decoration: BoxDecoration(
|
print('VALUE ${value.text}');
|
||||||
color: _messageInputTheme.inputBackgroundColor,
|
return DecoratedBox(
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: SafeArea(
|
color: _messageInputTheme.inputBackgroundColor,
|
||||||
child: GestureDetector(
|
),
|
||||||
onPanUpdate: (details) {
|
child: SafeArea(
|
||||||
if (details.delta.dy > 0) {
|
child: GestureDetector(
|
||||||
_focusNode.unfocus();
|
onPanUpdate: (details) {
|
||||||
if (_openFilePickerSection) {
|
if (details.delta.dy > 0) {
|
||||||
setState(() {
|
_focusNode.unfocus();
|
||||||
_openFilePickerSection = false;
|
if (_openFilePickerSection) {
|
||||||
});
|
setState(() {
|
||||||
|
_openFilePickerSection = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
if (_hasQuotedMessage)
|
||||||
if (_hasQuotedMessage)
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
|
||||||
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
|
child: Row(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: [
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.all(8),
|
||||||
padding: const EdgeInsets.all(8),
|
child: StreamSvgIcon.reply(
|
||||||
child: StreamSvgIcon.reply(
|
color: _streamChatTheme.colorTheme.disabled,
|
||||||
color: _streamChatTheme.colorTheme.disabled,
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
Text(
|
context.translations.replyToMessageLabel,
|
||||||
context.translations.replyToMessageLabel,
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
),
|
||||||
),
|
IconButton(
|
||||||
IconButton(
|
visualDensity: VisualDensity.compact,
|
||||||
visualDensity: VisualDensity.compact,
|
icon: StreamSvgIcon.closeSmall(),
|
||||||
icon: StreamSvgIcon.closeSmall(),
|
onPressed: widget.onQuotedMessageCleared,
|
||||||
onPressed: widget.onQuotedMessageCleared,
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: _buildTextField(context),
|
|
||||||
),
|
|
||||||
if (widget.parentMessage != null && !widget.hideSendAsDm)
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
right: 12,
|
child: _buildTextField(context),
|
||||||
left: 12,
|
|
||||||
bottom: 12,
|
|
||||||
),
|
|
||||||
child: _buildDmCheckbox(),
|
|
||||||
),
|
),
|
||||||
_buildFilePickerSection(),
|
if (messageInputController.value.parentId != null &&
|
||||||
],
|
!widget.hideSendAsDm)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 12,
|
||||||
|
left: 12,
|
||||||
|
bottom: 12,
|
||||||
|
),
|
||||||
|
child: _buildDmCheckbox(),
|
||||||
|
),
|
||||||
|
_buildFilePickerSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
if (widget.editMessage == null) {
|
if (_isEditing) {
|
||||||
child = Material(
|
child = Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
child: child,
|
child: child,
|
||||||
@@ -575,10 +563,8 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
: _streamChatTheme.colorTheme.barsBg,
|
: _streamChatTheme.colorTheme.barsBg,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
messageInputController.showInChannel =
|
||||||
messageInputController.showInChannel =
|
!messageInputController.showInChannel;
|
||||||
!messageInputController.showInChannel;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: AnimatedCrossFade(
|
child: AnimatedCrossFade(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
@@ -621,7 +607,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
onSendMessage: sendMessage,
|
onSendMessage: sendMessage,
|
||||||
timeOut: _timeOut,
|
timeOut: _timeOut,
|
||||||
isIdle: !_messageIsPresent && messageInputController.attachments.isEmpty,
|
isIdle: !_messageIsPresent && messageInputController.attachments.isEmpty,
|
||||||
isEditEnabled: widget.editMessage != null,
|
isEditEnabled: _isEditing,
|
||||||
idleSendButton: widget.idleSendButton,
|
idleSendButton: widget.idleSendButton,
|
||||||
activeSendButton: widget.activeSendButton,
|
activeSendButton: widget.activeSendButton,
|
||||||
);
|
);
|
||||||
@@ -668,7 +654,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
if (!widget.disableAttachments)
|
if (!widget.disableAttachments)
|
||||||
_buildAttachmentButton(context),
|
_buildAttachmentButton(context),
|
||||||
if (widget.showCommandsButton &&
|
if (widget.showCommandsButton &&
|
||||||
widget.editMessage == null &&
|
!_isEditing &&
|
||||||
channel.state != null &&
|
channel.state != null &&
|
||||||
channel.config?.commands.isNotEmpty == true)
|
channel.config?.commands.isNotEmpty == true)
|
||||||
_buildCommandButton(context),
|
_buildCommandButton(context),
|
||||||
@@ -791,7 +777,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_chosenCommand?.name.toUpperCase() ?? '',
|
messageInputController.value.command!.toUpperCase(),
|
||||||
style:
|
style:
|
||||||
_streamChatTheme.textTheme.footnoteBold.copyWith(
|
_streamChatTheme.textTheme.footnoteBold.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -825,9 +811,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
height: 24,
|
height: 24,
|
||||||
width: 24,
|
width: 24,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: messageInputController.clear,
|
||||||
setState(() => _commandEnabled = false);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!_commandEnabled &&
|
if (!_commandEnabled &&
|
||||||
@@ -848,8 +832,10 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
if (value.isNotEmpty) {
|
if (value.isNotEmpty) {
|
||||||
// ignore: no-empty-block
|
channel
|
||||||
channel.keyStroke(widget.parentMessage?.id).catchError((e) {});
|
.keyStroke(messageInputController.value.parentId)
|
||||||
|
// ignore: no-empty-block
|
||||||
|
.catchError((e) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionsLength = widget.actions.length;
|
var actionsLength = widget.actions.length;
|
||||||
@@ -869,7 +855,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
String _getHint(BuildContext context) {
|
String _getHint(BuildContext context) {
|
||||||
if (_commandEnabled && _chosenCommand!.name == 'giphy') {
|
if (_commandEnabled && messageInputController.value.command == 'giphy') {
|
||||||
return context.translations.searchGifLabel;
|
return context.translations.searchGifLabel;
|
||||||
}
|
}
|
||||||
if (messageInputController.attachments.isNotEmpty) {
|
if (messageInputController.attachments.isNotEmpty) {
|
||||||
@@ -1034,15 +1020,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
splits[splits.length - 1] = user.name;
|
splits[splits.length - 1] = user.name;
|
||||||
final rejoin = splits.join('@');
|
final rejoin = splits.join('@');
|
||||||
|
|
||||||
messageInputController.textEditingController.value = TextEditingValue(
|
messageInputController.text = rejoin +
|
||||||
text: rejoin +
|
messageInputController.text.substring(
|
||||||
messageInputController.text.substring(
|
messageInputController.selectionStart,
|
||||||
messageInputController.selectionStart,
|
);
|
||||||
),
|
|
||||||
selection: TextSelection.collapsed(
|
|
||||||
offset: rejoin.length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_onChangedDebounced.cancel();
|
_onChangedDebounced.cancel();
|
||||||
setState(() => _showMentionsOverlay = false);
|
setState(() => _showMentionsOverlay = false);
|
||||||
},
|
},
|
||||||
@@ -1077,22 +1059,17 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
void _chooseEmoji(List<String> splits, Emoji emoji) {
|
void _chooseEmoji(List<String> splits, Emoji emoji) {
|
||||||
final rejoin = splits.sublist(0, splits.length - 1).join(':') + emoji.char!;
|
final rejoin = splits.sublist(0, splits.length - 1).join(':') + emoji.char!;
|
||||||
|
|
||||||
messageInputController.textEditingController.value = TextEditingValue(
|
messageInputController.text = rejoin +
|
||||||
text: rejoin +
|
messageInputController.text.substring(
|
||||||
messageInputController.text.substring(
|
messageInputController.selectionStart,
|
||||||
messageInputController.selectionStart,
|
);
|
||||||
),
|
|
||||||
selection: TextSelection.collapsed(
|
|
||||||
offset: rejoin.length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setCommand(Command c) {
|
void _setCommand(Command c) {
|
||||||
messageInputController.clear();
|
messageInputController
|
||||||
|
..clear()
|
||||||
|
..command = c;
|
||||||
setState(() {
|
setState(() {
|
||||||
_chosenCommand = c;
|
|
||||||
_commandEnabled = true;
|
|
||||||
_showCommandsOverlay = false;
|
_showCommandsOverlay = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1133,9 +1110,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
(e) => ClipRRect(
|
(e) => ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
child: FileAttachment(
|
child: FileAttachment(
|
||||||
message: Message(
|
message: Message(), // dummy message
|
||||||
status: MessageSendingStatus.sending,
|
|
||||||
), // dummy message
|
|
||||||
attachment: e,
|
attachment: e,
|
||||||
size: Size(
|
size: Size(
|
||||||
MediaQuery.of(context).size.width * 0.65,
|
MediaQuery.of(context).size.width * 0.65,
|
||||||
@@ -1202,9 +1177,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
focusElevation: 0,
|
focusElevation: 0,
|
||||||
hoverElevation: 0,
|
hoverElevation: 0,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(
|
messageInputController.value =
|
||||||
() => messageInputController.attachments
|
messageInputController.value.copyWith(
|
||||||
.removeWhere((e) => e.id == attachment.id),
|
attachments: messageInputController.attachments
|
||||||
|
.where((it) => it.id != attachment.id)
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
fillColor:
|
fillColor:
|
||||||
@@ -1412,7 +1389,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
///
|
///
|
||||||
/// Note: Only meant to be used from outside the state.
|
/// Note: Only meant to be used from outside the state.
|
||||||
void addAttachment(Attachment attachment) {
|
void addAttachment(Attachment attachment) {
|
||||||
setState(() => _addAttachments([attachment]));
|
_addAttachments([attachment]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an attachment to the [messageInputController.attachments] map
|
/// Adds an attachment to the [messageInputController.attachments] map
|
||||||
@@ -1539,23 +1516,20 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
_addAttachments([
|
||||||
_addAttachments([
|
attachment.copyWith(
|
||||||
attachment.copyWith(
|
file: file,
|
||||||
file: file,
|
extraData: {...attachment.extraData}
|
||||||
extraData: {...attachment.extraData}
|
..update('file_size', ((_) => file!.size!)),
|
||||||
..update('file_size', ((_) => file!.size!)),
|
),
|
||||||
),
|
]);
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends the current message
|
/// Sends the current message
|
||||||
Future<void> sendMessage() async {
|
Future<void> sendMessage() async {
|
||||||
var text = messageInputController.text.trim();
|
var message = messageInputController.value;
|
||||||
final attachments = messageInputController.attachments;
|
|
||||||
|
|
||||||
if (text.isEmpty && attachments.isEmpty) {
|
if (messageInputController.isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1563,48 +1537,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
shouldKeepFocus ??= !_commandEnabled;
|
shouldKeepFocus ??= !_commandEnabled;
|
||||||
|
|
||||||
if (_commandEnabled) {
|
messageInputController.reset();
|
||||||
text = '${'/${_chosenCommand!.name} '}$text';
|
|
||||||
}
|
|
||||||
|
|
||||||
messageInputController
|
|
||||||
..text = ''
|
|
||||||
..clearAttachments();
|
|
||||||
widget.onQuotedMessageCleared?.call();
|
widget.onQuotedMessageCleared?.call();
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_commandEnabled = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
Message message;
|
|
||||||
if (widget.editMessage != null) {
|
|
||||||
message = widget.editMessage!.copyWith(
|
|
||||||
text: text,
|
|
||||||
attachments: attachments,
|
|
||||||
mentionedUsers: messageInputController.mentionedUsers
|
|
||||||
.where((u) => text.contains('@${u.name}'))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
message = (widget.initialMessage ?? Message()).copyWith(
|
|
||||||
parentId: widget.parentMessage?.id,
|
|
||||||
text: text,
|
|
||||||
attachments: attachments,
|
|
||||||
mentionedUsers: messageInputController.mentionedUsers
|
|
||||||
.where((u) => text.contains('@${u.name}'))
|
|
||||||
.toList(),
|
|
||||||
showInChannel: widget.parentMessage != null
|
|
||||||
? messageInputController.showInChannel
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.quotedMessage != null) {
|
|
||||||
message = message.copyWith(
|
|
||||||
quotedMessageId: widget.quotedMessage!.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.preMessageSending != null) {
|
if (widget.preMessageSending != null) {
|
||||||
message = await widget.preMessageSending!(message);
|
message = await widget.preMessageSending!(message);
|
||||||
}
|
}
|
||||||
@@ -1615,13 +1550,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
await streamChannel.reloadChannel();
|
await streamChannel.reloadChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
messageInputController.clearMentionedUsers();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Future sendingFuture;
|
Future sendingFuture;
|
||||||
if (widget.editMessage == null ||
|
if (!_isEditing) {
|
||||||
widget.editMessage!.status == MessageSendingStatus.failed ||
|
|
||||||
widget.editMessage!.status == MessageSendingStatus.sending) {
|
|
||||||
sendingFuture = channel.sendMessage(message);
|
sendingFuture = channel.sendMessage(message);
|
||||||
} else {
|
} else {
|
||||||
sendingFuture = channel.updateMessage(message);
|
sendingFuture = channel.updateMessage(message);
|
||||||
@@ -1635,7 +1566,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
final resp = await sendingFuture;
|
final resp = await sendingFuture;
|
||||||
if (resp.message?.type == 'error') {
|
if (resp.message?.type == 'error') {
|
||||||
_parseExistingMessage(message);
|
messageInputController.value = message;
|
||||||
}
|
}
|
||||||
_startSlowMode();
|
_startSlowMode();
|
||||||
widget.onMessageSent?.call(resp.message);
|
widget.onMessageSent?.call(resp.message);
|
||||||
@@ -1714,12 +1645,6 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _parseExistingMessage(Message message) {
|
|
||||||
final messageText = message.text;
|
|
||||||
if (messageText != null) messageInputController.text = messageText;
|
|
||||||
_addAttachments(message.attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
messageInputController.textEditingController
|
messageInputController.textEditingController
|
||||||
@@ -1731,19 +1656,12 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _initialized = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
_streamChatTheme = StreamChatTheme.of(context);
|
_streamChatTheme = StreamChatTheme.of(context);
|
||||||
_messageInputTheme = MessageInputTheme.of(context);
|
_messageInputTheme = MessageInputTheme.of(context);
|
||||||
if (widget.editMessage == null && _timeOut <= 0) _startSlowMode();
|
if (!_isEditing && _timeOut <= 0) _startSlowMode();
|
||||||
|
|
||||||
if ((widget.editMessage != null || widget.initialMessage != null) &&
|
|
||||||
!_initialized) {
|
|
||||||
FocusScope.of(context).requestFocus(_focusNode);
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -116,8 +116,8 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var _streamChatTheme = StreamChatTheme.of(context);
|
final _streamChatTheme = StreamChatTheme.of(context);
|
||||||
var messageInputController = widget.messageInputController;
|
final messageInputController = widget.messageInputController;
|
||||||
|
|
||||||
final _attachmentContainsImage =
|
final _attachmentContainsImage =
|
||||||
messageInputController.attachments.any((it) => it.type == 'image');
|
messageInputController.attachments.any((it) => it.type == 'image');
|
||||||
+3
-3
@@ -23,7 +23,7 @@ class StreamMessageSendButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var _streamChatTheme = StreamChatTheme.of(context);
|
final _streamChatTheme = StreamChatTheme.of(context);
|
||||||
|
|
||||||
late Widget sendButton;
|
late Widget sendButton;
|
||||||
if (timeOut > 0) {
|
if (timeOut > 0) {
|
||||||
@@ -46,7 +46,7 @@ class StreamMessageSendButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildIdleSendButton(BuildContext context) {
|
Widget _buildIdleSendButton(BuildContext context) {
|
||||||
var _messageInputTheme = MessageInputTheme.of(context);
|
final _messageInputTheme = MessageInputTheme.of(context);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
@@ -58,7 +58,7 @@ class StreamMessageSendButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSendButton(BuildContext context) {
|
Widget _buildSendButton(BuildContext context) {
|
||||||
var _messageInputTheme = MessageInputTheme.of(context);
|
final _messageInputTheme = MessageInputTheme.of(context);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
+2
@@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: prefer-trailing-comma, cascade_invocations
|
||||||
|
|
||||||
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
@@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart' hide TextTheme;
|
import 'package:flutter/material.dart' hide TextTheme;
|
||||||
import 'package:stream_chat_flutter/src/channel_preview.dart';
|
import 'package:stream_chat_flutter/src/channel_preview.dart';
|
||||||
import 'package:stream_chat_flutter/src/gradient_avatar.dart';
|
import 'package:stream_chat_flutter/src/gradient_avatar.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_input.dart';
|
import 'package:stream_chat_flutter/src/message_input/message_input.dart';
|
||||||
import 'package:stream_chat_flutter/src/reaction_icon.dart';
|
import 'package:stream_chat_flutter/src/reaction_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ export 'src/localization/stream_chat_localizations.dart';
|
|||||||
export 'src/localization/translations.dart' show DefaultTranslations;
|
export 'src/localization/translations.dart' show DefaultTranslations;
|
||||||
export 'src/mention_tile.dart';
|
export 'src/mention_tile.dart';
|
||||||
export 'src/message_action.dart';
|
export 'src/message_action.dart';
|
||||||
export 'src/message_input.dart';
|
export 'src/message_input/message_input.dart';
|
||||||
export 'src/message_list_view.dart';
|
export 'src/message_list_view.dart';
|
||||||
export 'src/message_search_item.dart';
|
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/message_input/countdown_button.dart';
|
||||||
export 'src/mip/stream_attachment_picker.dart';
|
export 'src/message_input/stream_attachment_picker.dart';
|
||||||
export 'src/mip/stream_message_send_button.dart';
|
export 'src/message_input/stream_message_send_button.dart';
|
||||||
export 'src/mip/stream_message_text_field.dart';
|
export 'src/message_input/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';
|
||||||
export 'src/reaction_picker.dart';
|
export 'src/reaction_picker.dart';
|
||||||
|
|||||||
@@ -4,29 +4,82 @@ 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';
|
||||||
|
|
||||||
|
typedef MessageValidator = bool Function(Message message);
|
||||||
|
|
||||||
/// Controller for storing and mutating a [Message] value.
|
/// 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.
|
||||||
///
|
///
|
||||||
/// This constructor treats a null [message] argument as if it were the empty
|
/// This constructor treats a null [message] argument as if it were the empty
|
||||||
/// message.
|
/// message.
|
||||||
factory MessageInputController({Message? message}) =>
|
factory MessageInputController({
|
||||||
MessageInputController._(message ?? Message());
|
Message? message,
|
||||||
|
MessageValidator? validator,
|
||||||
|
}) =>
|
||||||
|
MessageInputController._(
|
||||||
|
initialMessage: message ?? Message(),
|
||||||
|
validator: validator ?? _defaultValidator,
|
||||||
|
);
|
||||||
|
|
||||||
/// Creates a controller for an editable text field from an initial [text].
|
/// Creates a controller for an editable text field from an initial [text].
|
||||||
factory MessageInputController.fromText(String? text) =>
|
factory MessageInputController.fromText(
|
||||||
MessageInputController._(Message(text: text));
|
String? text, {
|
||||||
|
MessageValidator? validator,
|
||||||
|
}) =>
|
||||||
|
MessageInputController._(
|
||||||
|
initialMessage: Message(text: text),
|
||||||
|
validator: validator ?? _defaultValidator,
|
||||||
|
);
|
||||||
|
|
||||||
/// Creates a controller for an editable text field from an initial
|
/// Creates a controller for an editable text field from an initial
|
||||||
/// [attachments].
|
/// [attachments].
|
||||||
factory MessageInputController.fromAttachments(
|
factory MessageInputController.fromAttachments(
|
||||||
List<Attachment> attachments,
|
List<Attachment> attachments, {
|
||||||
) =>
|
MessageValidator? validator,
|
||||||
MessageInputController._(Message(attachments: attachments));
|
}) =>
|
||||||
|
MessageInputController._(
|
||||||
|
initialMessage: Message(attachments: attachments),
|
||||||
|
validator: validator ?? _defaultValidator,
|
||||||
|
);
|
||||||
|
|
||||||
MessageInputController._(Message message)
|
MessageInputController._({
|
||||||
: _textEditingController = TextEditingController(text: message.text),
|
required Message initialMessage,
|
||||||
super(message);
|
this.validator = _defaultValidator,
|
||||||
|
}) : _textEditingController =
|
||||||
|
TextEditingController(text: initialMessage.text),
|
||||||
|
_initialMessage = initialMessage,
|
||||||
|
super(initialMessage) {
|
||||||
|
addListener(_textEditingSyncer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A callback function that validates the message.
|
||||||
|
final MessageValidator validator;
|
||||||
|
|
||||||
|
/// Checks if the message is valid.
|
||||||
|
bool get isValid => validator(value);
|
||||||
|
|
||||||
|
static bool _defaultValidator(Message message) =>
|
||||||
|
message.text?.isNotEmpty != true && message.attachments.isEmpty;
|
||||||
|
|
||||||
|
void _textEditingSyncer() {
|
||||||
|
final cleanText = value.command == null
|
||||||
|
? value.text
|
||||||
|
: value.text?.replaceFirst(
|
||||||
|
'/${value.command} ',
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cleanText != _textEditingController.text) {
|
||||||
|
final previousOffset = _textEditingController.value.selection.start;
|
||||||
|
final previousText = _textEditingController.text;
|
||||||
|
final diff = (cleanText?.length ?? 0) - previousText.length;
|
||||||
|
_textEditingController
|
||||||
|
..text = cleanText ?? ''
|
||||||
|
..selection = TextSelection.collapsed(
|
||||||
|
offset: previousOffset + diff,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
TextEditingController get textEditingController => _textEditingController;
|
TextEditingController get textEditingController => _textEditingController;
|
||||||
@@ -35,24 +88,29 @@ class MessageInputController extends ValueNotifier<Message> {
|
|||||||
///
|
///
|
||||||
String get text => _textEditingController.text;
|
String get text => _textEditingController.text;
|
||||||
|
|
||||||
|
final Message _initialMessage;
|
||||||
|
|
||||||
///
|
///
|
||||||
set message(Message message) {
|
set message(Message message) {
|
||||||
value = message;
|
value = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
set text(String newText) {
|
///
|
||||||
value = value.copyWith(text: newText);
|
set command(Command command) {
|
||||||
_textEditingController
|
value = value.copyWith(
|
||||||
..text = newText
|
command: command.name,
|
||||||
..selection = TextSelection.fromPosition(
|
text: '/${command.name} ',
|
||||||
TextPosition(offset: _textEditingController.text.length),
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
set text(String newText) {
|
||||||
set textEditingValue(TextEditingValue newValue) {
|
var newTextWithCommand = newText;
|
||||||
_textEditingController.value = newValue;
|
if (value.command != null) {
|
||||||
value = value.copyWith(text: _textEditingController.text);
|
if (!newText.startsWith('/${value.command}')) {
|
||||||
|
newTextWithCommand = '/${value.command} $newText';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = value.copyWith(text: newTextWithCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -147,9 +205,13 @@ class MessageInputController extends ValueNotifier<Message> {
|
|||||||
_textEditingController.clear();
|
_textEditingController.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the [value] to the initial [Message] value.
|
||||||
|
void reset() => value = _initialMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
removeListener(_textEditingSyncer);
|
||||||
_textEditingController.dispose();
|
_textEditingController.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user