From 150a8b00a6b2ce759a72c44b404ee0fcee943a0e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 18 Nov 2020 09:35:23 +0100 Subject: [PATCH 01/11] Update README.md update readme adding file picker troubleshooting link --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7ca34145..ba5de4c1 100644 --- a/README.md +++ b/README.md @@ -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. 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 ### Business logic components From 7cf30b501f123914a531ab3d55a8f5d52de8d332 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 18 Nov 2020 13:32:18 +0100 Subject: [PATCH 02/11] fix qa --- example/ios/Flutter/.last_build_id | 2 +- example/pubspec.yaml | 2 +- lib/src/message_input.dart | 99 ++++++++++++++---------------- lib/src/video_thumbnail.dart | 37 +++++++++++ svgs/icon_camera.svg | 3 + 5 files changed, 87 insertions(+), 56 deletions(-) create mode 100644 lib/src/video_thumbnail.dart create mode 100644 svgs/icon_camera.svg diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id index 8aba7787..20c7c514 100644 --- a/example/ios/Flutter/.last_build_id +++ b/example/ios/Flutter/.last_build_id @@ -1 +1 @@ -bb5f9103d9045cd6244bcae1f5f343e5 \ No newline at end of file +c3e639ccf9b069e37a7d1345194e6b99 \ No newline at end of file diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 8fc2dadb..fca549d2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.59+61 +version: 1.0.60+62 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 6024169b..dc5bf2f6 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -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 /// ![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) @@ -182,7 +185,7 @@ class MessageInputState extends State { 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; @@ -654,9 +657,11 @@ class MessageInputState extends State { }, ), 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 +689,7 @@ class MessageInputState extends State { setState(() { _animateContainer = false; _filePickerSize = (_filePickerSize - update.delta.dy).clamp( - 240.0, + _kMinMediaPickerSize, MediaQuery.of(context).size.height / 1.7, ); }); @@ -753,9 +758,10 @@ class MessageInputState extends State { .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 +819,7 @@ class MessageInputState extends State { } 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 +827,23 @@ class MessageInputState extends State { _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 +858,10 @@ class MessageInputState extends State { 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 +1209,10 @@ class MessageInputState extends State { ); } + if (attachment.attachment == null) { + return SizedBox(); + } + switch (attachment.attachment.type) { case 'image': case 'giphy': @@ -1230,22 +1222,20 @@ class MessageInputState extends State { 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 +1304,7 @@ class MessageInputState extends State { setState(() { _animateContainer = true; _openFilePickerSection = false; - _filePickerSize = 250.0; + _filePickerSize = _kMinMediaPickerSize; }); } else { final status = await (Platform.isAndroid @@ -1450,6 +1440,9 @@ class MessageInputState extends State { } 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, @@ -1774,14 +1767,12 @@ class MessageInputState extends State { class _SendingAttachment { PlatformFile file; - PlatformFile thumbFile; Attachment attachment; bool uploaded; String id; _SendingAttachment({ this.file, - this.thumbFile, this.attachment, this.uploaded = false, this.id, diff --git a/lib/src/video_thumbnail.dart b/lib/src/video_thumbnail.dart new file mode 100644 index 00000000..c36ab327 --- /dev/null +++ b/lib/src/video_thumbnail.dart @@ -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 { + 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(); + } +} diff --git a/svgs/icon_camera.svg b/svgs/icon_camera.svg new file mode 100644 index 00000000..0bf3122d --- /dev/null +++ b/svgs/icon_camera.svg @@ -0,0 +1,3 @@ + + + From a793c623b299880641756b28d9f74936c28f61ee Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 10:58:10 +0100 Subject: [PATCH 03/11] use texteditingcontroller listener instead of onchanged --- lib/src/message_input.dart | 126 +++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 68 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 02fbbd8f..7ba3e4b9 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -77,25 +77,25 @@ enum DefaultAttachmentTypes { /// Modify it to change the widget appearance. class MessageInput extends StatefulWidget { /// Instantiate a new MessageInput - MessageInput( - {Key key, - this.onMessageSent, - this.preMessageSending, - this.parentMessage, - this.editMessage, - this.maxHeight = 150, - this.keyboardType = TextInputType.multiline, - this.disableAttachments = false, - this.doImageUploadRequest, - this.doFileUploadRequest, - this.initialMessage, - this.textEditingController, - this.actions, - this.actionsLocation = ActionsLocation.left, - this.attachmentThumbnailBuilders, - this.inputTextStyle, - this.attachmentIconColor}) - : super(key: key); + MessageInput({ + Key key, + this.onMessageSent, + this.preMessageSending, + this.parentMessage, + this.editMessage, + this.maxHeight = 150, + this.keyboardType = TextInputType.multiline, + this.disableAttachments = false, + this.doImageUploadRequest, + this.doFileUploadRequest, + this.initialMessage, + this.textEditingController, + this.actions, + this.actionsLocation = ActionsLocation.left, + this.attachmentThumbnailBuilders, + this.inputTextStyle, + this.attachmentIconColor, + }) : super(key: key); /// Message to edit final Message editMessage; @@ -252,36 +252,6 @@ class MessageInputState extends State { keyboardType: widget.keyboardType, controller: textEditingController, focusNode: _focusNode, - onChanged: (s) { - StreamChannel.of(context).channel.keyStroke( - widget.parentMessage?.id, - ); - - setState(() { - _messageIsPresent = s.trim().isNotEmpty; - }); - - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - - if (s.startsWith('/')) { - _commandsOverlay = _buildCommandsOverlayEntry(); - Overlay.of(context).insert(_commandsOverlay); - } - - if (textEditingController.selection.isCollapsed && - (s[textEditingController.selection.start - 1] == '@' || - textEditingController.text - .substring(0, textEditingController.selection.start) - .split(' ') - .last - .contains('@'))) { - _mentionsOverlay = _buildMentionsOverlayEntry(); - Overlay.of(context).insert(_mentionsOverlay); - } - }, onTap: () { setState(() { _typingStarted = true; @@ -403,7 +373,7 @@ class MessageInputState extends State { OverlayEntry _buildMentionsOverlayEntry() { final splits = textEditingController.text - .substring(0, textEditingController.value.selection.start) + .substring(0, textEditingController.value.selection.baseOffset) .split('@'); final query = splits.last.toLowerCase(); @@ -467,7 +437,7 @@ class MessageInputState extends State { text: rejoin + textEditingController.text.substring( textEditingController - .selection.start), + .selection.baseOffset), selection: TextSelection.collapsed( offset: rejoin.length, ), @@ -962,23 +932,7 @@ class MessageInputState extends State { if (!kIsWeb) { _keyboardListener = KeyboardVisibility.onChange.listen((visible) { if (visible) { - if (_commandsOverlay != null) { - if (textEditingController.text.startsWith('/')) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _commandsOverlay = _buildCommandsOverlayEntry(); - Overlay.of(context).insert(_commandsOverlay); - }); - } - } - - if (_mentionsOverlay != null) { - if (textEditingController.text.contains('@')) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _mentionsOverlay = _buildCommandsOverlayEntry(); - Overlay.of(context).insert(_mentionsOverlay); - }); - } - } + _onChange(); } else { if (_commandsOverlay != null) { _commandsOverlay.remove(); @@ -992,11 +946,47 @@ class MessageInputState extends State { textEditingController = widget.textEditingController ?? TextEditingController(); + + textEditingController.addListener(_onChange); + if (widget.editMessage != null || widget.initialMessage != null) { _parseExistingMessage(widget.editMessage ?? widget.initialMessage); } } + void _onChange() { + final s = textEditingController.text; + StreamChannel.of(context).channel.keyStroke( + widget.parentMessage?.id, + ); + + setState(() { + _messageIsPresent = s.trim().isNotEmpty; + }); + + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + + if (s.trim().startsWith('/')) { + _commandsOverlay = _buildCommandsOverlayEntry(); + Overlay.of(context).insert(_commandsOverlay); + } + + if (_messageIsPresent && + textEditingController.selection.isCollapsed && + textEditingController.selection.baseOffset > 0 && + textEditingController.text + .substring(0, textEditingController.selection.baseOffset) + .split(' ') + .last + .contains('@')) { + _mentionsOverlay = _buildMentionsOverlayEntry(); + Overlay.of(context).insert(_mentionsOverlay); + } + } + void _parseExistingMessage(Message message) { textEditingController.text = message.text; From 1c1cf66d9394cc32e1ee4b4243dddec30bcca1c7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 10:59:59 +0100 Subject: [PATCH 04/11] bump version --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 945199b9..675d40d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.13+1 + +- Use TextEditingController.addListener instea of TextField.onChanged + ## 0.2.13 - Update llc dependency diff --git a/pubspec.yaml b/pubspec.yaml index 00be2548..f667acfb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: 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. -version: 0.2.13 +version: 0.2.13+1 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 6a8e76dc189d992a183065e3c1b9bb91f51f73d9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 11:00:25 +0100 Subject: [PATCH 05/11] fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 675d40d0..7d5b93f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.2.13+1 -- Use TextEditingController.addListener instea of TextField.onChanged +- Use TextEditingController.addListener instead of TextField.onChanged ## 0.2.13 From 199831a7ec7fa8fe383b47156e80f2da1e3eed33 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 11:50:41 +0100 Subject: [PATCH 06/11] fix show overlay --- lib/src/message_input.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 7ba3e4b9..a4bfcd18 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -934,12 +934,10 @@ class MessageInputState extends State { if (visible) { _onChange(); } else { - if (_commandsOverlay != null) { - _commandsOverlay.remove(); - } - if (_mentionsOverlay != null) { - _mentionsOverlay.remove(); - } + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; } }); } From 04da9a06b34aee886ebfb4decfe498732df9ed55 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 11:58:30 +0100 Subject: [PATCH 07/11] update github action --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 373aa879..bb72c2f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Flutter action - uses: subosito/flutter-action@v1.3.2 + uses: subosito/flutter-action@v1.4.0 with: channel: 'stable' - name: Get dependencies From 7bb769e0a6f0ad49026f0d4a09fcd557402f6fec Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 12:32:28 +0100 Subject: [PATCH 08/11] update github action --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8e87c58..4b3e086a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Flutter action - uses: subosito/flutter-action@v1.3.2 + uses: subosito/flutter-action@v1.4.0 with: channel: 'stable' - name: Get dependencies From 26e8ffeae3ebd1f779661f72641148e36a984440 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 13:24:48 +0100 Subject: [PATCH 09/11] remove onchanged --- lib/src/message_input.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 5fef41f7..cd96119a 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -366,9 +366,6 @@ class MessageInputState extends State { keyboardType: widget.keyboardType, controller: textEditingController, focusNode: _focusNode, - onChanged: (s) { - _onChanged(context, s); - }, style: Theme.of(context).textTheme.bodyText2, autofocus: false, textAlignVertical: TextAlignVertical.center, From 5421a447ba7870fbc5bcc3dbbdf14efebef30311 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 19 Nov 2020 16:22:16 +0100 Subject: [PATCH 10/11] init notifications --- example/lib/advanced_options_page.dart | 4 ++++ example/lib/main.dart | 3 +++ example/pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/example/lib/advanced_options_page.dart b/example/lib/advanced_options_page.dart index 56c9dbf0..c84ef58e 100644 --- a/example/lib/advanced_options_page.dart +++ b/example/lib/advanced_options_page.dart @@ -282,6 +282,10 @@ class _AdvancedOptionsPageState extends State { }), userToken, ); + + if (!kIsWeb) { + initNotifications(client); + } } catch (e) { var errorText = 'Error connecting, retry'; if (e is Map) { diff --git a/example/lib/main.dart b/example/lib/main.dart index 2e3f43e2..d42bb7af 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,6 +30,9 @@ void main() async { User(id: userId), token, ); + if (!kIsWeb) { + initNotifications(client); + } } runApp(MyApp(client)); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index fca549d2..e5c11897 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.60+62 +version: 1.0.61+63 environment: sdk: ">=2.2.2 <3.0.0" From c7e27577b2dc9d28534522c1ce27cf0b129becce Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 20 Nov 2020 11:22:41 +0100 Subject: [PATCH 11/11] use fork of media gallery waiting for the pr to be merged --- lib/src/media_list_view.dart | 1 + pubspec.yaml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/media_list_view.dart b/lib/src/media_list_view.dart index 225d327b..da21111b 100644 --- a/lib/src/media_list_view.dart +++ b/lib/src/media_list_view.dart @@ -44,6 +44,7 @@ class _MediaListViewState extends State { placeholder: MemoryImage(kTransparentImage), image: MediaThumbnailProvider( media: media, + highQuality: true, ), fit: BoxFit.cover, ), diff --git a/pubspec.yaml b/pubspec.yaml index 9c1b0a29..97804c53 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,8 @@ dependencies: flutter_slidable: ^0.5.4 carousel_slider: ^2.2.1 clipboard: ^0.1.2+8 - media_gallery: ^0.1.5 + media_gallery: + git: https://github.com/imtoori/media_gallery.git permission_handler: ^5.0.1+1 transparent_image: ^1.0.0