#883 bulk converting motion photos to still images
This commit is contained in:
parent
87cfae1e9a
commit
978c22dc50
12 changed files with 328 additions and 227 deletions
|
@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
- Collection: stack RAW and JPEG with same file names
|
- Collection: stack RAW and JPEG with same file names
|
||||||
- Collection: ask to rename/replace/skip when converting items with name conflict
|
- Collection: ask to rename/replace/skip when converting items with name conflict
|
||||||
|
- Export: bulk converting motion photos to still images
|
||||||
|
|
||||||
## <a id="v1.11.3"></a>[v1.11.3] - 2024-06-17
|
## <a id="v1.11.3"></a>[v1.11.3] - 2024-06-17
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:collection';
|
||||||
|
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/entry/sort.dart';
|
import 'package:aves/model/entry/sort.dart';
|
||||||
import 'package:aves/model/favourites.dart';
|
import 'package:aves/model/favourites.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
@ -13,7 +14,6 @@ import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/rating.dart';
|
import 'package:aves/model/filters/rating.dart';
|
||||||
import 'package:aves/model/filters/trash.dart';
|
import 'package:aves/model/filters/trash.dart';
|
||||||
import 'package:aves/model/filters/type.dart';
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/events.dart';
|
import 'package:aves/model/source/events.dart';
|
||||||
|
@ -224,7 +224,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stackDevelopedRaws() {
|
void _stackDevelopedRaws() {
|
||||||
final allRawEntries = _filteredSortedEntries.where(TypeFilter.raw.test).toSet();
|
final allRawEntries = _filteredSortedEntries.where((entry) => entry.isRaw).toSet();
|
||||||
if (allRawEntries.isNotEmpty) {
|
if (allRawEntries.isNotEmpty) {
|
||||||
final allDevelopedEntries = _filteredSortedEntries.where(MimeFilter(MimeTypes.jpeg).test).toSet();
|
final allDevelopedEntries = _filteredSortedEntries.where(MimeFilter(MimeTypes.jpeg).test).toSet();
|
||||||
final rawEntriesByDir = groupBy<AvesEntry, String?>(allRawEntries, (entry) => entry.directory);
|
final rawEntriesByDir = groupBy<AvesEntry, String?>(allRawEntries, (entry) => entry.directory);
|
||||||
|
|
|
@ -193,15 +193,17 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class EntryConvertOptions extends Equatable {
|
class EntryConvertOptions extends Equatable {
|
||||||
|
final EntryConvertAction action;
|
||||||
final String mimeType;
|
final String mimeType;
|
||||||
final bool writeMetadata;
|
final bool writeMetadata;
|
||||||
final LengthUnit lengthUnit;
|
final LengthUnit lengthUnit;
|
||||||
final int width, height, quality;
|
final int width, height, quality;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [mimeType, writeMetadata, lengthUnit, width, height, quality];
|
List<Object?> get props => [action, mimeType, writeMetadata, lengthUnit, width, height, quality];
|
||||||
|
|
||||||
const EntryConvertOptions({
|
const EntryConvertOptions({
|
||||||
|
required this.action,
|
||||||
required this.mimeType,
|
required this.mimeType,
|
||||||
required this.writeMetadata,
|
required this.writeMetadata,
|
||||||
required this.lengthUnit,
|
required this.lengthUnit,
|
||||||
|
|
|
@ -5,45 +5,46 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
extension ExtraEntrySetActionView on EntrySetAction {
|
extension ExtraEntrySetActionView on EntrySetAction {
|
||||||
String getText(BuildContext context) {
|
String getText(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
// general
|
// general
|
||||||
EntrySetAction.configureView => context.l10n.menuActionConfigureView,
|
EntrySetAction.configureView => l10n.menuActionConfigureView,
|
||||||
EntrySetAction.select => context.l10n.menuActionSelect,
|
EntrySetAction.select => l10n.menuActionSelect,
|
||||||
EntrySetAction.selectAll => context.l10n.menuActionSelectAll,
|
EntrySetAction.selectAll => l10n.menuActionSelectAll,
|
||||||
EntrySetAction.selectNone => context.l10n.menuActionSelectNone,
|
EntrySetAction.selectNone => l10n.menuActionSelectNone,
|
||||||
// browsing
|
// browsing
|
||||||
EntrySetAction.searchCollection => MaterialLocalizations.of(context).searchFieldLabel,
|
EntrySetAction.searchCollection => MaterialLocalizations.of(context).searchFieldLabel,
|
||||||
EntrySetAction.toggleTitleSearch =>
|
EntrySetAction.toggleTitleSearch =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
context.l10n.collectionActionShowTitleSearch,
|
l10n.collectionActionShowTitleSearch,
|
||||||
EntrySetAction.addShortcut => context.l10n.collectionActionAddShortcut,
|
EntrySetAction.addShortcut => l10n.collectionActionAddShortcut,
|
||||||
EntrySetAction.setHome => context.l10n.collectionActionSetHome,
|
EntrySetAction.setHome => l10n.collectionActionSetHome,
|
||||||
EntrySetAction.emptyBin => context.l10n.collectionActionEmptyBin,
|
EntrySetAction.emptyBin => l10n.collectionActionEmptyBin,
|
||||||
// browsing or selecting
|
// browsing or selecting
|
||||||
EntrySetAction.map => context.l10n.menuActionMap,
|
EntrySetAction.map => l10n.menuActionMap,
|
||||||
EntrySetAction.slideshow => context.l10n.menuActionSlideshow,
|
EntrySetAction.slideshow => l10n.menuActionSlideshow,
|
||||||
EntrySetAction.stats => context.l10n.menuActionStats,
|
EntrySetAction.stats => l10n.menuActionStats,
|
||||||
EntrySetAction.rescan => context.l10n.collectionActionRescan,
|
EntrySetAction.rescan => l10n.collectionActionRescan,
|
||||||
// selecting
|
// selecting
|
||||||
EntrySetAction.share => context.l10n.entryActionShare,
|
EntrySetAction.share => l10n.entryActionShare,
|
||||||
EntrySetAction.delete => context.l10n.entryActionDelete,
|
EntrySetAction.delete => l10n.entryActionDelete,
|
||||||
EntrySetAction.restore => context.l10n.entryActionRestore,
|
EntrySetAction.restore => l10n.entryActionRestore,
|
||||||
EntrySetAction.copy => context.l10n.collectionActionCopy,
|
EntrySetAction.copy => l10n.collectionActionCopy,
|
||||||
EntrySetAction.move => context.l10n.collectionActionMove,
|
EntrySetAction.move => l10n.collectionActionMove,
|
||||||
EntrySetAction.rename => context.l10n.entryActionRename,
|
EntrySetAction.rename => l10n.entryActionRename,
|
||||||
EntrySetAction.convert => context.l10n.entryActionConvert,
|
EntrySetAction.convert => l10n.entryActionConvert,
|
||||||
EntrySetAction.toggleFavourite =>
|
EntrySetAction.toggleFavourite =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
context.l10n.entryActionAddFavourite,
|
l10n.entryActionAddFavourite,
|
||||||
EntrySetAction.rotateCCW => context.l10n.entryActionRotateCCW,
|
EntrySetAction.rotateCCW => l10n.entryActionRotateCCW,
|
||||||
EntrySetAction.rotateCW => context.l10n.entryActionRotateCW,
|
EntrySetAction.rotateCW => l10n.entryActionRotateCW,
|
||||||
EntrySetAction.flip => context.l10n.entryActionFlip,
|
EntrySetAction.flip => l10n.entryActionFlip,
|
||||||
EntrySetAction.editDate => context.l10n.entryInfoActionEditDate,
|
EntrySetAction.editDate => l10n.entryInfoActionEditDate,
|
||||||
EntrySetAction.editLocation => context.l10n.entryInfoActionEditLocation,
|
EntrySetAction.editLocation => l10n.entryInfoActionEditLocation,
|
||||||
EntrySetAction.editTitleDescription => context.l10n.entryInfoActionEditTitleDescription,
|
EntrySetAction.editTitleDescription => l10n.entryInfoActionEditTitleDescription,
|
||||||
EntrySetAction.editRating => context.l10n.entryInfoActionEditRating,
|
EntrySetAction.editRating => l10n.entryInfoActionEditRating,
|
||||||
EntrySetAction.editTags => context.l10n.entryInfoActionEditTags,
|
EntrySetAction.editTags => l10n.entryInfoActionEditTags,
|
||||||
EntrySetAction.removeMetadata => context.l10n.entryInfoActionRemoveMetadata,
|
EntrySetAction.removeMetadata => l10n.entryInfoActionRemoveMetadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
lib/view/src/metadata/convert_action.dart
Normal file
21
lib/view/src/metadata/convert_action.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves_model/aves_model.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
extension ExtraEntryConvertActionView on EntryConvertAction {
|
||||||
|
String getText(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return switch (this) {
|
||||||
|
EntryConvertAction.convert => l10n.entryActionConvert,
|
||||||
|
EntryConvertAction.convertMotionPhotoToStillImage => l10n.entryActionConvertMotionPhotoToStillImage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData getIconData() {
|
||||||
|
return switch (this) {
|
||||||
|
EntryConvertAction.convert => AIcons.convert,
|
||||||
|
EntryConvertAction.convertMotionPhotoToStillImage => AIcons.convertToStillImage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||||
import 'package:aves/model/entry/extensions/metadata_edition.dart';
|
import 'package:aves/model/entry/extensions/metadata_edition.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/favourites.dart';
|
import 'package:aves/model/favourites.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
@ -20,6 +21,7 @@ import 'package:aves/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/app_service.dart';
|
import 'package:aves/services/app_service.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/utils/collection_utils.dart';
|
import 'package:aves/utils/collection_utils.dart';
|
||||||
|
@ -34,6 +36,7 @@ import 'package:aves/widgets/common/search/route.dart';
|
||||||
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
|
import 'package:aves/widgets/dialogs/convert_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_set_page.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_set_page.dart';
|
||||||
import 'package:aves/widgets/dialogs/pick_dialogs/location_pick_page.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/location_pick_page.dart';
|
||||||
import 'package:aves/widgets/map/map_page.dart';
|
import 'package:aves/widgets/map/map_page.dart';
|
||||||
|
@ -366,9 +369,23 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
_browse(context);
|
_browse(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _convert(BuildContext context) {
|
Future<void> _convert(BuildContext context) async {
|
||||||
final entries = _getTargetItems(context);
|
final entries = _getTargetItems(context);
|
||||||
convert(context, entries);
|
|
||||||
|
final options = await showDialog<EntryConvertOptions>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConvertEntryDialog(entries: entries),
|
||||||
|
routeSettings: const RouteSettings(name: ConvertEntryDialog.routeName),
|
||||||
|
);
|
||||||
|
if (options == null) return;
|
||||||
|
|
||||||
|
switch (options.action) {
|
||||||
|
case EntryConvertAction.convert:
|
||||||
|
await doExport(context, entries, options);
|
||||||
|
case EntryConvertAction.convertMotionPhotoToStillImage:
|
||||||
|
final todoItems = entries.where((entry) => entry.isMotionPhoto).toSet();
|
||||||
|
await _edit(context, todoItems, (entry) => entry.removeTrailerVideo());
|
||||||
|
}
|
||||||
|
|
||||||
_browse(context);
|
_browse(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/convert_entry_dialog.dart';
|
|
||||||
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
|
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
|
||||||
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
||||||
|
@ -38,14 +37,7 @@ 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> convert(BuildContext context, Set<AvesEntry> targetEntries) async {
|
Future<void> doExport(BuildContext context, Set<AvesEntry> targetEntries, EntryConvertOptions options) async {
|
||||||
final options = await showDialog<EntryConvertOptions>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ConvertEntryDialog(entries: targetEntries),
|
|
||||||
routeSettings: const RouteSettings(name: ConvertEntryDialog.routeName),
|
|
||||||
);
|
|
||||||
if (options == null) return;
|
|
||||||
|
|
||||||
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
|
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
|
||||||
if (destinationAlbum == null) return;
|
if (destinationAlbum == null) return;
|
||||||
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return;
|
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/app/support.dart';
|
import 'package:aves/model/app/support.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/media/media_edit_service.dart';
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
|
@ -7,6 +8,7 @@ import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/text.dart';
|
import 'package:aves/theme/text.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/utils/mime_utils.dart';
|
import 'package:aves/utils/mime_utils.dart';
|
||||||
|
import 'package:aves/view/src/metadata/convert_action.dart';
|
||||||
import 'package:aves/view/view.dart';
|
import 'package:aves/view/view.dart';
|
||||||
import 'package:aves/widgets/common/basic/list_tiles/slider.dart';
|
import 'package:aves/widgets/common/basic/list_tiles/slider.dart';
|
||||||
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
||||||
|
@ -34,6 +36,8 @@ class ConvertEntryDialog extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
|
late List<EntryConvertAction> _actionOptions;
|
||||||
|
EntryConvertAction _action = EntryConvertAction.convert;
|
||||||
final TextEditingController _widthController = TextEditingController(), _heightController = TextEditingController();
|
final TextEditingController _widthController = TextEditingController(), _heightController = TextEditingController();
|
||||||
final ValueNotifier<bool> _isValidNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _isValidNotifier = ValueNotifier(false);
|
||||||
late ValueNotifier<String> _mimeTypeNotifier;
|
late ValueNotifier<String> _mimeTypeNotifier;
|
||||||
|
@ -44,14 +48,16 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
|
|
||||||
Set<AvesEntry> get entries => widget.entries;
|
Set<AvesEntry> get entries => widget.entries;
|
||||||
|
|
||||||
static const imageExportFormats = [
|
EdgeInsets get contentHorizontalPadding => const EdgeInsets.symmetric(horizontal: AvesDialog.defaultHorizontalContentPadding);
|
||||||
|
|
||||||
|
static const _imageExportFormats = [
|
||||||
MimeTypes.bmp,
|
MimeTypes.bmp,
|
||||||
MimeTypes.jpeg,
|
MimeTypes.jpeg,
|
||||||
MimeTypes.png,
|
MimeTypes.png,
|
||||||
MimeTypes.webp,
|
MimeTypes.webp,
|
||||||
];
|
];
|
||||||
|
|
||||||
static const qualityFormats = [
|
static const _qualityFormats = [
|
||||||
MimeTypes.jpeg,
|
MimeTypes.jpeg,
|
||||||
MimeTypes.webp,
|
MimeTypes.webp,
|
||||||
];
|
];
|
||||||
|
@ -59,6 +65,10 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_actionOptions = [
|
||||||
|
EntryConvertAction.convert,
|
||||||
|
if (entries.any((entry) => entry.isMotionPhoto)) EntryConvertAction.convertMotionPhotoToStillImage,
|
||||||
|
];
|
||||||
_mimeTypeNotifier = ValueNotifier(settings.convertMimeType);
|
_mimeTypeNotifier = ValueNotifier(settings.convertMimeType);
|
||||||
_quality = settings.convertQuality;
|
_quality = settings.convertQuality;
|
||||||
_writeMetadata = settings.convertWriteMetadata;
|
_writeMetadata = settings.convertWriteMetadata;
|
||||||
|
@ -95,192 +105,41 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
|
||||||
const contentHorizontalPadding = EdgeInsets.symmetric(horizontal: AvesDialog.defaultHorizontalContentPadding);
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
final trailingStyle = TextStyle(color: colorScheme.onSurfaceVariant);
|
|
||||||
final trailingChangeShadowColor = colorScheme.onSurface;
|
|
||||||
|
|
||||||
// used by the drop down to match input decoration
|
|
||||||
final textFieldDecorationBorder = Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: colorScheme.onSurface.withOpacity(0.38),
|
|
||||||
width: 1.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
scrollableContent: [
|
scrollableContent: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Padding(
|
if (_actionOptions.length > 1)
|
||||||
padding: contentHorizontalPadding,
|
Padding(
|
||||||
child: Row(
|
padding: contentHorizontalPadding,
|
||||||
|
child: TextDropdownButton<EntryConvertAction>(
|
||||||
|
values: _actionOptions,
|
||||||
|
valueText: (v) => v.getText(context),
|
||||||
|
valueIcon: (v) => v.getIconData(),
|
||||||
|
value: _action,
|
||||||
|
onChanged: (v) {
|
||||||
|
_action = v!;
|
||||||
|
_validate();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
isExpanded: true,
|
||||||
|
dropdownColor: Themes.thirdLayerColor(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: context.read<DurationsData>().formTransition,
|
||||||
|
switchInCurve: Curves.easeInOutCubic,
|
||||||
|
switchOutCurve: Curves.easeInOutCubic,
|
||||||
|
transitionBuilder: AvesTransitions.formTransitionBuilder,
|
||||||
|
child: Column(
|
||||||
|
key: ValueKey(_action),
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.exportEntryDialogFormat),
|
if (_action == EntryConvertAction.convert) ..._buildConvertContent(context),
|
||||||
const SizedBox(width: AvesDialog.controlCaptionPadding),
|
if (_action == EntryConvertAction.convertMotionPhotoToStillImage) const SizedBox(height: 16),
|
||||||
TextDropdownButton<String>(
|
|
||||||
values: imageExportFormats,
|
|
||||||
valueText: MimeUtils.displayType,
|
|
||||||
value: _mimeTypeNotifier.value,
|
|
||||||
onChanged: (selected) {
|
|
||||||
if (selected != null) {
|
|
||||||
setState(() => _mimeTypeNotifier.value = selected);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
|
||||||
padding: contentHorizontalPadding,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: _widthController,
|
|
||||||
decoration: InputDecoration(labelText: l10n.exportEntryDialogWidth),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
onChanged: (value) {
|
|
||||||
final width = int.tryParse(value);
|
|
||||||
if (width != null) {
|
|
||||||
switch (_lengthUnit) {
|
|
||||||
case LengthUnit.px:
|
|
||||||
_heightController.text = '${(width / entries.first.displayAspectRatio).round()}';
|
|
||||||
case LengthUnit.percent:
|
|
||||||
_heightController.text = '$width';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_heightController.text = '';
|
|
||||||
}
|
|
||||||
_validate();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text(AText.resolutionSeparator),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: _heightController,
|
|
||||||
decoration: InputDecoration(labelText: l10n.exportEntryDialogHeight),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
onChanged: (value) {
|
|
||||||
final height = int.tryParse(value);
|
|
||||||
if (height != null) {
|
|
||||||
switch (_lengthUnit) {
|
|
||||||
case LengthUnit.px:
|
|
||||||
_widthController.text = '${(height * entries.first.displayAspectRatio).round()}';
|
|
||||||
case LengthUnit.percent:
|
|
||||||
_widthController.text = '$height';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_widthController.text = '';
|
|
||||||
}
|
|
||||||
_validate();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
TextDropdownButton<LengthUnit>(
|
|
||||||
values: _lengthUnitOptions,
|
|
||||||
valueText: (v) => v.getText(context),
|
|
||||||
value: _lengthUnit,
|
|
||||||
onChanged: _lengthUnitOptions.length > 1
|
|
||||||
? (v) {
|
|
||||||
if (v != null && _lengthUnit != v) {
|
|
||||||
_lengthUnit = v;
|
|
||||||
_initDimensions();
|
|
||||||
_validate();
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
underline: Container(
|
|
||||||
height: 1.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: textFieldDecorationBorder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
itemHeight: 60,
|
|
||||||
dropdownColor: Themes.thirdLayerColor(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ValueListenableBuilder<String>(
|
|
||||||
valueListenable: _mimeTypeNotifier,
|
|
||||||
builder: (context, mimeType, child) {
|
|
||||||
Widget child;
|
|
||||||
if (qualityFormats.contains(mimeType)) {
|
|
||||||
child = SliderListTile(
|
|
||||||
value: _quality.toDouble(),
|
|
||||||
onChanged: (v) => setState(() => _quality = v.round()),
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
title: context.l10n.exportEntryDialogQuality,
|
|
||||||
titlePadding: contentHorizontalPadding,
|
|
||||||
titleTrailing: (context, value) => ChangeHighlightText(
|
|
||||||
'${value.round()}',
|
|
||||||
style: trailingStyle.copyWith(
|
|
||||||
shadows: [
|
|
||||||
Shadow(
|
|
||||||
color: trailingChangeShadowColor.withOpacity(0),
|
|
||||||
blurRadius: 0,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
changedStyle: trailingStyle.copyWith(
|
|
||||||
shadows: [
|
|
||||||
Shadow(
|
|
||||||
color: trailingChangeShadowColor,
|
|
||||||
blurRadius: 3,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
duration: context.read<DurationsData>().formTextStyleTransition,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
child = const SizedBox();
|
|
||||||
}
|
|
||||||
return AnimatedSwitcher(
|
|
||||||
duration: context.read<DurationsData>().formTransition,
|
|
||||||
switchInCurve: Curves.easeInOutCubic,
|
|
||||||
switchOutCurve: Curves.easeInOutCubic,
|
|
||||||
transitionBuilder: AvesTransitions.formTransitionBuilder,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ValueListenableBuilder<String>(
|
|
||||||
valueListenable: _mimeTypeNotifier,
|
|
||||||
builder: (context, mimeType, child) {
|
|
||||||
Widget child;
|
|
||||||
if (AppSupport.canEditExif(mimeType) || AppSupport.canEditIptc(mimeType) || AppSupport.canEditXmp(mimeType)) {
|
|
||||||
child = SwitchListTile(
|
|
||||||
value: _writeMetadata,
|
|
||||||
onChanged: (v) => setState(() => _writeMetadata = v),
|
|
||||||
title: Text(context.l10n.exportEntryDialogWriteMetadata),
|
|
||||||
contentPadding: const EdgeInsetsDirectional.only(
|
|
||||||
start: AvesDialog.defaultHorizontalContentPadding,
|
|
||||||
end: AvesDialog.defaultHorizontalContentPadding - 8,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
child = const SizedBox(height: 16);
|
|
||||||
}
|
|
||||||
return AnimatedSwitcher(
|
|
||||||
duration: context.read<DurationsData>().formTransition,
|
|
||||||
switchInCurve: Curves.easeInOutCubic,
|
|
||||||
switchOutCurve: Curves.easeInOutCubic,
|
|
||||||
transitionBuilder: AvesTransitions.formTransitionBuilder,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
const CancelButton(),
|
const CancelButton(),
|
||||||
|
@ -294,6 +153,7 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
final height = int.tryParse(_heightController.text);
|
final height = int.tryParse(_heightController.text);
|
||||||
final options = (width != null && height != null)
|
final options = (width != null && height != null)
|
||||||
? EntryConvertOptions(
|
? EntryConvertOptions(
|
||||||
|
action: _action,
|
||||||
mimeType: _mimeTypeNotifier.value,
|
mimeType: _mimeTypeNotifier.value,
|
||||||
writeMetadata: _writeMetadata,
|
writeMetadata: _writeMetadata,
|
||||||
lengthUnit: _lengthUnit,
|
lengthUnit: _lengthUnit,
|
||||||
|
@ -312,7 +172,7 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
Navigator.maybeOf(context)?.pop(options);
|
Navigator.maybeOf(context)?.pop(options);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Text(l10n.applyButtonLabel),
|
child: Text(context.l10n.applyButtonLabel),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -320,6 +180,193 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildConvertContent(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
final trailingStyle = TextStyle(color: colorScheme.onSurfaceVariant);
|
||||||
|
final trailingChangeShadowColor = colorScheme.onSurface;
|
||||||
|
|
||||||
|
// used by the drop down to match input decoration
|
||||||
|
final textFieldDecorationBorder = Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: colorScheme.onSurface.withOpacity(0.38),
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
Padding(
|
||||||
|
padding: contentHorizontalPadding,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(l10n.exportEntryDialogFormat),
|
||||||
|
const SizedBox(width: AvesDialog.controlCaptionPadding),
|
||||||
|
TextDropdownButton<String>(
|
||||||
|
values: _imageExportFormats,
|
||||||
|
valueText: MimeUtils.displayType,
|
||||||
|
value: _mimeTypeNotifier.value,
|
||||||
|
onChanged: (selected) {
|
||||||
|
if (selected != null) {
|
||||||
|
setState(() => _mimeTypeNotifier.value = selected);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: contentHorizontalPadding,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
|
textBaseline: TextBaseline.alphabetic,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _widthController,
|
||||||
|
decoration: InputDecoration(labelText: l10n.exportEntryDialogWidth),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
onChanged: (value) {
|
||||||
|
final width = int.tryParse(value);
|
||||||
|
if (width != null) {
|
||||||
|
switch (_lengthUnit) {
|
||||||
|
case LengthUnit.px:
|
||||||
|
_heightController.text = '${(width / entries.first.displayAspectRatio).round()}';
|
||||||
|
case LengthUnit.percent:
|
||||||
|
_heightController.text = '$width';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_heightController.text = '';
|
||||||
|
}
|
||||||
|
_validate();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Text(AText.resolutionSeparator),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _heightController,
|
||||||
|
decoration: InputDecoration(labelText: l10n.exportEntryDialogHeight),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
onChanged: (value) {
|
||||||
|
final height = int.tryParse(value);
|
||||||
|
if (height != null) {
|
||||||
|
switch (_lengthUnit) {
|
||||||
|
case LengthUnit.px:
|
||||||
|
_widthController.text = '${(height * entries.first.displayAspectRatio).round()}';
|
||||||
|
case LengthUnit.percent:
|
||||||
|
_widthController.text = '$height';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_widthController.text = '';
|
||||||
|
}
|
||||||
|
_validate();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
TextDropdownButton<LengthUnit>(
|
||||||
|
values: _lengthUnitOptions,
|
||||||
|
valueText: (v) => v.getText(context),
|
||||||
|
value: _lengthUnit,
|
||||||
|
onChanged: _lengthUnitOptions.length > 1
|
||||||
|
? (v) {
|
||||||
|
if (v != null && _lengthUnit != v) {
|
||||||
|
_lengthUnit = v;
|
||||||
|
_initDimensions();
|
||||||
|
_validate();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
underline: Container(
|
||||||
|
height: 1.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: textFieldDecorationBorder,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
itemHeight: 60,
|
||||||
|
dropdownColor: Themes.thirdLayerColor(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ValueListenableBuilder<String>(
|
||||||
|
valueListenable: _mimeTypeNotifier,
|
||||||
|
builder: (context, mimeType, child) {
|
||||||
|
Widget child;
|
||||||
|
if (_qualityFormats.contains(mimeType)) {
|
||||||
|
child = SliderListTile(
|
||||||
|
value: _quality.toDouble(),
|
||||||
|
onChanged: (v) => setState(() => _quality = v.round()),
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
title: context.l10n.exportEntryDialogQuality,
|
||||||
|
titlePadding: contentHorizontalPadding,
|
||||||
|
titleTrailing: (context, value) => ChangeHighlightText(
|
||||||
|
'${value.round()}',
|
||||||
|
style: trailingStyle.copyWith(
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: trailingChangeShadowColor.withOpacity(0),
|
||||||
|
blurRadius: 0,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
changedStyle: trailingStyle.copyWith(
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: trailingChangeShadowColor,
|
||||||
|
blurRadius: 3,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
duration: context.read<DurationsData>().formTextStyleTransition,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
child = const SizedBox();
|
||||||
|
}
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: context.read<DurationsData>().formTransition,
|
||||||
|
switchInCurve: Curves.easeInOutCubic,
|
||||||
|
switchOutCurve: Curves.easeInOutCubic,
|
||||||
|
transitionBuilder: AvesTransitions.formTransitionBuilder,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ValueListenableBuilder<String>(
|
||||||
|
valueListenable: _mimeTypeNotifier,
|
||||||
|
builder: (context, mimeType, child) {
|
||||||
|
Widget child;
|
||||||
|
if (AppSupport.canEditExif(mimeType) || AppSupport.canEditIptc(mimeType) || AppSupport.canEditXmp(mimeType)) {
|
||||||
|
child = SwitchListTile(
|
||||||
|
value: _writeMetadata,
|
||||||
|
onChanged: (v) => setState(() => _writeMetadata = v),
|
||||||
|
title: Text(context.l10n.exportEntryDialogWriteMetadata),
|
||||||
|
contentPadding: const EdgeInsetsDirectional.only(
|
||||||
|
start: AvesDialog.defaultHorizontalContentPadding,
|
||||||
|
end: AvesDialog.defaultHorizontalContentPadding - 8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
child = const SizedBox(height: 16);
|
||||||
|
}
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: context.read<DurationsData>().formTransition,
|
||||||
|
switchInCurve: Curves.easeInOutCubic,
|
||||||
|
switchOutCurve: Curves.easeInOutCubic,
|
||||||
|
transitionBuilder: AvesTransitions.formTransitionBuilder,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _validate() async {
|
Future<void> _validate() async {
|
||||||
final width = int.tryParse(_widthController.text);
|
final width = int.tryParse(_widthController.text);
|
||||||
final height = int.tryParse(_heightController.text);
|
final height = int.tryParse(_heightController.text);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/vaults/vaults.dart';
|
import 'package:aves/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
||||||
|
@ -26,6 +27,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
|
import 'package:aves/widgets/dialogs/convert_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
||||||
import 'package:aves/widgets/viewer/action/printer.dart';
|
import 'package:aves/widgets/viewer/action/printer.dart';
|
||||||
|
@ -198,7 +200,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
case EntryAction.restore:
|
case EntryAction.restore:
|
||||||
_move(context, targetEntry, moveType: MoveType.fromBin);
|
_move(context, targetEntry, moveType: MoveType.fromBin);
|
||||||
case EntryAction.convert:
|
case EntryAction.convert:
|
||||||
convert(context, {targetEntry});
|
_convert(context, targetEntry);
|
||||||
case EntryAction.print:
|
case EntryAction.print:
|
||||||
EntryPrinter(targetEntry).print(context);
|
EntryPrinter(targetEntry).print(context);
|
||||||
case EntryAction.rename:
|
case EntryAction.rename:
|
||||||
|
@ -444,6 +446,22 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
entries: {targetEntry},
|
entries: {targetEntry},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<void> _convert(BuildContext context, AvesEntry targetEntry) async {
|
||||||
|
final options = await showDialog<EntryConvertOptions>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConvertEntryDialog(entries: {targetEntry}),
|
||||||
|
routeSettings: const RouteSettings(name: ConvertEntryDialog.routeName),
|
||||||
|
);
|
||||||
|
if (options == null) return;
|
||||||
|
|
||||||
|
switch (options.action) {
|
||||||
|
case EntryConvertAction.convert:
|
||||||
|
await doExport(context, {targetEntry}, options);
|
||||||
|
case EntryConvertAction.convertMotionPhotoToStillImage:
|
||||||
|
await _metadataActionDelegate.onActionSelected(context, targetEntry, collection, EntryAction.convertMotionPhotoToStillImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _rename(BuildContext context, AvesEntry targetEntry) async {
|
Future<void> _rename(BuildContext context, AvesEntry targetEntry) async {
|
||||||
final newName = await showDialog<String>(
|
final newName = await showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
@ -92,7 +92,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onActionSelected(BuildContext context, AvesEntry targetEntry, CollectionLens? collection, EntryAction action) async {
|
Future<void> onActionSelected(BuildContext context, AvesEntry targetEntry, CollectionLens? collection, EntryAction action) async {
|
||||||
await reportService.log('$action');
|
await reportService.log('$action');
|
||||||
_eventStreamController.add(ActionStartedEvent(action));
|
_eventStreamController.add(ActionStartedEvent(action));
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
|
|
@ -94,7 +94,7 @@ class InfoAppBar extends StatelessWidget {
|
||||||
onSelected: (action) async {
|
onSelected: (action) async {
|
||||||
// wait for the popup menu to hide before proceeding with the action
|
// wait for the popup menu to hide before proceeding with the action
|
||||||
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
|
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
|
||||||
actionDelegate.onActionSelected(context, entry, collection, action);
|
await actionDelegate.onActionSelected(context, entry, collection, action);
|
||||||
},
|
},
|
||||||
popUpAnimationStyle: animations.popUpAnimationStyle,
|
popUpAnimationStyle: animations.popUpAnimationStyle,
|
||||||
),
|
),
|
||||||
|
|
|
@ -15,6 +15,8 @@ enum DateFieldSource {
|
||||||
exifGpsDate,
|
exifGpsDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum EntryConvertAction { convert, convertMotionPhotoToStillImage }
|
||||||
|
|
||||||
enum LengthUnit { px, percent }
|
enum LengthUnit { px, percent }
|
||||||
|
|
||||||
enum LocationEditAction {
|
enum LocationEditAction {
|
||||||
|
|
Loading…
Reference in a new issue