Skip to content

Commit

Permalink
#1201 keep selection when action on several items is interrupted befo…
Browse files Browse the repository at this point in the history
…re processing
  • Loading branch information
deckerst committed Feb 2, 2025
1 parent 16da0ec commit 0a3a792
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 39 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ All notable changes to this project will be documented in this file.
### Changed

- improved subsampling and filter quality strategy
- ignore moving an item to its current directory
- keep selection when action on several items is interrupted before processing
- upgraded Flutter to stable v3.27.3

### Fixed
Expand Down
49 changes: 29 additions & 20 deletions lib/widgets/collection/entry_set_action_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -292,26 +292,29 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
final details = vaults.getVault(entry.directory);
return details?.useBin ?? settings.enableBin;
});
await Future.forEach(
byBinUsage.entries,
(kv) => doDelete(
context: context,
entries: kv.value.toSet(),
enableBin: kv.key,
));
var completed = true;
await Future.forEach(byBinUsage.entries, (kv) async {
completed &= await doDelete(
context: context,
entries: kv.value.toSet(),
enableBin: kv.key,
);
});

_browse(context);
if (completed) {
_browse(context);
}
}

Future<void> doDelete({
// returns whether it completed the action (with or without failures)
Future<bool> doDelete({
required BuildContext context,
required Set<AvesEntry> entries,
required bool enableBin,
}) async {
final pureTrash = entries.every((entry) => entry.trashed);
if (enableBin && !pureTrash) {
await doMove(context, moveType: MoveType.toBin, entries: entries);
return;
return await doMove(context, moveType: MoveType.toBin, entries: entries);
}

final l10n = context.l10n;
Expand All @@ -325,10 +328,10 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
message: l10n.deleteEntriesConfirmationDialogMessage(todoCount),
confirmationButtonLabel: l10n.deleteButtonLabel,
)) {
return;
return false;
}

if (!await checkStoragePermissionForAlbums(context, storageDirs, entries: entries)) return;
if (!await checkStoragePermissionForAlbums(context, storageDirs, entries: entries)) return false;

source.pauseMonitoring();
final opId = mediaEditService.newOpId;
Expand All @@ -354,13 +357,16 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
await storageService.deleteEmptyRegularDirectories(storageDirs);
},
);
return true;
}

Future<void> _move(BuildContext context, {required MoveType moveType}) async {
final entries = _getTargetItems(context);
await doMove(context, moveType: moveType, entries: entries);
final completed = await doMove(context, moveType: moveType, entries: entries);

_browse(context);
if (completed) {
_browse(context);
}
}

Future<void> _rename(BuildContext context) async {
Expand All @@ -381,9 +387,11 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
return MapEntry(entry, '$newName${entry.extension}');
});
final entriesToNewName = Map.fromEntries(await Future.wait(namingFutures)).whereNotNullValue();
await rename(context, entriesToNewName: entriesToNewName, persist: true);
final completed = await rename(context, entriesToNewName: entriesToNewName, persist: true);

_browse(context);
if (completed) {
_browse(context);
}
}

Future<void> _convert(BuildContext context) async {
Expand All @@ -398,13 +406,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware

switch (options.action) {
case EntryConvertAction.convert:
await doExport(context, entries, options);
final completed = await doExport(context, entries, options);
if (completed) {
_browse(context);
}
case EntryConvertAction.convertMotionPhotoToStillImage:
final todoItems = entries.where((entry) => entry.isMotionPhoto).toSet();
await _edit(context, todoItems, (entry) => entry.removeTrailerVideo());
}

_browse(context);
}

Future<void> _toggleFavourite(BuildContext context) async {
Expand Down
45 changes: 26 additions & 19 deletions lib/widgets/common/action_mixins/entry_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
Future<void> doExport(BuildContext context, Set<AvesEntry> targetEntries, EntryConvertOptions options) async {
// returns whether it completed the action (with or without failures)
Future<bool> doExport(BuildContext context, Set<AvesEntry> targetEntries, EntryConvertOptions options) async {
final destinationAlbumFilter = await pickAlbum(context: context, moveType: MoveType.export, storedAlbumsOnly: true);
if (destinationAlbumFilter == null || destinationAlbumFilter is! StoredAlbumFilter) return;
if (destinationAlbumFilter == null || destinationAlbumFilter is! StoredAlbumFilter) return false;

final destinationAlbum = destinationAlbumFilter.album;
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return;
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return false;

if (!await checkFreeSpaceForMove(context, targetEntries, destinationAlbum, MoveType.export)) return;
if (!await checkFreeSpaceForMove(context, targetEntries, destinationAlbum, MoveType.export)) return false;

final transientMultiPageInfo = <MultiPageInfo>{};
final selection = <AvesEntry>{};
Expand Down Expand Up @@ -89,7 +90,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
),
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
);
if (value == null) return;
if (value == null) return false;
nameConflictStrategy = value;
}

