diff --git a/lib/models/search/year_search_result.dart b/lib/models/search/year_search_result.dart new file mode 100644 index 000000000..39452e2f8 --- /dev/null +++ b/lib/models/search/year_search_result.dart @@ -0,0 +1,9 @@ +import 'package:photos/models/file.dart'; +import 'package:photos/models/search/search_results.dart'; + +class YearSearchResult extends SearchResult { + final int year; + final List files; + + YearSearchResult(this.year, this.files); +} diff --git a/lib/services/search_service.dart b/lib/services/search_service.dart index 12e7a296a..f943bc330 100644 --- a/lib/services/search_service.dart +++ b/lib/services/search_service.dart @@ -139,6 +139,20 @@ class SearchService { return collectionSearchResults; } + Future> getYearSearchResults(int year) async { + final yearInMicroseconds = DateTime.utc(year).microsecondsSinceEpoch; + final nextYearInMicroseconds = + DateTime.utc(year + 1).microsecondsSinceEpoch; + final yearSearchResults = + await FilesDB.instance.getFilesCreatedWithinDurations( + [ + [yearInMicroseconds, nextYearInMicroseconds] + ], + null, + ); + return yearSearchResults; + } + bool _isValidLocation(Location location) { return location != null && location.latitude != null && diff --git a/lib/ui/viewer/search/collections/files_from_year_page.dart b/lib/ui/viewer/search/collections/files_from_year_page.dart new file mode 100644 index 000000000..8da88d91d --- /dev/null +++ b/lib/ui/viewer/search/collections/files_from_year_page.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:photos/core/event_bus.dart'; +import 'package:photos/events/files_updated_event.dart'; +import 'package:photos/events/local_photos_updated_event.dart'; +import 'package:photos/models/file_load_result.dart'; +import 'package:photos/models/gallery_type.dart'; +import 'package:photos/models/search/year_search_result.dart'; +import 'package:photos/models/selected_files.dart'; +import 'package:photos/ui/viewer/gallery/gallery.dart'; +import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import 'package:photos/ui/viewer/gallery/gallery_overlay_widget.dart'; + +class FilesFromYearPage extends StatelessWidget { + final YearSearchResult yearSearchResult; + final String tagPrefix; + + final _selectedFiles = SelectedFiles(); + static const GalleryType appBarType = GalleryType.searchResults; + static const GalleryType overlayType = GalleryType.searchResults; + + FilesFromYearPage( + this.yearSearchResult, + this.tagPrefix, { + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final gallery = Gallery( + asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) { + final result = yearSearchResult.files + .where( + (file) => + file.creationTime >= creationStartTime && + file.creationTime <= creationEndTime, + ) + .toList(); + return Future.value( + FileLoadResult( + result, + result.length < yearSearchResult.files.length, + ), + ); + }, + reloadEvent: Bus.instance.on(), + removalEventTypes: const { + EventType.deletedFromRemote, + EventType.deletedFromEverywhere, + }, + tagPrefix: tagPrefix, + selectedFiles: _selectedFiles, + initialFiles: [yearSearchResult.files[0]], + footer: const SizedBox(height: 120), + ); + return Scaffold( + appBar: PreferredSize( + preferredSize: const Size.fromHeight(50.0), + child: GalleryAppBarWidget( + appBarType, + yearSearchResult.year.toString(), + _selectedFiles, + ), + ), + body: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + GalleryOverlayWidget( + overlayType, + _selectedFiles, + ) + ], + ), + ); + } +} diff --git a/lib/ui/viewer/search/collections/files_in_location_page.dart b/lib/ui/viewer/search/collections/files_in_location_page.dart index 9a984d1db..34151a9e6 100644 --- a/lib/ui/viewer/search/collections/files_in_location_page.dart +++ b/lib/ui/viewer/search/collections/files_in_location_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/events/files_updated_event.dart'; +import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/models/file_load_result.dart'; import 'package:photos/models/gallery_type.dart'; import 'package:photos/models/search/location_search_result.dart'; @@ -41,14 +42,11 @@ class FilesInLocationPage extends StatelessWidget { ), ); }, - reloadEvent: Bus.instance.on().where( - (event) => - event.updatedFiles.firstWhere( - (element) => element.uploadedFileID != null, - orElse: () => null, - ) != - null, - ), + reloadEvent: Bus.instance.on(), + removalEventTypes: const { + EventType.deletedFromRemote, + EventType.deletedFromEverywhere, + }, forceReloadEvents: [ Bus.instance.on().where( (event) => diff --git a/lib/ui/viewer/search/search_result_widgets/filename_result_widget.dart b/lib/ui/viewer/search/search_result_widgets/file_result_widget.dart similarity index 100% rename from lib/ui/viewer/search/search_result_widgets/filename_result_widget.dart rename to lib/ui/viewer/search/search_result_widgets/file_result_widget.dart diff --git a/lib/ui/viewer/search/search_result_widgets/year_result_widget.dart b/lib/ui/viewer/search/search_result_widgets/year_result_widget.dart new file mode 100644 index 000000000..21de7ae76 --- /dev/null +++ b/lib/ui/viewer/search/search_result_widgets/year_result_widget.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:photos/ente_theme_data.dart'; +import 'package:photos/models/search/year_search_result.dart'; +import 'package:photos/ui/viewer/search/collections/files_from_year_page.dart'; +import 'package:photos/ui/viewer/search/search_result_widgets/search_result_thumbnail_widget.dart'; +import 'package:photos/utils/navigation_util.dart'; + +class YearSearchResultWidget extends StatelessWidget { + static const String _tagPrefix = "year_search"; + + final YearSearchResult yearSearchResult; + const YearSearchResultWidget(this.yearSearchResult, {Key key}) + : super(key: key); + @override + Widget build(BuildContext context) { + final noOfMemories = yearSearchResult.files.length; + final heroTagPrefix = _tagPrefix + yearSearchResult.year.toString(); + + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + color: Theme.of(context).colorScheme.searchResultsColor, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SearchResultThumbnailWidget( + yearSearchResult.files[0], + heroTagPrefix, + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Year', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.subTextColor, + ), + ), + const SizedBox(height: 8), + Text( + yearSearchResult.year.toString(), + style: const TextStyle(fontSize: 18), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + RichText( + text: TextSpan( + style: TextStyle( + color: Theme.of(context) + .colorScheme + .searchResultsCountTextColor, + ), + children: [ + TextSpan(text: noOfMemories.toString()), + TextSpan( + text: noOfMemories != 1 ? ' memories' : ' memory', + ), + ], + ), + ), + ], + ), + const Spacer(), + Icon( + Icons.chevron_right, + color: Theme.of(context).colorScheme.subTextColor, + ), + ], + ), + ), + ), + onTap: () { + routeToPage( + context, + FilesFromYearPage(yearSearchResult, heroTagPrefix), + forceCustomPageRoute: true, + ); + }, + ); + } +} diff --git a/lib/ui/viewer/search/search_suggestions.dart b/lib/ui/viewer/search/search_suggestions.dart index d1d8bb218..b28d81422 100644 --- a/lib/ui/viewer/search/search_suggestions.dart +++ b/lib/ui/viewer/search/search_suggestions.dart @@ -5,9 +5,11 @@ import 'package:photos/models/search/album_search_result.dart'; import 'package:photos/models/search/file_search_result.dart'; import 'package:photos/models/search/location_search_result.dart'; import 'package:photos/models/search/search_results.dart'; +import 'package:photos/models/search/year_search_result.dart'; import 'package:photos/ui/viewer/search/search_result_widgets/collection_result_widget.dart'; -import 'package:photos/ui/viewer/search/search_result_widgets/filename_result_widget.dart'; +import 'package:photos/ui/viewer/search/search_result_widgets/file_result_widget.dart'; import 'package:photos/ui/viewer/search/search_result_widgets/location_result_widget.dart'; +import 'package:photos/ui/viewer/search/search_result_widgets/year_result_widget.dart'; class SearchSuggestionsWidget extends StatelessWidget { final List results; @@ -58,6 +60,8 @@ class SearchSuggestionsWidget extends StatelessWidget { return LocationSearchResultWidget(result); } else if (result is FileSearchResult) { return FileSearchResultWidget(result); + } else if (result is YearSearchResult) { + return YearSearchResultWidget(result); } else { Logger('SearchSuggestionsWidget') .info("Invalid/Unsupported value"); diff --git a/lib/ui/viewer/search/search_widget.dart b/lib/ui/viewer/search/search_widget.dart index 570fea305..c56c2cf96 100644 --- a/lib/ui/viewer/search/search_widget.dart +++ b/lib/ui/viewer/search/search_widget.dart @@ -6,6 +6,7 @@ import 'package:photos/models/search/album_search_result.dart'; import 'package:photos/models/search/file_search_result.dart'; import 'package:photos/models/search/location_search_result.dart'; import 'package:photos/models/search/search_results.dart'; +import 'package:photos/models/search/year_search_result.dart'; import 'package:photos/services/search_service.dart'; import 'package:photos/ui/viewer/search/search_suggestions.dart'; import 'package:photos/utils/navigation_util.dart'; @@ -125,21 +126,38 @@ class _SearchWidgetState extends State { Future> getSearchResultsForQuery(String query) async { final List allResults = []; - final locationResults = - await SearchService.instance.getLocationSearchResults(query); - for (LocationSearchResult result in locationResults) { - allResults.add(result); + final queryAsIntForYear = int.tryParse(query); + if (isYearValid(queryAsIntForYear)) { + final yearResults = + await SearchService.instance.getYearSearchResults(queryAsIntForYear); + if (yearResults.isNotEmpty) { + allResults.add(YearSearchResult(queryAsIntForYear, yearResults)); + } } + final collectionResults = await SearchService.instance.getCollectionSearchResults(query); for (CollectionWithThumbnail collectionResult in collectionResults) { allResults.add(AlbumSearchResult(collectionResult)); } + + final locationResults = + await SearchService.instance.getLocationSearchResults(query); + for (LocationSearchResult result in locationResults) { + allResults.add(result); + } final fileResults = await SearchService.instance.getFileSearchResults(query); for (File file in fileResults) { allResults.add(FileSearchResult(file)); } + return allResults; } + + bool isYearValid(int year) { + return year != null && + year >= 1970 && + year <= int.parse(DateTime.now().year.toString()); + } }