diff --git a/lib/src/media_list_view.dart b/lib/src/media_list_view.dart new file mode 100644 index 00000000..5a2fc25f --- /dev/null +++ b/lib/src/media_list_view.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:media_gallery/media_gallery.dart'; +import 'package:stream_chat_flutter/src/stream_icons.dart'; +import 'package:transparent_image/transparent_image.dart'; + +class MediaListView extends StatefulWidget { + final List selectedIds; + final void Function(Media media) onSelect; + + const MediaListView({ + Key key, + this.selectedIds = const [], + this.onSelect, + }) : super(key: key); + @override + _MediaListViewState createState() => _MediaListViewState(); +} + +class _MediaListViewState extends State { + final _media = []; + final ScrollController _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + return GridView.builder( + itemCount: _media.length, + controller: _scrollController, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), + 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( + placeholder: MemoryImage(kTransparentImage), + image: MediaThumbnailProvider( + media: media, + ), + 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( + maxRadius: 12.0, + backgroundColor: Colors.white, + child: Icon( + StreamIcons.check, + 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', + ), + ), + ], + ), + onTap: () { + if (widget.onSelect != null) { + widget.onSelect(media); + } + }, + ), + ); + }, + ); + } + + @override + void initState() { + super.initState(); + + _scrollController.addListener(() { + if (_scrollController.offset > + _scrollController.position.maxScrollExtent - 100) { + _getMedia(); + } + }); + + _getMedia(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _getMedia() async { + final List collections = + await MediaGallery.listMediaCollections( + mediaTypes: [ + MediaType.video, + MediaType.image, + ], + ); + + if (collections.isEmpty) { + return; + } + 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); + }); + + print( + '_media.where((element) => element.mediaType == MediaType.video).length: ${_media.where((element) => element.mediaType == MediaType.video).length}'); + } +} diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index c26cd152..7e8a40c4 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -14,11 +14,11 @@ import 'package:media_gallery/media_gallery.dart'; import 'package:mime/mime.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:stream_chat/stream_chat.dart'; +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:substring_highlight/substring_highlight.dart'; -import 'package:transparent_image/transparent_image.dart'; import '../stream_chat_flutter.dart'; import 'stream_channel.dart'; @@ -183,8 +183,6 @@ class MessageInputState extends State { int _filePickerIndex = 0; double _filePickerSize = 250.0; - Iterable _media = []; - /// The editing controller passed to the input TextField TextEditingController textEditingController; @@ -699,15 +697,16 @@ class MessageInputState extends State { ), ), ), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8.0), + if (_openFilePickerSection) + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.0), + ), + child: _buildPickerSection(), ), - child: _buildPickerSection(), ), - ), ], ), ), @@ -717,68 +716,15 @@ class MessageInputState extends State { Widget _buildPickerSection() { switch (_filePickerIndex) { case 0: - return GridView.builder( - itemCount: _media.length, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), - itemBuilder: (context, position) { - return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 1.0, vertical: 1.0), - child: InkWell( - child: Stack( - children: [ - AspectRatio( - aspectRatio: 1.0, - child: FadeInImage( - placeholder: MemoryImage(kTransparentImage), - image: MediaThumbnailProvider( - media: _media.elementAt(position), - ), - fit: BoxFit.fill, - ), - ), - 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, - child: Icon( - StreamIcons.check, - color: Colors.black, - ), - ), - ), - ), - ), - ), - ], - ), - onTap: () { - if (!_attachments.any((element) => - element.id == _media.elementAt(position).id)) { - _addAttachment(_media.elementAt(position)); - } else { - _attachments.removeWhere((element) => - element.id == _media.elementAt(position).id); - setState(() {}); - } - }, - ), - ); + return MediaListView( + selectedIds: _attachments.map((e) => e.id).toList(), + onSelect: (media) { + if (!_attachments.any((element) => element.id == media.id)) { + _addAttachment(media); + } else { + _attachments.removeWhere((element) => element.id == media.id); + setState(() {}); + } }, ); break; @@ -845,32 +791,6 @@ class MessageInputState extends State { }); } - void _getAllMedia() async { - final List collections = - await MediaGallery.listMediaCollections(mediaTypes: [ - MediaType.image, - MediaType.video, - ]); - - if (collections.isEmpty) { - return; - } - - final page = await collections - .firstWhere( - (element) => element.name == 'All', - orElse: () => collections.first, - ) - .getMedias( - take: 50, - ); - - print('page.items.length: ${page.items.length}'); - setState(() { - _media = page.items; - }); - } - CircleAvatar _buildGiphyIcon() { if (kIsWeb) { return CircleAvatar( @@ -1275,6 +1195,13 @@ class MessageInputState extends State { ), ), onTap: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + if (_openFilePickerSection) { setState(() { _openFilePickerSection = false; @@ -1282,14 +1209,12 @@ class MessageInputState extends State { }); } else { final status = await Permission.storage.request(); - print('status: ${status}'); if (status.isDenied || status.isPermanentlyDenied) { final res = await openAppSettings(); if (!res) { return; } } - _getAllMedia(); showAttachmentModal(); } }, diff --git a/svgs/video_call_icon.svg b/svgs/video_call_icon.svg new file mode 100644 index 00000000..1c3832d6 --- /dev/null +++ b/svgs/video_call_icon.svg @@ -0,0 +1,3 @@ + + +