Merge pull request #495 from ente-io/daySearch

Search: Support for search by calendar date & specific day
This commit is contained in:
Neeraj Gupta 2022-09-17 14:32:54 +05:30 committed by GitHub
commit de9fefc9b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 186 additions and 58 deletions

View file

@ -603,10 +603,13 @@ class FilesDB {
Set<int> ignoredCollectionIDs, {
String order = 'ASC',
}) async {
if (durations.isEmpty) {
return <File>[];
}
final db = await instance.database;
String whereClause = "( ";
for (int index = 0; index < durations.length; index++) {
whereClause += "($columnCreationTime > " +
whereClause += "($columnCreationTime >= " +
durations[index][0].toString() +
" AND $columnCreationTime < " +
durations[index][1].toString() +

View file

@ -21,6 +21,7 @@ import 'package:photos/models/search/location_api_response.dart';
import 'package:photos/models/search/search_result.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/utils/date_time_util.dart';
import 'package:tuple/tuple.dart';
class SearchService {
Future<List<File>> _cachedFilesFuture;
@ -31,6 +32,7 @@ class SearchService {
static const _maximumResultsLimit = 20;
SearchService._privateConstructor();
static final SearchService instance = SearchService._privateConstructor();
Future<void> init() async {
@ -180,7 +182,7 @@ class SearchService {
if (holiday.name.toLowerCase().contains(query.toLowerCase())) {
final matchedFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
_getDurationsOfHolidayInEveryYear(holiday.day, holiday.month),
_getDurationsForCalendarDateInEveryYear(holiday.day, holiday.month),
null,
order: 'DESC',
);
@ -253,30 +255,61 @@ class SearchService {
Future<List<GenericSearchResult>> getMonthSearchResults(String query) async {
final List<GenericSearchResult> searchResults = [];
for (var month in allMonths) {
if (month.name.toLowerCase().startsWith(query.toLowerCase())) {
final matchedFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
_getDurationsOfMonthInEveryYear(month.monthNumber),
null,
order: 'DESC',
for (var month in _getMatchingMonths(query)) {
final matchedFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
_getDurationsOfMonthInEveryYear(month.monthNumber),
null,
order: 'DESC',
);
if (matchedFiles.isNotEmpty) {
searchResults.add(
GenericSearchResult(
ResultType.month,
month.name,
matchedFiles,
),
);
if (matchedFiles.isNotEmpty) {
searchResults.add(
GenericSearchResult(
ResultType.month,
month.name,
matchedFiles,
),
);
}
}
}
return searchResults;
}
Future<List<GenericSearchResult>> getDateResults(
String query,
) async {
final List<GenericSearchResult> searchResults = [];
final potentialDates = _getPossibleEventDate(query);
for (var potentialDate in potentialDates) {
final int day = potentialDate.item1;
final int month = potentialDate.item2.monthNumber;
final int year = potentialDate.item3; // nullable
final matchedFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
_getDurationsForCalendarDateInEveryYear(day, month, year: year),
null,
order: 'DESC',
);
if (matchedFiles.isNotEmpty) {
searchResults.add(
GenericSearchResult(ResultType.event,
'$day ${potentialDate.item2.name} ${year ?? ''}', matchedFiles),
);
}
}
return searchResults;
}
List<MonthData> _getMatchingMonths(String query) {
return allMonths
.where(
(monthData) =>
monthData.name.toLowerCase().startsWith(query.toLowerCase()),
)
.toList();
}
Future<List<File>> _getFilesInYear(List<int> durationOfYear) async {
return await FilesDB.instance.getFilesCreatedWithinDurations(
[durationOfYear],
@ -285,20 +318,28 @@ class SearchService {
);
}
List<List<int>> _getDurationsOfHolidayInEveryYear(int day, int month) {
List<List<int>> _getDurationsForCalendarDateInEveryYear(
int day,
int month, {
int year,
}) {
final List<List<int>> durationsOfHolidayInEveryYear = [];
for (var year = 1970; year <= currentYear; year++) {
durationsOfHolidayInEveryYear.add([
DateTime(year, month, day).microsecondsSinceEpoch,
DateTime(year, month, day + 1).microsecondsSinceEpoch,
]);
final int startYear = year ?? searchStartYear;
final int endYear = year ?? currentYear;
for (var yr = startYear; yr <= endYear; yr++) {
if (isValidDate(day: day, month: month, year: yr)) {
durationsOfHolidayInEveryYear.add([
DateTime(yr, month, day).microsecondsSinceEpoch,
DateTime(yr, month, day + 1).microsecondsSinceEpoch,
]);
}
}
return durationsOfHolidayInEveryYear;
}
List<List<int>> _getDurationsOfMonthInEveryYear(int month) {
final List<List<int>> durationsOfMonthInEveryYear = [];
for (var year = 1970; year < currentYear; year++) {
for (var year = searchStartYear; year <= currentYear; year++) {
durationsOfMonthInEveryYear.add([
DateTime.utc(year, month, 1).microsecondsSinceEpoch,
month == 12
@ -327,4 +368,52 @@ class SearchService {
location.longitude < locationData.bbox[2] &&
location.latitude < locationData.bbox[3];
}
List<Tuple3<int, MonthData, int>> _getPossibleEventDate(String query) {
final List<Tuple3<int, MonthData, int>> possibleEvents = [];
if (query.trim().isEmpty) {
return possibleEvents;
}
final result = query
.trim()
.split(RegExp('[ ,-/]+'))
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
final resultCount = result.length;
if (resultCount < 1 || resultCount > 4) {
return possibleEvents;
}
final int day = int.tryParse(result[0]);
if (day == null || day < 1 || day > 31) {
return possibleEvents;
}
final List<MonthData> potentialMonth =
resultCount > 1 ? _getMatchingMonths(result[1]) : allMonths;
final int parsedYear = resultCount >= 3 ? int.tryParse(result[2]) : null;
final List<int> matchingYears = [];
if (parsedYear != null) {
bool foundMatch = false;
for (int i = searchStartYear; i <= currentYear; i++) {
if (i.toString().startsWith(parsedYear.toString())) {
matchingYears.add(i);
foundMatch = foundMatch || (i == parsedYear);
}
}
if (!foundMatch && parsedYear > 1000 && parsedYear <= currentYear) {
matchingYears.add(parsedYear);
}
}
for (var element in potentialMonth) {
if (matchingYears.isEmpty) {
possibleEvents.add(Tuple3(day, element, null));
} else {
for (int yr in matchingYears) {
possibleEvents.add(Tuple3(day, element, yr));
}
}
}
return possibleEvents;
}
}

View file

@ -209,8 +209,7 @@ class _LandingPageWidgetState extends State<LandingPageWidget> {
final result = await showChoiceDialog(
context,
"Please login again",
'''Unfortunately, the ente app had to log you out because of some
technical issues. Sorry!\n\nPlease login again.''',
'''Unfortunately, the ente app had to log you out because of some technical issues. Sorry!\n\nPlease login again.''',
firstAction: "Cancel",
secondAction: "Login",
);

View file

@ -3,6 +3,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/models/search/search_result.dart';
import 'package:photos/services/feature_flag_service.dart';
@ -48,6 +49,7 @@ class _SearchIconWidgetState extends State<SearchIconWidget> {
class SearchWidget extends StatefulWidget {
const SearchWidget({Key key}) : super(key: key);
@override
State<SearchWidget> createState() => _SearchWidgetState();
}
@ -57,6 +59,7 @@ class _SearchWidgetState extends State<SearchWidget> {
final List<SearchResult> _results = [];
final _searchService = SearchService.instance;
final _debouncer = Debouncer(const Duration(milliseconds: 100));
final Logger _logger = Logger((_SearchWidgetState).toString());
@override
Widget build(BuildContext context) {
@ -84,7 +87,7 @@ class _SearchWidgetState extends State<SearchWidget> {
keyboardType: TextInputType.visiblePassword,
// Above parameters are to disable auto-suggestion
decoration: InputDecoration(
hintText: "Places, moments, albums...",
hintText: "Albums, months, days, years, ...",
filled: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
@ -125,7 +128,7 @@ class _SearchWidgetState extends State<SearchWidget> {
_query = value;
final List<SearchResult> allResults =
await getSearchResultsForQuery(value);
/*checking if _query == value to make sure that the results are from the current query
/*checking if _query == value to make sure that the results are from the current query
and not from the previous query (race condition).*/
if (mounted && _query == value) {
setState(() {
@ -178,34 +181,43 @@ class _SearchWidgetState extends State<SearchWidget> {
completer.complete(allResults);
return;
}
if (_isYearValid(query)) {
final yearResults = await _searchService.getYearSearchResults(query);
allResults.addAll(yearResults);
try {
if (_isYearValid(query)) {
final yearResults = await _searchService.getYearSearchResults(query);
allResults.addAll(yearResults);
}
final holidayResults =
await _searchService.getHolidaySearchResults(query);
allResults.addAll(holidayResults);
final fileTypeSearchResults =
await _searchService.getFileTypeResults(query);
allResults.addAll(fileTypeSearchResults);
final fileExtnResult =
await _searchService.getFileExtensionResults(query);
allResults.addAll(fileExtnResult);
final collectionResults =
await _searchService.getCollectionSearchResults(query);
allResults.addAll(collectionResults);
if (FeatureFlagService.instance.isInternalUserOrDebugBuild() &&
query.startsWith("l:")) {
final locationResults = await _searchService
.getLocationSearchResults(query.replaceAll("l:", ""));
allResults.addAll(locationResults);
}
final monthResults = await _searchService.getMonthSearchResults(query);
allResults.addAll(monthResults);
final possibleEvents = await _searchService.getDateResults(query);
allResults.addAll(possibleEvents);
} catch (e, s) {
_logger.severe("error during search", e, s);
}
final holidayResults = await _searchService.getHolidaySearchResults(query);
allResults.addAll(holidayResults);
final fileTypeSearchResults =
await _searchService.getFileTypeResults(query);
allResults.addAll(fileTypeSearchResults);
final fileExtnResult = await _searchService.getFileExtensionResults(query);
allResults.addAll(fileExtnResult);
final collectionResults =
await _searchService.getCollectionSearchResults(query);
allResults.addAll(collectionResults);
if (FeatureFlagService.instance.isInternalUserOrDebugBuild() &&
query.startsWith("l:")) {
final locationResults = await _searchService
.getLocationSearchResults(query.replaceAll("l:", ""));
allResults.addAll(locationResults);
}
final monthResults = await _searchService.getMonthSearchResults(query);
allResults.addAll(monthResults);
completer.complete(allResults);
}

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
const Set<int> monthWith31Days = {1, 3, 5, 7, 8, 10, 12};
const Set<int> monthWith30Days = {4, 6, 9, 11};
Map<int, String> _months = {
1: "Jan",
2: "Feb",
@ -42,6 +44,7 @@ Map<int, String> _days = {
};
final currentYear = int.parse(DateTime.now().year.toString());
const searchStartYear = 1970;
//Jun 2022
String getMonthAndYear(DateTime dateTime) {
@ -243,3 +246,25 @@ String secondsToHHMMSS(int value) {
return result;
}
bool isValidDate({
required int day,
required int month,
required int year,
}) {
if (day < 0 || day > 31 || month < 0 || month > 12 || year < 0) {
return false;
}
if (monthWith30Days.contains(month) && day > 30) {
return false;
}
if (month == 2) {
if (day > 29) {
return false;
}
if (day == 29 && year % 4 != 0) {
return false;
}
}
return true;
}