From 71577f884b38c17db141d19a87106a3942ac891b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 28 Apr 2020 16:38:07 +0200 Subject: [PATCH 1/8] add actions parameter to messageinput --- lib/src/message_input.dart | 61 +++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index bb60d891..87a67d30 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -18,6 +18,11 @@ import 'stream_channel.dart'; typedef FileUploader = Future Function(File, Channel); +enum ActionsLocation { + LEFT, + RIGHT, +} + /// Inactive state /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input_paint.png) @@ -73,6 +78,9 @@ class MessageInput extends StatefulWidget { this.doImageUploadRequest, this.doFileUploadRequest, this.initialMessage, + this.textEditingController, + this.actions, + this.actionsLocation = ActionsLocation.LEFT, }) : super(key: key); /// Message to edit @@ -102,6 +110,15 @@ class MessageInput extends StatefulWidget { /// Override file upload request final FileUploader doFileUploadRequest; + /// The text controller of the TextField + final TextEditingController textEditingController; + + /// List of action widgets + final List actions; + + /// The location of the custom actions + final ActionsLocation actionsLocation; + @override _MessageInputState createState() => _MessageInputState( doFileUploadRequest: doFileUploadRequest, @@ -116,7 +133,7 @@ class _MessageInputState extends State { FileUploader doImageUploadRequest; FileUploader doFileUploadRequest; - TextEditingController _textController; + TextEditingController textEditingController; bool _inputEnabled = true; bool _messageIsPresent = false; bool _typingStarted = false; @@ -165,8 +182,12 @@ class _MessageInputState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ if (!widget.disableAttachments) _buildAttachmentButton(), + if (widget.actionsLocation == ActionsLocation.LEFT) + ...widget.actions ?? [], _buildTextInput(context), _animateSendButton(context), + if (widget.actionsLocation == ActionsLocation.RIGHT) + ...widget.actions ?? [], ], ); } @@ -197,7 +218,7 @@ class _MessageInputState extends State { _sendMessage(context); }, keyboardType: widget.keyboardType, - controller: _textController, + controller: textEditingController, focusNode: _focusNode, onChanged: (s) { StreamChannel.of(context).channel.keyStroke(); @@ -216,10 +237,10 @@ class _MessageInputState extends State { Overlay.of(context).insert(_commandsOverlay); } - if (_textController.selection.isCollapsed && - (s[_textController.selection.start - 1] == '@' || - _textController.text - .substring(0, _textController.selection.start) + if (textEditingController.selection.isCollapsed && + (s[textEditingController.selection.start - 1] == '@' || + textEditingController.text + .substring(0, textEditingController.selection.start) .split(' ') .last .contains('@'))) { @@ -279,7 +300,7 @@ class _MessageInputState extends State { } OverlayEntry _buildCommandsOverlayEntry() { - final text = _textController.text; + final text = textEditingController.text; final commands = StreamChannel.of(context) .channel .config @@ -344,8 +365,8 @@ class _MessageInputState extends State { } OverlayEntry _buildMentionsOverlayEntry() { - final splits = _textController.text - .substring(0, _textController.value.selection.start) + final splits = textEditingController.text + .substring(0, textEditingController.value.selection.start) .split('@'); final query = splits.last.toLowerCase(); @@ -391,10 +412,10 @@ class _MessageInputState extends State { splits[splits.length - 1] = m.user.name; final rejoin = splits.join('@'); - _textController.value = TextEditingValue( + textEditingController.value = TextEditingValue( text: rejoin + - _textController.text - .substring(_textController.selection.start), + textEditingController.text.substring( + textEditingController.selection.start), selection: TextSelection.collapsed( offset: rejoin.length, ), @@ -413,7 +434,7 @@ class _MessageInputState extends State { } void _setCommand(Command c) { - _textController.value = TextEditingValue( + textEditingController.value = TextEditingValue( text: '/${c.name} ', selection: TextSelection.collapsed( offset: c.name.length + 2, @@ -726,14 +747,14 @@ class _MessageInputState extends State { } void _sendMessage(BuildContext context) { - final text = _textController.text.trim(); + final text = textEditingController.text.trim(); if (text.isEmpty && _attachments.isEmpty) { return; } final attachments = List<_SendingAttachment>.from(_attachments); - _textController.clear(); + textEditingController.clear(); _attachments.clear(); setState(() { @@ -821,7 +842,7 @@ class _MessageInputState extends State { _keyboardListener = KeyboardVisibility.onChange.listen((visible) { if (visible) { if (_commandsOverlay != null) { - if (_textController.text.startsWith('/')) { + if (textEditingController.text.startsWith('/')) { WidgetsBinding.instance.addPostFrameCallback((_) { _commandsOverlay = _buildCommandsOverlayEntry(); Overlay.of(context).insert(_commandsOverlay); @@ -830,7 +851,7 @@ class _MessageInputState extends State { } if (_mentionsOverlay != null) { - if (_textController.text.contains('@')) { + if (textEditingController.text.contains('@')) { WidgetsBinding.instance.addPostFrameCallback((_) { _mentionsOverlay = _buildCommandsOverlayEntry(); Overlay.of(context).insert(_mentionsOverlay); @@ -847,17 +868,17 @@ class _MessageInputState extends State { } }); + textEditingController = + widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null) { _parseExistingMessage(widget.editMessage); } else if (widget.initialMessage != null) { _parseExistingMessage(widget.initialMessage); - } else { - _textController = TextEditingController(); } } void _parseExistingMessage(Message message) { - _textController = TextEditingController(text: message.text); + textEditingController.text = message.text; _typingStarted = true; _messageIsPresent = true; From ff77d571c5a67028526bd9a5d3ea163fa3c1dfd5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 11:38:54 +0200 Subject: [PATCH 2/8] add AttachmentThumbnailBuilder and expose messageinputstate --- lib/src/message_input.dart | 248 +++++++++++++++++++++---------------- 1 file changed, 143 insertions(+), 105 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 87a67d30..a777c8b8 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -17,6 +17,8 @@ import '../stream_chat_flutter.dart'; import 'stream_channel.dart'; typedef FileUploader = Future Function(File, Channel); +typedef AttachmentThumbnailBuilder = Widget Function( + BuildContext, _SendingAttachment); enum ActionsLocation { LEFT, @@ -70,6 +72,7 @@ class MessageInput extends StatefulWidget { MessageInput({ Key key, this.onMessageSent, + this.preMessageSending, this.parentMessage, this.editMessage, this.maxHeight = 150, @@ -81,6 +84,7 @@ class MessageInput extends StatefulWidget { this.textEditingController, this.actions, this.actionsLocation = ActionsLocation.LEFT, + this.attachmentThumbnailBuilder, }) : super(key: key); /// Message to edit @@ -92,6 +96,10 @@ class MessageInput extends StatefulWidget { /// Function called after sending the message final void Function(Message) onMessageSent; + /// Function called right before sending the message + /// Use this to transform the message + final FutureOr Function(Message) preMessageSending; + /// Parent message in case of a thread final Message parentMessage; @@ -119,14 +127,31 @@ class MessageInput extends StatefulWidget { /// The location of the custom actions final ActionsLocation actionsLocation; + /// Map that defines a builder for an attachment type + final Map attachmentThumbnailBuilder; + @override - _MessageInputState createState() => _MessageInputState( + MessageInputState createState() => MessageInputState( doFileUploadRequest: doFileUploadRequest, doImageUploadRequest: doImageUploadRequest, ); + + /// Use this method to get the current [StreamChatState] instance + static MessageInputState of(BuildContext context) { + MessageInputState messageInputState; + + messageInputState = context.findAncestorStateOfType(); + + if (messageInputState == null) { + throw Exception( + 'You must have a MessageInput widget as anchestor of your widget tree'); + } + + return messageInputState; + } } -class _MessageInputState extends State { +class MessageInputState extends State { final List<_SendingAttachment> _attachments = []; final _focusNode = FocusNode(); final List _mentionedUsers = []; @@ -139,7 +164,7 @@ class _MessageInputState extends State { bool _typingStarted = false; OverlayEntry _commandsOverlay, _mentionsOverlay; - _MessageInputState({ + MessageInputState({ this.doImageUploadRequest, this.doFileUploadRequest, }) { @@ -215,7 +240,7 @@ class _MessageInputState extends State { minLines: null, maxLines: null, onSubmitted: (_) { - _sendMessage(context); + sendMessage(); }, keyboardType: widget.keyboardType, controller: textEditingController, @@ -473,34 +498,7 @@ class _MessageInputState extends State { width: 50, child: _buildAttachment(attachment), ), - Positioned( - height: 16, - width: 16, - top: 4, - right: 4, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - disabledElevation: 0, - hoverElevation: 0, - onPressed: () { - setState(() { - _attachments.remove(attachment); - }); - }, - fillColor: Colors.white.withOpacity(.5), - child: Center( - child: Icon( - Icons.close, - size: 15, - ), - ), - ), - ), + _buildRemoveButton(attachment), attachment.uploaded ? SizedBox() : Positioned.fill( @@ -520,20 +518,59 @@ class _MessageInputState extends State { ); } + Positioned _buildRemoveButton(_SendingAttachment attachment) { + return Positioned( + height: 16, + width: 16, + top: 4, + right: 4, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + disabledElevation: 0, + hoverElevation: 0, + onPressed: () { + setState(() { + _attachments.remove(attachment); + }); + }, + fillColor: Colors.white.withOpacity(.5), + child: Center( + child: Icon( + Icons.close, + size: 15, + ), + ), + ), + ); + } + Widget _buildAttachment(_SendingAttachment attachment) { - switch (attachment.type) { - case FileType.image: + if (widget.attachmentThumbnailBuilder + ?.containsKey(attachment.attachment.type) == + true) { + return widget.attachmentThumbnailBuilder[attachment.attachment.type]( + context, + attachment, + ); + } + switch (attachment.attachment.type) { + case 'image': return attachment.file != null ? Image.file( attachment.file, fit: BoxFit.cover, ) : Image.network( - attachment.url, + attachment.attachment.imageUrl, fit: BoxFit.cover, ); break; - case FileType.video: + case 'video': return Container( child: Icon(Icons.videocam), color: Colors.black26, @@ -596,7 +633,7 @@ class _MessageInputState extends State { leading: Icon(Icons.image), title: Text('Upload a photo'), onTap: () { - _pickFile(FileType.image, false); + pickFile('image', false); Navigator.pop(context); }, ), @@ -604,7 +641,7 @@ class _MessageInputState extends State { leading: Icon(Icons.video_library), title: Text('Upload a video'), onTap: () { - _pickFile(FileType.video, false); + pickFile('video', false); Navigator.pop(context); }, ), @@ -612,7 +649,7 @@ class _MessageInputState extends State { leading: Icon(Icons.camera_alt), title: Text('Photo from camera'), onTap: () { - _pickFile(FileType.image, true); + pickFile('image', true); Navigator.pop(context); }, ), @@ -620,7 +657,7 @@ class _MessageInputState extends State { leading: Icon(Icons.videocam), title: Text('Video from camera'), onTap: () { - _pickFile(FileType.video, true); + pickFile('video', true); Navigator.pop(context); }, ), @@ -628,7 +665,7 @@ class _MessageInputState extends State { leading: Icon(Icons.insert_drive_file), title: Text('Upload a file'), onTap: () { - _pickFile(FileType.any, false); + pickFile('file', false); Navigator.pop(context); }, ), @@ -637,12 +674,34 @@ class _MessageInputState extends State { }); } - void _pickFile(FileType type, bool camera) async { + /// Add an attachment to the sending message + /// Use this to add custom type attachments + void addAttachment(Attachment attachment) { + setState(() { + _attachments.add(_SendingAttachment( + attachment: attachment, + uploaded: true, + )); + }); + } + + /// Pick a file from the device + /// The [attachmentType] should be one of 'image', 'video' or 'file' + /// If [camera] is true then the camera will open + void pickFile(String attachmentType, [bool camera = false]) async { setState(() { _inputEnabled = false; }); File file; + FileType type; + if (attachmentType == 'image') { + type = FileType.image; + } else if (attachmentType == 'video') { + type = FileType.video; + } else if (attachmentType == 'file') { + type = FileType.any; + } if (camera) { if (type == FileType.image) { @@ -666,7 +725,10 @@ class _MessageInputState extends State { final attachment = _SendingAttachment( file: file, - type: type, + attachment: Attachment( + localUri: file.uri, + type: attachmentType, + ), ); setState(() { @@ -675,7 +737,15 @@ class _MessageInputState extends State { final url = await _uploadAttachment(file, type, channel); - attachment.url = url; + if (attachmentType == 'image') { + attachment.attachment = attachment.attachment.copyWith( + imageUrl: url, + ); + } else { + attachment.attachment = attachment.attachment.copyWith( + assetUrl: url, + ); + } setState(() { attachment.uploaded = true; @@ -735,7 +805,7 @@ class _MessageInputState extends State { child: IconButton( key: Key('sendButton'), onPressed: () { - _sendMessage(context); + sendMessage(); }, icon: Icon( Icons.send, @@ -746,7 +816,8 @@ class _MessageInputState extends State { ); } - void _sendMessage(BuildContext context) { + /// Sends the current message + void sendMessage() async { final text = textEditingController.text.trim(); if (text.isEmpty && _attachments.isEmpty) { return; @@ -782,15 +853,6 @@ class _MessageInputState extends State { mentionedUsers: _mentionedUsers.where((u) => text.contains('@${u.name}')).toList(), ); - - if (widget.editMessage.status == MessageSendingStatus.FAILED) { - sendingFuture = channel.sendMessage(message); - } - - sendingFuture = StreamChat.of(context).client.updateMessage( - message, - channel.cid, - ); } else { message = (widget.initialMessage ?? Message()).copyWith( parentId: widget.parentMessage?.id, @@ -799,10 +861,23 @@ class _MessageInputState extends State { mentionedUsers: _mentionedUsers.where((u) => text.contains('@${u.name}')).toList(), ); - sendingFuture = channel.sendMessage(message); } - sendingFuture.whenComplete(() { + if (widget.preMessageSending != null) { + message = await widget.preMessageSending(message); + } + + if (widget.editMessage == null || + widget.editMessage.status == MessageSendingStatus.FAILED) { + sendingFuture = channel.sendMessage(message); + } else { + sendingFuture = StreamChat.of(context).client.updateMessage( + message, + channel.cid, + ); + } + + return sendingFuture.whenComplete(() { if (widget.onMessageSent != null) { widget.onMessageSent(message); } @@ -811,23 +886,7 @@ class _MessageInputState extends State { Iterable _getAttachments(List<_SendingAttachment> attachments) { return attachments.map((attachment) { - String type; - switch (attachment.type) { - case FileType.image: - type = 'image'; - break; - case FileType.video: - type = 'video'; - break; - default: - type = 'file'; - } - return Attachment( - imageUrl: attachment.type == FileType.image ? attachment.url : null, - assetUrl: attachment.url, - type: type, - localUri: attachment.file.uri, - ); + return attachment.attachment; }); } @@ -870,10 +929,9 @@ class _MessageInputState extends State { textEditingController = widget.textEditingController ?? TextEditingController(); - if (widget.editMessage != null) { - _parseExistingMessage(widget.editMessage); - } else if (widget.initialMessage != null) { - _parseExistingMessage(widget.initialMessage); + if (widget.editMessage != null || widget.initialMessage != null) { + _parseExistingMessage( + widget.editMessage ?? widget.initialMessage != null); } } @@ -884,28 +942,10 @@ class _MessageInputState extends State { _messageIsPresent = true; message.attachments?.forEach((attachment) { - if (attachment.type == 'image') { - _attachments.add(_SendingAttachment( - type: FileType.image, - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl ?? - attachment.ogScrapeUrl, - uploaded: true, - )); - } else if (attachment.type == 'video') { - _attachments.add(_SendingAttachment( - type: FileType.video, - url: attachment.assetUrl, - uploaded: true, - )); - } else if (attachment.type != 'giphy') { - _attachments.add(_SendingAttachment( - type: FileType.any, - url: attachment.assetUrl, - uploaded: true, - )); - } + _attachments.add(_SendingAttachment( + attachment: attachment, + uploaded: true, + )); }); } @@ -929,15 +969,13 @@ class _MessageInputState extends State { } class _SendingAttachment { - final File file; - final FileType type; - String url; + File file; + Attachment attachment; bool uploaded; _SendingAttachment({ - this.url, this.file, - this.type, + this.attachment, this.uploaded = false, }); } From cc876a9b2d62134bcf3be290c3b59795717b92b9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 12:17:22 +0200 Subject: [PATCH 3/8] add DefaultAttachmentTypes --- lib/src/message_input.dart | 78 +++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index a777c8b8..8caacedf 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -18,11 +18,19 @@ import 'stream_channel.dart'; typedef FileUploader = Future Function(File, Channel); typedef AttachmentThumbnailBuilder = Widget Function( - BuildContext, _SendingAttachment); + BuildContext, + _SendingAttachment, +); enum ActionsLocation { - LEFT, - RIGHT, + left, + right, +} + +enum DefaultAttachmentTypes { + image, + video, + file, } /// Inactive state @@ -83,7 +91,7 @@ class MessageInput extends StatefulWidget { this.initialMessage, this.textEditingController, this.actions, - this.actionsLocation = ActionsLocation.LEFT, + this.actionsLocation = ActionsLocation.left, this.attachmentThumbnailBuilder, }) : super(key: key); @@ -207,11 +215,11 @@ class MessageInputState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.actionsLocation == ActionsLocation.LEFT) + if (widget.actionsLocation == ActionsLocation.left) ...widget.actions ?? [], _buildTextInput(context), _animateSendButton(context), - if (widget.actionsLocation == ActionsLocation.RIGHT) + if (widget.actionsLocation == ActionsLocation.right) ...widget.actions ?? [], ], ); @@ -558,15 +566,18 @@ class MessageInputState extends State { attachment, ); } + switch (attachment.attachment.type) { case 'image': + case 'giphy': return attachment.file != null ? Image.file( attachment.file, fit: BoxFit.cover, ) : Image.network( - attachment.attachment.imageUrl, + attachment.attachment.imageUrl ?? + attachment.attachment.thumbUrl, fit: BoxFit.cover, ); break; @@ -633,7 +644,7 @@ class MessageInputState extends State { leading: Icon(Icons.image), title: Text('Upload a photo'), onTap: () { - pickFile('image', false); + pickFile(DefaultAttachmentTypes.image, false); Navigator.pop(context); }, ), @@ -641,7 +652,7 @@ class MessageInputState extends State { leading: Icon(Icons.video_library), title: Text('Upload a video'), onTap: () { - pickFile('video', false); + pickFile(DefaultAttachmentTypes.video, false); Navigator.pop(context); }, ), @@ -649,7 +660,7 @@ class MessageInputState extends State { leading: Icon(Icons.camera_alt), title: Text('Photo from camera'), onTap: () { - pickFile('image', true); + pickFile(DefaultAttachmentTypes.image, true); Navigator.pop(context); }, ), @@ -657,7 +668,7 @@ class MessageInputState extends State { leading: Icon(Icons.videocam), title: Text('Video from camera'), onTap: () { - pickFile('video', true); + pickFile(DefaultAttachmentTypes.video, true); Navigator.pop(context); }, ), @@ -665,7 +676,7 @@ class MessageInputState extends State { leading: Icon(Icons.insert_drive_file), title: Text('Upload a file'), onTap: () { - pickFile('file', false); + pickFile(DefaultAttachmentTypes.file, false); Navigator.pop(context); }, ), @@ -686,30 +697,38 @@ class MessageInputState extends State { } /// Pick a file from the device - /// The [attachmentType] should be one of 'image', 'video' or 'file' /// If [camera] is true then the camera will open - void pickFile(String attachmentType, [bool camera = false]) async { + void pickFile(DefaultAttachmentTypes fileType, [bool camera = false]) async { setState(() { _inputEnabled = false; }); File file; - FileType type; - if (attachmentType == 'image') { - type = FileType.image; - } else if (attachmentType == 'video') { - type = FileType.video; - } else if (attachmentType == 'file') { - type = FileType.any; + String attachmentType; + + if (fileType == DefaultAttachmentTypes.image) { + attachmentType = 'image'; + } else if (fileType == DefaultAttachmentTypes.video) { + attachmentType = 'video'; + } else if (fileType == DefaultAttachmentTypes.file) { + attachmentType = 'file'; } if (camera) { - if (type == FileType.image) { + if (fileType == DefaultAttachmentTypes.image) { file = await ImagePicker.pickImage(source: ImageSource.camera); - } else if (type == FileType.video) { + } else if (fileType == DefaultAttachmentTypes.video) { file = await ImagePicker.pickVideo(source: ImageSource.camera); } } else { + FileType type; + if (fileType == DefaultAttachmentTypes.image) { + type = FileType.image; + } else if (fileType == DefaultAttachmentTypes.video) { + type = FileType.video; + } else if (fileType == DefaultAttachmentTypes.file) { + type = FileType.any; + } file = await FilePicker.getFile(type: type); } @@ -735,9 +754,9 @@ class MessageInputState extends State { _attachments.add(attachment); }); - final url = await _uploadAttachment(file, type, channel); + final url = await _uploadAttachment(file, fileType, channel); - if (attachmentType == 'image') { + if (fileType == DefaultAttachmentTypes.image) { attachment.attachment = attachment.attachment.copyWith( imageUrl: url, ); @@ -754,11 +773,11 @@ class MessageInputState extends State { Future _uploadAttachment( File file, - FileType type, + DefaultAttachmentTypes type, Channel channel, ) async { String url; - if (type == FileType.image) { + if (type == DefaultAttachmentTypes.image) { url = await doImageUploadRequest(file, channel); } else { url = await doFileUploadRequest(file, channel); @@ -846,10 +865,7 @@ class MessageInputState extends State { message = widget.editMessage.copyWith( parentId: widget.parentMessage?.id, text: text, - attachments: widget.editMessage.attachments - .where((attachment) => attachment.type == 'giphy') - .toList() + - _getAttachments(attachments).toList(), + attachments: _getAttachments(attachments).toList(), mentionedUsers: _mentionedUsers.where((u) => text.contains('@${u.name}')).toList(), ); From 6d45e2d9bb46e556950ca43204f6e115d3de6a57 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 14:02:42 +0200 Subject: [PATCH 4/8] expose showAttachmentModal --- lib/src/message_input.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 8caacedf..2d874bc9 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -604,7 +604,7 @@ class MessageInputState extends State { color: Colors.transparent, child: IconButton( onPressed: () { - _showAttachmentModal(); + showAttachmentModal(); }, icon: Icon( Icons.add_circle_outline, @@ -613,7 +613,7 @@ class MessageInputState extends State { ); } - void _showAttachmentModal() { + void showAttachmentModal() { if (_focusNode.hasFocus) { _focusNode.unfocus(); } From 9709010513de2400c586468803ccd29b551987c8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 14:08:19 +0200 Subject: [PATCH 5/8] cleanup --- lib/src/message_input.dart | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 2d874bc9..d70bd201 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -139,10 +139,7 @@ class MessageInput extends StatefulWidget { final Map attachmentThumbnailBuilder; @override - MessageInputState createState() => MessageInputState( - doFileUploadRequest: doFileUploadRequest, - doImageUploadRequest: doImageUploadRequest, - ); + MessageInputState createState() => MessageInputState(); /// Use this method to get the current [StreamChatState] instance static MessageInputState of(BuildContext context) { @@ -163,22 +160,14 @@ class MessageInputState extends State { final List<_SendingAttachment> _attachments = []; final _focusNode = FocusNode(); final List _mentionedUsers = []; - FileUploader doImageUploadRequest; - FileUploader doFileUploadRequest; - TextEditingController textEditingController; bool _inputEnabled = true; bool _messageIsPresent = false; bool _typingStarted = false; OverlayEntry _commandsOverlay, _mentionsOverlay; - MessageInputState({ - this.doImageUploadRequest, - this.doFileUploadRequest, - }) { - doImageUploadRequest ??= _uploadImage; - doFileUploadRequest ??= _uploadFile; - } + /// The editing controller passed to the input TextField + TextEditingController textEditingController; @override Widget build(BuildContext context) { @@ -613,6 +602,7 @@ class MessageInputState extends State { ); } + /// Show the attachment modal, making the user choose where to pick a media from void showAttachmentModal() { if (_focusNode.hasFocus) { _focusNode.unfocus(); @@ -778,9 +768,17 @@ class MessageInputState extends State { ) async { String url; if (type == DefaultAttachmentTypes.image) { - url = await doImageUploadRequest(file, channel); + if (widget.doImageUploadRequest != null) { + url = await widget.doImageUploadRequest(file, channel); + } else { + url = await _uploadImage(file, channel); + } } else { - url = await doFileUploadRequest(file, channel); + if (widget.doFileUploadRequest != null) { + url = await widget.doFileUploadRequest(file, channel); + } else { + url = await _uploadFile(file, channel); + } } return url; } From eee1426b52d32b859451ed72ddecbdc1911a3023 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 15:17:59 +0200 Subject: [PATCH 6/8] refactoring --- example/lib/main.dart | 1 - lib/src/message_input.dart | 7 +++++-- lib/src/message_widget.dart | 4 ---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index ac895cfa..50a82270 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -67,7 +67,6 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - persistenceEnabled: false, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, ); diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index d70bd201..d5d37c32 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -547,6 +547,9 @@ class MessageInputState extends State { } Widget _buildAttachment(_SendingAttachment attachment) { + print('attachment.attachment.toJson(): ${attachment.attachment.toJson()}'); + print( + 'widget.attachmentThumbnailBuilder: ${widget.attachmentThumbnailBuilder}'); if (widget.attachmentThumbnailBuilder ?.containsKey(attachment.attachment.type) == true) { @@ -944,8 +947,7 @@ class MessageInputState extends State { textEditingController = widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null || widget.initialMessage != null) { - _parseExistingMessage( - widget.editMessage ?? widget.initialMessage != null); + _parseExistingMessage(widget.editMessage ?? widget.initialMessage); } } @@ -956,6 +958,7 @@ class MessageInputState extends State { _messageIsPresent = true; message.attachments?.forEach((attachment) { + print('attachment: ${attachment.toJson()}'); _attachments.add(_SendingAttachment( attachment: attachment, uploaded: true, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 49b8fc10..aefb5524 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -712,10 +712,6 @@ class _MessageWidgetState extends State child: Icon( Icons.close, size: 15, - color: - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, ), ), ), From df4f60c1e088843d3becc0a7bc510122a064d81b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 17:29:17 +0200 Subject: [PATCH 7/8] update llc --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9848410a..0c0b8aea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: file_picker: ^1.6.3+2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+6 + stream_chat: ^0.2.0-alpha+8 mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From cb09f0d09e9462b9e9030134341f2e3aa6d2fb8d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 30 Apr 2020 11:10:33 +0200 Subject: [PATCH 8/8] add editMessageInputBuilder to customize the messageinput while editing messages --- lib/src/message_input.dart | 18 ++++++++--------- lib/src/message_list_view.dart | 8 ++++++++ lib/src/message_widget.dart | 36 ++++++++++++++++++++-------------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index d5d37c32..e4de6915 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -92,7 +92,7 @@ class MessageInput extends StatefulWidget { this.textEditingController, this.actions, this.actionsLocation = ActionsLocation.left, - this.attachmentThumbnailBuilder, + this.attachmentThumbnailBuilders, }) : super(key: key); /// Message to edit @@ -135,8 +135,8 @@ class MessageInput extends StatefulWidget { /// The location of the custom actions final ActionsLocation actionsLocation; - /// Map that defines a builder for an attachment type - final Map attachmentThumbnailBuilder; + /// Map that defines a thumbnail builder for an attachment type + final Map attachmentThumbnailBuilders; @override MessageInputState createState() => MessageInputState(); @@ -547,13 +547,10 @@ class MessageInputState extends State { } Widget _buildAttachment(_SendingAttachment attachment) { - print('attachment.attachment.toJson(): ${attachment.attachment.toJson()}'); - print( - 'widget.attachmentThumbnailBuilder: ${widget.attachmentThumbnailBuilder}'); - if (widget.attachmentThumbnailBuilder + if (widget.attachmentThumbnailBuilders ?.containsKey(attachment.attachment.type) == true) { - return widget.attachmentThumbnailBuilder[attachment.attachment.type]( + return widget.attachmentThumbnailBuilders[attachment.attachment.type]( context, attachment, ); @@ -897,6 +894,10 @@ class MessageInputState extends State { return sendingFuture.whenComplete(() { if (widget.onMessageSent != null) { widget.onMessageSent(message); + } else { + if (widget.editMessage != null) { + Navigator.pop(context); + } } }); } @@ -958,7 +959,6 @@ class MessageInputState extends State { _messageIsPresent = true; message.attachments?.forEach((attachment) { - print('attachment: ${attachment.toJson()}'); _attachments.add(_SendingAttachment( attachment: attachment, uploaded: true, diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 077c5289..fe407971 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -73,6 +73,7 @@ class MessageListView extends StatefulWidget { this.attachmentBuilders, this.dateDividerBuilder, this.showAvatar = true, + this.editMessageInputBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -115,6 +116,9 @@ class MessageListView extends StatefulWidget { /// if true shows the user avatar final bool showAvatar; + /// Builder used to build the message input to edit a message + final Widget Function(BuildContext, Message) editMessageInputBuilder; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -177,6 +181,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), @@ -243,6 +248,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ); } } @@ -337,6 +343,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ); } @@ -379,6 +386,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index aefb5524..237a14fc 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -51,6 +51,7 @@ class MessageWidget extends StatefulWidget { this.showVideoFullScreen = true, this.attachmentBuilders, this.showAvatar = true, + this.editMessageInputBuilder, }) : super(key: key); /// Function called on mention tap @@ -89,6 +90,9 @@ class MessageWidget extends StatefulWidget { /// if true shows the user avatar final bool showAvatar; + /// Builder used to build the message input to edit a message + final Widget Function(BuildContext, Message) editMessageInputBuilder; + @override _MessageWidgetState createState() => _MessageWidgetState(); } @@ -723,21 +727,23 @@ class _MessageWidgetState extends State padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), - child: MessageInput( - editMessage: widget.message, - parentMessage: widget.isParent - ? StreamChannel.of(context) - .channel - .state - .messages - .firstWhere((message) => - message.id == widget.message.parentId) - : null, - onMessageSent: (_) { - FocusScope.of(context).unfocus(); - Navigator.pop(context); - }, - ), + child: widget.editMessageInputBuilder != null + ? widget.editMessageInputBuilder(context, widget.message) + : MessageInput( + editMessage: widget.message, + parentMessage: widget.isParent + ? StreamChannel.of(context) + .channel + .state + .messages + .firstWhere((message) => + message.id == widget.message.parentId) + : null, + onMessageSent: (_) { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + }, + ), ), ], ),