diff --git a/packages/stream_chat_flutter/lib/src/media_list_view.dart b/packages/stream_chat_flutter/lib/src/media_list_view.dart index 62072b0a..81a5fa4e 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:photo_manager/photo_manager.dart'; +import 'package:stream_chat_flutter/src/media_list_view_controller.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; extension on Duration { @@ -25,6 +26,7 @@ class MediaListView extends StatefulWidget { Key? key, this.selectedIds = const [], this.onSelect, + this.controller, }) : super(key: key); /// Stores the media selected @@ -33,18 +35,28 @@ class MediaListView extends StatefulWidget { /// Callback for on media selected final void Function(AssetEntity media)? onSelect; + /// Controller that handles MediaListView + final MediaListViewController? controller; + @override _MediaListViewState createState() => _MediaListViewState(); } class _MediaListViewState extends State { - final _media = []; - final ScrollController _scrollController = ScrollController(); - int _currentPage = 0; + var _media = []; + var _currentPage = 0; + final _scrollController = ScrollController(); + + /// Controller necessary to verify limited access to photo gallery in iOS and + /// update the media list when listerners are emitted + late final controller = widget.controller ?? MediaListViewController(); @override Widget build(BuildContext context) => LazyLoadScrollView( - onEndOfPage: () async => _getMedia(), + onEndOfPage: () async { + await _getMedia(); + _updatePage(); + }, child: GridView.builder( itemCount: _media.length, controller: _scrollController, @@ -141,9 +153,29 @@ class _MediaListViewState extends State { @override void initState() { super.initState(); + controller.addListener(_updateMediaList); _getMedia(); } + @override + void dispose() { + super.dispose(); + controller.removeListener(_updateMediaList); + if (widget.controller == null) { + controller.dispose(); + } + } + + void _updateMediaList() { + if (controller.shouldUpdateMedia) { + _getMedia(); + } + } + + void _updatePage() { + ++_currentPage; + } + Future _getMedia() async { final assetList = (await PhotoManager.getAssetPathList( filterOption: FilterOptionGroup( @@ -162,13 +194,11 @@ class _MediaListViewState extends State { page: _currentPage, size: 50, ); - if (media?.isNotEmpty == true) { setState(() { - _media.addAll(media!); + _media = media!; }); } - ++_currentPage; } } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index bb82386f..43528f73 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -15,6 +15,7 @@ import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; +import 'package:stream_chat_flutter/src/media_list_view_controller.dart'; import 'package:stream_chat_flutter/src/multi_overlay.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/user_mentions_overlay.dart'; @@ -343,6 +344,7 @@ class MessageInputState extends State { final List _mentionedUsers = []; final _imagePicker = ImagePicker(); + final _mediaListViewController = MediaListViewController(); late final _focusNode = widget.focusNode ?? FocusNode(); late final _isInternalFocusNode = widget.focusNode == null; bool _inputEnabled = true; @@ -1056,6 +1058,26 @@ class MessageInputState extends State { ); }, ), + const Spacer(), + FutureBuilder( + future: PhotoManager.requestPermissionExtend(), + builder: (context, snapshot) { + if (snapshot.hasData && + snapshot.data == PermissionState.limited) { + return TextButton( + child: Text(context.translations.viewLibrary), + onPressed: () async { + await PhotoManager.presentLimited(); + _mediaListViewController.updateMedia( + newValue: true, + ); + }, + ); + } + + return const SizedBox.shrink(); + }, + ), ], ), DecoratedBox( @@ -1088,6 +1110,7 @@ class MessageInputState extends State { borderRadius: BorderRadius.circular(8), ), child: _PickerWidget( + mediaListViewController: _mediaListViewController, filePickerIndex: _filePickerIndex, streamChatTheme: _streamChatTheme, containsFile: _attachmentContainsFile, @@ -1966,6 +1989,7 @@ class _PickerWidget extends StatefulWidget { required this.onAddMoreFilesClick, required this.onMediaSelected, required this.streamChatTheme, + required this.mediaListViewController, }) : super(key: key); final int filePickerIndex; @@ -1974,6 +1998,7 @@ class _PickerWidget extends StatefulWidget { final void Function(DefaultAttachmentTypes) onAddMoreFilesClick; final void Function(AssetEntity) onMediaSelected; final StreamChatThemeData streamChatTheme; + final MediaListViewController mediaListViewController; @override _PickerWidgetState createState() => _PickerWidgetState(); @@ -2021,7 +2046,9 @@ class _PickerWidgetState extends State<_PickerWidget> { ), ); } + return MediaListView( + controller: widget.mediaListViewController, selectedIds: widget.selectedMedias, onSelect: widget.onMediaSelected, );