diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index a90d828e..6fb8664b 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -6,10 +6,14 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + + + android:icon="@mipmap/ic_launcher" + android:requestLegacyExternalStorage="true" + > 4.7.0) - - firebase_core (0.5.1): + - firebase_core (0.5.2): - Firebase/CoreOnly (~> 6.33.0) - Flutter - firebase_messaging (7.0.3): @@ -113,6 +113,8 @@ PODS: - nanopb/encode (1.30906.0) - path_provider (0.0.1): - Flutter + - photo_gallery (0.0.1): + - Flutter - PromisesObjC (1.2.11) - Protobuf (3.13.0) - SDWebImage (5.9.4): @@ -163,6 +165,7 @@ DEPENDENCIES: - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) + - photo_gallery (from `.symlinks/plugins/photo_gallery/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) @@ -214,6 +217,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" + photo_gallery: + :path: ".symlinks/plugins/photo_gallery/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" sqflite: @@ -232,7 +237,7 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 - firebase_core: aa25a5dc6b492ecab37587c53d8420135f0cac90 + firebase_core: 350ba329d1641211bc6183a3236893cafdacfea7 firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 @@ -250,6 +255,7 @@ SPEC CHECKSUMS: image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + photo_gallery: 9f95e57747cd22c10676ece3660d1ffe6c603ee5 PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748 SDWebImage: b69257f4ab14e9b6a2ef53e910fdf914d8f757c1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6d377561..de8fcb9a 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -328,6 +328,7 @@ "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", + "${BUILT_PRODUCTS_DIR}/photo_gallery/photo_gallery.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", "${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework", @@ -357,6 +358,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_gallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework", diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0cba8d30..79b9638e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,7 +1,7 @@ name: example description: A new Flutter project. -version: 1.0.43+45 +version: 1.0.44+46 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 9fd4e746..c26cd152 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -8,15 +8,17 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:http_parser/http_parser.dart'; +import 'package:http_parser/http_parser.dart' as httpParser; import 'package:image_picker/image_picker.dart'; +import 'package:media_gallery/media_gallery.dart'; import 'package:mime/mime.dart'; -import 'package:photo_gallery/photo_gallery.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:stream_chat/stream_chat.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:substring_highlight/substring_highlight.dart'; +import 'package:transparent_image/transparent_image.dart'; import '../stream_chat_flutter.dart'; import 'stream_channel.dart'; @@ -181,7 +183,7 @@ class MessageInputState extends State { int _filePickerIndex = 0; double _filePickerSize = 250.0; - Tuple2, List> _mediaData = Tuple2([], []); + Iterable _media = []; /// The editing controller passed to the input TextField TextEditingController textEditingController; @@ -716,7 +718,7 @@ class MessageInputState extends State { switch (_filePickerIndex) { case 0: return GridView.builder( - itemCount: _mediaData.item2.length, + itemCount: _media.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemBuilder: (context, position) { @@ -728,24 +730,29 @@ class MessageInputState extends State { children: [ AspectRatio( aspectRatio: 1.0, - child: Image.memory( - _mediaData.item2[position], - fit: _mediaData.item1[position].height > - _mediaData.item1[position].width - ? BoxFit.fitWidth - : BoxFit.fitHeight, + child: FadeInImage( + placeholder: MemoryImage(kTransparentImage), + image: MediaThumbnailProvider( + media: _media.elementAt(position), + ), + fit: BoxFit.fill, ), ), - IgnorePointer( - child: AnimatedOpacity( - duration: Duration(milliseconds: 300), - opacity: _attachments.any((element) => - element.id == _mediaData.item1[position].id) - ? 1.0 - : 0.0, - child: Container( - color: Colors.black.withOpacity(0.5), - child: Center( + Positioned.fill( + child: IgnorePointer( + child: AnimatedOpacity( + duration: Duration(milliseconds: 300), + opacity: _attachments.any((element) => + element.id == _media.elementAt(position).id) + ? 1.0 + : 0.0, + child: Container( + color: Colors.black.withOpacity(0.5), + alignment: Alignment.topRight, + padding: const EdgeInsets.only( + top: 8, + right: 8, + ), child: CircleAvatar( maxRadius: 12.0, backgroundColor: Colors.white, @@ -762,11 +769,11 @@ class MessageInputState extends State { ), onTap: () { if (!_attachments.any((element) => - element.id == _mediaData.item1[position].id)) { - _addAttachment(_mediaData.item1[position]); + element.id == _media.elementAt(position).id)) { + _addAttachment(_media.elementAt(position)); } else { _attachments.removeWhere((element) => - element.id == _mediaData.item1[position].id); + element.id == _media.elementAt(position).id); setState(() {}); } }, @@ -782,10 +789,10 @@ class MessageInputState extends State { } } - void _addAttachment(Medium medium) async { - var mediaFile = await PhotoGallery.getFile(mediumId: medium.id); + void _addAttachment(Media medium) async { + final mediaFile = await medium.getFile(); - var file = PlatformFile( + final file = PlatformFile( path: mediaFile.path, bytes: mediaFile.readAsBytesSync(), ); @@ -803,7 +810,7 @@ class MessageInputState extends State { file: file, attachment: Attachment( localUri: file.path != null ? Uri.parse(file.path) : null, - type: medium.mediumType == MediumType.image ? 'image' : 'video', + type: medium.mediaType == MediaType.image ? 'image' : 'video', ), id: medium.id, ); @@ -814,12 +821,12 @@ class MessageInputState extends State { final url = await _uploadAttachment( file, - medium.mediumType == MediumType.image + medium.mediaType == MediaType.image ? DefaultAttachmentTypes.image : DefaultAttachmentTypes.video, channel); - var fileType = medium.mediumType == MediumType.image + final fileType = medium.mediaType == MediaType.image ? DefaultAttachmentTypes.image : DefaultAttachmentTypes.video; @@ -839,32 +846,28 @@ class MessageInputState extends State { } void _getAllMedia() async { - var allAlbums = await PhotoGallery.listAlbums(mediumType: MediumType.image); - var allVideoAlbums = - await PhotoGallery.listAlbums(mediumType: MediumType.video); - List resultList = []; - List> resultThumbnailList = []; + final List collections = + await MediaGallery.listMediaCollections(mediaTypes: [ + MediaType.image, + MediaType.video, + ]); - for (var album in allAlbums) { - var data = await album.listMedia(); - resultList.addAll(data.items); + if (collections.isEmpty) { + return; } - for (var album in allVideoAlbums) { - var data = await album.listMedia(); - resultList.addAll(data.items); - } - - resultList.sort((a, b) => a.modifiedDate.compareTo(b.modifiedDate)); - resultList = resultList.toSet().toList(); - - for (var e in resultList) { - resultThumbnailList.add(await PhotoGallery.getThumbnail(mediumId: e.id)); - } + final page = await collections + .firstWhere( + (element) => element.name == 'All', + orElse: () => collections.first, + ) + .getMedias( + take: 50, + ); + print('page.items.length: ${page.items.length}'); setState(() { - _mediaData = - Tuple2, List>(resultList, resultThumbnailList); + _media = page.items; }); } @@ -1271,13 +1274,21 @@ class MessageInputState extends State { color: Color(0xFF000000).withAlpha(128), ), ), - onTap: () { + onTap: () async { if (_openFilePickerSection) { setState(() { _openFilePickerSection = false; _filePickerSize = 250.0; }); } else { + final status = await Permission.storage.request(); + print('status: ${status}'); + if (status.isDenied || status.isPermanentlyDenied) { + final res = await openAppSettings(); + if (!res) { + return; + } + } _getAllMedia(); showAttachmentModal(); } @@ -1494,7 +1505,7 @@ class MessageInputState extends State { MultipartFile.fromBytes( bytes, filename: filename, - contentType: MediaType.parse(lookupMimeType(filename)), + contentType: httpParser.MediaType.parse(lookupMimeType(filename)), ), ); return res.file; @@ -1507,7 +1518,7 @@ class MessageInputState extends State { MultipartFile.fromBytes( bytes, filename: filename, - contentType: MediaType.parse(lookupMimeType(filename)), + contentType: httpParser.MediaType.parse(lookupMimeType(filename)), ), ); return res.file; diff --git a/pubspec.yaml b/pubspec.yaml index 6b2aeff1..ce0b3633 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,9 @@ dependencies: flutter_slidable: ^0.5.4 carousel_slider: ^2.2.1 clipboard: ^0.1.2+8 - photo_gallery: ^0.3.0 + media_gallery: ^0.1.5 + permission_handler: ^5.0.1+1 + transparent_image: ^1.0.0 flutter: assets: