Merge branch 'feature/new-ui' of github.com:GetStream/stream-chat-flutter into feature/new-chat-screens

 Conflicts:
	lib/src/message_input.dart
This commit is contained in:
xsahil03x
2020-11-19 18:22:56 +05:30
8 changed files with 147 additions and 106 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Flutter action - name: Flutter action
uses: subosito/flutter-action@v1.3.2 uses: subosito/flutter-action@v1.4.0
with: with:
channel: 'stable' channel: 'stable'
- name: Get dependencies - name: Get dependencies
+4
View File
@@ -1,3 +1,7 @@
## 0.2.13+1
- Use TextEditingController.addListener instead of TextField.onChanged
## 0.2.13 ## 0.2.13
- Update llc dependency - Update llc dependency
+5
View File
@@ -55,6 +55,11 @@ We also use [video_player](https://pub.dev/packages/video_player) to reproduce v
To pick images from the camera, we use the [image_picker](https://pub.dev/packages/image_picker) plugin. To pick images from the camera, we use the [image_picker](https://pub.dev/packages/image_picker) plugin.
Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check the requirements. Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check the requirements.
### Troubleshooting
It may happen that you have some problems building the app.
If it seems related to the [flutter file picker plugin](https://github.com/miguelpruivo/flutter_file_picker) make sure to check [this page](https://github.com/miguelpruivo/flutter_file_picker/wiki/Troubleshooting)
## Docs ## Docs
### Business logic components ### Business logic components
+1 -1
View File
@@ -1 +1 @@
bb5f9103d9045cd6244bcae1f5f343e5 c3e639ccf9b069e37a7d1345194e6b99
+95 -103
View File
@@ -19,6 +19,7 @@ import 'package:stream_chat_flutter/src/media_list_view.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 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:stream_chat_flutter/src/video_thumbnail.dart';
import 'package:substring_highlight/substring_highlight.dart'; import 'package:substring_highlight/substring_highlight.dart';
import '../stream_chat_flutter.dart'; import '../stream_chat_flutter.dart';
@@ -41,6 +42,8 @@ enum DefaultAttachmentTypes {
file, file,
} }
const _kMinMediaPickerSize = 360.0;
/// Inactive state /// 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)
@@ -182,7 +185,7 @@ class MessageInputState extends State<MessageInput> {
bool _sendAsDm = false; bool _sendAsDm = false;
bool _openFilePickerSection = false; bool _openFilePickerSection = false;
int _filePickerIndex = 0; int _filePickerIndex = 0;
double _filePickerSize = 250.0; double _filePickerSize = _kMinMediaPickerSize;
/// The editing controller passed to the input TextField /// The editing controller passed to the input TextField
TextEditingController textEditingController; TextEditingController textEditingController;
@@ -363,30 +366,6 @@ class MessageInputState extends State<MessageInput> {
keyboardType: widget.keyboardType, keyboardType: widget.keyboardType,
controller: textEditingController, controller: textEditingController,
focusNode: _focusNode, focusNode: _focusNode,
onChanged: (s) {
StreamChannel.of(context)
.channel
.keyStroke()
.catchError((e) {});
setState(() {
_messageIsPresent = s.trim().isNotEmpty;
_actionsShrunk = s.trim().isNotEmpty;
});
_commandsOverlay?.remove();
_commandsOverlay = null;
_mentionsOverlay?.remove();
_mentionsOverlay = null;
_emojiOverlay?.remove();
_emojiOverlay = null;
_checkCommands(s, context);
_checkMentions(s, context);
_checkEmoji(s, context);
},
style: Theme.of(context).textTheme.bodyText2, style: Theme.of(context).textTheme.bodyText2,
autofocus: false, autofocus: false,
textAlignVertical: TextAlignVertical.center, textAlignVertical: TextAlignVertical.center,
@@ -443,6 +422,28 @@ class MessageInputState extends State<MessageInput> {
); );
} }
void _onChanged(BuildContext context, String s) {
StreamChannel.of(context).channel.keyStroke();
setState(() {
_messageIsPresent = s.trim().isNotEmpty;
_actionsShrunk = s.trim().isNotEmpty;
});
_commandsOverlay?.remove();
_commandsOverlay = null;
_mentionsOverlay?.remove();
_mentionsOverlay = null;
_emojiOverlay?.remove();
_emojiOverlay = null;
_checkCommands(s.trim(), context);
_checkMentions(s, context);
_checkEmoji(s, context);
}
String _getHint() { String _getHint() {
if (_commandEnabled && _chosenCommand.name == 'giphy') { if (_commandEnabled && _chosenCommand.name == 'giphy') {
return 'Search GIFs'; return 'Search GIFs';
@@ -454,14 +455,14 @@ class MessageInputState extends State<MessageInput> {
} }
void _checkEmoji(String s, BuildContext context) { void _checkEmoji(String s, BuildContext context) {
if (textEditingController.selection.isCollapsed && if (s.isNotEmpty &&
(s.isNotEmpty && s[textEditingController.selection.start - 1] == ':' || textEditingController.selection.baseOffset > 0 &&
textEditingController.text textEditingController.text
.substring( .substring(
0, 0,
textEditingController.selection.start, textEditingController.selection.baseOffset,
) )
.contains(':'))) { .contains(':')) {
final textToSelection = textEditingController.text final textToSelection = textEditingController.text
.substring(0, textEditingController.value.selection.start); .substring(0, textEditingController.value.selection.start);
final splits = textToSelection.split(':'); final splits = textToSelection.split(':');
@@ -481,15 +482,13 @@ class MessageInputState extends State<MessageInput> {
} }
void _checkMentions(String s, BuildContext context) { void _checkMentions(String s, BuildContext context) {
if (textEditingController.selection.isCollapsed && if (s.isNotEmpty &&
(s.isNotEmpty && textEditingController.selection.baseOffset > 0 &&
textEditingController.selection.start > 0 && textEditingController.text
s[textEditingController.selection.start - 1] == '@' || .substring(0, textEditingController.selection.baseOffset)
textEditingController.text .split(' ')
.substring(0, textEditingController.selection.start) .last
.split(' ') .contains('@')) {
.last
.contains('@'))) {
_mentionsOverlay = _buildMentionsOverlayEntry(); _mentionsOverlay = _buildMentionsOverlayEntry();
Overlay.of(context).insert(_mentionsOverlay); Overlay.of(context).insert(_mentionsOverlay);
} }
@@ -659,9 +658,11 @@ class MessageInputState extends State<MessageInput> {
}, },
), ),
IconButton( IconButton(
icon: Icon( icon: SvgPicture.asset(
StreamIcons.camera, 'svgs/icon_camera.svg',
size: 24, package: 'stream_chat_flutter',
height: 24,
width: 24,
color: _filePickerIndex == 2 color: _filePickerIndex == 2
? StreamChatTheme.of(context).accentColor ? StreamChatTheme.of(context).accentColor
: Colors.black.withOpacity(0.5), : Colors.black.withOpacity(0.5),
@@ -689,7 +690,7 @@ class MessageInputState extends State<MessageInput> {
setState(() { setState(() {
_animateContainer = false; _animateContainer = false;
_filePickerSize = (_filePickerSize - update.delta.dy).clamp( _filePickerSize = (_filePickerSize - update.delta.dy).clamp(
240.0, _kMinMediaPickerSize,
MediaQuery.of(context).size.height / 1.7, MediaQuery.of(context).size.height / 1.7,
); );
}); });
@@ -758,9 +759,10 @@ class MessageInputState extends State<MessageInput> {
.any((element) => element.id == media.id)) { .any((element) => element.id == media.id)) {
_addAttachment(media); _addAttachment(media);
} else { } else {
_attachments setState(() {
.removeWhere((element) => element.id == media.id); _attachments
setState(() {}); .removeWhere((element) => element.id == media.id);
});
} }
}, },
); );
@@ -818,35 +820,7 @@ class MessageInputState extends State<MessageInput> {
} }
void _addAttachment(Media medium) async { void _addAttachment(Media medium) async {
final mediaFile = await medium.getFile();
final thumbBytes = await medium.getThumbnail();
final file = PlatformFile(
path: mediaFile.path,
bytes: mediaFile.readAsBytesSync(),
);
final thumbFile = PlatformFile(
bytes: thumbBytes,
name: '${file.name ?? file.path?.split('/')?.last}_thumbnail.jpeg',
);
setState(() {
_inputEnabled = true;
});
if (file == null) {
return;
}
final channel = StreamChannel.of(context).channel;
final attachment = _SendingAttachment( final attachment = _SendingAttachment(
file: file,
thumbFile: thumbFile,
attachment: Attachment(
localUri: file.path != null ? Uri.parse(file.path) : null,
type: medium.mediaType == MediaType.image ? 'image' : 'video',
),
id: medium.id, id: medium.id,
); );
@@ -854,11 +828,23 @@ class MessageInputState extends State<MessageInput> {
_attachments.add(attachment); _attachments.add(attachment);
}); });
final thumbUrl = await _uploadImage( final mediaFile = await medium.getFile();
thumbFile,
channel, final file = PlatformFile(
path: mediaFile.path,
bytes: mediaFile.readAsBytesSync(),
); );
final channel = StreamChannel.of(context).channel;
setState(() {
attachment
..file = file
..attachment = Attachment(
localUri: file.path != null ? Uri.parse(file.path) : null,
type: medium.mediaType == MediaType.image ? 'image' : 'video',
);
});
final url = await _uploadAttachment( final url = await _uploadAttachment(
file, file,
medium.mediaType == MediaType.image medium.mediaType == MediaType.image
@@ -873,12 +859,10 @@ class MessageInputState extends State<MessageInput> {
if (fileType == DefaultAttachmentTypes.image) { if (fileType == DefaultAttachmentTypes.image) {
attachment.attachment = attachment.attachment.copyWith( attachment.attachment = attachment.attachment.copyWith(
imageUrl: url, imageUrl: url,
thumbUrl: thumbUrl,
); );
} else { } else {
attachment.attachment = attachment.attachment.copyWith( attachment.attachment = attachment.attachment.copyWith(
assetUrl: url, assetUrl: url,
thumbUrl: thumbUrl,
); );
} }
@@ -1226,6 +1210,10 @@ class MessageInputState extends State<MessageInput> {
); );
} }
if (attachment.attachment == null) {
return SizedBox();
}
switch (attachment.attachment.type) { switch (attachment.attachment.type) {
case 'image': case 'image':
case 'giphy': case 'giphy':
@@ -1235,22 +1223,20 @@ class MessageInputState extends State<MessageInput> {
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: Image.network( : Image.network(
attachment.attachment.imageUrl ?? attachment.attachment.imageUrl,
attachment.attachment.thumbUrl,
fit: BoxFit.cover, fit: BoxFit.cover,
); );
break; break;
case 'video': case 'video':
return Stack( return Stack(
children: [ children: [
Container( Positioned.fill(
child: attachment.thumbFile != null child: Container(
? Image.memory( child: VideoThumbnail(
attachment.thumbFile.bytes, file: File(
fit: BoxFit.cover, attachment.file.path,
) )),
: Icon(Icons.videocam), ),
color: Colors.black26,
), ),
Positioned( Positioned(
left: 8, left: 8,
@@ -1319,7 +1305,7 @@ class MessageInputState extends State<MessageInput> {
setState(() { setState(() {
_animateContainer = true; _animateContainer = true;
_openFilePickerSection = false; _openFilePickerSection = false;
_filePickerSize = 250.0; _filePickerSize = _kMinMediaPickerSize;
}); });
} else { } else {
final status = await (Platform.isAndroid final status = await (Platform.isAndroid
@@ -1455,6 +1441,9 @@ class MessageInputState extends State<MessageInput> {
} else if (fileType == DefaultAttachmentTypes.video) { } else if (fileType == DefaultAttachmentTypes.video) {
pickedFile = await _imagePicker.getVideo(source: ImageSource.camera); pickedFile = await _imagePicker.getVideo(source: ImageSource.camera);
} }
if (pickedFile == null) {
return;
}
final bytes = await pickedFile.readAsBytes(); final bytes = await pickedFile.readAsBytes();
file = PlatformFile( file = PlatformFile(
path: pickedFile.path, path: pickedFile.path,
@@ -1574,9 +1563,14 @@ class MessageInputState extends State<MessageInput> {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Center( child: Center(
child: Icon( child: InkWell(
_getIdleSendIcon(), onTap: () {
color: Colors.grey, sendMessage();
},
child: Icon(
_getIdleSendIcon(),
color: Colors.grey,
),
)), )),
), ),
); );
@@ -1718,11 +1712,7 @@ class MessageInputState extends State<MessageInput> {
if (!kIsWeb) { if (!kIsWeb) {
_keyboardListener = KeyboardVisibility.onChange.listen((visible) { _keyboardListener = KeyboardVisibility.onChange.listen((visible) {
if (visible) { _onChanged(context, textEditingController.text);
_checkCommands(textEditingController.text, context);
_checkMentions(textEditingController.text, context);
_checkEmoji(textEditingController.text, context);
}
}); });
} }
@@ -1732,6 +1722,10 @@ class MessageInputState extends State<MessageInput> {
_parseExistingMessage(widget.editMessage ?? widget.initialMessage); _parseExistingMessage(widget.editMessage ?? widget.initialMessage);
} }
textEditingController.addListener(() {
_onChanged(context, textEditingController.text);
});
_focusNode.addListener(() { _focusNode.addListener(() {
if (_focusNode.hasFocus) { if (_focusNode.hasFocus) {
_openFilePickerSection = false; _openFilePickerSection = false;
@@ -1774,14 +1768,12 @@ class MessageInputState extends State<MessageInput> {
class _SendingAttachment { class _SendingAttachment {
PlatformFile file; PlatformFile file;
PlatformFile thumbFile;
Attachment attachment; Attachment attachment;
bool uploaded; bool uploaded;
String id; String id;
_SendingAttachment({ _SendingAttachment({
this.file, this.file,
this.thumbFile,
this.attachment, this.attachment,
this.uploaded = false, this.uploaded = false,
this.id, this.id,
+37
View File
@@ -0,0 +1,37 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideoThumbnail extends StatefulWidget {
final File file;
const VideoThumbnail({
Key key,
@required this.file,
}) : super(key: key);
@override
_VideoThumbnailState createState() => _VideoThumbnailState();
}
class _VideoThumbnailState extends State<VideoThumbnail> {
VideoPlayerController _videoPlayerController;
@override
Widget build(BuildContext context) {
return VideoPlayer(_videoPlayerController);
}
@override
void initState() {
_videoPlayerController = VideoPlayerController.file(widget.file)
..initialize();
super.initState();
}
@override
void dispose() {
_videoPlayerController.dispose();
super.dispose();
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 0.2.13 version: 0.2.13+1
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 3H9L7 5H3C2.44771 5 2 5.44772 2 6V20C2 20.5523 2.44771 21 3 21H21C21.5523 21 22 20.5523 22 20V6C22 5.44772 21.5523 5 21 5H17L15 3ZM7.828 7L9.828 5H14.172L16.172 7H20V19H4V7H7.828ZM12 18C8.96243 18 6.5 15.5376 6.5 12.5C6.5 9.46243 8.96243 7 12 7C15.0376 7 17.5 9.46243 17.5 12.5C17.5 15.5376 15.0376 18 12 18ZM15.5 12.5C15.5 14.433 13.933 16 12 16C10.067 16 8.5 14.433 8.5 12.5C8.5 10.567 10.067 9 12 9C13.933 9 15.5 10.567 15.5 12.5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 606 B