Skip to content

Instantly share code, notes, and snippets.

@xsahil03x
Last active October 25, 2021 12:03
Show Gist options
  • Save xsahil03x/c554330517002125b502d0101ed0d3ff to your computer and use it in GitHub Desktop.
Save xsahil03x/c554330517002125b502d0101ed0d3ff to your computer and use it in GitHub Desktop.
StreamMessageTextField
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_chat/stream_chat.dart';
class MessageInputController extends ValueNotifier<Message> {
/// Creates a controller for an editable text field.
///
/// This constructor treats a null [message] argument as if it were the empty
/// message.
factory MessageInputController({Message? message}) =>
MessageInputController._(message ?? Message());
/// Creates a controller for an editable text field from an initial [text].
factory MessageInputController.fromText(String? text) =>
MessageInputController._(Message(text: text));
/// Creates a controller for an editable text field from an initial [attachments].
factory MessageInputController.fromAttachments(
List<Attachment> attachments,
) =>
MessageInputController._(Message(attachments: attachments));
MessageInputController._(Message message)
: _textEditingController = TextEditingController(text: message.text),
super(message) {
_textEditingController.addListener(_textEditingListener);
}
void _textEditingListener() {
final newText = _textEditingController.text;
value = value.copyWith(text: newText);
}
///
TextEditingController get textEditingController => _textEditingController;
final TextEditingController _textEditingController;
///
String get text => _textEditingController.text;
set text(String newText) {
_textEditingController.text = newText;
}
///
List<Attachment> get attachments => value.attachments;
set attachments(List<Attachment> attachments) {
value = value.copyWith(attachments: attachments);
}
///
void addAttachment(Attachment attachment) {
attachments = [...attachments, attachment];
}
///
void addAttachmentAt(int index, Attachment attachment) {
attachments = [...attachments]..insert(index, attachment);
}
///
void removeAttachment(Attachment attachment) {
attachments = [...attachments]..remove(attachment);
}
///
void removeAttachmentById(String attachmentId) {
attachments = [...attachments]..removeWhere((it) => it.id == attachmentId);
}
///
void removeAttachmentAt(int index) {
attachments = [...attachments]..removeAt(index);
}
///
List<User> get mentionedUsers => value.mentionedUsers;
set mentionedUsers(List<User> users) {
value = value.copyWith(mentionedUsers: users);
}
///
void addMentionedUser(User user) {
mentionedUsers = [...mentionedUsers, user];
}
///
void removeMentionedUser(User user) {
mentionedUsers = [...mentionedUsers]..remove(user);
}
///
void removeMentionedUserById(String userId) {
mentionedUsers = [...mentionedUsers]..removeWhere((it) => it.id == userId);
}
/// Set the [value] to empty.
///
/// 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,
/// 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() {
value = Message();
_textEditingController.clear();
}
@override
void dispose() {
_textEditingController.removeListener(_textEditingListener);
super.dispose();
}
}
/// A [RestorableProperty] that knows how to store and restore a
/// [MessageInputController].
///
/// The [MessageInputController] is accessible via the [value] getter. During
/// state restoration, the property will restore [MessageInputController.value] to
/// the value it had when the restoration data it is getting restored from was
/// collected.
class RestorableMessageInputController
extends RestorableChangeNotifier<MessageInputController> {
/// Creates a [RestorableMessageInputController].
///
/// This constructor treats a null `text` argument as if it were the empty
/// string.
RestorableMessageInputController({Message? message})
: _initialValue = message ?? Message();
/// Creates a [RestorableMessageInputController] from an initial
/// [TextEditingValue].
///
/// This constructor treats a null `value` argument as if it were
/// [TextEditingValue.empty].
factory RestorableMessageInputController.fromText(String? text) =>
RestorableMessageInputController(message: Message(text: text));
final Message _initialValue;
@override
MessageInputController createDefaultValue() {
return MessageInputController(message: _initialValue);
}
@override
MessageInputController fromPrimitives(Object? data) {
final message = Message.fromJson(json.decode(data! as String));
return MessageInputController(message: message);
}
@override
String toPrimitives() => json.encode(value.value);
}
class StreamMessageTextField extends StatefulWidget {
const StreamMessageTextField({
Key? key,
this.controller,
this.restorationId,
this.focusNode,
this.decoration = const InputDecoration(),
}) : super(key: key);
/// Controls the message being edited.
///
/// If null, this widget will create its own [MessageInputController].
final MessageInputController? controller;
/// Defines the keyboard focus for this widget.
///
/// The [focusNode] is a long-lived object that's typically managed by a
/// [StatefulWidget] parent. See [FocusNode] for more information.
///
/// To give the keyboard focus to this widget, provide a [focusNode] and then
/// use the current [FocusScope] to request the focus:
///
/// ```dart
/// FocusScope.of(context).requestFocus(myFocusNode);
/// ```
///
/// This happens automatically when the widget is tapped.
///
/// To be notified when the widget gains or loses the focus, add a listener
/// to the [focusNode]:
///
/// ```dart
/// focusNode.addListener(() { print(myFocusNode.hasFocus); });
/// ```
///
/// If null, this widget will create its own [FocusNode].
///
/// ## Keyboard
///
/// Requesting the focus will typically cause the keyboard to be shown
/// if it's not showing already.
///
/// On Android, the user can hide the keyboard - without changing the focus -
/// with the system back button. They can restore the keyboard's visibility
/// by tapping on a text field. The user might hide the keyboard and
/// switch to a physical keyboard, or they might just need to get it
/// out of the way for a moment, to expose something it's
/// obscuring. In this case requesting the focus again will not
/// cause the focus to change, and will not make the keyboard visible.
///
/// This widget builds an [EditableText] and will ensure that the keyboard is
/// showing when it is tapped by calling [EditableTextState.requestKeyboard()].
final FocusNode? focusNode;
/// The decoration to show around the text field.
///
/// By default, draws a horizontal line under the text field but can be
/// configured to show an icon, label, hint text, and error text.
///
/// Specify null to remove the decoration entirely (including the
/// extra padding introduced by the decoration to save space for the labels).
final InputDecoration? decoration;
/// Restoration ID to save and restore the state of the text field.
///
/// If non-null, the text field will persist and restore its current scroll
/// offset and - if no [controller] has been provided - the content of the
/// text field. If a [controller] has been provided, it is the responsibility
/// of the owner of that controller to persist and restore it, e.g. by using
/// a [RestorableTextEditingController].
///
/// The state of this widget is persisted in a [RestorationBucket] claimed
/// from the surrounding [RestorationScope] using the provided restoration ID.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
final String? restorationId;
@override
_StreamMessageTextFieldState createState() => _StreamMessageTextFieldState();
}
class _StreamMessageTextFieldState extends State<StreamMessageTextField>
with RestorationMixin<StreamMessageTextField> {
RestorableMessageInputController? _controller;
MessageInputController get _effectiveController =>
widget.controller ?? _controller!.value;
@override
void initState() {
super.initState();
if (widget.controller == null) {
_createLocalController();
}
}
void _createLocalController([Message? message]) {
assert(_controller == null, '');
_controller = RestorableMessageInputController(message: message);
}
@override
void didUpdateWidget(covariant StreamMessageTextField oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller == null && oldWidget.controller != null) {
_createLocalController(oldWidget.controller!.value);
} else if (widget.controller != null && oldWidget.controller == null) {
unregisterFromRestoration(_controller!);
_controller!.dispose();
_controller = null;
}
}
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
if (_controller != null) {
_registerController();
}
}
@override
String? get restorationId => widget.restorationId;
void _registerController() {
assert(_controller != null, '');
registerForRestoration(_controller!, 'controller');
}
late final _onChangedDebounced = debounce(
(String newText) => _effectiveController.text = newText,
const Duration(milliseconds: 350),
leading: true,
);
@override
Widget build(BuildContext context) => TextField(
focusNode: widget.focusNode,
decoration: widget.decoration,
controller: _effectiveController.textEditingController,
onChanged: (newText) => _onChangedDebounced([newText]),
);
@override
void dispose() {
_onChangedDebounced.cancel();
_controller?.dispose();
super.dispose();
}
}
@xsahil03x
Copy link
Author

Usage: channel.sendMessage(controller.value)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment