diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index a060db61..b4a7df41 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -41,5 +41,14 @@
UIViewControllerBasedStatusBarAppearance
+ UIBackgroundModes
+
+ fetch
+ remote-notification
+
+ NSAppleMusicUsageDescription
+ Used to send message attachments
+ NSPhotoLibraryUsageDescription
+ Used to send message attachments
diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart
index d6cb0f00..a586c704 100644
--- a/lib/src/message_input.dart
+++ b/lib/src/message_input.dart
@@ -1,3 +1,7 @@
+import 'dart:io';
+
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
@@ -22,79 +26,186 @@ class _MessageInputState extends State {
final _textController = TextEditingController();
bool _messageIsPresent = false;
bool _typingStarted = false;
+ final List<_SendingAttachment> _attachments = [];
@override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
- child: Container(
- width: MediaQuery.of(context).size.width,
- padding: EdgeInsets.all(2),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(10.0),
- gradient: _typingStarted
- ? StreamChatTheme.of(context).channelTheme.inputGradient
- : null,
- ),
- child: Container(
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(10.0),
- ),
- child: Container(
- decoration: BoxDecoration(
- color: StreamChatTheme.of(context).channelTheme.inputBackground,
- borderRadius: BorderRadius.circular(10.0),
- border: Border.all(
- color: _typingStarted
- ? Colors.transparent
- : Colors.black.withOpacity(.2)),
- ),
- child: Flex(
- direction: Axis.horizontal,
- children: [
- Expanded(
- child: TextField(
- minLines: null,
- maxLines: null,
- onSubmitted: (_) {
- _sendMessage(context);
- },
- controller: _textController,
- onChanged: (s) {
- StreamChannel.of(context).channel.keyStroke();
- setState(() {
- _messageIsPresent = s.trim().isNotEmpty;
- });
- },
- onTap: () {
- setState(() {
- _typingStarted = true;
- });
- },
- style: Theme.of(context).textTheme.body1,
- autofocus: false,
- decoration: InputDecoration(
- hintText: 'Write a message',
- prefixText: ' ',
- border: InputBorder.none,
- ),
+ child: Stack(
+ overflow: Overflow.visible,
+ children: [
+ Positioned.fill(
+ child: Container(
+ width: MediaQuery.of(context).size.width,
+ padding: EdgeInsets.all(2),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(10.0),
+ gradient: _typingStarted
+ ? StreamChatTheme.of(context).channelTheme.inputGradient
+ : null,
+ ),
+ child: Container(
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(10.0),
+ ),
+ child: Container(
+ decoration: BoxDecoration(
+ color: StreamChatTheme.of(context)
+ .channelTheme
+ .inputBackground,
+ borderRadius: BorderRadius.circular(10.0),
+ border: Border.all(
+ color: _typingStarted
+ ? Colors.transparent
+ : Colors.black.withOpacity(.2)),
),
),
- AnimatedCrossFade(
- crossFadeState: _messageIsPresent
- ? CrossFadeState.showFirst
- : CrossFadeState.showSecond,
- firstChild: _buildSendButton(context),
- secondChild: SizedBox(),
- duration: Duration(milliseconds: 300),
- alignment: Alignment.center,
- ),
- ],
+ ),
),
),
- ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _buildAttachments(),
+ Flex(
+ direction: Axis.horizontal,
+ children: [
+ _buildAttachmentButton(),
+ Expanded(
+ child: TextField(
+ minLines: null,
+ maxLines: null,
+ onSubmitted: (_) {
+ _sendMessage(context);
+ },
+ controller: _textController,
+ onChanged: (s) {
+ StreamChannel.of(context).channel.keyStroke();
+ setState(() {
+ _messageIsPresent = s.trim().isNotEmpty;
+ });
+ },
+ onTap: () {
+ setState(() {
+ _typingStarted = true;
+ });
+ },
+ style: Theme.of(context).textTheme.body1,
+ autofocus: false,
+ decoration: InputDecoration(
+ hintText: 'Write a message',
+ prefixText: ' ',
+ border: InputBorder.none,
+ ),
+ ),
+ ),
+ AnimatedCrossFade(
+ crossFadeState:
+ (_messageIsPresent || _attachments.isNotEmpty)
+ ? CrossFadeState.showFirst
+ : CrossFadeState.showSecond,
+ firstChild: _buildSendButton(context),
+ secondChild: SizedBox(),
+ duration: Duration(milliseconds: 300),
+ alignment: Alignment.center,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildAttachments() {
+ return Wrap(
+ direction: Axis.horizontal,
+ children: _attachments
+ .map(
+ (attachment) => Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(10),
+ child: Stack(
+ children: [
+ Container(
+ height: 50,
+ width: 50,
+ child: Image.file(attachment.file),
+ ),
+ 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,
+ color: Colors.black,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ )
+ .toList(),
+ );
+ }
+
+ Material _buildAttachmentButton() {
+ return Material(
+ clipBehavior: Clip.hardEdge,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(32),
+ ),
+ color: Colors.transparent,
+ child: IconButton(
+ onPressed: () async {
+ final file = await FilePicker.getFile(type: FileType.IMAGE);
+ print(file.path);
+
+ final channel = StreamChannel.of(context).channel;
+
+ final bytes = await file.readAsBytes();
+ final res = await channel.sendFile(MultipartFile.fromBytes(
+ bytes,
+ filename: file.path.split('/').last,
+ ));
+
+ setState(() {
+ _attachments.add(_SendingAttachment(
+ url: res.file,
+ extension: file.path.split('.').last,
+ file: file,
+ ));
+ });
+ },
+ icon: Icon(
+ Icons.add_circle_outline,
),
),
);
@@ -104,12 +215,19 @@ class _MessageInputState extends State {
return IconTheme(
data:
StreamChatTheme.of(context).channelTheme.messageInputButtonIconTheme,
- child: IconButton(
- onPressed: () {
- _sendMessage(context);
- },
- icon: Icon(
- Icons.send,
+ child: Material(
+ clipBehavior: Clip.hardEdge,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(32),
+ ),
+ color: Colors.transparent,
+ child: IconButton(
+ onPressed: () {
+ _sendMessage(context);
+ },
+ icon: Icon(
+ Icons.send,
+ ),
),
),
);
@@ -117,11 +235,15 @@ class _MessageInputState extends State {
void _sendMessage(BuildContext context) {
final text = _textController.text.trim();
- if (text.isEmpty) {
+ if (text.isEmpty && _attachments.isEmpty) {
return;
}
+ final attachments = List.from(_attachments);
+
_textController.clear();
+ _attachments.clear();
+
setState(() {
_messageIsPresent = false;
_typingStarted = false;
@@ -134,6 +256,13 @@ class _MessageInputState extends State {
Message(
parentId: widget.parentMessage?.id,
text: text,
+ attachments: attachments.map((attachment) {
+ return Attachment(
+ imageUrl: attachment.url,
+ assetUrl: attachment.url,
+ type: 'image',
+ );
+ }).toList(),
),
)
.then((_) {
@@ -143,3 +272,15 @@ class _MessageInputState extends State {
});
}
}
+
+class _SendingAttachment {
+ final String url;
+ final String extension;
+ final File file;
+
+ _SendingAttachment({
+ this.url,
+ this.extension,
+ this.file,
+ });
+}
diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart
index e6ecf5e7..6fd6e4ae 100644
--- a/lib/src/message_widget.dart
+++ b/lib/src/message_widget.dart
@@ -211,7 +211,7 @@ class _MessageWidgetState extends State
borderRadius: boxDecoration.borderRadius,
child: Container(
decoration: boxDecoration,
- constraints: BoxConstraints.loose(Size.fromWidth(300)),
+ constraints: BoxConstraints.loose(Size(300, 500)),
child: Stack(
children: [
Column(
@@ -222,7 +222,7 @@ class _MessageWidgetState extends State
attachment.title != null
? Container(
constraints:
- BoxConstraints.loose(Size.fromHeight(70)),
+ BoxConstraints.loose(Size(300, 500)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
@@ -549,7 +549,8 @@ class _MessageWidgetState extends State
Attachment attachment,
) {
return CachedNetworkImage(
- imageUrl: attachment.thumbUrl ?? attachment.imageUrl,
+ imageUrl:
+ attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl,
fit: BoxFit.cover,
);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 3f13ea59..cf0220ca 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -18,7 +18,9 @@ dependencies:
url_launcher: ^5.4.1
video_player: ^0.10.7
chewie: ^0.9.8+1
- stream_chat: ^0.1.11
+ file_picker: ^1.4.3+2
+ stream_chat:
+ path: ../stream_chat_dart
dev_dependencies:
pedantic: ^1.8.0+1