#601 tag editor: added save button, check modification when leaving, fixed adding placeholder filter

This commit is contained in:
Thibault Deckers 2023-04-28 19:35:38 +02:00
parent e4b0997f94
commit 6c11fd179e
6 changed files with 264 additions and 151 deletions

View file

@ -12,12 +12,14 @@ All notable changes to this project will be documented in this file.
### Changed
- Info: editing tags now requires explicitly tapping the save button
- upgraded Flutter to stable v3.7.12
### Fixed
- Video: switching to PiP when going home with gesture navigation
- Viewer: multi-page context update when removing burst entries
- Info: editing tags with placeholders
- prevent editing item when Exif editing changes mime type
- parsing videos with skippable boxes in `meta` box

View file

@ -46,6 +46,7 @@
"applyButtonLabel": "APPLY",
"deleteButtonLabel": "DELETE",
"discardButtonLabel": "DISCARD",
"nextButtonLabel": "NEXT",
"showButtonLabel": "SHOW",
"hideButtonLabel": "HIDE",

View file

@ -82,26 +82,27 @@ mixin EntryEditorMixin {
Future<Map<AvesEntry, Set<String>>?> selectTags(BuildContext context, Set<AvesEntry> entries) async {
if (entries.isEmpty) return null;
final filtersByEntry = Map.fromEntries(entries.map((v) {
final oldTagsByEntry = Map.fromEntries(entries.map((v) {
return MapEntry(v, v.tags.map(TagFilter.new).toSet());
}));
await Navigator.maybeOf(context)?.push(
final filtersByEntry = await Navigator.maybeOf(context)?.push<Map<AvesEntry, Set<CollectionFilter>>>(
MaterialPageRoute(
settings: const RouteSettings(name: TagEditorPage.routeName),
builder: (context) => TagEditorPage(
filtersByEntry: filtersByEntry,
tagsByEntry: oldTagsByEntry,
),
),
);
) ??
oldTagsByEntry;
final tagsByEntry = <AvesEntry, Set<String>>{};
final newTagsByEntry = <AvesEntry, Set<String>>{};
await Future.forEach(filtersByEntry.entries, (kv) async {
final entry = kv.key;
final filters = kv.value;
tagsByEntry[entry] = await getTagsFromFilters(filters, entry);
newTagsByEntry[entry] = await getTagsFromFilters(filters, entry);
});
return tagsByEntry;
return newTagsByEntry;
}
Future<Set<String>> getTagsFromFilters(Set<CollectionFilter> filters, AvesEntry entry) async {

View file

@ -30,8 +30,9 @@ mixin VaultAwareMixin on FeedbackMixin {
localizedReason: context.l10n.authenticateToUnlockVault,
);
} on PlatformException catch (e, stack) {
if (e.code != 'auth_in_progress') {
if (!{'auth_in_progress', 'NotAvailable'}.contains(e.code)) {
// `auth_in_progress`: `Authentication in progress`
// `NotAvailable`: `Required security features not enabled`
await reportService.recordError(e, stack);
}
}

View file

@ -10,17 +10,18 @@ import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/expandable_filter_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TagEditorPage extends StatefulWidget {
static const routeName = '/info/tag_editor';
final Map<AvesEntry, Set<CollectionFilter>> filtersByEntry;
final Map<AvesEntry, Set<TagFilter>> tagsByEntry;
const TagEditorPage({
super.key,
required this.filtersByEntry,
required this.tagsByEntry,
});
@override
@ -31,6 +32,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
final TextEditingController _newTagTextController = TextEditingController();
final FocusNode _newTagTextFocusNode = FocusNode();
final ValueNotifier<String?> _expandedSectionNotifier = ValueNotifier(null);
late final Map<AvesEntry, Set<CollectionFilter>> filtersByEntry;
late final List<CollectionFilter> _topTags;
final List<CollectionFilter> _userAddedFilters = [];
@ -41,11 +43,10 @@ class _TagEditorPageState extends State<TagEditorPage> {
PlaceholderFilter.place,
];
Map<AvesEntry, Set<CollectionFilter>> get tagsByEntry => widget.filtersByEntry;
@override
void initState() {
super.initState();
filtersByEntry = widget.tagsByEntry.map((key, value) => MapEntry(key, value.cast<CollectionFilter>().toSet()));
_expandedSectionNotifier.value = settings.tagEditorExpandedSection;
_expandedSectionNotifier.addListener(() => settings.tagEditorExpandedSection = _expandedSectionNotifier.value);
_initTopTags();
@ -61,14 +62,35 @@ class _TagEditorPageState extends State<TagEditorPage> {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final showCount = tagsByEntry.length > 1;
final showCount = filtersByEntry.length > 1;
final Map<CollectionFilter, int> entryCountByTag = {};
tagsByEntry.entries.forEach((kv) {
filtersByEntry.entries.forEach((kv) {
kv.value.forEach((tag) => entryCountByTag[tag] = (entryCountByTag[tag] ?? 0) + 1);
});
List<MapEntry<CollectionFilter, int>> sortedTags = _sortCurrentTags(entryCountByTag);
return AvesScaffold(
return WillPopScope(
onWillPop: () async {
if (!_isModified) return true;
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AvesDialog(
content: Text(context.l10n.genericDangerWarningDialogMessage),
actions: [
const CancelButton(),
TextButton(
onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(context.l10n.discardButtonLabel),
),
],
),
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
) ??
false;
return confirmed;
},
child: AvesScaffold(
appBar: AppBar(
title: Text(l10n.tagEditorPageTitle),
actions: [
@ -77,6 +99,11 @@ class _TagEditorPageState extends State<TagEditorPage> {
onPressed: _reset,
tooltip: l10n.resetTooltip,
),
IconButton(
icon: const Icon(AIcons.apply),
onPressed: () => Navigator.maybeOf(context)?.pop(filtersByEntry),
tooltip: l10n.saveTooltip,
),
],
),
body: SafeArea(
@ -164,9 +191,9 @@ class _TagEditorPageState extends State<TagEditorPage> {
)
: null,
onTap: (filter) {
if (tagsByEntry.keys.length > 1) {
if (filtersByEntry.keys.length > 1) {
// for multiple entries, set tag for all of them
tagsByEntry.forEach((entry, filters) => filters.add(filter));
filtersByEntry.forEach((entry, filters) => filters.add(filter));
setState(() {});
} else {
// for single entry, remove tag (like pressing on the remove icon)
@ -206,6 +233,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
},
),
),
),
);
}
@ -239,10 +267,19 @@ class _TagEditorPageState extends State<TagEditorPage> {
});
}
bool get _isModified {
for (final kv in filtersByEntry.entries) {
final oldFilters = kv.key.tags.map(TagFilter.new).toSet();
final newFilters = kv.value;
if (newFilters.length != oldFilters.length || !newFilters.containsAll(oldFilters)) return true;
}
return false;
}
void _reset() {
_userAddedFilters.clear();
tagsByEntry.forEach((entry, tags) {
final Set<TagFilter> originalFilters = entry.tags.map(TagFilter.new).toSet();
filtersByEntry.forEach((entry, tags) {
final originalFilters = entry.tags.map(TagFilter.new).toSet();
tags
..clear()
..addAll(originalFilters);
@ -263,14 +300,14 @@ class _TagEditorPageState extends State<TagEditorPage> {
_userAddedFilters
..remove(filter)
..add(filter);
tagsByEntry.forEach((entry, tags) => tags.add(filter));
filtersByEntry.forEach((entry, tags) => tags.add(filter));
_newTagTextController.clear();
setState(() {});
}
void _removeTag(CollectionFilter filter) {
_userAddedFilters.remove(filter);
tagsByEntry.forEach((entry, filters) => filters.remove(filter));
filtersByEntry.forEach((entry, filters) => filters.remove(filter));
setState(() {});
}
}

View file

@ -6,6 +6,7 @@
"timeMinutes",
"timeDays",
"focalLength",
"discardButtonLabel",
"pickTooltip",
"sourceStateLoading",
"sourceStateCataloguing",
@ -614,6 +615,7 @@
],
"ckb": [
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionShowCountryStates",
"entryActionRotateCCW",
@ -1194,7 +1196,28 @@
"filePickerUseThisFolder"
],
"cs": [
"discardButtonLabel"
],
"de": [
"discardButtonLabel"
],
"el": [
"discardButtonLabel"
],
"es": [
"discardButtonLabel"
],
"eu": [
"discardButtonLabel"
],
"fa": [
"discardButtonLabel",
"clearTooltip",
"chipActionGoToPlacePage",
"chipActionLock",
@ -1678,8 +1701,13 @@
"filePickerUseThisFolder"
],
"fr": [
"discardButtonLabel"
],
"gl": [
"columnCount",
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
@ -2203,6 +2231,7 @@
"focalLength",
"applyButtonLabel",
"deleteButtonLabel",
"discardButtonLabel",
"nextButtonLabel",
"showButtonLabel",
"hideButtonLabel",
@ -2840,6 +2869,7 @@
],
"hi": [
"discardButtonLabel",
"resetTooltip",
"saveTooltip",
"pickTooltip",
@ -3464,12 +3494,22 @@
"filePickerUseThisFolder"
],
"hu": [
"discardButtonLabel"
],
"id": [
"discardButtonLabel"
],
"it": [
"discardButtonLabel",
"settingsCollectionBurstPatternsTile"
],
"ja": [
"columnCount",
"discardButtonLabel",
"chipActionShowCountryStates",
"chipActionCreateVault",
"chipActionConfigureVault",
@ -3499,8 +3539,13 @@
"statsTopStatesSectionTitle"
],
"ko": [
"discardButtonLabel"
],
"lt": [
"columnCount",
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
@ -3556,6 +3601,7 @@
],
"nb": [
"discardButtonLabel",
"chipActionShowCountryStates",
"viewerActionLock",
"viewerActionUnlock",
@ -3576,6 +3622,7 @@
"nl": [
"columnCount",
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
@ -3644,6 +3691,7 @@
"nn": [
"columnCount",
"discardButtonLabel",
"sourceStateCataloguing",
"chipActionGoToPlacePage",
"chipActionLock",
@ -3984,6 +4032,7 @@
"focalLength",
"applyButtonLabel",
"deleteButtonLabel",
"discardButtonLabel",
"nextButtonLabel",
"continueButtonLabel",
"cancelTooltip",
@ -4551,7 +4600,20 @@
"filePickerUseThisFolder"
],
"pl": [
"discardButtonLabel"
],
"pt": [
"discardButtonLabel"
],
"ro": [
"discardButtonLabel"
],
"ru": [
"discardButtonLabel",
"chipActionShowCountryStates",
"viewerActionLock",
"viewerActionUnlock",
@ -4574,6 +4636,7 @@
"itemCount",
"columnCount",
"timeSeconds",
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
@ -5011,6 +5074,7 @@
"timeDays",
"focalLength",
"applyButtonLabel",
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
@ -5372,6 +5436,7 @@
],
"tr": [
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
@ -5417,7 +5482,12 @@
"tagPlaceholderState"
],
"uk": [
"discardButtonLabel"
],
"zh": [
"discardButtonLabel",
"chipActionGoToPlacePage",
"viewerActionLock",
"viewerActionUnlock",
@ -5469,6 +5539,7 @@
"zh_Hant": [
"columnCount",
"discardButtonLabel",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",