#203 info: copy date from other item
This commit is contained in:
parent
318ee1385e
commit
150e94cee5
13 changed files with 174 additions and 47 deletions
|
@ -335,6 +335,7 @@
|
|||
"editEntryDateDialogTitle": "Date & Time",
|
||||
"editEntryDateDialogSetCustom": "Set custom date",
|
||||
"editEntryDateDialogCopyField": "Copy from other date",
|
||||
"editEntryDateDialogCopyItem": "Copy from other item",
|
||||
"editEntryDateDialogExtractFromTitle": "Extract from title",
|
||||
"editEntryDateDialogShift": "Shift",
|
||||
"editEntryDateDialogSourceFileModifiedDate": "File modified date",
|
||||
|
|
|
@ -42,6 +42,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
|||
switch (appliedModifier.action) {
|
||||
case DateEditAction.setCustom:
|
||||
case DateEditAction.copyField:
|
||||
case DateEditAction.copyItem:
|
||||
case DateEditAction.extractFromTitle:
|
||||
editCreateDateXmp(descriptions, appliedModifier.setDateTime);
|
||||
break;
|
||||
|
@ -319,6 +320,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
|||
final date = parseUnknownDateFormat(bestTitle);
|
||||
return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null;
|
||||
case DateEditAction.setCustom:
|
||||
case DateEditAction.copyItem:
|
||||
return DateModifier.setCustom(mainMetadataDate(), modifier.setDateTime!);
|
||||
case DateEditAction.shift:
|
||||
case DateEditAction.remove:
|
||||
|
|
|
@ -24,30 +24,30 @@ class DateModifier extends Equatable {
|
|||
List<Object?> get props => [action, fields, setDateTime, copyFieldSource, shiftMinutes];
|
||||
|
||||
const DateModifier._private(
|
||||
this.action,
|
||||
this.fields, {
|
||||
this.action, {
|
||||
this.fields = const {},
|
||||
this.setDateTime,
|
||||
this.copyFieldSource,
|
||||
this.shiftMinutes,
|
||||
});
|
||||
|
||||
factory DateModifier.setCustom(Set<MetadataField> fields, DateTime dateTime) {
|
||||
return DateModifier._private(DateEditAction.setCustom, fields, setDateTime: dateTime);
|
||||
return DateModifier._private(DateEditAction.setCustom, fields: fields, setDateTime: dateTime);
|
||||
}
|
||||
|
||||
factory DateModifier.copyField(Set<MetadataField> fields, DateFieldSource copyFieldSource) {
|
||||
return DateModifier._private(DateEditAction.copyField, fields, copyFieldSource: copyFieldSource);
|
||||
factory DateModifier.copyField(DateFieldSource copyFieldSource) {
|
||||
return DateModifier._private(DateEditAction.copyField, copyFieldSource: copyFieldSource);
|
||||
}
|
||||
|
||||
factory DateModifier.extractFromTitle(Set<MetadataField> fields) {
|
||||
return DateModifier._private(DateEditAction.extractFromTitle, fields);
|
||||
factory DateModifier.extractFromTitle() {
|
||||
return const DateModifier._private(DateEditAction.extractFromTitle);
|
||||
}
|
||||
|
||||
factory DateModifier.shift(Set<MetadataField> fields, int shiftMinutes) {
|
||||
return DateModifier._private(DateEditAction.shift, fields, shiftMinutes: shiftMinutes);
|
||||
return DateModifier._private(DateEditAction.shift, fields: fields, shiftMinutes: shiftMinutes);
|
||||
}
|
||||
|
||||
factory DateModifier.remove(Set<MetadataField> fields) {
|
||||
return DateModifier._private(DateEditAction.remove, fields);
|
||||
return DateModifier._private(DateEditAction.remove, fields: fields);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/model/metadata/fields.dart';
|
|||
enum DateEditAction {
|
||||
setCustom,
|
||||
copyField,
|
||||
copyItem,
|
||||
extractFromTitle,
|
||||
shift,
|
||||
remove,
|
||||
|
|
|
@ -427,7 +427,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final todoItems = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditDate);
|
||||
if (todoItems == null || todoItems.isEmpty) return;
|
||||
|
||||
final modifier = await selectDateModifier(context, todoItems);
|
||||
final collection = context.read<CollectionLens>();
|
||||
final modifier = await selectDateModifier(context, todoItems, collection);
|
||||
if (modifier == null) return;
|
||||
|
||||
await _edit(context, todoItems, (entry) => entry.editDate(modifier));
|
||||
|
|
|
@ -14,13 +14,14 @@ import 'package:flutter/material.dart';
|
|||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
mixin EntryEditorMixin {
|
||||
Future<DateModifier?> selectDateModifier(BuildContext context, Set<AvesEntry> entries) async {
|
||||
Future<DateModifier?> selectDateModifier(BuildContext context, Set<AvesEntry> entries, CollectionLens? collection) async {
|
||||
if (entries.isEmpty) return null;
|
||||
|
||||
final modifier = await showDialog<DateModifier>(
|
||||
context: context,
|
||||
builder: (context) => EditEntryDateDialog(
|
||||
entry: entries.first,
|
||||
collection: collection,
|
||||
),
|
||||
);
|
||||
return modifier;
|
||||
|
|
|
@ -56,7 +56,7 @@ class GridItemSelectionOverlay<T> extends StatelessWidget {
|
|||
return child;
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
: const SizedBox();
|
||||
return AnimatedSwitcher(
|
||||
duration: duration,
|
||||
child: child,
|
||||
|
|
|
@ -2,10 +2,9 @@ import 'package:aves/model/covers.dart';
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/fx/borders.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||
import 'package:aves/widgets/dialogs/item_pick_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/item_picker.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -66,7 +65,11 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
|
|||
Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: _buildCover(_coverEntry!, extent),
|
||||
child: ItemPicker(
|
||||
extent: extent,
|
||||
entry: _coverEntry!,
|
||||
onTap: _pickEntry,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24),
|
||||
|
@ -103,29 +106,6 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildCover(AvesEntry entry, double extent) {
|
||||
return GestureDetector(
|
||||
onTap: _pickEntry,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: AvesBorder.border(context),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(32)),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(32)),
|
||||
child: SizedBox(
|
||||
width: extent,
|
||||
height: extent,
|
||||
child: ThumbnailImage(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickEntry() async {
|
||||
final _collection = widget.collection;
|
||||
if (_collection == null) return;
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/model/metadata/date_modifier.dart';
|
||||
import 'package:aves/model/metadata/enums.dart';
|
||||
import 'package:aves/model/metadata/fields.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/format.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
|
@ -10,15 +11,19 @@ import 'package:aves/widgets/common/basic/wheel.dart';
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/item_pick_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/item_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class EditEntryDateDialog extends StatefulWidget {
|
||||
final AvesEntry entry;
|
||||
final CollectionLens? collection;
|
||||
|
||||
const EditEntryDateDialog({
|
||||
Key? key,
|
||||
required this.entry,
|
||||
this.collection,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -28,16 +33,20 @@ class EditEntryDateDialog extends StatefulWidget {
|
|||
class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||
DateEditAction _action = DateEditAction.setCustom;
|
||||
DateFieldSource _copyFieldSource = DateFieldSource.fileModifiedDate;
|
||||
late AvesEntry _copyItemSource;
|
||||
late DateTime _setDateTime;
|
||||
late ValueNotifier<int> _shiftHour, _shiftMinute;
|
||||
late ValueNotifier<String> _shiftSign;
|
||||
bool _showOptions = false;
|
||||
final Set<MetadataField> _fields = {...DateModifier.writableDateFields};
|
||||
|
||||
DateTime get copyItemDate => _copyItemSource.bestDate ?? DateTime.now();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initSet();
|
||||
_initCopyItem();
|
||||
_initShift(60);
|
||||
}
|
||||
|
||||
|
@ -45,6 +54,10 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
_setDateTime = widget.entry.bestDate ?? DateTime.now();
|
||||
}
|
||||
|
||||
void _initCopyItem() {
|
||||
_copyItemSource = widget.entry;
|
||||
}
|
||||
|
||||
void _initShift(int initialMinutes) {
|
||||
final abs = initialMinutes.abs();
|
||||
_shiftHour = ValueNotifier(abs ~/ 60);
|
||||
|
@ -91,6 +104,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
children: [
|
||||
if (_action == DateEditAction.setCustom) _buildSetCustomContent(context),
|
||||
if (_action == DateEditAction.copyField) _buildCopyFieldContent(context),
|
||||
if (_action == DateEditAction.copyItem) _buildCopyItemContent(context),
|
||||
if (_action == DateEditAction.shift) _buildShiftContent(context),
|
||||
(_action == DateEditAction.shift || _action == DateEditAction.remove) ? _buildDestinationFields(context) : const SizedBox(height: 8),
|
||||
],
|
||||
|
@ -170,6 +184,27 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildCopyItemContent(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final locale = l10n.localeName;
|
||||
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 16, end: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(formatDateTime(copyItemDate, locale, use24hour))),
|
||||
const SizedBox(width: 8),
|
||||
ItemPicker(
|
||||
extent: 48,
|
||||
entry: _copyItemSource,
|
||||
onTap: _pickCopyItemSource,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShiftContent(BuildContext context) {
|
||||
const textStyle = TextStyle(fontSize: 34);
|
||||
return Center(
|
||||
|
@ -268,6 +303,8 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
return l10n.editEntryDateDialogSetCustom;
|
||||
case DateEditAction.copyField:
|
||||
return l10n.editEntryDateDialogCopyField;
|
||||
case DateEditAction.copyItem:
|
||||
return l10n.editEntryDateDialogCopyItem;
|
||||
case DateEditAction.extractFromTitle:
|
||||
return l10n.editEntryDateDialogExtractFromTitle;
|
||||
case DateEditAction.shift:
|
||||
|
@ -335,6 +372,27 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
));
|
||||
}
|
||||
|
||||
Future<void> _pickCopyItemSource() async {
|
||||
final _collection = widget.collection;
|
||||
if (_collection == null) return;
|
||||
|
||||
final entry = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
settings: const RouteSettings(name: ItemPickDialog.routeName),
|
||||
builder: (context) => ItemPickDialog(
|
||||
collection: CollectionLens(
|
||||
source: _collection.source,
|
||||
),
|
||||
),
|
||||
fullscreenDialog: true,
|
||||
),
|
||||
);
|
||||
if (entry != null) {
|
||||
setState(() => _copyItemSource = entry);
|
||||
}
|
||||
}
|
||||
|
||||
DateModifier _getModifier() {
|
||||
// fields to modify are only set for the `shift` and `remove` actions,
|
||||
// as the effective fields for the other actions will depend on
|
||||
|
@ -343,9 +401,11 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
case DateEditAction.setCustom:
|
||||
return DateModifier.setCustom(const {}, _setDateTime);
|
||||
case DateEditAction.copyField:
|
||||
return DateModifier.copyField(const {}, _copyFieldSource);
|
||||
return DateModifier.copyField(_copyFieldSource);
|
||||
case DateEditAction.copyItem:
|
||||
return DateModifier.setCustom(const {}, copyItemDate);
|
||||
case DateEditAction.extractFromTitle:
|
||||
return DateModifier.extractFromTitle(const {});
|
||||
return DateModifier.extractFromTitle();
|
||||
case DateEditAction.shift:
|
||||
final shiftTotalMinutes = (_shiftHour.value * 60 + _shiftMinute.value) * (_shiftSign.value == '+' ? 1 : -1);
|
||||
return DateModifier.shift(_fields, shiftTotalMinutes);
|
||||
|
|
|
@ -2,11 +2,11 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/item_pick_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/item_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -72,10 +72,11 @@ class _CoverSelectionDialogState extends State<CoverSelectionDialog> {
|
|||
children: [
|
||||
title,
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(AIcons.setCover),
|
||||
onPressed: _isCustom ? _pickEntry : null,
|
||||
tooltip: context.l10n.changeTooltip,
|
||||
if (_customEntry != null)
|
||||
ItemPicker(
|
||||
extent: 46,
|
||||
entry: _customEntry!,
|
||||
onTap: _pickEntry,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
72
lib/widgets/dialogs/item_picker.dart
Normal file
72
lib/widgets/dialogs/item_picker.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/fx/borders.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ItemPicker extends StatelessWidget {
|
||||
final double extent;
|
||||
final AvesEntry entry;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
const ItemPicker({
|
||||
Key? key,
|
||||
required this.extent,
|
||||
required this.entry,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageBorderRadius = BorderRadius.all(Radius.circular(extent * .25));
|
||||
final actionBoxDimension = min(40.0, extent * .4);
|
||||
final actionBoxBorderRadius = BorderRadiusDirectional.only(topStart: Radius.circular(actionBoxDimension * .6));
|
||||
return Tooltip(
|
||||
message: context.l10n.changeTooltip,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: AvesBorder.border(context),
|
||||
borderRadius: imageBorderRadius,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: imageBorderRadius,
|
||||
child: SizedBox(
|
||||
width: extent,
|
||||
height: extent,
|
||||
child: Stack(
|
||||
children: [
|
||||
ThumbnailImage(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
),
|
||||
PositionedDirectional(
|
||||
end: -1,
|
||||
bottom: -1,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.dark ? const Color(0xAA000000) : const Color(0xCCFFFFFF),
|
||||
border: AvesBorder.border(context),
|
||||
borderRadius: actionBoxBorderRadius,
|
||||
),
|
||||
width: actionBoxDimension,
|
||||
height: actionBoxDimension,
|
||||
child: Icon(
|
||||
AIcons.edit,
|
||||
size: actionBoxDimension * .6,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -97,7 +97,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
}
|
||||
|
||||
Future<void> _editDate(BuildContext context) async {
|
||||
final modifier = await selectDateModifier(context, {entry});
|
||||
final modifier = await selectDateModifier(context, {entry}, collection);
|
||||
if (modifier == null) return;
|
||||
|
||||
await edit(context, () => entry.editDate(modifier));
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -12,6 +13,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -21,6 +23,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -36,6 +39,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsViewerShowOverlayThumbnails",
|
||||
"settingsVideoControlsTile",
|
||||
"settingsVideoControlsTitle",
|
||||
|
@ -52,6 +56,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -61,6 +66,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -70,6 +76,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
@ -79,6 +86,7 @@
|
|||
"themeBrightnessLight",
|
||||
"themeBrightnessDark",
|
||||
"themeBrightnessBlack",
|
||||
"editEntryDateDialogCopyItem",
|
||||
"settingsSectionDisplay",
|
||||
"settingsThemeBrightness",
|
||||
"settingsThemeColorful"
|
||||
|
|
Loading…
Reference in a new issue