info: changed date edit dialog
This commit is contained in:
parent
b5e4fecf2f
commit
30d875f1cf
12 changed files with 159 additions and 171 deletions
|
@ -178,7 +178,7 @@
|
|||
"renameEntryDialogLabel": "Neuer Name",
|
||||
|
||||
"editEntryDateDialogTitle": "Datum & Uhrzeit",
|
||||
"editEntryDateDialogSet": "Festlegen",
|
||||
"editEntryDateDialogExtractFromTitle": "Auszug aus dem Titel",
|
||||
"editEntryDateDialogShift": "Verschieben",
|
||||
"editEntryDateDialogClear": "Aufräumen",
|
||||
"editEntryDateDialogHours": "Stunden",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -180,12 +180,11 @@
|
|||
"renameEntryDialogLabel": "이름",
|
||||
|
||||
"editEntryDateDialogTitle": "날짜 및 시간",
|
||||
"editEntryDateDialogSet": "편집",
|
||||
"editEntryDateDialogSetCustom": "지정 날짜로 편집",
|
||||
"editEntryDateDialogCopyField": "다른 날짜에서 지정",
|
||||
"editEntryDateDialogExtractFromTitle": "제목에서 추출",
|
||||
"editEntryDateDialogShift": "시간 이동",
|
||||
"editEntryDateDialogClear": "삭제",
|
||||
"editEntryDateDialogSourceFieldLabel": "값:",
|
||||
"editEntryDateDialogSourceCustomDate": "지정 날짜",
|
||||
"editEntryDateDialogSourceTitle": "제목에서 추출",
|
||||
"editEntryDateDialogSourceFileModifiedDate": "파일 수정한 날짜",
|
||||
"editEntryDateDialogTargetFieldsHeader": "수정할 필드",
|
||||
"editEntryDateDialogHours": "시간",
|
||||
|
|
|
@ -178,7 +178,7 @@
|
|||
"renameEntryDialogLabel": "Новое название",
|
||||
|
||||
"editEntryDateDialogTitle": "Дата и время",
|
||||
"editEntryDateDialogSet": "Задать",
|
||||
"editEntryDateDialogExtractFromTitle": "Извлечь из названия",
|
||||
"editEntryDateDialogShift": "Сдвиг",
|
||||
"editEntryDateDialogClear": "Очистить",
|
||||
"editEntryDateDialogHours": "Часов",
|
||||
|
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue