info: changed date edit dialog

This commit is contained in:
Thibault Deckers 2021-12-30 18:19:53 +09:00
parent b5e4fecf2f
commit 30d875f1cf
12 changed files with 159 additions and 171 deletions

View file

@ -178,7 +178,7 @@
"renameEntryDialogLabel": "Neuer Name",
"editEntryDateDialogTitle": "Datum & Uhrzeit",
"editEntryDateDialogSet": "Festlegen",
"editEntryDateDialogExtractFromTitle": "Auszug aus dem Titel",
"editEntryDateDialogShift": "Verschieben",
"editEntryDateDialogClear": "Aufräumen",
"editEntryDateDialogHours": "Stunden",

View file

@ -275,13 +275,12 @@
"renameEntryDialogLabel": "New name",
"editEntryDateDialogTitle": "Date & Time",
"editEntryDateDialogSet": "Set",
"editEntryDateDialogSetCustom": "Set custom date",
"editEntryDateDialogCopyField": "Set from other date",
"editEntryDateDialogExtractFromTitle": "Extract from title",
"editEntryDateDialogShift": "Shift",
"editEntryDateDialogClear": "Clear",
"editEntryDateDialogSourceFieldLabel": "Value:",
"editEntryDateDialogSourceCustomDate": "custom date",
"editEntryDateDialogSourceTitle": "extracted from title",
"editEntryDateDialogSourceFileModifiedDate": "file modified date",
"editEntryDateDialogSourceFileModifiedDate": "File modified date",
"editEntryDateDialogTargetFieldsHeader": "Fields to modify",
"editEntryDateDialogHours": "Hours",
"editEntryDateDialogMinutes": "Minutes",

View file

@ -180,13 +180,12 @@
"renameEntryDialogLabel": "Nouveau nom",
"editEntryDateDialogTitle": "Date & Heure",
"editEntryDateDialogSet": "Régler",
"editEntryDateDialogSetCustom": "Régler une date personnalisée",
"editEntryDateDialogCopyField": "Copier d'une autre date",
"editEntryDateDialogExtractFromTitle": "Extraire du titre",
"editEntryDateDialogShift": "Décaler",
"editEntryDateDialogClear": "Effacer",
"editEntryDateDialogSourceFieldLabel": "Valeur :",
"editEntryDateDialogSourceCustomDate": "date personnalisée",
"editEntryDateDialogSourceTitle": "extraite du titre",
"editEntryDateDialogSourceFileModifiedDate": "date de modification du fichier",
"editEntryDateDialogSourceFileModifiedDate": "Date de modification du fichier",
"editEntryDateDialogTargetFieldsHeader": "Champs à modifier",
"editEntryDateDialogHours": "Heures",
"editEntryDateDialogMinutes": "Minutes",

View file

@ -180,12 +180,11 @@
"renameEntryDialogLabel": "이름",
"editEntryDateDialogTitle": "날짜 및 시간",
"editEntryDateDialogSet": "편집",
"editEntryDateDialogSetCustom": "지정 날짜로 편집",
"editEntryDateDialogCopyField": "다른 날짜에서 지정",
"editEntryDateDialogExtractFromTitle": "제목에서 추출",
"editEntryDateDialogShift": "시간 이동",
"editEntryDateDialogClear": "삭제",
"editEntryDateDialogSourceFieldLabel": "값:",
"editEntryDateDialogSourceCustomDate": "지정 날짜",
"editEntryDateDialogSourceTitle": "제목에서 추출",
"editEntryDateDialogSourceFileModifiedDate": "파일 수정한 날짜",
"editEntryDateDialogTargetFieldsHeader": "수정할 필드",
"editEntryDateDialogHours": "시간",

View file

@ -178,7 +178,7 @@
"renameEntryDialogLabel": "Новое название",
"editEntryDateDialogTitle": "Дата и время",
"editEntryDateDialogSet": "Задать",
"editEntryDateDialogExtractFromTitle": "Извлечь из названия",
"editEntryDateDialogShift": "Сдвиг",
"editEntryDateDialogClear": "Очистить",
"editEntryDateDialogHours": "Часов",

View file

@ -675,55 +675,42 @@ class AvesEntry {
}
Future<Set<EntryDataType>> editDate(DateModifier modifier) async {
final action = modifier.action;
if (action == DateEditAction.set) {
final source = modifier.setSource;
if (source == null) {
await reportService.recordError('edit date with action=$action but source is null', null);
return {};
}
switch (source) {
case DateSetSource.title:
final _title = bestTitle;
if (_title == null) return {};
final date = parseUnknownDateFormat(_title);
if (date == null) {
await reportService.recordError('failed to parse date from title=$_title', null);
return {};
switch (modifier.action) {
case DateEditAction.copyField:
DateTime? date;
final source = modifier.copyFieldSource;
if (source != null) {
switch (source) {
case DateFieldSource.fileModifiedDate:
try {
date = path != null ? await File(path!).lastModified() : null;
} on FileSystemException catch (_) {}
break;
default:
date = await metadataFetchService.getDate(this, source.toMetadataField()!);
break;
}
modifier = DateModifier(DateEditAction.set, modifier.fields, setDateTime: date);
break;
case DateSetSource.fileModifiedDate:
final _path = path;
if (_path == null) {
await reportService.recordError('edit date with action=$action, source=$source but entry has no path, uri=$uri', null);
return {};
}
try {
final fileModifiedDate = await File(_path).lastModified();
modifier = DateModifier(DateEditAction.set, modifier.fields, setDateTime: fileModifiedDate);
} on FileSystemException catch (error, stack) {
await reportService.recordError(error, stack);
return {};
}
break;
case DateSetSource.custom:
break;
default:
final field = source.toMetadataField();
if (field == null) {
await reportService.recordError('failed to get field for action=$action, source=$source, uri=$uri', null);
return {};
}
final fieldDate = await metadataFetchService.getDate(this, field);
if (fieldDate == null) {
await reportService.recordError('failed to get date for field=$field, source=$source, uri=$uri', null);
return {};
}
modifier = DateModifier(DateEditAction.set, modifier.fields, setDateTime: fieldDate);
break;
}
}
if (date != null) {
modifier = DateModifier.setCustom(modifier.fields, date);
} else {
await reportService.recordError('failed to get date for modifier=$modifier, uri=$uri', null);
return {};
}
break;
case DateEditAction.extractFromTitle:
final date = parseUnknownDateFormat(bestTitle);
if (date != null) {
modifier = DateModifier.setCustom(modifier.fields, date);
} else {
await reportService.recordError('failed to get date for modifier=$modifier, uri=$uri', null);
return {};
}
break;
case DateEditAction.setCustom:
case DateEditAction.shift:
case DateEditAction.clear:
break;
}
final newFields = await metadataEditService.editDate(this, modifier);
return newFields.isEmpty
@ -746,10 +733,9 @@ class AvesEntry {
final metadataDate = catalogMetadata?.dateMillis;
if (metadataDate != null && metadataDate > 0) return {};
return await editDate(const DateModifier(
DateEditAction.set,
{MetadataField.exifDateOriginal},
setSource: DateSetSource.fileModifiedDate,
return await editDate(DateModifier.copyField(
const {MetadataField.exifDateOriginal},
DateFieldSource.fileModifiedDate,
));
}

View file

@ -13,15 +13,35 @@ class DateModifier {
final DateEditAction action;
final Set<MetadataField> fields;
final DateSetSource? setSource;
final DateTime? setDateTime;
final DateFieldSource? copyFieldSource;
final int? shiftMinutes;
const DateModifier(
const DateModifier._private(
this.action,
this.fields, {
this.setSource,
this.setDateTime,
this.copyFieldSource,
this.shiftMinutes,
});
factory DateModifier.setCustom(Set<MetadataField> fields, DateTime dateTime) {
return DateModifier._private(DateEditAction.setCustom, fields, setDateTime: dateTime);
}
factory DateModifier.copyField(Set<MetadataField> fields, DateFieldSource copyFieldSource) {
return DateModifier._private(DateEditAction.copyField, fields, copyFieldSource: copyFieldSource);
}
factory DateModifier.extractFromTitle(Set<MetadataField> fields) {
return DateModifier._private(DateEditAction.extractFromTitle, fields);
}
factory DateModifier.shift(Set<MetadataField> fields, int shiftMinutes) {
return DateModifier._private(DateEditAction.shift, fields, shiftMinutes: shiftMinutes);
}
factory DateModifier.clear(Set<MetadataField> fields) {
return DateModifier._private(DateEditAction.clear, fields);
}
}

View file

@ -6,14 +6,14 @@ enum MetadataField {
}
enum DateEditAction {
set,
setCustom,
copyField,
extractFromTitle,
shift,
clear,
}
enum DateSetSource {
custom,
title,
enum DateFieldSource {
fileModifiedDate,
exifDate,
exifDateOriginal,
@ -105,20 +105,18 @@ extension ExtraMetadataField on MetadataField {
}
}
extension ExtraDateSetSource on DateSetSource {
extension ExtraDateFieldSource on DateFieldSource {
MetadataField? toMetadataField() {
switch (this) {
case DateSetSource.custom:
case DateSetSource.title:
case DateSetSource.fileModifiedDate:
case DateFieldSource.fileModifiedDate:
return null;
case DateSetSource.exifDate:
case DateFieldSource.exifDate:
return MetadataField.exifDate;
case DateSetSource.exifDateOriginal:
case DateFieldSource.exifDateOriginal:
return MetadataField.exifDateOriginal;
case DateSetSource.exifDateDigitized:
case DateFieldSource.exifDateDigitized:
return MetadataField.exifDateDigitized;
case DateSetSource.exifGpsDate:
case DateFieldSource.exifGpsDate:
return MetadataField.exifGpsDate;
}
}

View file

@ -18,7 +18,9 @@ final _unixStampMillisPattern = RegExp(r'\d{13}');
final _unixStampSecPattern = RegExp(r'\d{10}');
final _plainPattern = RegExp(r'(\d{8})([_-\s](\d{6})([_-\s](\d{3}))?)?');
DateTime? parseUnknownDateFormat(String s) {
DateTime? parseUnknownDateFormat(String? s) {
if (s == null) return null;
var match = _unixStampMillisPattern.firstMatch(s);
if (match != null) {
final stampString = match.group(0);

View file

@ -24,8 +24,8 @@ class EditEntryDateDialog extends StatefulWidget {
}
class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
DateEditAction _action = DateEditAction.set;
DateSetSource _setSource = DateSetSource.custom;
DateEditAction _action = DateEditAction.setCustom;
DateFieldSource _copyFieldSource = DateFieldSource.fileModifiedDate;
late DateTime _setDateTime;
late ValueNotifier<int> _shiftHour, _shiftMinute;
late ValueNotifier<String> _shiftSign;
@ -96,7 +96,8 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
key: ValueKey(_action),
mainAxisSize: MainAxisSize.min,
children: [
if (_action == DateEditAction.set) ..._buildSetContent(context),
if (_action == DateEditAction.setCustom) _buildSetCustomContent(context),
if (_action == DateEditAction.copyField) _buildCopyFieldContent(context),
if (_action == DateEditAction.shift) _buildShiftContent(context),
],
),
@ -128,67 +129,52 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
),
);
List<Widget> _buildSetContent(BuildContext context) {
Widget _buildSetCustomContent(BuildContext context) {
final l10n = context.l10n;
final locale = l10n.localeName;
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
return [
Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Row(
children: [
Text(l10n.editEntryDateDialogSourceFieldLabel),
const SizedBox(width: 8),
Expanded(
child: DropdownButton<DateSetSource>(
items: DateSetSource.values
.map((v) => DropdownMenuItem<DateSetSource>(
value: v,
child: Text(_setSourceText(context, v)),
))
.toList(),
selectedItemBuilder: (context) => DateSetSource.values
.map((v) => DropdownMenuItem<DateSetSource>(
value: v,
child: Text(
_setSourceText(context, v),
softWrap: false,
overflow: TextOverflow.fade,
),
))
.toList(),
value: _setSource,
onChanged: (v) => setState(() => _setSource = v!),
isExpanded: true,
dropdownColor: dropdownColor,
),
),
],
),
return Padding(
padding: const EdgeInsets.only(left: 16, top: 4, right: 12),
child: Row(
children: [
Expanded(child: Text(formatDateTime(_setDateTime, locale, use24hour))),
IconButton(
icon: const Icon(AIcons.edit),
onPressed: _editDate,
tooltip: l10n.changeTooltip,
),
],
),
AnimatedSwitcher(
duration: context.read<DurationsData>().formTransition,
switchInCurve: Curves.easeInOutCubic,
switchOutCurve: Curves.easeInOutCubic,
transitionBuilder: _formTransitionBuilder,
child: _setSource == DateSetSource.custom
? Padding(
padding: const EdgeInsets.only(left: 16, right: 12),
child: Row(
children: [
Expanded(child: Text(formatDateTime(_setDateTime, locale, use24hour))),
IconButton(
icon: const Icon(AIcons.edit),
onPressed: _editDate,
tooltip: l10n.changeTooltip,
),
],
),
)
: const SizedBox(),
);
}
Widget _buildCopyFieldContent(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: DropdownButton<DateFieldSource>(
items: DateFieldSource.values
.map((v) => DropdownMenuItem<DateFieldSource>(
value: v,
child: Text(_setSourceText(context, v)),
))
.toList(),
selectedItemBuilder: (context) => DateFieldSource.values
.map((v) => DropdownMenuItem<DateFieldSource>(
value: v,
child: Text(
_setSourceText(context, v),
softWrap: false,
overflow: TextOverflow.fade,
),
))
.toList(),
value: _copyFieldSource,
onChanged: (v) => setState(() => _copyFieldSource = v!),
isExpanded: true,
dropdownColor: dropdownColor,
),
];
);
}
Widget _buildShiftContent(BuildContext context) {
@ -283,8 +269,12 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
String _actionText(BuildContext context, DateEditAction action) {
final l10n = context.l10n;
switch (action) {
case DateEditAction.set:
return l10n.editEntryDateDialogSet;
case DateEditAction.setCustom:
return l10n.editEntryDateDialogSetCustom;
case DateEditAction.copyField:
return l10n.editEntryDateDialogCopyField;
case DateEditAction.extractFromTitle:
return l10n.editEntryDateDialogExtractFromTitle;
case DateEditAction.shift:
return l10n.editEntryDateDialogShift;
case DateEditAction.clear:
@ -292,22 +282,18 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
}
}
String _setSourceText(BuildContext context, DateSetSource source) {
String _setSourceText(BuildContext context, DateFieldSource source) {
final l10n = context.l10n;
switch (source) {
case DateSetSource.custom:
return l10n.editEntryDateDialogSourceCustomDate;
case DateSetSource.title:
return l10n.editEntryDateDialogSourceTitle;
case DateSetSource.fileModifiedDate:
case DateFieldSource.fileModifiedDate:
return l10n.editEntryDateDialogSourceFileModifiedDate;
case DateSetSource.exifDate:
case DateFieldSource.exifDate:
return 'Exif date';
case DateSetSource.exifDateOriginal:
case DateFieldSource.exifDateOriginal:
return 'Exif original date';
case DateSetSource.exifDateDigitized:
case DateFieldSource.exifDateDigitized:
return 'Exif digitized date';
case DateSetSource.exifGpsDate:
case DateFieldSource.exifGpsDate:
return 'Exif GPS date';
}
}
@ -350,20 +336,21 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
));
}
void _submit(BuildContext context) {
late DateModifier modifier;
DateModifier _getModifier() {
switch (_action) {
case DateEditAction.set:
modifier = DateModifier(_action, _fields, setSource: _setSource, setDateTime: _setDateTime);
break;
case DateEditAction.setCustom:
return DateModifier.setCustom(_fields, _setDateTime);
case DateEditAction.copyField:
return DateModifier.copyField(_fields, _copyFieldSource);
case DateEditAction.extractFromTitle:
return DateModifier.extractFromTitle(_fields);
case DateEditAction.shift:
final shiftTotalMinutes = (_shiftHour.value * 60 + _shiftMinute.value) * (_shiftSign.value == '+' ? 1 : -1);
modifier = DateModifier(_action, _fields, shiftMinutes: shiftTotalMinutes);
break;
return DateModifier.shift(_fields, shiftTotalMinutes);
case DateEditAction.clear:
modifier = DateModifier(_action, _fields);
break;
return DateModifier.clear(_fields);
}
Navigator.pop(context, modifier);
}
void _submit(BuildContext context) => Navigator.pop(context, _getModifier());
}

View file

@ -183,7 +183,7 @@ class CollectionSearchDelegate {
_buildFilterRow(
context: context,
title: context.l10n.searchSectionRating,
filters: [0, 5, 4, 3, 2, 1, -1].map((rating) => RatingFilter(rating)).toList(),
filters: [0, 5, 4, 3, 2, 1, -1].map((rating) => RatingFilter(rating)).where((f) => containQuery(f.getLabel(context))).toList(),
),
],
);

View file

@ -4,9 +4,8 @@
"filterRatingRejectedLabel",
"missingSystemFilePickerDialogTitle",
"missingSystemFilePickerDialogMessage",
"editEntryDateDialogSourceFieldLabel",
"editEntryDateDialogSourceCustomDate",
"editEntryDateDialogSourceTitle",
"editEntryDateDialogSetCustom",
"editEntryDateDialogCopyField",
"editEntryDateDialogSourceFileModifiedDate",
"editEntryDateDialogTargetFieldsHeader",
"collectionSortRating",
@ -29,9 +28,8 @@
"filterRatingRejectedLabel",
"missingSystemFilePickerDialogTitle",
"missingSystemFilePickerDialogMessage",
"editEntryDateDialogSourceFieldLabel",
"editEntryDateDialogSourceCustomDate",
"editEntryDateDialogSourceTitle",
"editEntryDateDialogSetCustom",
"editEntryDateDialogCopyField",
"editEntryDateDialogSourceFileModifiedDate",
"editEntryDateDialogTargetFieldsHeader",
"collectionSortRating",