import 'dart:async'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/widgets/collection/collection_actions.dart'; import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/action_delegates/create_album_dialog.dart'; import 'package:aves/widgets/common/action_delegates/feedback.dart'; import 'package:aves/widgets/common/action_delegates/permission_aware.dart'; import 'package:aves/widgets/common/aves_dialog.dart'; import 'package:aves/widgets/common/entry_actions.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/common/chip_actions.dart'; import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart'; import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin { final CollectionLens collection; SelectionActionDelegate({ @required this.collection, }); void onEntryActionSelected(BuildContext context, EntryAction action) { switch (action) { case EntryAction.delete: _showDeleteDialog(context); break; case EntryAction.share: AndroidAppService.share(collection.selection); break; default: break; } } void onCollectionActionSelected(BuildContext context, CollectionAction action) { switch (action) { case CollectionAction.copy: _moveSelection(context, copy: true); break; case CollectionAction.move: _moveSelection(context, copy: false); break; case CollectionAction.refreshMetadata: _refreshSelectionMetadata(); break; default: break; } } Future _moveSelection(BuildContext context, {@required bool copy}) async { final source = collection.source; final chipSetActionDelegate = AlbumChipSetActionDelegate(source: source); final destinationAlbum = await Navigator.push( context, MaterialPageRoute( builder: (context) { return Selector( selector: (context, s) => s.albumSortFactor, builder: (context, sortFactor, child) { return FilterGridPage( source: source, appBar: SliverAppBar( leading: BackButton(), title: Text(copy ? 'Copy to Album' : 'Move to Album'), actions: [ IconButton( icon: Icon(AIcons.createAlbum), onPressed: () async { final newAlbum = await showDialog( context: context, builder: (context) => CreateAlbumDialog(), ); if (newAlbum != null && newAlbum.isNotEmpty) { Navigator.pop(context, newAlbum); } }, tooltip: 'Create album', ), IconButton( icon: Icon(AIcons.sort), onPressed: () => chipSetActionDelegate.onActionSelected(context, ChipSetAction.sort), ), ], floating: true, ), filterEntries: AlbumListPage.getAlbumEntries(source), filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)), emptyBuilder: () => EmptyContent( icon: AIcons.album, text: 'No albums', ), onTap: (filter) => Navigator.pop(context, (filter as AlbumFilter)?.album), ); }, ); }, ), ); if (destinationAlbum == null || destinationAlbum.isEmpty) return; if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return; final selection = collection.selection.toList(); if (!await checkStoragePermission(context, selection)) return; showOpReport( context: context, selection: selection, opStream: ImageFileService.move(selection, copy: copy, destinationAlbum: destinationAlbum), onDone: (processed) async { final movedOps = processed.where((e) => e.success); final movedCount = movedOps.length; final selectionCount = selection.length; if (movedCount < selectionCount) { final count = selectionCount - movedCount; showFeedback(context, 'Failed to ${copy ? 'copy' : 'move'} ${Intl.plural(count, one: '$count item', other: '$count items')}'); } else { final count = movedCount; showFeedback(context, '${copy ? 'Copied' : 'Moved'} ${Intl.plural(count, one: '$count item', other: '$count items')}'); } await source.updateAfterMove( selection: selection, copy: copy, destinationAlbum: destinationAlbum, movedOps: movedOps, ); collection.clearSelection(); collection.browse(); }, ); } Future _refreshSelectionMetadata() async { collection.selection.forEach((entry) => entry.clearMetadata()); final source = collection.source; source.stateNotifier.value = SourceState.cataloguing; await source.catalogEntries(); source.stateNotifier.value = SourceState.locating; await source.locateEntries(); source.stateNotifier.value = SourceState.ready; } Future _showDeleteDialog(BuildContext context) async { final selection = collection.selection.toList(); final count = selection.length; final confirmed = await showDialog( context: context, builder: (context) { return AvesDialog( content: Text('Are you sure you want to delete ${Intl.plural(count, one: 'this item', other: 'these $count items')}?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Cancel'.toUpperCase()), ), TextButton( onPressed: () => Navigator.pop(context, true), child: Text('Delete'.toUpperCase()), ), ], ); }, ); if (confirmed == null || !confirmed) return; if (!await checkStoragePermission(context, selection)) return; showOpReport( context: context, selection: selection, opStream: ImageFileService.delete(selection), onDone: (processed) { final deletedUris = processed.where((e) => e.success).map((e) => e.uri).toList(); final deletedCount = deletedUris.length; final selectionCount = selection.length; if (deletedCount < selectionCount) { final count = selectionCount - deletedCount; showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); } if (deletedCount > 0) { collection.source.removeEntries(selection.where((e) => deletedUris.contains(e.uri)).toList()); } collection.clearSelection(); collection.browse(); }, ); } }