use photo manager
This commit is contained in:
+140
-106
@@ -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<String> 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<MediaListView> {
|
||||
final _media = <Media>[];
|
||||
final _media = <AssetEntity>[];
|
||||
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<MediaListView> {
|
||||
}
|
||||
|
||||
void _getMedia() async {
|
||||
final List<MediaCollection> 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<MediaThumbnailProvider> {
|
||||
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<ui.Codec> _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<MediaThumbnailProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture<MediaThumbnailProvider>(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}")';
|
||||
}
|
||||
|
||||
@@ -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<MessageInput> {
|
||||
}
|
||||
}
|
||||
|
||||
void _addAttachment(Media medium) async {
|
||||
void _addAttachment(AssetEntity medium) async {
|
||||
final attachment = _SendingAttachment(
|
||||
id: medium.id,
|
||||
);
|
||||
@@ -853,16 +853,16 @@ class MessageInputState extends State<MessageInput> {
|
||||
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<MessageInput> {
|
||||
..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<MessageInput> {
|
||||
setState(() {
|
||||
_attachments.remove(attachment);
|
||||
});
|
||||
print(e);
|
||||
print(s);
|
||||
Scaffold.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
||||
Reference in New Issue
Block a user