This commit is contained in:
Thibault Deckers 2022-11-30 16:53:42 +01:00
parent f57e2306e2
commit 31ea0ba3dc
18 changed files with 227 additions and 69 deletions

View file

@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file.
### Added
- Viewer: optionally show rating & tags on overlay
- Viewer: long press on copy/move/rating quick action for quicker action
- Viewer: long press on copy/move/rating/tag quick action for quicker action
- Search: missing address, portrait, landscape filters
- Lithuanian translation (thanks Gediminas Murauskas)

View file

@ -11,6 +11,7 @@ import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/missing.dart';
import 'package:aves/model/filters/path.dart';
import 'package:aves/model/filters/placeholder.dart';
import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/recent.dart';
@ -69,6 +70,8 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
return MissingFilter.fromMap(jsonMap);
case PathFilter.type:
return PathFilter.fromMap(jsonMap);
case PlaceholderFilter.type:
return PlaceholderFilter.fromMap(jsonMap);
case QueryFilter.type:
return QueryFilter.fromMap(jsonMap);
case RatingFilter.type:

View file

@ -29,8 +29,7 @@ class Settings extends ChangeNotifier {
Settings._private();
static const int moveDestinationAlbumMax = 3;
static const int _recentFilterHistoryMax = 10;
static const Set<String> _internalKeys = {
hasAcceptedTermsKey,
catalogTimeZoneKey,
@ -39,7 +38,8 @@ class Settings extends ChangeNotifier {
platformAccelerometerRotationKey,
platformTransitionAnimationScaleKey,
topEntryIdsKey,
moveDestinationAlbumsKey,
recentDestinationAlbumsKey,
recentTagsKey,
};
static const _widgetKeyPrefix = 'widget_';
@ -54,7 +54,8 @@ class Settings extends ChangeNotifier {
static const tileLayoutPrefixKey = 'tile_layout_';
static const entryRenamingPatternKey = 'entry_renaming_pattern';
static const topEntryIdsKey = 'top_entry_ids';
static const moveDestinationAlbumsKey = 'move_destination_albums';
static const recentDestinationAlbumsKey = 'recent_destination_albums';
static const recentTagsKey = 'recent_tags';
// display
static const displayRefreshRateModeKey = 'display_refresh_rate_mode';
@ -318,9 +319,13 @@ class Settings extends ChangeNotifier {
set topEntryIds(List<int>? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
List<String> get moveDestinationAlbums => getStringList(moveDestinationAlbumsKey) ?? [];
List<String> get recentDestinationAlbums => getStringList(recentDestinationAlbumsKey) ?? [];
set moveDestinationAlbums(List<String> newValue) => setAndNotify(moveDestinationAlbumsKey, newValue.take(Settings.moveDestinationAlbumMax).toList());
set recentDestinationAlbums(List<String> newValue) => setAndNotify(recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList());
List<CollectionFilter> get recentTags => (getStringList(recentTagsKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
set recentTags(List<CollectionFilter> newValue) => setAndNotify(recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
// display

View file

@ -96,16 +96,19 @@ mixin EntryEditorMixin {
await Future.forEach(filtersByEntry.entries, (kv) async {
final entry = kv.key;
final filters = kv.value;
final tags = filters.whereType<TagFilter>().map((v) => v.tag).toSet();
tagsByEntry[entry] = tags;
final placeholderTags = await Future.wait(filters.whereType<PlaceholderFilter>().map((v) => v.toTag(entry)));
tags.addAll(placeholderTags.whereNotNull().where((v) => v.isNotEmpty));
tagsByEntry[entry] = await getTagsFromFilters(filters, entry);
});
return tagsByEntry;
}
Future<Set<String>> getTagsFromFilters(Set<CollectionFilter> filters, AvesEntry entry) async {
final tags = filters.whereType<TagFilter>().map((v) => v.tag).toSet();
final placeholderTags = await Future.wait(filters.whereType<PlaceholderFilter>().map((v) => v.toTag(entry)));
tags.addAll(placeholderTags.whereNotNull().where((v) => v.isNotEmpty));
return tags;
}
Future<Set<MetadataType>?> selectMetadataToRemove(BuildContext context, Set<AvesEntry> entries) async {
if (entries.isEmpty) return null;

View file

@ -209,7 +209,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
final destinationAlbum = await pickAlbum(context: context, moveType: moveType);
if (destinationAlbum == null) return;
settings.moveDestinationAlbums = settings.moveDestinationAlbums
settings.recentDestinationAlbums = settings.recentDestinationAlbums
..remove(destinationAlbum)
..insert(0, destinationAlbum);
entriesByDestination[destinationAlbum] = entries;

View file

@ -5,6 +5,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/album_chooser.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/chooser_button.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/filter_chooser.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
import 'package:collection/collection.dart';
@ -23,10 +24,10 @@ class MoveButton extends ChooserQuickButton<String> {
});
@override
State<MoveButton> createState() => _MoveQuickButtonState();
State<MoveButton> createState() => _MoveButtonState();
}
class _MoveQuickButtonState extends ChooserQuickButtonState<MoveButton, String> {
class _MoveButtonState extends ChooserQuickButtonState<MoveButton, String> {
EntryAction get action => widget.copy ? EntryAction.copy : EntryAction.move;
@override
@ -35,13 +36,10 @@ class _MoveQuickButtonState extends ChooserQuickButtonState<MoveButton, String>
@override
String get tooltip => action.getText(context);
@override
String? get defaultValue => null;
@override
Widget buildChooser(Animation<double> animation) {
final options = settings.moveDestinationAlbums;
final takeCount = Settings.moveDestinationAlbumMax - options.length;
final options = settings.recentDestinationAlbums;
final takeCount = FilterQuickChooser.maxOptionCount - options.length;
if (takeCount > 0) {
final source = context.read<CollectionSource>();
final filters = source.rawAlbums.whereNot(options.contains).map((album) => AlbumFilter(album, null)).toSet();
@ -57,8 +55,8 @@ class _MoveQuickButtonState extends ChooserQuickButtonState<MoveButton, String>
scale: animation,
child: AlbumQuickChooser(
valueNotifier: chooserValueNotifier,
pointerGlobalPosition: pointerGlobalPosition,
options: widget.chooserPosition == PopupMenuPosition.over ? options.reversed.toList() : options,
pointerGlobalPosition: pointerGlobalPosition,
),
),
),

View file

@ -7,8 +7,8 @@ import 'package:provider/provider.dart';
abstract class ChooserQuickButton<T> extends StatefulWidget {
final PopupMenuPosition? chooserPosition;
final ValueSetter<T?>? onChooserValue;
final VoidCallback onPressed;
final ValueSetter<T>? onChooserValue;
final VoidCallback? onPressed;
const ChooserQuickButton({
super.key,
@ -29,7 +29,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
String get tooltip;
U? get defaultValue;
U? get defaultValue => null;
Duration get animationDuration => context.read<DurationsData>().quickChooserAnimation;
@ -61,7 +61,10 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
onLongPressEnd: isChooserEnabled
? (details) {
_clearChooserOverlayEntry();
onChooserValue.call(_chooserValueNotifier.value);
final selectedValue = _chooserValueNotifier.value;
if (selectedValue != null) {
onChooserValue(selectedValue);
}
}
: null,
onLongPressCancel: _clearChooserOverlayEntry,

View file

@ -11,13 +11,15 @@ class FilterQuickChooser<T> extends StatefulWidget {
final Stream<Offset> pointerGlobalPosition;
final Widget Function(BuildContext context, T album) buildFilterChip;
const FilterQuickChooser({
static const int maxOptionCount = 3;
FilterQuickChooser({
super.key,
required this.valueNotifier,
required this.options,
required List<T> options,
required this.pointerGlobalPosition,
required this.buildFilterChip,
});
}) : options = options.take(maxOptionCount).toList();
@override
State<FilterQuickChooser<T>> createState() => _FilterQuickChooserState<T>();
@ -134,8 +136,8 @@ class _FilterQuickChooserState<T> extends State<FilterQuickChooser<T>> {
T? selectedValue;
if (0 < dx && dx < contentWidth && 0 < dy && dy < contentHeight) {
final index = (options.length * dy / contentHeight).floor();
if (0 <= index && index < options.length) {
final index = (optionCount * dy / contentHeight).floor();
if (0 <= index && index < optionCount) {
selectedValue = options[index];
final top = index * (itemHeight + intraPadding);
_selectedRowRect.value = Rect.fromLTWH(0, top, contentWidth, itemHeight);

View file

@ -0,0 +1,32 @@
import 'dart:async';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/filter_chooser.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:flutter/material.dart';
class TagQuickChooser extends StatelessWidget {
final ValueNotifier<CollectionFilter?> valueNotifier;
final List<CollectionFilter> options;
final Stream<Offset> pointerGlobalPosition;
const TagQuickChooser({
super.key,
required this.valueNotifier,
required this.options,
required this.pointerGlobalPosition,
});
@override
Widget build(BuildContext context) {
return FilterQuickChooser<CollectionFilter>(
valueNotifier: valueNotifier,
options: options,
pointerGlobalPosition: pointerGlobalPosition,
buildFilterChip: (context, filter) => AvesFilterChip(
filter: filter,
showGenericIcon: false,
),
);
}
}

View file

@ -12,10 +12,10 @@ class RateButton extends ChooserQuickButton<int> {
});
@override
State<RateButton> createState() => _RateQuickButtonState();
State<RateButton> createState() => _RateButtonState();
}
class _RateQuickButtonState extends ChooserQuickButtonState<RateButton, int> {
class _RateButtonState extends ChooserQuickButtonState<RateButton, int> {
static const _action = EntryAction.editRating;
@override

View file

@ -0,0 +1,62 @@
import 'package:aves/model/actions/entry_actions.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/widgets/common/app_bar/quick_choosers/chooser_button.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/filter_chooser.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/tag_chooser.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TagButton extends ChooserQuickButton<CollectionFilter> {
const TagButton({
super.key,
super.chooserPosition,
super.onChooserValue,
required super.onPressed,
});
@override
State<TagButton> createState() => _TagButtonState();
}
class _TagButtonState extends ChooserQuickButtonState<TagButton, CollectionFilter> {
EntryAction get action => EntryAction.editTags;
@override
Widget get icon => action.getIcon();
@override
String get tooltip => action.getText(context);
@override
Widget buildChooser(Animation<double> animation) {
final options = settings.recentTags;
final takeCount = FilterQuickChooser.maxOptionCount - options.length;
if (takeCount > 0) {
final source = context.read<CollectionSource>();
final filters = source.sortedTags.map(TagFilter.new).whereNot(options.contains).toSet();
final allMapEntries = filters.map((filter) => FilterGridItem(filter, source.recentEntry(filter))).toList();
allMapEntries.sort(FilterNavigationPage.compareFiltersByDate);
options.addAll(allMapEntries.take(takeCount).map((v) => v.filter));
}
return MediaQueryDataProvider(
child: FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: TagQuickChooser(
valueNotifier: chooserValueNotifier,
options: widget.chooserPosition == PopupMenuPosition.over ? options.reversed.toList() : options,
pointerGlobalPosition: pointerGlobalPosition,
),
),
),
);
}
}

View file

@ -65,6 +65,8 @@ class DebugSettingsSection extends StatelessWidget {
'pinnedFilters': toMultiline(settings.pinnedFilters),
'hiddenFilters': toMultiline(settings.hiddenFilters),
'searchHistory': toMultiline(settings.searchHistory),
'recentDestinationAlbums': toMultiline(settings.recentDestinationAlbums),
'recentTags': toMultiline(settings.recentTags),
'locale': '${settings.locale}',
'systemLocales': '${WidgetsBinding.instance.window.locales}',
'topEntryIds': '${settings.topEntryIds}',

View file

@ -1,9 +1,8 @@
import 'dart:math';
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/placeholder.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/theme/durations.dart';
import 'package:aves/theme/icons.dart';
@ -35,10 +34,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
late final List<CollectionFilter> _topTags;
late final List<PlaceholderFilter> _placeholders = [PlaceholderFilter.country, PlaceholderFilter.place];
static final List<CollectionFilter> _recentTags = [];
static const Color untaggedColor = Colors.blueGrey;
static const int tagHistoryCount = 10;
Map<AvesEntry, Set<CollectionFilter>> get tagsByEntry => widget.filtersByEntry;
@ -79,7 +75,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
builder: (context, value, child) {
final upQuery = value.text.trim().toUpperCase();
bool containQuery(CollectionFilter v) => v.getLabel(context).toUpperCase().contains(upQuery);
final recentFilters = _recentTags.where(containQuery).toList();
final recentFilters = settings.recentTags.where(containQuery).toList();
final topTagFilters = _topTags.where(containQuery).toList();
final placeholderFilters = _placeholders.where(containQuery).toList();
return ListView(
@ -220,13 +216,10 @@ class _TagEditorPageState extends State<TagEditorPage> {
}
void _addTag(CollectionFilter newTag) {
setState(() {
_recentTags
settings.recentTags = settings.recentTags
..remove(newTag)
..insert(0, newTag)
..removeRange(min(tagHistoryCount, _recentTags.length), _recentTags.length);
tagsByEntry.forEach((entry, tags) => tags.add(newTag));
});
..insert(0, newTag);
setState(() => tagsByEntry.forEach((entry, tags) => tags.add(newTag)));
_newTagTextController.clear();
}

View file

@ -8,6 +8,7 @@ import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_metadata_edition.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
@ -280,9 +281,9 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
}
}
void quickMove(BuildContext context, String? album, {required bool copy}) {
final targetEntry = _getTargetEntry(context, EntryAction.editRating);
if (album == null || (!copy && targetEntry.directory == album)) return;
void quickMove(BuildContext context, String album, {required bool copy}) {
final targetEntry = _getTargetEntry(context, copy ? EntryAction.copy : EntryAction.move);
if (!copy && targetEntry.directory == album) return;
doQuickMove(
context,
@ -293,11 +294,16 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
);
}
void quickRate(BuildContext context, int? rating) {
void quickRate(BuildContext context, int rating) {
final targetEntry = _getTargetEntry(context, EntryAction.editRating);
_metadataActionDelegate.quickRate(context, targetEntry, rating);
}
void quickTag(BuildContext context, CollectionFilter filter) {
final targetEntry = _getTargetEntry(context, EntryAction.editTags);
_metadataActionDelegate.quickTag(context, targetEntry, filter);
}
Future<void> _addShortcut(BuildContext context, AvesEntry targetEntry) async {
final result = await showDialog<Tuple2<AvesEntry?, String>>(
context: context,

View file

@ -6,6 +6,7 @@ import 'package:aves/model/actions/events.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_info.dart';
import 'package:aves/model/entry_metadata_edition.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/geotiff.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/ref/mime_types.dart';
@ -144,20 +145,34 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
Future<void> _editRating(BuildContext context, AvesEntry targetEntry) async {
final rating = await selectRating(context, {targetEntry});
if (rating == null) return;
await quickRate(context, targetEntry, rating);
}
Future<void> quickRate(BuildContext context, AvesEntry targetEntry, int? rating) async {
if (rating == null || targetEntry.rating == rating) return;
Future<void> quickRate(BuildContext context, AvesEntry targetEntry, int rating) async {
if (targetEntry.rating == rating) return;
await edit(context, targetEntry, () => targetEntry.editRating(rating));
}
Future<void> _editTags(BuildContext context, AvesEntry targetEntry) async {
final newTagsByEntry = await selectTags(context, {targetEntry});
if (newTagsByEntry == null) return;
final tagsByEntry = await selectTags(context, {targetEntry});
if (tagsByEntry == null) return;
final newTags = newTagsByEntry[targetEntry] ?? targetEntry.tags;
final newTags = tagsByEntry[targetEntry] ?? targetEntry.tags;
await _applyTags(context, targetEntry, newTags);
}
Future<void> quickTag(BuildContext context, AvesEntry targetEntry, CollectionFilter filter) async {
final newTags = {
...targetEntry.tags,
...await getTagsFromFilters({filter}, targetEntry),
};
await _applyTags(context, targetEntry, newTags);
}
Future<void> _applyTags(BuildContext context, AvesEntry targetEntry, Set<String> newTags) async {
final currentTags = targetEntry.tags;
if (newTags.length == currentTags.length && newTags.every(currentTags.contains)) return;

View file

@ -16,6 +16,8 @@ import 'package:aves/theme/colors.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/file_utils.dart';
import 'package:aves/widgets/common/app_bar/rate_button.dart';
import 'package:aves/widgets/common/app_bar/tag_button.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
@ -32,6 +34,8 @@ class BasicSection extends StatelessWidget {
final ValueNotifier<EntryAction?> isEditingMetadataNotifier;
final FilterCallback onFilter;
static const quickChooserPosition = PopupMenuPosition.over;
const BasicSection({
super.key,
required this.entry,
@ -126,6 +130,31 @@ class BasicSection extends StatelessWidget {
valueListenable: isEditingMetadataNotifier,
builder: (context, editingAction, child) {
final isEditing = editingAction != null;
final onPressed = isEditing ? null : () => actionDelegate.onActionSelected(context, entry, collection, action);
Widget button;
switch (action) {
case EntryAction.editRating:
button = RateButton(
chooserPosition: quickChooserPosition,
onChooserValue: (rating) => actionDelegate.quickRate(context, entry, rating),
onPressed: onPressed,
);
break;
case EntryAction.editTags:
button = TagButton(
chooserPosition: quickChooserPosition,
onChooserValue: (filter) => actionDelegate.quickTag(context, entry, filter),
onPressed: onPressed,
);
break;
default:
button = IconButton(
icon: action.getIcon(),
onPressed: onPressed,
tooltip: action.getText(context),
);
break;
}
return Stack(
children: [
DecoratedBox(
@ -136,11 +165,7 @@ class BasicSection extends StatelessWidget {
)),
borderRadius: const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)),
),
child: IconButton(
icon: action.getIcon(),
onPressed: isEditing ? null : () => actionDelegate.onActionSelected(context, entry, collection, action),
tooltip: action.getText(context),
),
child: button,
),
Positioned.fill(
child: Visibility(

View file

@ -7,6 +7,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/app_bar/favourite_toggler.dart';
import 'package:aves/widgets/common/app_bar/move_button.dart';
import 'package:aves/widgets/common/app_bar/rate_button.dart';
import 'package:aves/widgets/common/app_bar/tag_button.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup_menu_button.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -90,6 +91,7 @@ class ViewerButtonRowContent extends StatelessWidget {
AvesEntry get favouriteTargetEntry => mainEntry.isBurst ? pageEntry : mainEntry;
static const double padding = 8;
static const quickChooserPosition = PopupMenuPosition.over;
ViewerButtonRowContent({
super.key,
@ -208,16 +210,16 @@ class ViewerButtonRowContent extends StatelessWidget {
case EntryAction.copy:
child = MoveButton(
copy: true,
chooserPosition: PopupMenuPosition.over,
onChooserValue: (album) => _quickMove(context, album, copy: true),
chooserPosition: quickChooserPosition,
onChooserValue: (album) => _entryActionDelegate.quickMove(context, album, copy: true),
onPressed: onPressed,
);
break;
case EntryAction.move:
child = MoveButton(
copy: false,
chooserPosition: PopupMenuPosition.over,
onChooserValue: (album) => _quickMove(context, album, copy: false),
chooserPosition: quickChooserPosition,
onChooserValue: (album) => _entryActionDelegate.quickMove(context, album, copy: false),
onPressed: onPressed,
);
break;
@ -250,8 +252,15 @@ class ViewerButtonRowContent extends StatelessWidget {
break;
case EntryAction.editRating:
child = RateButton(
chooserPosition: PopupMenuPosition.over,
onChooserValue: (rating) => _quickRate(context, rating),
chooserPosition: quickChooserPosition,
onChooserValue: (rating) => _entryActionDelegate.quickRate(context, rating),
onPressed: onPressed,
);
break;
case EntryAction.editTags:
child = TagButton(
chooserPosition: quickChooserPosition,
onChooserValue: (filter) => _entryActionDelegate.quickTag(context, filter),
onPressed: onPressed,
);
break;
@ -381,8 +390,4 @@ class ViewerButtonRowContent extends StatelessWidget {
EntryActionDelegate get _entryActionDelegate => EntryActionDelegate(mainEntry, pageEntry, collection);
void _onActionSelected(BuildContext context, EntryAction action) => _entryActionDelegate.onActionSelected(context, action);
void _quickMove(BuildContext context, String? album, {required bool copy}) => _entryActionDelegate.quickMove(context, album, copy: copy);
void _quickRate(BuildContext context, int? rating) => _entryActionDelegate.quickRate(context, rating);
}

View file

@ -8,6 +8,7 @@ import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/missing.dart';
import 'package:aves/model/filters/path.dart';
import 'package:aves/model/filters/placeholder.dart';
import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/recent.dart';
@ -64,6 +65,9 @@ void main() {
final path = PathFilter('/some/path/');
expect(path, jsonRoundTrip(path));
final placeholder = PlaceholderFilter.country;
expect(placeholder, jsonRoundTrip(placeholder));
final query = QueryFilter('some query');
expect(query, jsonRoundTrip(query));