Merge branch 'feature/new-ui' into feature/loading
This commit is contained in:
@@ -44,6 +44,7 @@ class _MediaListViewState extends State<MediaListView> {
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
image: MediaThumbnailProvider(
|
||||
media: media,
|
||||
highQuality: true,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
|
||||
+87
-95
@@ -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/stream_chat_theme.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 '../stream_chat_flutter.dart';
|
||||
@@ -41,6 +42,8 @@ enum DefaultAttachmentTypes {
|
||||
file,
|
||||
}
|
||||
|
||||
const _kMinMediaPickerSize = 360.0;
|
||||
|
||||
/// Inactive state
|
||||
/// 
|
||||
/// 
|
||||
@@ -182,7 +185,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
bool _sendAsDm = false;
|
||||
bool _openFilePickerSection = false;
|
||||
int _filePickerIndex = 0;
|
||||
double _filePickerSize = 250.0;
|
||||
double _filePickerSize = _kMinMediaPickerSize;
|
||||
|
||||
/// The editing controller passed to the input TextField
|
||||
TextEditingController textEditingController;
|
||||
@@ -363,27 +366,6 @@ class MessageInputState extends State<MessageInput> {
|
||||
keyboardType: widget.keyboardType,
|
||||
controller: textEditingController,
|
||||
focusNode: _focusNode,
|
||||
onChanged: (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, context);
|
||||
|
||||
_checkMentions(s, context);
|
||||
|
||||
_checkEmoji(s, context);
|
||||
},
|
||||
style: Theme.of(context).textTheme.bodyText2,
|
||||
autofocus: false,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
@@ -440,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() {
|
||||
if (_commandEnabled && _chosenCommand.name == 'giphy') {
|
||||
return 'Search GIFs';
|
||||
@@ -451,14 +455,14 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
void _checkEmoji(String s, BuildContext context) {
|
||||
if (textEditingController.selection.isCollapsed &&
|
||||
(s.isNotEmpty && s[textEditingController.selection.start - 1] == ':' ||
|
||||
textEditingController.text
|
||||
.substring(
|
||||
0,
|
||||
textEditingController.selection.start,
|
||||
)
|
||||
.contains(':'))) {
|
||||
if (s.isNotEmpty &&
|
||||
textEditingController.selection.baseOffset > 0 &&
|
||||
textEditingController.text
|
||||
.substring(
|
||||
0,
|
||||
textEditingController.selection.baseOffset,
|
||||
)
|
||||
.contains(':')) {
|
||||
final textToSelection = textEditingController.text
|
||||
.substring(0, textEditingController.value.selection.start);
|
||||
final splits = textToSelection.split(':');
|
||||
@@ -478,13 +482,13 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
void _checkMentions(String s, BuildContext context) {
|
||||
if (textEditingController.selection.isCollapsed &&
|
||||
(s.isNotEmpty && s[textEditingController.selection.start - 1] == '@' ||
|
||||
textEditingController.text
|
||||
.substring(0, textEditingController.selection.start)
|
||||
.split(' ')
|
||||
.last
|
||||
.contains('@'))) {
|
||||
if (s.isNotEmpty &&
|
||||
textEditingController.selection.baseOffset > 0 &&
|
||||
textEditingController.text
|
||||
.substring(0, textEditingController.selection.baseOffset)
|
||||
.split(' ')
|
||||
.last
|
||||
.contains('@')) {
|
||||
_mentionsOverlay = _buildMentionsOverlayEntry();
|
||||
Overlay.of(context).insert(_mentionsOverlay);
|
||||
}
|
||||
@@ -654,9 +658,11 @@ class MessageInputState extends State<MessageInput> {
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
StreamIcons.camera,
|
||||
size: 24,
|
||||
icon: SvgPicture.asset(
|
||||
'svgs/icon_camera.svg',
|
||||
package: 'stream_chat_flutter',
|
||||
height: 24,
|
||||
width: 24,
|
||||
color: _filePickerIndex == 2
|
||||
? StreamChatTheme.of(context).accentColor
|
||||
: Colors.black.withOpacity(0.5),
|
||||
@@ -684,7 +690,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
setState(() {
|
||||
_animateContainer = false;
|
||||
_filePickerSize = (_filePickerSize - update.delta.dy).clamp(
|
||||
240.0,
|
||||
_kMinMediaPickerSize,
|
||||
MediaQuery.of(context).size.height / 1.7,
|
||||
);
|
||||
});
|
||||
@@ -753,9 +759,10 @@ class MessageInputState extends State<MessageInput> {
|
||||
.any((element) => element.id == media.id)) {
|
||||
_addAttachment(media);
|
||||
} else {
|
||||
_attachments
|
||||
.removeWhere((element) => element.id == media.id);
|
||||
setState(() {});
|
||||
setState(() {
|
||||
_attachments
|
||||
.removeWhere((element) => element.id == media.id);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -813,35 +820,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
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(
|
||||
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,
|
||||
);
|
||||
|
||||
@@ -849,11 +828,23 @@ class MessageInputState extends State<MessageInput> {
|
||||
_attachments.add(attachment);
|
||||
});
|
||||
|
||||
final thumbUrl = await _uploadImage(
|
||||
thumbFile,
|
||||
channel,
|
||||
final mediaFile = await medium.getFile();
|
||||
|
||||
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(
|
||||
file,
|
||||
medium.mediaType == MediaType.image
|
||||
@@ -868,12 +859,10 @@ class MessageInputState extends State<MessageInput> {
|
||||
if (fileType == DefaultAttachmentTypes.image) {
|
||||
attachment.attachment = attachment.attachment.copyWith(
|
||||
imageUrl: url,
|
||||
thumbUrl: thumbUrl,
|
||||
);
|
||||
} else {
|
||||
attachment.attachment = attachment.attachment.copyWith(
|
||||
assetUrl: url,
|
||||
thumbUrl: thumbUrl,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1221,6 +1210,10 @@ class MessageInputState extends State<MessageInput> {
|
||||
);
|
||||
}
|
||||
|
||||
if (attachment.attachment == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
switch (attachment.attachment.type) {
|
||||
case 'image':
|
||||
case 'giphy':
|
||||
@@ -1230,22 +1223,20 @@ class MessageInputState extends State<MessageInput> {
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.network(
|
||||
attachment.attachment.imageUrl ??
|
||||
attachment.attachment.thumbUrl,
|
||||
attachment.attachment.imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
break;
|
||||
case 'video':
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
child: attachment.thumbFile != null
|
||||
? Image.memory(
|
||||
attachment.thumbFile.bytes,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Icon(Icons.videocam),
|
||||
color: Colors.black26,
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
child: VideoThumbnail(
|
||||
file: File(
|
||||
attachment.file.path,
|
||||
)),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 8,
|
||||
@@ -1314,7 +1305,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
setState(() {
|
||||
_animateContainer = true;
|
||||
_openFilePickerSection = false;
|
||||
_filePickerSize = 250.0;
|
||||
_filePickerSize = _kMinMediaPickerSize;
|
||||
});
|
||||
} else {
|
||||
final status = await (Platform.isAndroid
|
||||
@@ -1450,6 +1441,9 @@ class MessageInputState extends State<MessageInput> {
|
||||
} else if (fileType == DefaultAttachmentTypes.video) {
|
||||
pickedFile = await _imagePicker.getVideo(source: ImageSource.camera);
|
||||
}
|
||||
if (pickedFile == null) {
|
||||
return;
|
||||
}
|
||||
final bytes = await pickedFile.readAsBytes();
|
||||
file = PlatformFile(
|
||||
path: pickedFile.path,
|
||||
@@ -1718,11 +1712,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
|
||||
if (!kIsWeb) {
|
||||
_keyboardListener = KeyboardVisibility.onChange.listen((visible) {
|
||||
if (visible) {
|
||||
_checkCommands(textEditingController.text, context);
|
||||
_checkMentions(textEditingController.text, context);
|
||||
_checkEmoji(textEditingController.text, context);
|
||||
}
|
||||
_onChanged(context, textEditingController.text);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1732,6 +1722,10 @@ class MessageInputState extends State<MessageInput> {
|
||||
_parseExistingMessage(widget.editMessage ?? widget.initialMessage);
|
||||
}
|
||||
|
||||
textEditingController.addListener(() {
|
||||
_onChanged(context, textEditingController.text);
|
||||
});
|
||||
|
||||
_focusNode.addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
_openFilePickerSection = false;
|
||||
@@ -1774,14 +1768,12 @@ class MessageInputState extends State<MessageInput> {
|
||||
|
||||
class _SendingAttachment {
|
||||
PlatformFile file;
|
||||
PlatformFile thumbFile;
|
||||
Attachment attachment;
|
||||
bool uploaded;
|
||||
String id;
|
||||
|
||||
_SendingAttachment({
|
||||
this.file,
|
||||
this.thumbFile,
|
||||
this.attachment,
|
||||
this.uploaded = false,
|
||||
this.id,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user