diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b028e9b7..707e9fcb 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -108,8 +108,6 @@ PODS: - GoogleUtilities/Logger - image_picker (0.0.1): - Flutter - - media_gallery (0.0.1): - - Flutter - nanopb (1.30906.0): - nanopb/decode (= 1.30906.0) - nanopb/encode (= 1.30906.0) @@ -119,6 +117,8 @@ PODS: - Flutter - "permission_handler (5.0.1+1)": - Flutter + - photo_manager (0.0.1): + - Flutter - PromisesObjC (1.2.11) - Protobuf (3.13.0) - SDWebImage (5.10.0): @@ -171,9 +171,9 @@ DEPENDENCIES: - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - - media_gallery (from `.symlinks/plugins/media_gallery/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - permission_handler (from `.symlinks/plugins/permission_handler/ios`) + - photo_manager (from `.symlinks/plugins/photo_manager/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`) @@ -226,12 +226,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" image_picker: :path: ".symlinks/plugins/image_picker/ios" - media_gallery: - :path: ".symlinks/plugins/media_gallery/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" permission_handler: :path: ".symlinks/plugins/permission_handler/ios" + photo_manager: + :path: ".symlinks/plugins/photo_manager/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" sqflite: @@ -269,10 +269,10 @@ SPEC CHECKSUMS: GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 - media_gallery: 834a04455a476b975c280aa1f24ed2853dfe36de nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c permission_handler: eac8e15b4a1a3fba55b761d19f3f4e6b005d15b6 + photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67 PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748 SDWebImage: 9169792e9eec3e45bba2a0c02f74bf8bd922d1ee diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index bb101716..1a50ae5b 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -327,9 +327,9 @@ "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", "${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework", "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", - "${BUILT_PRODUCTS_DIR}/media_gallery/media_gallery.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", + "${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", "${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework", @@ -359,9 +359,9 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_gallery.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_manager.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 f1d492ed..e0be8fc9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.77+79 +version: 1.0.78+80 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/media_list_view.dart b/lib/src/media_list_view.dart index e8735d69..80686d62 100644 --- a/lib/src/media_list_view.dart +++ b/lib/src/media_list_view.dart @@ -1,11 +1,14 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:media_gallery/media_gallery.dart'; +import 'package:photo_manager/photo_manager.dart'; +import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'dart:ui' as ui; class MediaListView extends StatefulWidget { final List selectedIds; - final void Function(Media media) onSelect; + final void Function(AssetEntity media) onSelect; const MediaListView({ Key key, @@ -17,88 +20,94 @@ class MediaListView extends StatefulWidget { } class _MediaListViewState extends State { - final _media = []; + final _media = []; final ScrollController _scrollController = ScrollController(); + int _currentPage = 0; + bool _endPagination = false; @override Widget build(BuildContext context) { - return GridView.builder( - itemCount: _media.length, - controller: _scrollController, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - ), - cacheExtent: 1000, - itemBuilder: ( - context, - position, - ) { - final media = _media.elementAt(position); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 1.0), - child: InkWell( - child: Stack( - children: [ - AspectRatio( - aspectRatio: 1.0, - child: FadeInImage( - fadeInDuration: Duration(milliseconds: 300), - placeholder: AssetImage( - 'images/placeholder.png', - package: 'stream_chat_flutter', + return LazyLoadScrollView( + onEndOfPage: () { + _getMedia(); + }, + child: GridView.builder( + itemCount: _media.length, + controller: _scrollController, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + ), + cacheExtent: 1000, + itemBuilder: ( + context, + position, + ) { + final media = _media.elementAt(position); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 1.0), + child: InkWell( + child: Stack( + children: [ + AspectRatio( + aspectRatio: 1.0, + child: FadeInImage( + fadeInDuration: Duration(milliseconds: 300), + placeholder: AssetImage( + 'images/placeholder.png', + package: 'stream_chat_flutter', + ), + image: MediaThumbnailProvider( + media: media, + ), + fit: BoxFit.cover, ), - image: MediaThumbnailProvider( - media: media, - highQuality: true, - ), - fit: BoxFit.cover, ), - ), - Positioned.fill( - child: IgnorePointer( - child: AnimatedOpacity( - duration: Duration(milliseconds: 300), - opacity: widget.selectedIds.any((id) => id == media.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( - radius: 12, - backgroundColor: Colors.white, - child: StreamSvgIcon.check( - size: 24, - color: Colors.black, + Positioned.fill( + child: IgnorePointer( + child: AnimatedOpacity( + duration: Duration(milliseconds: 300), + opacity: widget.selectedIds.any((id) => id == media.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( + radius: 12, + backgroundColor: Colors.white, + child: StreamSvgIcon.check( + size: 24, + color: Colors.black, + ), ), ), ), ), ), - ), - if (media.mediaType == MediaType.video) - Positioned( - left: 8, - bottom: 10, - child: SvgPicture.asset( - 'svgs/video_call_icon.svg', - package: 'stream_chat_flutter', + if (media.type == AssetType.video) + Positioned( + left: 8, + bottom: 10, + child: SvgPicture.asset( + 'svgs/video_call_icon.svg', + package: 'stream_chat_flutter', + ), ), - ), - ], + ], + ), + onTap: () { + if (widget.onSelect != null) { + widget.onSelect(media); + } + }, ), - onTap: () { - if (widget.onSelect != null) { - widget.onSelect(media); - } - }, - ), - ); - }, + ); + }, + ), ); } @@ -109,42 +118,67 @@ class _MediaListViewState extends State { } void _getMedia() async { - final List collections = - await MediaGallery.listMediaCollections( - mediaTypes: [ - MediaType.video, - MediaType.image, - ], - ); + final assetList = await PhotoManager.getAssetPathList( + hasAll: true, + ).then((value) => value.singleWhere((element) => element.isAll)); - if (collections.isEmpty) { - return; + final media = await assetList.getAssetListPaged(_currentPage, 50); + + if (media.isEmpty) { + setState(() { + _endPagination = true; + }); + } else { + setState(() { + _media.addAll(media); + }); } - final collection = collections.firstWhere( - (element) => element.isAllCollection, - orElse: () => collections.first, - ); - - final videoPage = await collection.getMedias( - mediaType: MediaType.video, - take: 500, - ); - final imagePage = await collection.getMedias( - mediaType: MediaType.image, - take: 500, - ); - - final allItems = [ - ...videoPage.items, - ...imagePage.items, - ]..sort(( - a, - b, - ) => - b.creationDate.compareTo(a.creationDate)); - - setState(() { - _media.addAll(allItems); - }); + ++_currentPage; } } + +class MediaThumbnailProvider extends ImageProvider { + const MediaThumbnailProvider({ + @required this.media, + }) : assert(media != null); + + final AssetEntity media; + + @override + ImageStreamCompleter load(key, decode) { + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key, decode), + scale: 1.0, + informationCollector: () sync* { + yield ErrorDescription('Id: ${media?.id}'); + }, + ); + } + + Future _loadAsync( + MediaThumbnailProvider key, DecoderCallback decode) async { + assert(key == this); + final bytes = await media.thumbData; + if (bytes.isEmpty) return null; + + return await decode(bytes); + } + + @override + Future obtainKey(ImageConfiguration configuration) { + return SynchronousFuture(this); + } + + @override + bool operator ==(dynamic other) { + if (other.runtimeType != runtimeType) return false; + final MediaThumbnailProvider typedOther = other; + return media?.id == typedOther.media?.id; + } + + @override + int get hashCode => media?.id?.hashCode ?? 0; + + @override + String toString() => '$runtimeType("${media?.id}")'; +} diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 5314e755..10fe6595 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -10,9 +10,9 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_svg/flutter_svg.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:permission_handler/permission_handler.dart'; +import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/compress_video_service.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; @@ -845,7 +845,7 @@ class MessageInputState extends State { } } - void _addAttachment(Media medium) async { + void _addAttachment(AssetEntity medium) async { final attachment = _SendingAttachment( id: medium.id, ); @@ -853,16 +853,16 @@ class MessageInputState extends State { setState(() { _attachments.add(attachment); }); - final mediaFile = await medium.getFile(); + final mediaFile = await medium.file; var file = PlatformFile( path: mediaFile.path, - size: (await mediaFile.length()) ~/ 1024, + size: ((await mediaFile.length()) / 1024).ceil(), bytes: mediaFile.readAsBytesSync(), ); if (file.size > _kMaxAttachmentSize) { - if (medium.mediaType == MediaType.video) { + if (medium.type == AssetType.video) { final mediaInfo = await CompressVideoService.compressVideo(file.path); if (mediaInfo.filesize / (1024 * 1024) > _kMaxAttachmentSize) { @@ -902,18 +902,18 @@ class MessageInputState extends State { ..file = file ..attachment = Attachment( localUri: file.path != null ? Uri.parse(file.path) : null, - type: medium.mediaType == MediaType.image ? 'image' : 'video', + type: medium.type == AssetType.image ? 'image' : 'video', ); }); final url = await _uploadAttachment( file, - medium.mediaType == MediaType.image + medium.type == AssetType.image ? DefaultAttachmentTypes.image : DefaultAttachmentTypes.video, channel); - final fileType = medium.mediaType == MediaType.image + final fileType = medium.type == AssetType.image ? DefaultAttachmentTypes.image : DefaultAttachmentTypes.video; @@ -936,6 +936,7 @@ class MessageInputState extends State { setState(() { _attachments.remove(attachment); }); + print(e); print(s); Scaffold.of(context).showSnackBar( SnackBar( diff --git a/pubspec.yaml b/pubspec.yaml index b0f65c71..5ecb60de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,8 +38,7 @@ dependencies: flutter_slidable: ^0.5.4 carousel_slider: ^2.2.1 clipboard: ^0.1.2+8 - media_gallery: - git: https://github.com/imtoori/media_gallery.git + photo_manager: ^0.5.8 permission_handler: ^5.0.1+1 transparent_image: ^1.0.0 ezanimation: ^0.4.1