add edit message feature

This commit is contained in:
Salvatore Giordano
2020-03-03 11:03:36 +01:00
parent d97a67d523
commit f3a6ba68a4
4 changed files with 227 additions and 48 deletions
+7 -7
View File
@@ -3,13 +3,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
void main() async { void main() async {
final client = Client( final client = Client(
'b67pax5b2wdq', 's2dxdhpxd94g',
logLevel: Level.INFO, logLevel: Level.INFO,
); );
await client.setUser( await client.setUser(
User(id: 'falling-mountain-7'), User(id: 'falling-mountain-7'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNDE5MjI5YTMtODRhMC00MDZiLTkzNzEtN2NlOWE0ZTBhNjc2In0.J3SjGH4e4v7b3cg5EgWkljTxXj_HeHpCWn5ujEVv_H8',
); );
runApp(MyApp(client)); runApp(MyApp(client));
@@ -38,11 +38,11 @@ class ChannelListPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ChannelListView( body: ChannelListView(
filter: { // filter: {
'members': { // 'members': {
'\$in': [StreamChat.of(context).user.id], // '\$in': [StreamChat.of(context).user.id],
} // }
}, // },
sort: [SortOption('last_message_at')], sort: [SortOption('last_message_at')],
pagination: PaginationParams( pagination: PaginationParams(
limit: 20, limit: 20,
+112 -39
View File
@@ -8,10 +8,13 @@ import 'package:stream_chat/stream_chat.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/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import '../stream_chat_flutter.dart';
import 'stream_channel.dart'; import 'stream_channel.dart';
/// 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.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input_paint.png)
/// Focused state
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2_paint.png)
/// ///
@@ -55,8 +58,12 @@ class MessageInput extends StatefulWidget {
Key key, Key key,
this.onMessageSent, this.onMessageSent,
this.parentMessage, this.parentMessage,
this.editMessage,
}) : super(key: key); }) : super(key: key);
/// Message to edit
final Message editMessage;
/// Function called after sending the message /// Function called after sending the message
final void Function(Message) onMessageSent; final void Function(Message) onMessageSent;
@@ -68,11 +75,13 @@ class MessageInput extends StatefulWidget {
} }
class _MessageInputState extends State<MessageInput> { class _MessageInputState extends State<MessageInput> {
final _textController = TextEditingController(); TextEditingController _textController;
bool _messageIsPresent = false; bool _messageIsPresent = false;
bool _typingStarted = false; bool _typingStarted = false;
final List<_SendingAttachment> _attachments = []; final List<_SendingAttachment> _attachments = [];
final _focusNode = FocusNode();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
@@ -87,9 +96,7 @@ class _MessageInputState extends State<MessageInput> {
padding: EdgeInsets.all(2), padding: EdgeInsets.all(2),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
gradient: _typingStarted gradient: _getGradient(context),
? StreamChatTheme.of(context).channelTheme.inputGradient
: null,
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -127,6 +134,7 @@ class _MessageInputState extends State<MessageInput> {
_sendMessage(context); _sendMessage(context);
}, },
controller: _textController, controller: _textController,
focusNode: _focusNode,
onChanged: (s) { onChanged: (s) {
StreamChannel.of(context).channel.keyStroke(); StreamChannel.of(context).channel.keyStroke();
setState(() { setState(() {
@@ -167,6 +175,19 @@ class _MessageInputState extends State<MessageInput> {
); );
} }
Gradient _getGradient(BuildContext context) {
if (_typingStarted) {
if (widget.editMessage == null) {
return StreamChatTheme.of(context).channelTheme.inputGradient;
}
return LinearGradient(
colors: [Colors.lightGreen, Colors.green],
);
} else {
return null;
}
}
Widget _buildAttachments() { Widget _buildAttachments() {
return Wrap( return Wrap(
direction: Axis.horizontal, direction: Axis.horizontal,
@@ -234,10 +255,15 @@ class _MessageInputState extends State<MessageInput> {
Widget _buildAttachment(_SendingAttachment attachment) { Widget _buildAttachment(_SendingAttachment attachment) {
switch (attachment.type) { switch (attachment.type) {
case FileType.IMAGE: case FileType.IMAGE:
return Image.file( return attachment.file != null
attachment.file, ? Image.file(
fit: BoxFit.cover, attachment.file,
); fit: BoxFit.cover,
)
: Image.network(
attachment.url,
fit: BoxFit.cover,
);
break; break;
case FileType.VIDEO: case FileType.VIDEO:
return Container( return Container(
@@ -414,50 +440,97 @@ class _MessageInputState extends State<MessageInput> {
}); });
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
StreamChannel.of(context) if (widget.editMessage != null) {
.channel final message = widget.editMessage.copyWith(
.sendMessage( parentId: widget.parentMessage?.id,
Message( text: text,
parentId: widget.parentMessage?.id, attachments: _getAttachments(attachments).toList(),
text: text, );
attachments: attachments.map((attachment) { StreamChat.of(context).client.updateMessage(message).then((_) {
String type; if (widget.onMessageSent != null) {
switch (attachment.type) { widget.onMessageSent(message);
case FileType.IMAGE: }
type = 'image'; });
break; } else {
case FileType.VIDEO: final message = Message(
type = 'video'; parentId: widget.parentMessage?.id,
break; text: text,
default: attachments: _getAttachments(attachments).toList(),
type = 'file'; );
} StreamChannel.of(context).channel.sendMessage(message).then((_) {
return Attachment( if (widget.onMessageSent != null) {
imageUrl: widget.onMessageSent(message);
attachment.type == FileType.IMAGE ? attachment.url : null, }
assetUrl: attachment.url, });
type: type, }
); }
}).toList(),
), Iterable<Attachment> _getAttachments(List<_SendingAttachment> attachments) {
) return attachments.map((attachment) {
.then((_) { String type;
if (widget.onMessageSent != null) { switch (attachment.type) {
widget.onMessageSent(Message(text: text)); 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,
);
}); });
} }
@override
void initState() {
super.initState();
if (widget.editMessage != null) {
_textController = TextEditingController(text: widget.editMessage.text);
_typingStarted = true;
_messageIsPresent = true;
widget.editMessage.attachments.forEach((attachment) {
if (attachment.type == 'image') {
_attachments.add(_SendingAttachment(
type: FileType.IMAGE,
url: attachment.imageUrl,
uploaded: true,
));
}
});
} else {
_textController = TextEditingController();
}
}
bool _focused = false;
@override
void didChangeDependencies() {
if (widget.editMessage != null && !_focused) {
FocusScope.of(context).requestFocus(_focusNode);
_focused = true;
}
super.didChangeDependencies();
}
} }
class _SendingAttachment { class _SendingAttachment {
final File file; final File file;
final FileType type; final FileType type;
String url; String url;
bool uploaded = false; bool uploaded;
_SendingAttachment({ _SendingAttachment({
this.url, this.url,
this.file, this.file,
this.type, this.type,
this.uploaded = false,
}); });
} }
+107 -1
View File
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/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/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
@@ -34,8 +35,12 @@ class MessageWidget extends StatefulWidget {
@required this.nextMessage, @required this.nextMessage,
this.onThreadTap, this.onThreadTap,
this.isParent = false, this.isParent = false,
this.onMessageEdit,
}) : super(key: key); }) : super(key: key);
/// Function called when editing a message
final Function(Message) onMessageEdit;
/// This message /// This message
final Message message; final Message message;
@@ -399,7 +404,7 @@ class _MessageWidgetState extends State<MessageWidget>
), ),
), ),
context: context, context: context,
builder: (context) { builder: (_) {
final fontSize = 30.0; final fontSize = 30.0;
final textStyle = TextStyle( final textStyle = TextStyle(
fontSize: fontSize, fontSize: fontSize,
@@ -478,6 +483,22 @@ class _MessageWidgetState extends State<MessageWidget>
}, },
) )
: SizedBox(), : SizedBox(),
isMyMessage
? FlatButton(
child: Padding(
padding: const EdgeInsets.all(28.0),
child: Text(
'Edit message',
style: Theme.of(context).textTheme.headline,
),
),
onPressed: () async {
await Navigator.pop(context);
_showEditBottomSheet(context);
},
)
: SizedBox(),
streamChannel.channel.config.replies streamChannel.channel.config.replies
? FlatButton( ? FlatButton(
child: Padding( child: Padding(
@@ -499,6 +520,91 @@ class _MessageWidgetState extends State<MessageWidget>
}); });
} }
void _showEditBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
elevation: 2,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
builder: (context) {
return Flex(
direction: Axis.vertical,
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
top: 16.0,
left: 16.0,
right: 16.0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'Edit message',
style: Theme.of(context).textTheme.title,
),
Container(
height: 30,
padding: const EdgeInsets.all(2.0),
child: AspectRatio(
aspectRatio: 1,
child: RawMaterialButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
elevation: 0,
highlightElevation: 0,
focusElevation: 0,
disabledElevation: 0,
hoverElevation: 0,
onPressed: () {
Navigator.of(context).pop();
},
fillColor: Colors.black.withOpacity(.1),
padding: EdgeInsets.all(4),
child: Icon(
Icons.close,
size: 15,
color: Colors.black,
),
),
),
),
],
),
),
Padding(
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: (_) {
Navigator.pop(context);
},
),
),
],
);
},
);
}
Widget _buildReactions(bool isMyMessage) { Widget _buildReactions(bool isMyMessage) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
+1 -1
View File
@@ -20,7 +20,7 @@ dependencies:
chewie: ^0.9.8+1 chewie: ^0.9.8+1
file_picker: ^1.4.3+2 file_picker: ^1.4.3+2
image_picker: ^0.6.3+4 image_picker: ^0.6.3+4
stream_chat: ^0.1.12 stream_chat: ^0.1.13
dev_dependencies: dev_dependencies:
pedantic: ^1.8.0+1 pedantic: ^1.8.0+1