Expand Down Expand Up @@ -157,9 +158,11 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
},
);
transientMultiPageInfo.forEach((v) => v.dispose());
return true;
}

Future<void> doQuickMove(
// returns whether it completed the action (with or without failures)
Future<bool> doQuickMove(
BuildContext context, {
required MoveType moveType,
required Map<String, Set<AvesEntry>> entriesByDestination,
Expand All @@ -176,23 +179,23 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {

final entries = entriesByDestination.values.expand((v) => v).toSet();
final todoCount = entries.length;
if (todoCount == 0) return;
if (todoCount == 0) return true;

final toBin = moveType == MoveType.toBin;
final copy = moveType == MoveType.copy;

// permission for modification at destinations
final destinationAlbums = entriesByDestination.keys.toSet();
if (!await checkStoragePermissionForAlbums(context, destinationAlbums)) return;
if (!await checkStoragePermissionForAlbums(context, destinationAlbums)) return false;

// permission for modification at origins
final originAlbums = entries.map((e) => e.directory).nonNulls.toSet();
if ({MoveType.move, MoveType.toBin}.contains(moveType) && !await checkStoragePermissionForAlbums(context, originAlbums, entries: entries)) return;
if ({MoveType.move, MoveType.toBin}.contains(moveType) && !await checkStoragePermissionForAlbums(context, originAlbums, entries: entries)) return false;

final hasEnoughSpaceByDestination = await Future.wait(destinationAlbums.map((destinationAlbum) {
return checkFreeSpaceForMove(context, entries, destinationAlbum, moveType);
}));
if (hasEnoughSpaceByDestination.any((v) => !v)) return;
if (hasEnoughSpaceByDestination.any((v) => !v)) return false;

final l10n = context.l10n;
var nameConflictStrategy = NameConflictStrategy.rename;
Expand All @@ -217,12 +220,12 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
),
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
);
if (value == null) return;
if (value == null) return false;
nameConflictStrategy = value;
}
}

if ({MoveType.move, MoveType.copy}.contains(moveType) && !await _checkUndatedItems(context, entries)) return;
if ({MoveType.move, MoveType.copy}.contains(moveType) && !await _checkUndatedItems(context, entries)) return false;

final source = context.read<CollectionSource>();
source.pauseMonitoring();
Expand Down Expand Up @@ -321,9 +324,11 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
}
},
);
return true;
}

Future<void> doMove(
// returns whether it completed the action (with or without failures)
Future<bool> doMove(
BuildContext context, {
required MoveType moveType,
required Set<AvesEntry> entries,
Expand All @@ -338,7 +343,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
message: l10n.binEntriesConfirmationDialogMessage(entries.length),
confirmationButtonLabel: l10n.deleteButtonLabel,
)) {
return;
return false;
}
}

Expand All @@ -348,7 +353,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
case MoveType.move:
case MoveType.export:
final destinationAlbumFilter = await pickAlbum(context: context, moveType: moveType, storedAlbumsOnly: true);
if (destinationAlbumFilter == null || destinationAlbumFilter is! StoredAlbumFilter) return;
if (destinationAlbumFilter == null || destinationAlbumFilter is! StoredAlbumFilter) return false;

final destinationAlbum = destinationAlbumFilter.album;
settings.recentDestinationAlbums = settings.recentDestinationAlbums
Expand All @@ -365,15 +370,16 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
});
}

await doQuickMove(
return await doQuickMove(
context,
moveType: moveType,
entriesByDestination: entriesByDestination,
onSuccess: onSuccess,
);
}

Future<void> rename(
// returns whether it completed the action (with or without failures)
Future<bool> rename(
BuildContext context, {
required Map<AvesEntry, String> entriesToNewName,
required bool persist,
Expand All @@ -383,9 +389,9 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
final todoCount = entries.length;
assert(todoCount > 0);

if (!await checkStoragePermission(context, entries)) return;
if (!await checkStoragePermission(context, entries)) return false;

if (!await _checkUndatedItems(context, entries)) return;
if (!await _checkUndatedItems(context, entries)) return false;

final source = context.read<CollectionSource>();
source.pauseMonitoring();
Expand Down Expand Up @@ -420,6 +426,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
}
},
);
return true;
}

Future<bool> _checkUndatedItems(BuildContext context, Set<AvesEntry> entries) async {
Expand Down

0 comments on commit 0a3a792

Please sign in to comment.