#203 info: copy date from other item

This commit is contained in:
Thibault Deckers 2022-03-18 17:06:27 +09:00
parent 318ee1385e
commit 150e94cee5
13 changed files with 174 additions and 47 deletions

View file

@ -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",

View file

@ -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:

View file

@ -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);
}
}

View file

@ -3,6 +3,7 @@ import 'package:aves/model/metadata/fields.dart';
enum DateEditAction {
setCustom,
copyField,
copyItem,
extractFromTitle,
shift,
remove,

View file

@ -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));

View file

@ -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;

View file

@ -56,7 +56,7 @@ class GridItemSelectionOverlay<T> extends StatelessWidget {
return child;
},
)
: const SizedBox.shrink();
: const SizedBox();
return AnimatedSwitcher(
duration: duration,
child: child,

View file

@ -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;

View file

@ -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);

View file

@ -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,
),
],
)

View 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,
),
),
),
],
),
),
),
),
),
);
}
}

View file

@ -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));

View file

@ -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"