#1201 keep selection when action on several items is interrupted before processing
This commit is contained in:
parent
16da0ec3f5
commit
0a3a792a7e
3 changed files with 57 additions and 39 deletions
|
@ -11,6 +11,8 @@ All notable changes to this project will be documented in this file.
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- improved subsampling and filter quality strategy
|
- 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
|
- upgraded Flutter to stable v3.27.3
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -292,26 +292,29 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
final details = vaults.getVault(entry.directory);
|
final details = vaults.getVault(entry.directory);
|
||||||
return details?.useBin ?? settings.enableBin;
|
return details?.useBin ?? settings.enableBin;
|
||||||
});
|
});
|
||||||
await Future.forEach(
|
var completed = true;
|
||||||
byBinUsage.entries,
|
await Future.forEach(byBinUsage.entries, (kv) async {
|
||||||
(kv) => doDelete(
|
completed &= await doDelete(
|
||||||
context: context,
|
context: context,
|
||||||
entries: kv.value.toSet(),
|
entries: kv.value.toSet(),
|
||||||
enableBin: kv.key,
|
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 BuildContext context,
|
||||||
required Set<AvesEntry> entries,
|
required Set<AvesEntry> entries,
|
||||||
required bool enableBin,
|
required bool enableBin,
|
||||||
}) async {
|
}) async {
|
||||||
final pureTrash = entries.every((entry) => entry.trashed);
|
final pureTrash = entries.every((entry) => entry.trashed);
|
||||||
if (enableBin && !pureTrash) {
|
if (enableBin && !pureTrash) {
|
||||||
await doMove(context, moveType: MoveType.toBin, entries: entries);
|
return await doMove(context, moveType: MoveType.toBin, entries: entries);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
|
@ -325,10 +328,10 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
message: l10n.deleteEntriesConfirmationDialogMessage(todoCount),
|
message: l10n.deleteEntriesConfirmationDialogMessage(todoCount),
|
||||||
confirmationButtonLabel: l10n.deleteButtonLabel,
|
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();
|
source.pauseMonitoring();
|
||||||
final opId = mediaEditService.newOpId;
|
final opId = mediaEditService.newOpId;
|
||||||
|
@ -354,13 +357,16 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
await storageService.deleteEmptyRegularDirectories(storageDirs);
|
await storageService.deleteEmptyRegularDirectories(storageDirs);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _move(BuildContext context, {required MoveType moveType}) async {
|
Future<void> _move(BuildContext context, {required MoveType moveType}) async {
|
||||||
final entries = _getTargetItems(context);
|
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 {
|
Future<void> _rename(BuildContext context) async {
|
||||||
|
@ -381,9 +387,11 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
return MapEntry(entry, '$newName${entry.extension}');
|
return MapEntry(entry, '$newName${entry.extension}');
|
||||||
});
|
});
|
||||||
final entriesToNewName = Map.fromEntries(await Future.wait(namingFutures)).whereNotNullValue();
|
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 {
|
Future<void> _convert(BuildContext context) async {
|
||||||
|
@ -398,13 +406,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
|
|
||||||
switch (options.action) {
|
switch (options.action) {
|
||||||
case EntryConvertAction.convert:
|
case EntryConvertAction.convert:
|
||||||
await doExport(context, entries, options);
|
final completed = await doExport(context, entries, options);
|
||||||
|
if (completed) {
|
||||||
|
_browse(context);
|
||||||
|
}
|
||||||
case EntryConvertAction.convertMotionPhotoToStillImage:
|
case EntryConvertAction.convertMotionPhotoToStillImage:
|
||||||
final todoItems = entries.where((entry) => entry.isMotionPhoto).toSet();
|
final todoItems = entries.where((entry) => entry.isMotionPhoto).toSet();
|
||||||
await _edit(context, todoItems, (entry) => entry.removeTrailerVideo());
|
await _edit(context, todoItems, (entry) => entry.removeTrailerVideo());
|
||||||
}
|
}
|
||||||
|
|
||||||
_browse(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _toggleFavourite(BuildContext context) async {
|
Future<void> _toggleFavourite(BuildContext context) async {
|
||||||
|
|
|
@ -37,14 +37,15 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
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);
|
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;
|
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 transientMultiPageInfo = <MultiPageInfo>{};
|
||||||
final selection = <AvesEntry>{};
|
final selection = <AvesEntry>{};
|
||||||
|
@ -89,7 +90,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
),
|
),
|
||||||
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
|
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
|
||||||
);
|
);
|
||||||
if (value == null) return;
|
if (value == null) return false;
|
||||||
nameConflictStrategy = value;
|
nameConflictStrategy = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,9 +158,11 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
transientMultiPageInfo.forEach((v) => v.dispose());
|
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, {
|
BuildContext context, {
|
||||||
required MoveType moveType,
|
required MoveType moveType,
|
||||||
required Map<String, Set<AvesEntry>> entriesByDestination,
|
required Map<String, Set<AvesEntry>> entriesByDestination,
|
||||||
|
@ -176,23 +179,23 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
|
|
||||||
final entries = entriesByDestination.values.expand((v) => v).toSet();
|
final entries = entriesByDestination.values.expand((v) => v).toSet();
|
||||||
final todoCount = entries.length;
|
final todoCount = entries.length;
|
||||||
if (todoCount == 0) return;
|
if (todoCount == 0) return true;
|
||||||
|
|
||||||
final toBin = moveType == MoveType.toBin;
|
final toBin = moveType == MoveType.toBin;
|
||||||
final copy = moveType == MoveType.copy;
|
final copy = moveType == MoveType.copy;
|
||||||
|
|
||||||
// permission for modification at destinations
|
// permission for modification at destinations
|
||||||
final destinationAlbums = entriesByDestination.keys.toSet();
|
final destinationAlbums = entriesByDestination.keys.toSet();
|
||||||
if (!await checkStoragePermissionForAlbums(context, destinationAlbums)) return;
|
if (!await checkStoragePermissionForAlbums(context, destinationAlbums)) return false;
|
||||||
|
|
||||||
// permission for modification at origins
|
// permission for modification at origins
|
||||||
final originAlbums = entries.map((e) => e.directory).nonNulls.toSet();
|
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) {
|
final hasEnoughSpaceByDestination = await Future.wait(destinationAlbums.map((destinationAlbum) {
|
||||||
return checkFreeSpaceForMove(context, entries, destinationAlbum, moveType);
|
return checkFreeSpaceForMove(context, entries, destinationAlbum, moveType);
|
||||||
}));
|
}));
|
||||||
if (hasEnoughSpaceByDestination.any((v) => !v)) return;
|
if (hasEnoughSpaceByDestination.any((v) => !v)) return false;
|
||||||
|
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
var nameConflictStrategy = NameConflictStrategy.rename;
|
var nameConflictStrategy = NameConflictStrategy.rename;
|
||||||
|
@ -217,12 +220,12 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
),
|
),
|
||||||
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
|
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
|
||||||
);
|
);
|
||||||
if (value == null) return;
|
if (value == null) return false;
|
||||||
nameConflictStrategy = value;
|
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>();
|
final source = context.read<CollectionSource>();
|
||||||
source.pauseMonitoring();
|
source.pauseMonitoring();
|
||||||
|
@ -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, {
|
BuildContext context, {
|
||||||
required MoveType moveType,
|
required MoveType moveType,
|
||||||
required Set<AvesEntry> entries,
|
required Set<AvesEntry> entries,
|
||||||
|
@ -338,7 +343,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
message: l10n.binEntriesConfirmationDialogMessage(entries.length),
|
message: l10n.binEntriesConfirmationDialogMessage(entries.length),
|
||||||
confirmationButtonLabel: l10n.deleteButtonLabel,
|
confirmationButtonLabel: l10n.deleteButtonLabel,
|
||||||
)) {
|
)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +353,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
case MoveType.move:
|
case MoveType.move:
|
||||||
case MoveType.export:
|
case MoveType.export:
|
||||||
final destinationAlbumFilter = await pickAlbum(context: context, moveType: moveType, storedAlbumsOnly: true);
|
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;
|
final destinationAlbum = destinationAlbumFilter.album;
|
||||||
settings.recentDestinationAlbums = settings.recentDestinationAlbums
|
settings.recentDestinationAlbums = settings.recentDestinationAlbums
|
||||||
|
@ -365,7 +370,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await doQuickMove(
|
return await doQuickMove(
|
||||||
context,
|
context,
|
||||||
moveType: moveType,
|
moveType: moveType,
|
||||||
entriesByDestination: entriesByDestination,
|
entriesByDestination: entriesByDestination,
|
||||||
|
@ -373,7 +378,8 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> rename(
|
// returns whether it completed the action (with or without failures)
|
||||||
|
Future<bool> rename(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required Map<AvesEntry, String> entriesToNewName,
|
required Map<AvesEntry, String> entriesToNewName,
|
||||||
required bool persist,
|
required bool persist,
|
||||||
|
@ -383,9 +389,9 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
final todoCount = entries.length;
|
final todoCount = entries.length;
|
||||||
assert(todoCount > 0);
|
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>();
|
final source = context.read<CollectionSource>();
|
||||||
source.pauseMonitoring();
|
source.pauseMonitoring();
|
||||||
|
@ -420,6 +426,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _checkUndatedItems(BuildContext context, Set<AvesEntry> entries) async {
|
Future<bool> _checkUndatedItems(BuildContext context, Set<AvesEntry> entries) async {
|
||||||
|
|
Loading…
Reference in a new issue