diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index 81d30936f..aced1a04a 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -480,15 +480,15 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { } } - xmpMeta.getSafeInt(XMP.XMP_SCHEMA_NS, XMP.XMP_RATING_PROP_NAME) { if (it in RATING_RANGE) metadataMap[KEY_RATING] = it } + xmpMeta.getSafeInt(XMP.XMP_SCHEMA_NS, XMP.XMP_RATING_PROP_NAME) { metadataMap[KEY_RATING] = it } if (!metadataMap.containsKey(KEY_RATING)) { xmpMeta.getSafeInt(XMP.MICROSOFTPHOTO_SCHEMA_NS, XMP.MS_RATING_PROP_NAME) { percentRating -> // values of 1,25,50,75,99% correspond to 1,2,3,4,5 stars val standardRating = (percentRating / 25f).roundToInt() + 1 - if (standardRating in RATING_RANGE) metadataMap[KEY_RATING] = standardRating + metadataMap[KEY_RATING] = standardRating } if (!metadataMap.containsKey(KEY_RATING)) { - xmpMeta.getSafeInt(XMP.ACDSEE_SCHEMA_NS, XMP.ACDSEE_RATING_PROP_NAME) { if (it in RATING_RANGE) metadataMap[KEY_RATING] = it } + xmpMeta.getSafeInt(XMP.ACDSEE_SCHEMA_NS, XMP.ACDSEE_RATING_PROP_NAME) { metadataMap[KEY_RATING] = it } } } @@ -991,7 +991,6 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { private const val MASK_IS_360 = 1 shl 3 private const val MASK_IS_MULTIPAGE = 1 shl 4 private const val XMP_SUBJECTS_SEPARATOR = ";" - private val RATING_RANGE = 1..5 // overlay metadata private const val KEY_APERTURE = "aperture" diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fa91e031e..3dd1d5446 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -94,6 +94,8 @@ "filterFavouriteLabel": "Favourite", "filterLocationEmptyLabel": "Unlocated", "filterTagEmptyLabel": "Untagged", + "filterRatingUnratedLabel": "Unrated", + "filterRatingRejectedLabel": "Rejected", "filterTypeAnimatedLabel": "Animated", "filterTypeMotionPhotoLabel": "Motion Photo", "filterTypePanoramaLabel": "Panorama", @@ -381,6 +383,7 @@ "collectionSortDate": "By date", "collectionSortSize": "By size", "collectionSortName": "By album & file name", + "collectionSortRating": "By rating", "collectionGroupAlbum": "By album", "collectionGroupMonth": "By month", @@ -494,6 +497,7 @@ "searchSectionCountries": "Countries", "searchSectionPlaces": "Places", "searchSectionTags": "Tags", + "searchSectionRating": "Ratings", "settingsPageTitle": "Settings", "settingsSystemDefault": "System", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a00495769..9825b9225 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -79,6 +79,8 @@ "filterFavouriteLabel": "Favori", "filterLocationEmptyLabel": "Sans lieu", "filterTagEmptyLabel": "Sans libellé", + "filterRatingUnratedLabel": "Sans notation", + "filterRatingRejectedLabel": "Rejeté", "filterTypeAnimatedLabel": "Animation", "filterTypeMotionPhotoLabel": "Photo animée", "filterTypePanoramaLabel": "Panorama", @@ -273,6 +275,7 @@ "collectionSortDate": "par date", "collectionSortSize": "par taille", "collectionSortName": "alphabétique", + "collectionSortRating": "par notation", "collectionGroupAlbum": "par album", "collectionGroupMonth": "par mois", @@ -346,6 +349,7 @@ "searchSectionCountries": "Pays", "searchSectionPlaces": "Lieux", "searchSectionTags": "Libellés", + "searchSectionRating": "Notations", "settingsPageTitle": "Réglages", "settingsSystemDefault": "Système", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index a9de9ce8d..84b4c2c3d 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -79,6 +79,8 @@ "filterFavouriteLabel": "즐겨찾기", "filterLocationEmptyLabel": "장소 없음", "filterTagEmptyLabel": "태그 없음", + "filterRatingUnratedLabel": "별점 없음", + "filterRatingRejectedLabel": "거부됨", "filterTypeAnimatedLabel": "애니메이션", "filterTypeMotionPhotoLabel": "모션 포토", "filterTypePanoramaLabel": "파노라마", @@ -273,6 +275,7 @@ "collectionSortDate": "날짜", "collectionSortSize": "크기", "collectionSortName": "이름", + "collectionSortRating": "별점", "collectionGroupAlbum": "앨범별로", "collectionGroupMonth": "월별로", @@ -346,6 +349,7 @@ "searchSectionCountries": "국가", "searchSectionPlaces": "장소", "searchSectionTags": "태그", + "searchSectionRating": "별점", "settingsPageTitle": "설정", "settingsSystemDefault": "시스템", diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 9d0925459..0153f4590 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -361,7 +361,7 @@ class AvesEntry { return _bestDate; } - int? get rating => _catalogMetadata?.rating; + int get rating => _catalogMetadata?.rating ?? 0; int get rotationDegrees => _catalogMetadata?.rotationDegrees ?? sourceRotationDegrees; @@ -861,14 +861,6 @@ class AvesEntry { return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? ''); } - // compare by: - // 1) size descending - // 2) name ascending - static int compareBySize(AvesEntry a, AvesEntry b) { - final c = (b.sizeBytes ?? 0).compareTo(a.sizeBytes ?? 0); - return c != 0 ? c : compareByName(a, b); - } - static final _epoch = DateTime.fromMillisecondsSinceEpoch(0); // compare by: @@ -879,4 +871,20 @@ class AvesEntry { if (c != 0) return c; return compareByName(b, a); } + + // compare by: + // 1) rating descending + // 2) date descending + static int compareByRating(AvesEntry a, AvesEntry b) { + final c = b.rating.compareTo(a.rating); + return c != 0 ? c : compareByDate(a, b); + } + + // compare by: + // 1) size descending + // 2) date descending + static int compareBySize(AvesEntry a, AvesEntry b) { + final c = (b.sizeBytes ?? 0).compareTo(a.sizeBytes ?? 0); + return c != 0 ? c : compareByDate(a, b); + } } diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 2da4daaa9..f2389dce7 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -8,6 +8,7 @@ import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/query.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/utils/color_utils.dart'; @@ -26,6 +27,7 @@ abstract class CollectionFilter extends Equatable implements Comparable get props => [rating]; + + const RatingFilter(this.rating); + + RatingFilter.fromMap(Map json) + : this( + json['rating'] ?? 0, + ); + + @override + Map toMap() => { + 'type': type, + 'rating': rating, + }; + + @override + EntryFilter get test => (entry) => entry.rating == rating; + + @override + String get universalLabel => '$rating'; + + @override + String getLabel(BuildContext context) => 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; + } + } + + @override + String get category => type; + + @override + String get key => '$type-$rating'; + + static String formatRating(BuildContext context, int rating) { + switch (rating) { + case -1: + return context.l10n.filterRatingRejectedLabel; + case 0: + return context.l10n.filterRatingUnratedLabel; + default: + return '\u2B50' * rating; + } + } +} diff --git a/lib/model/metadata/catalog.dart b/lib/model/metadata/catalog.dart index 3f204ea72..008065451 100644 --- a/lib/model/metadata/catalog.dart +++ b/lib/model/metadata/catalog.dart @@ -5,10 +5,11 @@ class CatalogMetadata { final int? contentId, dateMillis; final bool isAnimated, isGeotiff, is360, isMultiPage; bool isFlipped; - int? rating, rotationDegrees; + int? rotationDegrees; final String? mimeType, xmpSubjects, xmpTitleDescription; double? latitude, longitude; Address? address; + int rating; static const double _precisionErrorTolerance = 1e-9; static const _isAnimatedMask = 1 << 0; @@ -31,7 +32,7 @@ class CatalogMetadata { this.xmpTitleDescription, double? latitude, double? longitude, - this.rating, + this.rating = 0, }) { // Geocoder throws an `IllegalArgumentException` when a coordinate has a funky value like `1.7056881853375E7` // We also exclude zero coordinates, taking into account precision errors (e.g. {5.952380952380953e-11,-2.7777777777777777e-10}), @@ -89,8 +90,7 @@ class CatalogMetadata { xmpTitleDescription: map['xmpTitleDescription'] ?? '', latitude: map['latitude'], longitude: map['longitude'], - // `rotationDegrees` should default to `null`, not 0 - rating: map['rating'], + rating: map['rating'] ?? 0, ); } diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 59166c762..f94a085a8 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -9,6 +9,7 @@ import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/query.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/events.dart'; @@ -108,15 +109,27 @@ class CollectionLens with ChangeNotifier { } bool get showHeaders { - if (sortFactor == EntrySortFactor.size) return false; + bool showAlbumHeaders() => !filters.any((f) => f is AlbumFilter); - if (sortFactor == EntrySortFactor.date && sectionFactor == EntryGroupFactor.none) return false; - - final albumSections = sortFactor == EntrySortFactor.name || (sortFactor == EntrySortFactor.date && sectionFactor == EntryGroupFactor.album); - final filterByAlbum = filters.any((f) => f is AlbumFilter); - if (albumSections && filterByAlbum) return false; - - return true; + switch (sortFactor) { + case EntrySortFactor.date: + switch (sectionFactor) { + case EntryGroupFactor.none: + return false; + case EntryGroupFactor.album: + return showAlbumHeaders(); + case EntryGroupFactor.month: + return true; + case EntryGroupFactor.day: + return true; + } + case EntrySortFactor.name: + return showAlbumHeaders(); + case EntrySortFactor.rating: + return !filters.any((f) => f is RatingFilter); + case EntrySortFactor.size: + return false; + } } void addFilter(CollectionFilter filter) { @@ -181,12 +194,15 @@ class CollectionLens with ChangeNotifier { case EntrySortFactor.date: _filteredSortedEntries.sort(AvesEntry.compareByDate); break; - case EntrySortFactor.size: - _filteredSortedEntries.sort(AvesEntry.compareBySize); - break; case EntrySortFactor.name: _filteredSortedEntries.sort(AvesEntry.compareByName); break; + case EntrySortFactor.rating: + _filteredSortedEntries.sort(AvesEntry.compareByRating); + break; + case EntrySortFactor.size: + _filteredSortedEntries.sort(AvesEntry.compareBySize); + break; } } @@ -210,15 +226,18 @@ class CollectionLens with ChangeNotifier { break; } break; + case EntrySortFactor.name: + final byAlbum = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); + sections = SplayTreeMap>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!)); + break; + case EntrySortFactor.rating: + sections = groupBy(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); + break; case EntrySortFactor.size: sections = Map.fromEntries([ MapEntry(const SectionKey(), _filteredSortedEntries), ]); break; - case EntrySortFactor.name: - final byAlbum = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); - sections = SplayTreeMap>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!)); - break; } sections = Map.unmodifiable(sections); _sortedEntries = null; diff --git a/lib/model/source/enums.dart b/lib/model/source/enums.dart index ec4f816b5..e39413950 100644 --- a/lib/model/source/enums.dart +++ b/lib/model/source/enums.dart @@ -4,7 +4,7 @@ enum ChipSortFactor { date, name, count } enum AlbumChipGroupFactor { none, importance, volume } -enum EntrySortFactor { date, size, name } +enum EntrySortFactor { date, name, rating, size } enum EntryGroupFactor { none, album, month, day } diff --git a/lib/model/source/section_keys.dart b/lib/model/source/section_keys.dart index fd455cf02..52cfb4879 100644 --- a/lib/model/source/section_keys.dart +++ b/lib/model/source/section_keys.dart @@ -23,3 +23,12 @@ class EntryDateSectionKey extends SectionKey with EquatableMixin { const EntryDateSectionKey(this.date); } + +class EntryRatingSectionKey extends SectionKey with EquatableMixin { + final int rating; + + @override + List get props => [rating]; + + const EntryRatingSectionKey(this.rating); +} diff --git a/lib/services/metadata/metadata_fetch_service.dart b/lib/services/metadata/metadata_fetch_service.dart index 574269843..3bf7e42e8 100644 --- a/lib/services/metadata/metadata_fetch_service.dart +++ b/lib/services/metadata/metadata_fetch_service.dart @@ -66,7 +66,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { // 'dateMillis': date taken in milliseconds since Epoch (long) // 'isAnimated': animated gif/webp (bool) // 'isFlipped': flipped according to EXIF orientation (bool) - // 'rating': rating in [1,5] (int) + // 'rating': rating in [-1,5] (int) // 'rotationDegrees': rotation degrees according to EXIF orientation or other metadata (int) // 'latitude': latitude (double) // 'longitude': longitude (double) diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index 87c5380db..19830fd48 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -22,6 +22,8 @@ class AIcons { static const IconData locationOff = Icons.location_off_outlined; static const IconData mainStorage = Icons.smartphone_outlined; static const IconData privacy = MdiIcons.shieldAccountOutline; + static const IconData ratingRejected = MdiIcons.starRemoveOutline; + static const IconData ratingUnrated = MdiIcons.starOffOutline; static const IconData raw = Icons.raw_on_outlined; static const IconData shooting = Icons.camera_outlined; static const IconData removableStorage = Icons.sd_storage_outlined; diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index fe2f0a577..491819f81 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -210,7 +210,6 @@ class _CollectionAppBarState extends State with SingleTickerPr action, appMode: appMode, isSelecting: isSelecting, - sortFactor: collection.sortFactor, itemCount: collection.entryCount, selectedItemCount: selectedItemCount, ); @@ -448,6 +447,7 @@ class _CollectionAppBarState extends State with SingleTickerPr EntrySortFactor.date: l10n.collectionSortDate, EntrySortFactor.size: l10n.collectionSortSize, EntrySortFactor.name: l10n.collectionSortName, + EntrySortFactor.rating: l10n.collectionSortRating, }, groupOptions: { EntryGroupFactor.album: l10n.collectionGroupAlbum, diff --git a/lib/widgets/collection/draggable_thumb_label.dart b/lib/widgets/collection/draggable_thumb_label.dart index b96b7e599..90017bdc0 100644 --- a/lib/widgets/collection/draggable_thumb_label.dart +++ b/lib/widgets/collection/draggable_thumb_label.dart @@ -1,4 +1,5 @@ import 'package:aves/model/entry.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; @@ -47,6 +48,11 @@ class CollectionDraggableThumbLabel extends StatelessWidget { if (_showAlbumName(context, entry)) _getAlbumName(context, entry), if (entry.bestTitle != null) entry.bestTitle!, ]; + case EntrySortFactor.rating: + return [ + RatingFilter.formatRating(context, entry.rating), + DraggableThumbLabel.formatMonthThumbLabel(context, entry.bestDate), + ]; case EntrySortFactor.size: return [ if (entry.sizeBytes != null) formatFileSize(context.l10n.localeName, entry.sizeBytes!, round: 0), diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index ce6f01dd2..36f36a0bb 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -15,7 +15,6 @@ import 'package:aves/model/selection.dart'; import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/media/enums.dart'; @@ -44,7 +43,6 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa EntrySetAction action, { required AppMode appMode, required bool isSelecting, - required EntrySortFactor sortFactor, required int itemCount, required int selectedItemCount, }) { diff --git a/lib/widgets/collection/grid/headers/any.dart b/lib/widgets/collection/grid/headers/any.dart index d2654a8db..7e1340005 100644 --- a/lib/widgets/collection/grid/headers/any.dart +++ b/lib/widgets/collection/grid/headers/any.dart @@ -7,6 +7,7 @@ import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/widgets/collection/grid/headers/album.dart'; import 'package:aves/widgets/collection/grid/headers/date.dart'; +import 'package:aves/widgets/collection/grid/headers/rating.dart'; import 'package:aves/widgets/common/grid/header.dart'; import 'package:flutter/material.dart'; @@ -49,6 +50,8 @@ class CollectionSectionHeader extends StatelessWidget { break; case EntrySortFactor.name: return _buildAlbumHeader(context); + case EntrySortFactor.rating: + return RatingSectionHeader(key: ValueKey(sectionKey), rating: (sectionKey as EntryRatingSectionKey).rating); case EntrySortFactor.size: break; } diff --git a/lib/widgets/collection/grid/headers/rating.dart b/lib/widgets/collection/grid/headers/rating.dart new file mode 100644 index 000000000..225e6923e --- /dev/null +++ b/lib/widgets/collection/grid/headers/rating.dart @@ -0,0 +1,21 @@ +import 'package:aves/model/filters/rating.dart'; +import 'package:aves/model/source/section_keys.dart'; +import 'package:aves/widgets/common/grid/header.dart'; +import 'package:flutter/material.dart'; + +class RatingSectionHeader extends StatelessWidget { + final int rating; + + const RatingSectionHeader({ + Key? key, + required this.rating, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SectionHeader( + sectionKey: EntryRatingSectionKey(rating), + title: RatingFilter.formatRating(context, rating), + ); + } +} diff --git a/lib/widgets/common/grid/scaling.dart b/lib/widgets/common/grid/scaling.dart index 4b62f3a9f..58aacf147 100644 --- a/lib/widgets/common/grid/scaling.dart +++ b/lib/widgets/common/grid/scaling.dart @@ -316,7 +316,6 @@ class _ScaleOverlayState extends State<_ScaleOverlay> { colors: const [ Colors.black, Colors.black54, - // Colors.amber, ], ), ) diff --git a/lib/widgets/common/thumbnail/overlay.dart b/lib/widgets/common/thumbnail/overlay.dart index cd0c9de0d..5b35a8ccf 100644 --- a/lib/widgets/common/thumbnail/overlay.dart +++ b/lib/widgets/common/thumbnail/overlay.dart @@ -25,7 +25,7 @@ class ThumbnailEntryOverlay extends StatelessWidget { else if (entry.isAnimated) const AnimatedImageIcon() else ...[ - if (entry.rating != null && context.select((t) => t.showRating)) RatingIcon(entry: entry), + if (entry.rating != 0 && context.select((t) => t.showRating)) RatingIcon(entry: entry), if (entry.isRaw && context.select((t) => t.showRaw)) const RawIcon(), if (entry.isGeotiff) const GeotiffIcon(), if (entry.is360) const SphericalImageIcon(), diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index 24927b16c..48dc7c1b6 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -4,6 +4,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/query.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/settings/settings.dart'; @@ -179,6 +180,11 @@ class CollectionSearchDelegate { ], ); }), + _buildFilterRow( + context: context, + title: context.l10n.searchSectionRating, + filters: [0, -1, 5, 4, 3, 2, 1].map((rating) => RatingFilter(rating)).toList(), + ), ], ); }); diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index b53ea7bfa..d8c414987 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -4,6 +4,7 @@ import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -75,31 +76,12 @@ class BasicSection extends StatelessWidget { }, ), OwnerProp(entry: entry), - _buildRatingRow(), _buildChips(context), ], ); }); } - Widget _buildRatingRow() { - final rating = entry.rating; - return rating != null - ? Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - children: List.generate( - 5, - (i) => Icon( - Icons.star, - color: rating > i ? Colors.amber : Colors.grey[800], - ), - ), - ), - ) - : const SizedBox(); - } - Widget _buildChips(BuildContext context) { final tags = entry.tags.toList()..sort(compareAsciiUpperCase); final album = entry.directory; @@ -113,6 +95,7 @@ class BasicSection extends StatelessWidget { if (entry.isVideo && entry.is360) TypeFilter.sphericalVideo, if (entry.isVideo && !entry.is360) MimeFilter.video, if (album != null) AlbumFilter(album, collection?.source.getAlbumDisplayName(context, album)), + if (entry.rating != 0) RatingFilter(entry.rating), ...tags.map((tag) => TagFilter(tag)), }; return AnimatedBuilder( diff --git a/test/model/filters_test.dart b/test/model/filters_test.dart index af979fad6..b8eaf5ce5 100644 --- a/test/model/filters_test.dart +++ b/test/model/filters_test.dart @@ -6,6 +6,7 @@ import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/query.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/services/common/services.dart'; @@ -50,6 +51,9 @@ void main() { final query = QueryFilter('some query'); expect(query, jsonRoundTrip(query)); + const rating = RatingFilter(3); + expect(rating, jsonRoundTrip(rating)); + final tag = TagFilter('some tag'); expect(tag, jsonRoundTrip(tag)); diff --git a/untranslated.json b/untranslated.json index 723499108..20b2b2e7b 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,10 +1,14 @@ { "de": [ + "filterRatingUnratedLabel", + "filterRatingRejectedLabel", "editEntryDateDialogSourceFieldLabel", "editEntryDateDialogSourceCustomDate", "editEntryDateDialogSourceTitle", "editEntryDateDialogSourceFileModifiedDate", "editEntryDateDialogTargetFieldsHeader", + "collectionSortRating", + "searchSectionRating", "settingsThumbnailShowRatingIcon" ], @@ -17,11 +21,15 @@ ], "ru": [ + "filterRatingUnratedLabel", + "filterRatingRejectedLabel", "editEntryDateDialogSourceFieldLabel", "editEntryDateDialogSourceCustomDate", "editEntryDateDialogSourceTitle", "editEntryDateDialogSourceFileModifiedDate", "editEntryDateDialogTargetFieldsHeader", + "collectionSortRating", + "searchSectionRating", "settingsThumbnailShowRatingIcon" ] }