diff --git a/CHANGELOG.md b/CHANGELOG.md index a0824a583..799b9ac47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ All notable changes to this project will be documented in this file. ### Added -- Search: `on this day` filter -- Stats: histogram and date filters +- Search: `on this day` and month filters in date filter section +- Stats: histogram and contextual date filters - Screen saver ### Changed diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 893f94f9f..e543cae1d 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "Sammlung durchsuchen", "searchSectionRecent": "Neueste", + "searchSectionDate": "Datum", "searchSectionAlbums": "Alben", "searchSectionCountries": "Länder", "searchSectionPlaces": "Orte", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1a37ff721..345f12cde 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -598,6 +598,7 @@ "searchCollectionFieldHint": "Search collection", "searchSectionRecent": "Recent", + "searchSectionDate": "Date", "searchSectionAlbums": "Albums", "searchSectionCountries": "Countries", "searchSectionPlaces": "Places", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f99ee17d3..1d725a268 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "Buscar en colección", "searchSectionRecent": "Reciente", + "searchSectionDate": "Fecha", "searchSectionAlbums": "Álbumes", "searchSectionCountries": "Países", "searchSectionPlaces": "Lugares", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 7c7437f10..6c2d0f77f 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "Recherche", "searchSectionRecent": "Historique", + "searchSectionDate": "Date", "searchSectionAlbums": "Albums", "searchSectionCountries": "Pays", "searchSectionPlaces": "Lieux", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 6961078b8..d182ca74d 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "Cari koleksi", "searchSectionRecent": "Terkini", + "searchSectionDate": "Tanggal", "searchSectionAlbums": "Album", "searchSectionCountries": "Negara", "searchSectionPlaces": "Tempat", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 33b957412..b64b82fdc 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "Cerca raccolta", "searchSectionRecent": "Recenti", + "searchSectionDate": "Data", "searchSectionAlbums": "Album", "searchSectionCountries": "Paesi", "searchSectionPlaces": "Luoghi", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index aebfcb13c..b96f08213 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "コレクションを検索", "searchSectionRecent": "最近", + "searchSectionDate": "日付", "searchSectionAlbums": "アルバム", "searchSectionCountries": "国", "searchSectionPlaces": "場所", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 91bc57c73..f76ab18ed 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "미디어 검색", "searchSectionRecent": "최근 검색기록", + "searchSectionDate": "날짜", "searchSectionAlbums": "앨범", "searchSectionCountries": "국가", "searchSectionPlaces": "장소", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index de05648f1..3e4c18869 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "Pesquisar coleção", "searchSectionRecent": "Recente", + "searchSectionDate": "Data", "searchSectionAlbums": "Álbuns", "searchSectionCountries": "Países", "searchSectionPlaces": "Locais", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index b8f7efa3a..9169b5ed6 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -399,6 +399,7 @@ "searchCollectionFieldHint": "Поиск по коллекции", "searchSectionRecent": "Недавние", + "searchSectionDate": "Дата", "searchSectionAlbums": "Альбомы", "searchSectionCountries": "Страны", "searchSectionPlaces": "Локации", diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 84c152411..d9700f348 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -418,6 +418,7 @@ "searchCollectionFieldHint": "Koleksiyonu ara", "searchSectionRecent": "Yakın zamanda", + "searchSectionDate": "Tarih", "searchSectionAlbums": "Albümler", "searchSectionCountries": "Ülkeler", "searchSectionPlaces": "Yerler", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 1cadd97fd..06217ad01 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -417,6 +417,7 @@ "searchCollectionFieldHint": "搜索媒体集", "searchSectionRecent": "最近", + "searchSectionDate": "日期", "searchSectionAlbums": "相册", "searchSectionCountries": "国家", "searchSectionPlaces": "地点", diff --git a/lib/model/filters/date.dart b/lib/model/filters/date.dart index 42b61bcb1..6262bd8c5 100644 --- a/lib/model/filters/date.dart +++ b/lib/model/filters/date.dart @@ -7,7 +7,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; -class DateFilter extends CoveredCollectionFilter { +class DateFilter extends CollectionFilter { static const type = 'date'; final DateLevel level; @@ -69,6 +69,32 @@ class DateFilter extends CoveredCollectionFilter { @override EntryFilter get test => _test; + @override + bool isCompatible(CollectionFilter other) { + if (other is DateFilter) { + return isCompatibleLevel(level, other.level); + } else { + return true; + } + } + + static bool isCompatibleLevel(DateLevel a, DateLevel b) { + switch (a) { + case DateLevel.y: + return {DateLevel.md, DateLevel.m, DateLevel.d}.contains(b); + case DateLevel.ym: + return DateLevel.d == b; + case DateLevel.ymd: + return false; + case DateLevel.md: + return DateLevel.y == b; + case DateLevel.m: + return {DateLevel.y, DateLevel.d}.contains(b); + case DateLevel.d: + return {DateLevel.y, DateLevel.ym, DateLevel.m}.contains(b); + } + } + @override String get universalLabel => _effectiveDate.toIso8601String(); diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 813eede4d..f0787538c 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -89,7 +89,7 @@ abstract class CollectionFilter extends Equatable implements Comparable true; + bool isCompatible(CollectionFilter other) => category != other.category; String get universalLabel; diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index 8e6eb7875..7785b0345 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -75,7 +75,7 @@ class QueryFilter extends CollectionFilter { EntryFilter get test => _test; @override - bool get isUnique => false; + bool isCompatible(CollectionFilter other) => true; @override String get universalLabel => query; diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index aa9b09453..8c57586ab 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -38,7 +38,7 @@ class TagFilter extends CoveredCollectionFilter { EntryFilter get test => _test; @override - bool get isUnique => false; + bool isCompatible(CollectionFilter other) => true; @override String get universalLabel => tag; diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index d0598213e..71c94c239 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -150,9 +150,7 @@ class CollectionLens with ChangeNotifier { void addFilter(CollectionFilter filter) { if (filters.contains(filter)) return; - if (filter.isUnique) { - filters.removeWhere((old) => old.category == filter.category); - } + filters.removeWhere((other) => !filter.isCompatible(other)); filters.add(filter); _onFilterChanged(); } diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index c9a24baea..6b0793035 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -42,9 +42,10 @@ class CollectionSearchDelegate extends AvesSearchDelegate { TypeFilter.geotiff, TypeFilter.raw, MimeFilter(MimeTypes.svg), - DateFilter.onThisDay, ]; + static final _monthFilters = List.generate(12, (i) => DateFilter(DateLevel.m, DateTime(1, i + 1))); + CollectionSearchDelegate({ required super.searchFieldLabel, required this.source, @@ -97,66 +98,12 @@ class CollectionSearchDelegate extends AvesSearchDelegate { title: context.l10n.searchSectionRecent, filters: history, ), - StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - final filters = source.rawAlbums - .map((album) => AlbumFilter( - album, - source.getAlbumDisplayName(context, album), - )) - .where((filter) => containQuery(filter.displayName ?? filter.album)) - .toList() - ..sort(); - return _buildFilterRow( - context: context, - title: context.l10n.searchSectionAlbums, - filters: filters, - ); - }), - StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - final filters = source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)).toList(); - return _buildFilterRow( - context: context, - title: context.l10n.searchSectionCountries, - filters: filters, - ); - }), - StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - final filters = source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s)); - final noFilter = LocationFilter(LocationLevel.place, ''); - return _buildFilterRow( - context: context, - title: context.l10n.searchSectionPlaces, - filters: [ - if (containQuery(noFilter.getLabel(context))) noFilter, - ...filters, - ], - ); - }), - StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - final filters = source.sortedTags.where(containQuery).map(TagFilter.new); - final noFilter = TagFilter(''); - return _buildFilterRow( - context: context, - title: context.l10n.searchSectionTags, - filters: [ - if (containQuery(noFilter.getLabel(context))) noFilter, - ...filters, - ], - ); - }), - _buildFilterRow( - context: context, - title: context.l10n.searchSectionRating, - filters: [0, 5, 4, 3, 2, 1, -1].map(RatingFilter.new).where((f) => containQuery(f.getLabel(context))).toList(), - ), + _buildDateFilters(context, containQuery), + _buildAlbumFilters(containQuery), + _buildCountryFilters(containQuery), + _buildPlaceFilters(containQuery), + _buildTagFilters(containQuery), + _buildRatingFilters(context, containQuery), ], ); }); @@ -180,6 +127,97 @@ class CollectionSearchDelegate extends AvesSearchDelegate { ); } + Widget _buildDateFilters(BuildContext context, _ContainQuery containQuery) { + final filters = [ + DateFilter.onThisDay, + ..._monthFilters, + ].where((f) => containQuery(f.getLabel(context))).toList(); + return _buildFilterRow( + context: context, + title: context.l10n.searchSectionDate, + filters: filters, + ); + } + + Widget _buildAlbumFilters(_ContainQuery containQuery) { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) { + final filters = source.rawAlbums + .map((album) => AlbumFilter( + album, + source.getAlbumDisplayName(context, album), + )) + .where((filter) => containQuery(filter.displayName ?? filter.album)) + .toList() + ..sort(); + return _buildFilterRow( + context: context, + title: context.l10n.searchSectionAlbums, + filters: filters, + ); + }, + ); + } + + Widget _buildCountryFilters(_ContainQuery containQuery) { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) { + final filters = source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)).toList(); + return _buildFilterRow( + context: context, + title: context.l10n.searchSectionCountries, + filters: filters, + ); + }, + ); + } + + Widget _buildPlaceFilters(_ContainQuery containQuery) { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) { + final filters = source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s)); + final noFilter = LocationFilter(LocationLevel.place, ''); + return _buildFilterRow( + context: context, + title: context.l10n.searchSectionPlaces, + filters: [ + if (containQuery(noFilter.getLabel(context))) noFilter, + ...filters, + ], + ); + }, + ); + } + + Widget _buildTagFilters(_ContainQuery containQuery) { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) { + final filters = source.sortedTags.where(containQuery).map(TagFilter.new); + final noFilter = TagFilter(''); + return _buildFilterRow( + context: context, + title: context.l10n.searchSectionTags, + filters: [ + if (containQuery(noFilter.getLabel(context))) noFilter, + ...filters, + ], + ); + }, + ); + } + + Widget _buildRatingFilters(BuildContext context, _ContainQuery containQuery) { + return _buildFilterRow( + context: context, + title: context.l10n.searchSectionRating, + filters: [0, 5, 4, 3, 2, 1, -1].map(RatingFilter.new).where((f) => containQuery(f.getLabel(context))).toList(), + ); + } + @override Widget buildResults(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -240,3 +278,5 @@ class CollectionSearchDelegate extends AvesSearchDelegate { ); } } + +typedef _ContainQuery = bool Function(String s);