filtering by rating range
This commit is contained in:
parent
55bf563eab
commit
6a7991dd62
11 changed files with 103 additions and 61 deletions
|
@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Added
|
||||
|
||||
- Collection: filtering by rating range
|
||||
- About: data usage
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -8,18 +8,34 @@ class RatingFilter extends CollectionFilter {
|
|||
static const type = 'rating';
|
||||
|
||||
final int rating;
|
||||
final String op;
|
||||
late final EntryFilter _test;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [rating, reversed];
|
||||
static const opEqual = '=';
|
||||
static const opOrLower = '<=';
|
||||
static const opOrGreater = '>=';
|
||||
|
||||
RatingFilter(this.rating, {super.reversed = false}) {
|
||||
_test = (entry) => entry.rating == rating;
|
||||
@override
|
||||
List<Object?> get props => [rating, op, reversed];
|
||||
|
||||
RatingFilter(this.rating, {this.op = opEqual, super.reversed = false}) {
|
||||
_test = switch (op) {
|
||||
opOrLower => (entry) => entry.rating <= rating && entry.rating > 0,
|
||||
opOrGreater => (entry) => entry.rating >= rating,
|
||||
opEqual || _ => (entry) => entry.rating == rating,
|
||||
};
|
||||
}
|
||||
|
||||
RatingFilter copyWith(String op) => RatingFilter(
|
||||
rating,
|
||||
op: op,
|
||||
reversed: reversed,
|
||||
);
|
||||
|
||||
factory RatingFilter.fromMap(Map<String, dynamic> json) {
|
||||
return RatingFilter(
|
||||
json['rating'] ?? 0,
|
||||
op: json['op'] ?? opEqual,
|
||||
reversed: json['reversed'] ?? false,
|
||||
);
|
||||
}
|
||||
|
@ -28,6 +44,7 @@ class RatingFilter extends CollectionFilter {
|
|||
Map<String, dynamic> toMap() => {
|
||||
'type': type,
|
||||
'rating': rating,
|
||||
'op': op,
|
||||
'reversed': reversed,
|
||||
};
|
||||
|
||||
|
@ -38,37 +55,42 @@ class RatingFilter extends CollectionFilter {
|
|||
bool get exclusiveProp => true;
|
||||
|
||||
@override
|
||||
String get universalLabel => '$rating';
|
||||
String get universalLabel => '$op $rating';
|
||||
|
||||
@override
|
||||
String getLabel(BuildContext context) => formatRating(context, rating);
|
||||
String getLabel(BuildContext context) => switch (op) {
|
||||
opOrLower || opOrGreater => '${UniChars.whiteMediumStar} ${formatRatingRange(context, rating, op)}',
|
||||
opEqual || _ => formatRating(context, rating),
|
||||
};
|
||||
|
||||
@override
|
||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
|
||||
switch (rating) {
|
||||
case -1:
|
||||
return Icon(AIcons.ratingRejected, size: size);
|
||||
case 0:
|
||||
return Icon(AIcons.ratingUnrated, size: size);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return switch (rating) {
|
||||
-1 => Icon(AIcons.ratingRejected, size: size),
|
||||
0 => Icon(AIcons.ratingUnrated, size: size),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => '$type-$reversed-$rating';
|
||||
String get key => '$type-$reversed-$rating-$op';
|
||||
|
||||
static String formatRating(BuildContext context, int rating) {
|
||||
switch (rating) {
|
||||
case -1:
|
||||
return context.l10n.filterRatingRejectedLabel;
|
||||
case 0:
|
||||
return context.l10n.filterNoRatingLabel;
|
||||
default:
|
||||
return UniChars.whiteMediumStar * rating;
|
||||
}
|
||||
return switch (rating) {
|
||||
-1 => context.l10n.filterRatingRejectedLabel,
|
||||
0 => context.l10n.filterNoRatingLabel,
|
||||
_ => UniChars.whiteMediumStar * rating,
|
||||
};
|
||||
}
|
||||
|
||||
static String formatRatingRange(BuildContext context, int rating, String op) {
|
||||
return switch (op) {
|
||||
opOrLower => '1~$rating',
|
||||
opOrGreater => '$rating~5',
|
||||
opEqual || _ => '$rating',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ extension ExtraChipActionView on ChipAction {
|
|||
return context.l10n.chipActionGoToPlacePage;
|
||||
case ChipAction.goToTagPage:
|
||||
return context.l10n.chipActionGoToTagPage;
|
||||
case ChipAction.ratingOrGreater:
|
||||
case ChipAction.ratingOrLower:
|
||||
// different data depending on state
|
||||
return toString();
|
||||
case ChipAction.reverse:
|
||||
// different data depending on state
|
||||
return context.l10n.chipActionFilterOut;
|
||||
|
@ -26,22 +30,14 @@ extension ExtraChipActionView on ChipAction {
|
|||
|
||||
Widget getIcon() => Icon(_getIconData());
|
||||
|
||||
IconData _getIconData() {
|
||||
switch (this) {
|
||||
case ChipAction.goToAlbumPage:
|
||||
return AIcons.album;
|
||||
case ChipAction.goToCountryPage:
|
||||
return AIcons.country;
|
||||
case ChipAction.goToPlacePage:
|
||||
return AIcons.place;
|
||||
case ChipAction.goToTagPage:
|
||||
return AIcons.tag;
|
||||
case ChipAction.reverse:
|
||||
return AIcons.reverse;
|
||||
case ChipAction.hide:
|
||||
return AIcons.hide;
|
||||
case ChipAction.lockVault:
|
||||
return AIcons.vaultLock;
|
||||
}
|
||||
}
|
||||
IconData _getIconData() => switch (this) {
|
||||
ChipAction.goToAlbumPage => AIcons.album,
|
||||
ChipAction.goToCountryPage => AIcons.country,
|
||||
ChipAction.goToPlacePage => AIcons.place,
|
||||
ChipAction.goToTagPage => AIcons.tag,
|
||||
ChipAction.ratingOrGreater || ChipAction.ratingOrLower => AIcons.rating,
|
||||
ChipAction.reverse => AIcons.reverse,
|
||||
ChipAction.hide => AIcons.hide,
|
||||
ChipAction.lockVault => AIcons.vaultLock,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -194,9 +194,9 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
),
|
||||
),
|
||||
if (showFilterBar)
|
||||
NotificationListener<ReverseFilterNotification>(
|
||||
NotificationListener<FilterNotification>(
|
||||
onNotification: (notification) {
|
||||
collection.addFilter(notification.reversedFilter);
|
||||
collection.addFilter(notification.filter);
|
||||
return true;
|
||||
},
|
||||
child: FilterBar(
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/covers.dart';
|
|||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/filters/rating.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
|
@ -100,6 +101,10 @@ class AvesFilterChip extends StatefulWidget {
|
|||
if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage,
|
||||
if ((filter is LocationFilter && filter.level == LocationLevel.place)) ChipAction.goToPlacePage,
|
||||
if (filter is TagFilter) ChipAction.goToTagPage,
|
||||
if (filter is RatingFilter && 1 < filter.rating && filter.rating < 5) ...[
|
||||
if (filter.op != RatingFilter.opOrGreater) ChipAction.ratingOrGreater,
|
||||
if (filter.op != RatingFilter.opOrLower) ChipAction.ratingOrLower,
|
||||
],
|
||||
ChipAction.reverse,
|
||||
ChipAction.hide,
|
||||
ChipAction.lockVault,
|
||||
|
@ -122,10 +127,19 @@ class AvesFilterChip extends StatefulWidget {
|
|||
const PopupMenuDivider(),
|
||||
...actions.where((action) => actionDelegate.isVisible(action, filter: filter)).map((action) {
|
||||
late String text;
|
||||
if (action == ChipAction.reverse) {
|
||||
text = filter.reversed ? context.l10n.chipActionFilterIn : context.l10n.chipActionFilterOut;
|
||||
} else {
|
||||
text = action.getText(context);
|
||||
switch (action) {
|
||||
case ChipAction.reverse:
|
||||
text = filter.reversed ? context.l10n.chipActionFilterIn : context.l10n.chipActionFilterOut;
|
||||
break;
|
||||
case ChipAction.ratingOrGreater:
|
||||
text = RatingFilter.formatRatingRange(context, (filter as RatingFilter).rating, RatingFilter.opOrGreater);
|
||||
break;
|
||||
case ChipAction.ratingOrLower:
|
||||
text = RatingFilter.formatRatingRange(context, (filter as RatingFilter).rating, RatingFilter.opOrLower);
|
||||
break;
|
||||
default:
|
||||
text = action.getText(context);
|
||||
break;
|
||||
}
|
||||
return PopupMenuItem(
|
||||
value: action,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/rating.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/vaults/vaults.dart';
|
||||
|
@ -26,6 +27,8 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin {
|
|||
case ChipAction.goToCountryPage:
|
||||
case ChipAction.goToPlacePage:
|
||||
case ChipAction.goToTagPage:
|
||||
case ChipAction.ratingOrGreater:
|
||||
case ChipAction.ratingOrLower:
|
||||
case ChipAction.reverse:
|
||||
return true;
|
||||
case ChipAction.hide:
|
||||
|
@ -46,8 +49,12 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin {
|
|||
_goTo(context, filter, PlaceListPage.routeName, (context) => const PlaceListPage());
|
||||
case ChipAction.goToTagPage:
|
||||
_goTo(context, filter, TagListPage.routeName, (context) => const TagListPage());
|
||||
case ChipAction.ratingOrGreater:
|
||||
FilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrGreater)).dispatch(context);
|
||||
case ChipAction.ratingOrLower:
|
||||
FilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrLower)).dispatch(context);
|
||||
case ChipAction.reverse:
|
||||
ReverseFilterNotification(filter).dispatch(context);
|
||||
FilterNotification(filter.reverse()).dispatch(context);
|
||||
case ChipAction.hide:
|
||||
_hide(context, filter);
|
||||
case ChipAction.lockVault:
|
||||
|
@ -95,8 +102,8 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin {
|
|||
}
|
||||
|
||||
@immutable
|
||||
class ReverseFilterNotification extends Notification {
|
||||
final CollectionFilter reversedFilter;
|
||||
class FilterNotification extends Notification {
|
||||
final CollectionFilter filter;
|
||||
|
||||
ReverseFilterNotification(CollectionFilter filter) : reversedFilter = filter.reverse();
|
||||
const FilterNotification(this.filter);
|
||||
}
|
||||
|
|
|
@ -174,8 +174,8 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
onNotification: (notification) {
|
||||
if (notification is FilterSelectedNotification) {
|
||||
_goToCollection(notification.filter);
|
||||
} else if (notification is ReverseFilterNotification) {
|
||||
_goToCollection(notification.reversedFilter);
|
||||
} else if (notification is FilterNotification) {
|
||||
_goToCollection(notification.filter);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -89,9 +89,9 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va
|
|||
final upQuery = query.trim().toUpperCase();
|
||||
bool containQuery(String s) => s.toUpperCase().contains(upQuery);
|
||||
return SafeArea(
|
||||
child: NotificationListener<ReverseFilterNotification>(
|
||||
child: NotificationListener<FilterNotification>(
|
||||
onNotification: (notification) {
|
||||
_select(context, notification.reversedFilter);
|
||||
_select(context, notification.filter);
|
||||
return true;
|
||||
},
|
||||
child: ValueListenableBuilder<String?>(
|
||||
|
|
|
@ -166,9 +166,9 @@ class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMix
|
|||
],
|
||||
),
|
||||
);
|
||||
child = NotificationListener<ReverseFilterNotification>(
|
||||
child = NotificationListener<FilterNotification>(
|
||||
onNotification: (notification) {
|
||||
_onFilterSelection(context, notification.reversedFilter);
|
||||
_onFilterSelection(context, notification.filter);
|
||||
return true;
|
||||
},
|
||||
child: AnimationLimiter(
|
||||
|
@ -378,9 +378,9 @@ class StatsTopPage extends StatelessWidget {
|
|||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Builder(builder: (context) {
|
||||
return NotificationListener<ReverseFilterNotification>(
|
||||
return NotificationListener<FilterNotification>(
|
||||
onNotification: (notification) {
|
||||
onFilterSelection(notification.reversedFilter);
|
||||
onFilterSelection(notification.filter);
|
||||
return true;
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
|
|
|
@ -238,9 +238,9 @@ class _InfoPageContentState extends State<_InfoPageContent> {
|
|||
metadataNotifier: _metadataNotifier,
|
||||
);
|
||||
|
||||
return NotificationListener<ReverseFilterNotification>(
|
||||
return NotificationListener<FilterNotification>(
|
||||
onNotification: (notification) {
|
||||
_onFilter(notification.reversedFilter);
|
||||
_onFilter(notification.filter);
|
||||
return true;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
|
|
|
@ -3,6 +3,8 @@ enum ChipAction {
|
|||
goToCountryPage,
|
||||
goToPlacePage,
|
||||
goToTagPage,
|
||||
ratingOrGreater,
|
||||
ratingOrLower,
|
||||
reverse,
|
||||
hide,
|
||||
lockVault,
|
||||
|
|
Loading…
Reference in a new issue