#601 tag editor: added save button, check modification when leaving, fixed adding placeholder filter
This commit is contained in:
parent
e4b0997f94
commit
6c11fd179e
6 changed files with 264 additions and 151 deletions
|
@ -12,12 +12,14 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Info: editing tags now requires explicitly tapping the save button
|
||||||
- upgraded Flutter to stable v3.7.12
|
- upgraded Flutter to stable v3.7.12
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Video: switching to PiP when going home with gesture navigation
|
- Video: switching to PiP when going home with gesture navigation
|
||||||
- Viewer: multi-page context update when removing burst entries
|
- Viewer: multi-page context update when removing burst entries
|
||||||
|
- Info: editing tags with placeholders
|
||||||
- prevent editing item when Exif editing changes mime type
|
- prevent editing item when Exif editing changes mime type
|
||||||
- parsing videos with skippable boxes in `meta` box
|
- parsing videos with skippable boxes in `meta` box
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
|
|
||||||
"applyButtonLabel": "APPLY",
|
"applyButtonLabel": "APPLY",
|
||||||
"deleteButtonLabel": "DELETE",
|
"deleteButtonLabel": "DELETE",
|
||||||
|
"discardButtonLabel": "DISCARD",
|
||||||
"nextButtonLabel": "NEXT",
|
"nextButtonLabel": "NEXT",
|
||||||
"showButtonLabel": "SHOW",
|
"showButtonLabel": "SHOW",
|
||||||
"hideButtonLabel": "HIDE",
|
"hideButtonLabel": "HIDE",
|
||||||
|
|
|
@ -82,26 +82,27 @@ mixin EntryEditorMixin {
|
||||||
Future<Map<AvesEntry, Set<String>>?> selectTags(BuildContext context, Set<AvesEntry> entries) async {
|
Future<Map<AvesEntry, Set<String>>?> selectTags(BuildContext context, Set<AvesEntry> entries) async {
|
||||||
if (entries.isEmpty) return null;
|
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());
|
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(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: TagEditorPage.routeName),
|
settings: const RouteSettings(name: TagEditorPage.routeName),
|
||||||
builder: (context) => TagEditorPage(
|
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 {
|
await Future.forEach(filtersByEntry.entries, (kv) async {
|
||||||
final entry = kv.key;
|
final entry = kv.key;
|
||||||
final filters = kv.value;
|
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 {
|
Future<Set<String>> getTagsFromFilters(Set<CollectionFilter> filters, AvesEntry entry) async {
|
||||||
|
|
|
@ -30,8 +30,9 @@ mixin VaultAwareMixin on FeedbackMixin {
|
||||||
localizedReason: context.l10n.authenticateToUnlockVault,
|
localizedReason: context.l10n.authenticateToUnlockVault,
|
||||||
);
|
);
|
||||||
} on PlatformException catch (e, stack) {
|
} 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`
|
// `auth_in_progress`: `Authentication in progress`
|
||||||
|
// `NotAvailable`: `Required security features not enabled`
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/expandable_filter_row.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class TagEditorPage extends StatefulWidget {
|
class TagEditorPage extends StatefulWidget {
|
||||||
static const routeName = '/info/tag_editor';
|
static const routeName = '/info/tag_editor';
|
||||||
|
|
||||||
final Map<AvesEntry, Set<CollectionFilter>> filtersByEntry;
|
final Map<AvesEntry, Set<TagFilter>> tagsByEntry;
|
||||||
|
|
||||||
const TagEditorPage({
|
const TagEditorPage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.filtersByEntry,
|
required this.tagsByEntry,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,6 +32,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
|
||||||
final TextEditingController _newTagTextController = TextEditingController();
|
final TextEditingController _newTagTextController = TextEditingController();
|
||||||
final FocusNode _newTagTextFocusNode = FocusNode();
|
final FocusNode _newTagTextFocusNode = FocusNode();
|
||||||
final ValueNotifier<String?> _expandedSectionNotifier = ValueNotifier(null);
|
final ValueNotifier<String?> _expandedSectionNotifier = ValueNotifier(null);
|
||||||
|
late final Map<AvesEntry, Set<CollectionFilter>> filtersByEntry;
|
||||||
late final List<CollectionFilter> _topTags;
|
late final List<CollectionFilter> _topTags;
|
||||||
final List<CollectionFilter> _userAddedFilters = [];
|
final List<CollectionFilter> _userAddedFilters = [];
|
||||||
|
|
||||||
|
@ -41,11 +43,10 @@ class _TagEditorPageState extends State<TagEditorPage> {
|
||||||
PlaceholderFilter.place,
|
PlaceholderFilter.place,
|
||||||
];
|
];
|
||||||
|
|
||||||
Map<AvesEntry, Set<CollectionFilter>> get tagsByEntry => widget.filtersByEntry;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
filtersByEntry = widget.tagsByEntry.map((key, value) => MapEntry(key, value.cast<CollectionFilter>().toSet()));
|
||||||
_expandedSectionNotifier.value = settings.tagEditorExpandedSection;
|
_expandedSectionNotifier.value = settings.tagEditorExpandedSection;
|
||||||
_expandedSectionNotifier.addListener(() => settings.tagEditorExpandedSection = _expandedSectionNotifier.value);
|
_expandedSectionNotifier.addListener(() => settings.tagEditorExpandedSection = _expandedSectionNotifier.value);
|
||||||
_initTopTags();
|
_initTopTags();
|
||||||
|
@ -61,149 +62,176 @@ class _TagEditorPageState extends State<TagEditorPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
final showCount = tagsByEntry.length > 1;
|
final showCount = filtersByEntry.length > 1;
|
||||||
final Map<CollectionFilter, int> entryCountByTag = {};
|
final Map<CollectionFilter, int> entryCountByTag = {};
|
||||||
tagsByEntry.entries.forEach((kv) {
|
filtersByEntry.entries.forEach((kv) {
|
||||||
kv.value.forEach((tag) => entryCountByTag[tag] = (entryCountByTag[tag] ?? 0) + 1);
|
kv.value.forEach((tag) => entryCountByTag[tag] = (entryCountByTag[tag] ?? 0) + 1);
|
||||||
});
|
});
|
||||||
List<MapEntry<CollectionFilter, int>> sortedTags = _sortCurrentTags(entryCountByTag);
|
List<MapEntry<CollectionFilter, int>> sortedTags = _sortCurrentTags(entryCountByTag);
|
||||||
|
|
||||||
return AvesScaffold(
|
return WillPopScope(
|
||||||
appBar: AppBar(
|
onWillPop: () async {
|
||||||
title: Text(l10n.tagEditorPageTitle),
|
if (!_isModified) return true;
|
||||||
actions: [
|
|
||||||
IconButton(
|
final confirmed = await showDialog<bool>(
|
||||||
icon: const Icon(AIcons.reset),
|
context: context,
|
||||||
onPressed: _reset,
|
builder: (context) => AvesDialog(
|
||||||
tooltip: l10n.resetTooltip,
|
content: Text(context.l10n.genericDangerWarningDialogMessage),
|
||||||
),
|
actions: [
|
||||||
],
|
const CancelButton(),
|
||||||
),
|
TextButton(
|
||||||
body: SafeArea(
|
onPressed: () => Navigator.maybeOf(context)?.pop(true),
|
||||||
child: ValueListenableBuilder<String?>(
|
child: Text(context.l10n.discardButtonLabel),
|
||||||
valueListenable: _expandedSectionNotifier,
|
),
|
||||||
builder: (context, expandedSection, child) {
|
],
|
||||||
return ValueListenableBuilder<TextEditingValue>(
|
),
|
||||||
valueListenable: _newTagTextController,
|
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
|
||||||
builder: (context, value, child) {
|
) ??
|
||||||
final upQuery = value.text.trim().toUpperCase();
|
false;
|
||||||
bool containQuery(CollectionFilter v) => v.getLabel(context).toUpperCase().contains(upQuery);
|
return confirmed;
|
||||||
final recentFilters = settings.recentTags.where(containQuery).toList();
|
},
|
||||||
final topTagFilters = _topTags.where(containQuery).toList();
|
child: AvesScaffold(
|
||||||
final placeholderFilters = _placeholders.where(containQuery).toList();
|
appBar: AppBar(
|
||||||
return ListView(
|
title: Text(l10n.tagEditorPageTitle),
|
||||||
children: [
|
actions: [
|
||||||
Padding(
|
IconButton(
|
||||||
padding: const EdgeInsetsDirectional.only(start: 8, end: 16),
|
icon: const Icon(AIcons.reset),
|
||||||
child: Row(
|
onPressed: _reset,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
tooltip: l10n.resetTooltip,
|
||||||
children: [
|
),
|
||||||
Expanded(
|
IconButton(
|
||||||
child: TextField(
|
icon: const Icon(AIcons.apply),
|
||||||
controller: _newTagTextController,
|
onPressed: () => Navigator.maybeOf(context)?.pop(filtersByEntry),
|
||||||
focusNode: _newTagTextFocusNode,
|
tooltip: l10n.saveTooltip,
|
||||||
decoration: InputDecoration(
|
),
|
||||||
labelText: l10n.tagEditorPageNewTagFieldLabel,
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ValueListenableBuilder<String?>(
|
||||||
|
valueListenable: _expandedSectionNotifier,
|
||||||
|
builder: (context, expandedSection, child) {
|
||||||
|
return ValueListenableBuilder<TextEditingValue>(
|
||||||
|
valueListenable: _newTagTextController,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
final upQuery = value.text.trim().toUpperCase();
|
||||||
|
bool containQuery(CollectionFilter v) => v.getLabel(context).toUpperCase().contains(upQuery);
|
||||||
|
final recentFilters = settings.recentTags.where(containQuery).toList();
|
||||||
|
final topTagFilters = _topTags.where(containQuery).toList();
|
||||||
|
final placeholderFilters = _placeholders.where(containQuery).toList();
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 8, end: 16),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _newTagTextController,
|
||||||
|
focusNode: _newTagTextFocusNode,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.tagEditorPageNewTagFieldLabel,
|
||||||
|
),
|
||||||
|
autofocus: true,
|
||||||
|
onSubmitted: (newTag) {
|
||||||
|
_addCustomTag(newTag);
|
||||||
|
_newTagTextFocusNode.requestFocus();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
autofocus: true,
|
),
|
||||||
onSubmitted: (newTag) {
|
ValueListenableBuilder<TextEditingValue>(
|
||||||
_addCustomTag(newTag);
|
valueListenable: _newTagTextController,
|
||||||
_newTagTextFocusNode.requestFocus();
|
builder: (context, value, child) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(AIcons.add),
|
||||||
|
onPressed: value.text.isEmpty ? null : () => _addCustomTag(_newTagTextController.text),
|
||||||
|
tooltip: l10n.tagEditorPageAddTagTooltip,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
Selector<Settings, bool>(
|
||||||
ValueListenableBuilder<TextEditingValue>(
|
selector: (context, s) => s.tagEditorCurrentFilterSectionExpanded,
|
||||||
valueListenable: _newTagTextController,
|
builder: (context, isExpanded, child) {
|
||||||
builder: (context, value, child) {
|
return IconButton(
|
||||||
return IconButton(
|
icon: Icon(isExpanded ? AIcons.collapse : AIcons.expand),
|
||||||
icon: const Icon(AIcons.add),
|
onPressed: sortedTags.isEmpty ? null : () => settings.tagEditorCurrentFilterSectionExpanded = !isExpanded,
|
||||||
onPressed: value.text.isEmpty ? null : () => _addCustomTag(_newTagTextController.text),
|
tooltip: isExpanded ? MaterialLocalizations.of(context).expandedIconTapHint : MaterialLocalizations.of(context).collapsedIconTapHint,
|
||||||
tooltip: l10n.tagEditorPageAddTagTooltip,
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
Selector<Settings, bool>(
|
),
|
||||||
selector: (context, s) => s.tagEditorCurrentFilterSectionExpanded,
|
|
||||||
builder: (context, isExpanded, child) {
|
|
||||||
return IconButton(
|
|
||||||
icon: Icon(isExpanded ? AIcons.collapse : AIcons.expand),
|
|
||||||
onPressed: sortedTags.isEmpty ? null : () => settings.tagEditorCurrentFilterSectionExpanded = !isExpanded,
|
|
||||||
tooltip: isExpanded ? MaterialLocalizations.of(context).expandedIconTapHint : MaterialLocalizations.of(context).collapsedIconTapHint,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
child: AnimatedCrossFade(
|
||||||
child: AnimatedCrossFade(
|
firstChild: ConstrainedBox(
|
||||||
firstChild: ConstrainedBox(
|
constraints: const BoxConstraints(minHeight: AvesFilterChip.minChipHeight),
|
||||||
constraints: const BoxConstraints(minHeight: AvesFilterChip.minChipHeight),
|
child: Center(
|
||||||
child: Center(
|
child: Row(
|
||||||
child: Row(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
const Icon(AIcons.tagUntagged, color: untaggedColor),
|
||||||
const Icon(AIcons.tagUntagged, color: untaggedColor),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
Text(
|
||||||
Text(
|
l10n.filterNoTagLabel,
|
||||||
l10n.filterNoTagLabel,
|
style: const TextStyle(color: untaggedColor),
|
||||||
style: const TextStyle(color: untaggedColor),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
secondChild: ExpandableFilterRow(
|
||||||
|
filters: sortedTags.map((kv) => kv.key).toList(),
|
||||||
|
isExpanded: context.select<Settings, bool>((v) => v.tagEditorCurrentFilterSectionExpanded),
|
||||||
|
showGenericIcon: false,
|
||||||
|
leadingBuilder: showCount
|
||||||
|
? (filter) => _TagCount(
|
||||||
|
count: sortedTags.firstWhere((kv) => kv.key == filter).value,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
onTap: (filter) {
|
||||||
|
if (filtersByEntry.keys.length > 1) {
|
||||||
|
// for multiple entries, set tag for all of them
|
||||||
|
filtersByEntry.forEach((entry, filters) => filters.add(filter));
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
// for single entry, remove tag (like pressing on the remove icon)
|
||||||
|
_removeTag(filter);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRemove: _removeTag,
|
||||||
|
onLongPress: null,
|
||||||
|
),
|
||||||
|
crossFadeState: sortedTags.isEmpty ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||||
|
duration: Durations.tagEditorTransition,
|
||||||
),
|
),
|
||||||
secondChild: ExpandableFilterRow(
|
|
||||||
filters: sortedTags.map((kv) => kv.key).toList(),
|
|
||||||
isExpanded: context.select<Settings, bool>((v) => v.tagEditorCurrentFilterSectionExpanded),
|
|
||||||
showGenericIcon: false,
|
|
||||||
leadingBuilder: showCount
|
|
||||||
? (filter) => _TagCount(
|
|
||||||
count: sortedTags.firstWhere((kv) => kv.key == filter).value,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
onTap: (filter) {
|
|
||||||
if (tagsByEntry.keys.length > 1) {
|
|
||||||
// for multiple entries, set tag for all of them
|
|
||||||
tagsByEntry.forEach((entry, filters) => filters.add(filter));
|
|
||||||
setState(() {});
|
|
||||||
} else {
|
|
||||||
// for single entry, remove tag (like pressing on the remove icon)
|
|
||||||
_removeTag(filter);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRemove: _removeTag,
|
|
||||||
onLongPress: null,
|
|
||||||
),
|
|
||||||
crossFadeState: sortedTags.isEmpty ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
|
||||||
duration: Durations.tagEditorTransition,
|
|
||||||
),
|
),
|
||||||
),
|
const Divider(height: 0),
|
||||||
const Divider(height: 0),
|
_FilterRow(
|
||||||
_FilterRow(
|
title: l10n.statsTopTagsSectionTitle,
|
||||||
title: l10n.statsTopTagsSectionTitle,
|
filters: topTagFilters,
|
||||||
filters: topTagFilters,
|
expandedNotifier: _expandedSectionNotifier,
|
||||||
expandedNotifier: _expandedSectionNotifier,
|
onTap: _addTag,
|
||||||
onTap: _addTag,
|
),
|
||||||
),
|
_FilterRow(
|
||||||
_FilterRow(
|
title: l10n.tagEditorSectionRecent,
|
||||||
title: l10n.tagEditorSectionRecent,
|
filters: recentFilters,
|
||||||
filters: recentFilters,
|
expandedNotifier: _expandedSectionNotifier,
|
||||||
expandedNotifier: _expandedSectionNotifier,
|
onTap: _addTag,
|
||||||
onTap: _addTag,
|
),
|
||||||
),
|
_FilterRow(
|
||||||
_FilterRow(
|
title: l10n.tagEditorSectionPlaceholders,
|
||||||
title: l10n.tagEditorSectionPlaceholders,
|
filters: placeholderFilters,
|
||||||
filters: placeholderFilters,
|
expandedNotifier: _expandedSectionNotifier,
|
||||||
expandedNotifier: _expandedSectionNotifier,
|
onTap: _addTag,
|
||||||
onTap: _addTag,
|
),
|
||||||
),
|
],
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -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() {
|
void _reset() {
|
||||||
_userAddedFilters.clear();
|
_userAddedFilters.clear();
|
||||||
tagsByEntry.forEach((entry, tags) {
|
filtersByEntry.forEach((entry, tags) {
|
||||||
final Set<TagFilter> originalFilters = entry.tags.map(TagFilter.new).toSet();
|
final originalFilters = entry.tags.map(TagFilter.new).toSet();
|
||||||
tags
|
tags
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(originalFilters);
|
..addAll(originalFilters);
|
||||||
|
@ -263,14 +300,14 @@ class _TagEditorPageState extends State<TagEditorPage> {
|
||||||
_userAddedFilters
|
_userAddedFilters
|
||||||
..remove(filter)
|
..remove(filter)
|
||||||
..add(filter);
|
..add(filter);
|
||||||
tagsByEntry.forEach((entry, tags) => tags.add(filter));
|
filtersByEntry.forEach((entry, tags) => tags.add(filter));
|
||||||
_newTagTextController.clear();
|
_newTagTextController.clear();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeTag(CollectionFilter filter) {
|
void _removeTag(CollectionFilter filter) {
|
||||||
_userAddedFilters.remove(filter);
|
_userAddedFilters.remove(filter);
|
||||||
tagsByEntry.forEach((entry, filters) => filters.remove(filter));
|
filtersByEntry.forEach((entry, filters) => filters.remove(filter));
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"timeMinutes",
|
"timeMinutes",
|
||||||
"timeDays",
|
"timeDays",
|
||||||
"focalLength",
|
"focalLength",
|
||||||
|
"discardButtonLabel",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
"sourceStateLoading",
|
"sourceStateLoading",
|
||||||
"sourceStateCataloguing",
|
"sourceStateCataloguing",
|
||||||
|
@ -614,6 +615,7 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"ckb": [
|
"ckb": [
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"entryActionRotateCCW",
|
"entryActionRotateCCW",
|
||||||
|
@ -1194,7 +1196,28 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"cs": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
|
"de": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
|
"el": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
|
"es": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
|
"eu": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
|
"discardButtonLabel",
|
||||||
"clearTooltip",
|
"clearTooltip",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
|
@ -1678,8 +1701,13 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"fr": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
"gl": [
|
"gl": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
@ -2203,6 +2231,7 @@
|
||||||
"focalLength",
|
"focalLength",
|
||||||
"applyButtonLabel",
|
"applyButtonLabel",
|
||||||
"deleteButtonLabel",
|
"deleteButtonLabel",
|
||||||
|
"discardButtonLabel",
|
||||||
"nextButtonLabel",
|
"nextButtonLabel",
|
||||||
"showButtonLabel",
|
"showButtonLabel",
|
||||||
"hideButtonLabel",
|
"hideButtonLabel",
|
||||||
|
@ -2840,6 +2869,7 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
|
"discardButtonLabel",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
"saveTooltip",
|
"saveTooltip",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
|
@ -3464,12 +3494,22 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"hu": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
|
"id": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
|
"discardButtonLabel",
|
||||||
"settingsCollectionBurstPatternsTile"
|
"settingsCollectionBurstPatternsTile"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
|
@ -3499,8 +3539,13 @@
|
||||||
"statsTopStatesSectionTitle"
|
"statsTopStatesSectionTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ko": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
"lt": [
|
"lt": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
@ -3556,6 +3601,7 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"nb": [
|
"nb": [
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -3576,6 +3622,7 @@
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
@ -3644,6 +3691,7 @@
|
||||||
|
|
||||||
"nn": [
|
"nn": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
"discardButtonLabel",
|
||||||
"sourceStateCataloguing",
|
"sourceStateCataloguing",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
|
@ -3984,6 +4032,7 @@
|
||||||
"focalLength",
|
"focalLength",
|
||||||
"applyButtonLabel",
|
"applyButtonLabel",
|
||||||
"deleteButtonLabel",
|
"deleteButtonLabel",
|
||||||
|
"discardButtonLabel",
|
||||||
"nextButtonLabel",
|
"nextButtonLabel",
|
||||||
"continueButtonLabel",
|
"continueButtonLabel",
|
||||||
"cancelTooltip",
|
"cancelTooltip",
|
||||||
|
@ -4551,7 +4600,20 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"pl": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
|
"pt": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ro": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -4574,6 +4636,7 @@
|
||||||
"itemCount",
|
"itemCount",
|
||||||
"columnCount",
|
"columnCount",
|
||||||
"timeSeconds",
|
"timeSeconds",
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
@ -5011,6 +5074,7 @@
|
||||||
"timeDays",
|
"timeDays",
|
||||||
"focalLength",
|
"focalLength",
|
||||||
"applyButtonLabel",
|
"applyButtonLabel",
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
@ -5372,6 +5436,7 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
@ -5417,7 +5482,12 @@
|
||||||
"tagPlaceholderState"
|
"tagPlaceholderState"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"uk": [
|
||||||
|
"discardButtonLabel"
|
||||||
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -5469,6 +5539,7 @@
|
||||||
|
|
||||||
"zh_Hant": [
|
"zh_Hant": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
"discardButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
|
Loading…
Reference in a new issue