collection: rotate/flip in bulk
This commit is contained in:
parent
3c682b37b5
commit
bd47d52412
4 changed files with 99 additions and 16 deletions
|
@ -20,6 +20,9 @@ enum EntrySetAction {
|
|||
copy,
|
||||
move,
|
||||
rescan,
|
||||
rotateCCW,
|
||||
rotateCW,
|
||||
flip,
|
||||
editDate,
|
||||
removeMetadata,
|
||||
}
|
||||
|
@ -69,6 +72,12 @@ extension ExtraEntrySetAction on EntrySetAction {
|
|||
return context.l10n.collectionActionMove;
|
||||
case EntrySetAction.rescan:
|
||||
return context.l10n.collectionActionRescan;
|
||||
case EntrySetAction.rotateCCW:
|
||||
return context.l10n.entryActionRotateCCW;
|
||||
case EntrySetAction.rotateCW:
|
||||
return context.l10n.entryActionRotateCW;
|
||||
case EntrySetAction.flip:
|
||||
return context.l10n.entryActionFlip;
|
||||
case EntrySetAction.editDate:
|
||||
return context.l10n.entryInfoActionEditDate;
|
||||
case EntrySetAction.removeMetadata:
|
||||
|
@ -112,6 +121,12 @@ extension ExtraEntrySetAction on EntrySetAction {
|
|||
return AIcons.move;
|
||||
case EntrySetAction.rescan:
|
||||
return AIcons.refresh;
|
||||
case EntrySetAction.rotateCCW:
|
||||
return AIcons.rotateLeft;
|
||||
case EntrySetAction.rotateCW:
|
||||
return AIcons.rotateRight;
|
||||
case EntrySetAction.flip:
|
||||
return AIcons.flip;
|
||||
case EntrySetAction.editDate:
|
||||
return AIcons.date;
|
||||
case EntrySetAction.removeMetadata:
|
||||
|
|
|
@ -222,6 +222,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
icon: AIcons.edit,
|
||||
title: context.l10n.collectionActionEdit,
|
||||
items: [
|
||||
_buildRotateAndFlipMenuItems(context, enabled: hasSelection),
|
||||
_toMenuItem(EntrySetAction.editDate, enabled: hasSelection),
|
||||
_toMenuItem(EntrySetAction.removeMetadata, enabled: hasSelection),
|
||||
],
|
||||
|
@ -273,6 +274,41 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
);
|
||||
}
|
||||
|
||||
PopupMenuItem<EntrySetAction> _buildRotateAndFlipMenuItems(BuildContext context, {required bool enabled}) {
|
||||
Widget buildDivider() => const SizedBox(
|
||||
height: 16,
|
||||
child: VerticalDivider(
|
||||
width: 1,
|
||||
thickness: 1,
|
||||
),
|
||||
);
|
||||
|
||||
Widget buildItem(EntrySetAction action) => Expanded(
|
||||
child: PopupMenuItem(
|
||||
value: action,
|
||||
enabled: enabled,
|
||||
child: Tooltip(
|
||||
message: action.getText(context),
|
||||
child: Center(child: action.getIcon()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
buildDivider(),
|
||||
buildItem(EntrySetAction.rotateCCW),
|
||||
buildDivider(),
|
||||
buildItem(EntrySetAction.rotateCW),
|
||||
buildDivider(),
|
||||
buildItem(EntrySetAction.flip),
|
||||
buildDivider(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onActivityChange() {
|
||||
if (context.read<Selection<AvesEntry>>().isSelecting) {
|
||||
_browseToSelectAnimation.forward();
|
||||
|
@ -303,6 +339,9 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
case EntrySetAction.rescan:
|
||||
case EntrySetAction.map:
|
||||
case EntrySetAction.stats:
|
||||
case EntrySetAction.rotateCCW:
|
||||
case EntrySetAction.rotateCW:
|
||||
case EntrySetAction.flip:
|
||||
case EntrySetAction.editDate:
|
||||
case EntrySetAction.removeMetadata:
|
||||
_actionDelegate.onActionSelected(context, action);
|
||||
|
|
|
@ -49,6 +49,15 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa
|
|||
case EntrySetAction.rescan:
|
||||
_rescan(context);
|
||||
break;
|
||||
case EntrySetAction.rotateCCW:
|
||||
_rotate(context, clockwise: false);
|
||||
break;
|
||||
case EntrySetAction.rotateCW:
|
||||
_rotate(context, clockwise: true);
|
||||
break;
|
||||
case EntrySetAction.flip:
|
||||
_flip(context);
|
||||
break;
|
||||
case EntrySetAction.editDate:
|
||||
_editDate(context);
|
||||
break;
|
||||
|
@ -313,12 +322,16 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa
|
|||
);
|
||||
}
|
||||
|
||||
Future<bool> _checkEditableFormats(
|
||||
Future<Set<AvesEntry>?> _getEditableItems(
|
||||
BuildContext context, {
|
||||
required Set<AvesEntry> supported,
|
||||
required Set<AvesEntry> unsupported,
|
||||
required Set<AvesEntry> selectedItems,
|
||||
required bool Function(AvesEntry entry) canEdit,
|
||||
}) async {
|
||||
if (unsupported.isEmpty) return true;
|
||||
final bySupported = groupBy<AvesEntry, bool>(selectedItems, canEdit);
|
||||
final supported = (bySupported[true] ?? []).toSet();
|
||||
final unsupported = (bySupported[false] ?? []).toSet();
|
||||
|
||||
if (unsupported.isEmpty) return supported;
|
||||
|
||||
final unsupportedTypes = unsupported.map((entry) => entry.mimeType).toSet().map(MimeUtils.displayType).toList()..sort();
|
||||
final confirmed = await showDialog<bool>(
|
||||
|
@ -343,19 +356,37 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa
|
|||
);
|
||||
},
|
||||
);
|
||||
if (confirmed == null || !confirmed) return false;
|
||||
if (confirmed == null || !confirmed) return null;
|
||||
|
||||
return true;
|
||||
return supported;
|
||||
}
|
||||
|
||||
Future<void> _rotate(BuildContext context, {required bool clockwise}) async {
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
final selectedItems = _getExpandedSelectedItems(selection);
|
||||
|
||||
final todoItems = await _getEditableItems(context, selectedItems: selectedItems, canEdit: (entry) => entry.canRotateAndFlip);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
|
||||
await _edit(context, selection, todoItems, (entry) => entry.rotate(clockwise: clockwise, persist: true));
|
||||
}
|
||||
|
||||
Future<void> _flip(BuildContext context) async {
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
final selectedItems = _getExpandedSelectedItems(selection);
|
||||
|
||||
final todoItems = await _getEditableItems(context, selectedItems: selectedItems, canEdit: (entry) => entry.canRotateAndFlip);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
|
||||
await _edit(context, selection, todoItems, (entry) => entry.flip(persist: true));
|
||||
}
|
||||
|
||||
Future<void> _editDate(BuildContext context) async {
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
final selectedItems = _getExpandedSelectedItems(selection);
|
||||
|
||||
final bySupported = groupBy<AvesEntry, bool>(selectedItems, (entry) => entry.canEditExif);
|
||||
final todoItems = (bySupported[true] ?? []).toSet();
|
||||
final unsupported = (bySupported[false] ?? []).toSet();
|
||||
if (!await _checkEditableFormats(context, supported: todoItems, unsupported: unsupported)) return;
|
||||
final todoItems = await _getEditableItems(context, selectedItems: selectedItems, canEdit: (entry) => entry.canEditExif);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
|
||||
final modifier = await selectDateModifier(context, todoItems);
|
||||
if (modifier == null) return;
|
||||
|
@ -367,13 +398,11 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa
|
|||
final selection = context.read<Selection<AvesEntry>>();
|
||||
final selectedItems = _getExpandedSelectedItems(selection);
|
||||
|
||||
final bySupported = groupBy<AvesEntry, bool>(selectedItems, (entry) => entry.canRemoveMetadata);
|
||||
final todoItems = (bySupported[true] ?? []).toSet();
|
||||
final unsupported = (bySupported[false] ?? []).toSet();
|
||||
if (!await _checkEditableFormats(context, supported: todoItems, unsupported: unsupported)) return;
|
||||
final todoItems = await _getEditableItems(context, selectedItems: selectedItems, canEdit: (entry) => entry.canRemoveMetadata);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
|
||||
final types = await selectMetadataToRemove(context, todoItems);
|
||||
if (types == null) return;
|
||||
if (types == null || types.isEmpty) return;
|
||||
|
||||
await _edit(context, selection, todoItems, (entry) => entry.removeMetadata(types));
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ class ViewerActionEditorPage extends StatelessWidget {
|
|||
EntryAction.copyToClipboard,
|
||||
EntryAction.print,
|
||||
EntryAction.rotateScreen,
|
||||
EntryAction.flip,
|
||||
EntryAction.rotateCCW,
|
||||
EntryAction.rotateCW,
|
||||
EntryAction.flip,
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
Loading…
Reference in a new issue