diff --git a/CHANGELOG.md b/CHANGELOG.md index 662ffe0cb..566f16916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - Info: improved state/place display (requires rescan, limited to AU/GB/IN/US) - Info: edit tags with state placeholder - Countries: show states for selected countries +- Tags: delete selected tags from all media in collection - improved support for system font scale ### Changed diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 7cff8d465..767315ca2 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -635,6 +635,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware await _edit(context, entries, (entry) => entry.editTags(newTagsByEntry[entry]!)); } + Future removeTags(BuildContext context, {required Set entries, required Set tags}) async { + final newTagsByEntry = Map.fromEntries(entries.map((v) { + return MapEntry(v, v.tags.whereNot(tags.contains).toSet()); + })); + + await _edit(context, entries, (entry) => entry.editTags(newTagsByEntry[entry]!)); + } + Future _removeMetadata(BuildContext context) async { final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canRemoveMetadata); if (entries == null || entries.isEmpty) return; diff --git a/lib/widgets/common/action_mixins/entry_editor.dart b/lib/widgets/common/action_mixins/entry_editor.dart index 6dd05e50c..34248cb63 100644 --- a/lib/widgets/common/action_mixins/entry_editor.dart +++ b/lib/widgets/common/action_mixins/entry_editor.dart @@ -83,9 +83,7 @@ mixin EntryEditorMixin { if (entries.isEmpty) return null; final filtersByEntry = Map.fromEntries(entries.map((v) { - // use `{...}` instead of `toSet()` to circumvent an implicit typing issue, as of Dart v2.18.2 - final filters = {...v.tags.map(TagFilter.new)}; - return MapEntry(v, filters); + return MapEntry(v, v.tags.map(TagFilter.new).toSet()); })); await Navigator.maybeOf(context)?.push( MaterialPageRoute( diff --git a/lib/widgets/filter_grids/common/action_delegates/tag_set.dart b/lib/widgets/filter_grids/common/action_delegates/tag_set.dart index 6362f7281..d669b6fab 100644 --- a/lib/widgets/filter_grids/common/action_delegates/tag_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/tag_set.dart @@ -1,9 +1,17 @@ +import 'package:aves/app_mode.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/widgets/collection/entry_set_action_delegate.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves_model/aves_model.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class TagChipSetActionDelegate extends ChipSetActionDelegate { final Iterable> _items; @@ -30,4 +38,68 @@ class TagChipSetActionDelegate extends ChipSetActionDelegate { @override set tileLayout(TileLayout tileLayout) => settings.setTileLayout(TagListPage.routeName, tileLayout); + + @override + bool isVisible( + ChipSetAction action, { + required AppMode appMode, + required bool isSelecting, + required int itemCount, + required Set selectedFilters, + }) { + final isMain = appMode == AppMode.main; + + switch (action) { + case ChipSetAction.delete: + return isMain && isSelecting && !settings.isReadOnly; + default: + return super.isVisible( + action, + appMode: appMode, + isSelecting: isSelecting, + itemCount: itemCount, + selectedFilters: selectedFilters, + ); + } + } + + @override + void onActionSelected(BuildContext context, Set filters, ChipSetAction action) { + reportService.log('$action'); + switch (action) { + // single/multiple filters + case ChipSetAction.delete: + _delete(context, filters); + break; + default: + break; + } + super.onActionSelected(context, filters, action); + } + + Future _delete(BuildContext context, Set filters) async { + final source = context.read(); + final todoEntries = source.visibleEntries.where((entry) => filters.any((f) => f.test(entry))).toSet(); + final todoTags = filters.map((v) => v.tag).toSet(); + + final confirmed = await showDialog( + context: context, + builder: (context) => AvesDialog( + content: Text(context.l10n.genericDangerWarningDialogMessage), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.maybeOf(context)?.pop(true), + child: Text(context.l10n.applyButtonLabel), + ), + ], + ), + routeSettings: const RouteSettings(name: AvesDialog.warningRouteName), + ); + if (confirmed == null || !confirmed) return; + + await EntrySetActionDelegate().removeTags(context, entries: todoEntries, tags: todoTags); + + browse(context); + } }