2022-08-08 05:15:06 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2022-08-04 06:46:39 +00:00
|
|
|
import 'package:photos/core/event_bus.dart';
|
2023-02-03 07:39:04 +00:00
|
|
|
import 'package:photos/core/network/network.dart';
|
2022-08-16 06:54:05 +00:00
|
|
|
import 'package:photos/data/holidays.dart';
|
2022-08-17 10:50:40 +00:00
|
|
|
import 'package:photos/data/months.dart';
|
2022-08-22 05:18:28 +00:00
|
|
|
import 'package:photos/data/years.dart';
|
2022-08-04 06:46:39 +00:00
|
|
|
import 'package:photos/db/files_db.dart';
|
|
|
|
import 'package:photos/events/local_photos_updated_event.dart';
|
2023-01-12 05:24:54 +00:00
|
|
|
import 'package:photos/models/collection.dart';
|
2022-08-09 15:09:18 +00:00
|
|
|
import 'package:photos/models/collection_items.dart';
|
2022-08-04 06:46:39 +00:00
|
|
|
import 'package:photos/models/file.dart';
|
2022-09-15 04:29:46 +00:00
|
|
|
import 'package:photos/models/file_type.dart';
|
2022-08-08 05:15:06 +00:00
|
|
|
import 'package:photos/models/location.dart';
|
2022-08-16 11:49:58 +00:00
|
|
|
import 'package:photos/models/search/album_search_result.dart';
|
2022-09-14 10:55:45 +00:00
|
|
|
import 'package:photos/models/search/generic_search_result.dart';
|
2022-08-10 13:27:04 +00:00
|
|
|
import 'package:photos/models/search/location_api_response.dart';
|
2023-02-24 07:56:33 +00:00
|
|
|
import "package:photos/models/search/search_types.dart";
|
2022-08-09 15:09:18 +00:00
|
|
|
import 'package:photos/services/collections_service.dart';
|
2022-08-15 12:02:56 +00:00
|
|
|
import 'package:photos/utils/date_time_util.dart';
|
2022-09-16 12:11:16 +00:00
|
|
|
import 'package:tuple/tuple.dart';
|
2022-08-04 06:46:39 +00:00
|
|
|
|
|
|
|
class SearchService {
|
2022-12-30 08:03:46 +00:00
|
|
|
Future<List<File>>? _cachedFilesFuture;
|
2023-02-03 07:39:04 +00:00
|
|
|
final _enteDio = NetworkClient.instance.enteDio;
|
2022-08-11 14:38:43 +00:00
|
|
|
final _logger = Logger((SearchService).toString());
|
2022-08-09 15:09:18 +00:00
|
|
|
final _collectionService = CollectionsService.instance;
|
2022-08-10 10:09:56 +00:00
|
|
|
static const _maximumResultsLimit = 20;
|
2022-08-04 06:46:39 +00:00
|
|
|
|
|
|
|
SearchService._privateConstructor();
|
2022-09-16 12:11:16 +00:00
|
|
|
|
2022-08-04 06:46:39 +00:00
|
|
|
static final SearchService instance = SearchService._privateConstructor();
|
|
|
|
|
2022-11-06 06:14:46 +00:00
|
|
|
void init() {
|
2022-08-04 06:46:39 +00:00
|
|
|
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
|
2022-10-02 18:13:25 +00:00
|
|
|
// only invalidate, let the load happen on demand
|
2022-08-11 12:30:15 +00:00
|
|
|
_cachedFilesFuture = null;
|
2022-08-04 06:46:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-10-31 10:44:20 +00:00
|
|
|
Set<int> ignoreCollections() {
|
|
|
|
return CollectionsService.instance.getHiddenCollections();
|
|
|
|
}
|
|
|
|
|
2022-09-14 03:58:27 +00:00
|
|
|
Future<List<File>> _getAllFiles() async {
|
2022-08-11 12:30:15 +00:00
|
|
|
if (_cachedFilesFuture != null) {
|
2022-12-30 08:03:46 +00:00
|
|
|
return _cachedFilesFuture!;
|
2022-08-06 13:44:52 +00:00
|
|
|
}
|
2022-09-14 03:58:27 +00:00
|
|
|
_logger.fine("Reading all files from db");
|
2022-10-31 10:44:20 +00:00
|
|
|
_cachedFilesFuture =
|
|
|
|
FilesDB.instance.getAllFilesFromDB(ignoreCollections());
|
2022-12-30 08:03:46 +00:00
|
|
|
return _cachedFilesFuture!;
|
2022-08-06 13:44:52 +00:00
|
|
|
}
|
|
|
|
|
2022-08-09 15:09:18 +00:00
|
|
|
void clearCache() {
|
2022-08-11 12:30:15 +00:00
|
|
|
_cachedFilesFuture = null;
|
2022-08-06 12:35:29 +00:00
|
|
|
}
|
2022-08-08 05:15:06 +00:00
|
|
|
|
2022-09-14 10:55:45 +00:00
|
|
|
Future<List<GenericSearchResult>> getLocationSearchResults(
|
2022-08-08 05:15:06 +00:00
|
|
|
String query,
|
|
|
|
) async {
|
2022-09-14 10:55:45 +00:00
|
|
|
final List<GenericSearchResult> searchResults = [];
|
2022-08-08 05:15:06 +00:00
|
|
|
try {
|
2022-09-14 03:58:27 +00:00
|
|
|
final List<File> allFiles = await _getAllFiles();
|
2023-02-10 14:03:59 +00:00
|
|
|
// This code used an deprecated API earlier. We've retained the
|
|
|
|
// scaffolding for when we implement a client side location search, and
|
|
|
|
// meanwhile have replaced the API response.data with an empty map here.
|
|
|
|
final matchedLocationSearchResults = LocationApiResponse.fromMap({});
|
2022-08-10 13:27:04 +00:00
|
|
|
|
2022-08-17 08:35:16 +00:00
|
|
|
for (var locationData in matchedLocationSearchResults.results) {
|
2022-08-10 13:27:04 +00:00
|
|
|
final List<File> filesInLocation = [];
|
2022-08-09 12:23:43 +00:00
|
|
|
|
2022-08-17 08:35:16 +00:00
|
|
|
for (var file in allFiles) {
|
2022-08-10 13:27:04 +00:00
|
|
|
if (_isValidLocation(file.location) &&
|
2022-12-30 08:03:46 +00:00
|
|
|
_isLocationWithinBounds(file.location!, locationData)) {
|
2022-08-10 13:27:04 +00:00
|
|
|
filesInLocation.add(file);
|
2022-08-08 05:15:06 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-11 07:44:42 +00:00
|
|
|
filesInLocation.sort(
|
2022-12-30 08:03:46 +00:00
|
|
|
(first, second) =>
|
|
|
|
second.creationTime!.compareTo(first.creationTime!),
|
2022-08-11 07:44:42 +00:00
|
|
|
);
|
2022-08-10 13:27:04 +00:00
|
|
|
if (filesInLocation.isNotEmpty) {
|
2022-09-14 10:55:45 +00:00
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(
|
2022-09-16 06:13:24 +00:00
|
|
|
ResultType.location,
|
|
|
|
locationData.place,
|
|
|
|
filesInLocation,
|
|
|
|
),
|
2022-08-10 13:27:04 +00:00
|
|
|
);
|
|
|
|
}
|
2022-08-08 05:15:06 +00:00
|
|
|
}
|
2022-08-11 08:04:58 +00:00
|
|
|
} catch (e) {
|
|
|
|
_logger.severe(e);
|
2022-08-08 05:15:06 +00:00
|
|
|
}
|
2022-09-14 10:55:45 +00:00
|
|
|
return searchResults;
|
2022-08-08 05:15:06 +00:00
|
|
|
}
|
|
|
|
|
2022-08-09 15:09:18 +00:00
|
|
|
// getFilteredCollectionsWithThumbnail removes deleted or archived or
|
|
|
|
// collections which don't have a file from search result
|
2022-08-16 11:49:58 +00:00
|
|
|
Future<List<AlbumSearchResult>> getCollectionSearchResults(
|
2022-08-09 15:09:18 +00:00
|
|
|
String query,
|
|
|
|
) async {
|
2022-10-19 11:56:06 +00:00
|
|
|
final List<CollectionWithThumbnail> collectionWithThumbnails =
|
|
|
|
await _collectionService.getCollectionsWithThumbnails(
|
|
|
|
includedOwnedByOthers: true,
|
|
|
|
);
|
2022-08-11 05:15:42 +00:00
|
|
|
|
2022-08-16 11:49:58 +00:00
|
|
|
final List<AlbumSearchResult> collectionSearchResults = [];
|
2022-08-11 05:15:42 +00:00
|
|
|
|
2022-10-19 11:56:06 +00:00
|
|
|
for (var c in collectionWithThumbnails) {
|
2022-08-11 06:20:39 +00:00
|
|
|
if (collectionSearchResults.length >= _maximumResultsLimit) {
|
2022-08-11 05:40:29 +00:00
|
|
|
break;
|
|
|
|
}
|
2022-11-06 09:15:46 +00:00
|
|
|
|
2023-02-03 07:39:04 +00:00
|
|
|
if (!c.collection.isHidden() &&
|
|
|
|
c.collection.type != CollectionType.uncategorized &&
|
2022-12-30 08:03:46 +00:00
|
|
|
c.collection.name!.toLowerCase().contains(
|
2022-11-06 09:15:46 +00:00
|
|
|
query.toLowerCase(),
|
|
|
|
)) {
|
2022-10-19 11:56:06 +00:00
|
|
|
collectionSearchResults.add(AlbumSearchResult(c));
|
2022-08-11 05:15:42 +00:00
|
|
|
}
|
2022-08-09 15:09:18 +00:00
|
|
|
}
|
2022-08-11 05:15:42 +00:00
|
|
|
|
2022-08-11 06:20:39 +00:00
|
|
|
return collectionSearchResults;
|
2022-08-09 15:09:18 +00:00
|
|
|
}
|
2022-08-10 13:27:04 +00:00
|
|
|
|
2022-09-14 10:55:45 +00:00
|
|
|
Future<List<GenericSearchResult>> getYearSearchResults(
|
2022-08-22 06:02:06 +00:00
|
|
|
String yearFromQuery,
|
2022-08-22 05:18:28 +00:00
|
|
|
) async {
|
2022-09-14 10:55:45 +00:00
|
|
|
final List<GenericSearchResult> searchResults = [];
|
2022-08-22 05:18:28 +00:00
|
|
|
for (var yearData in YearsData.instance.yearsData) {
|
2022-08-22 06:02:06 +00:00
|
|
|
if (yearData.year.startsWith(yearFromQuery)) {
|
|
|
|
final List<File> filesInYear = await _getFilesInYear(yearData.duration);
|
2022-08-22 05:18:28 +00:00
|
|
|
if (filesInYear.isNotEmpty) {
|
2022-09-14 10:55:45 +00:00
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(
|
|
|
|
ResultType.year,
|
2022-08-22 05:18:28 +00:00
|
|
|
yearData.year,
|
|
|
|
filesInYear,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-08-16 11:49:58 +00:00
|
|
|
}
|
2022-09-14 10:55:45 +00:00
|
|
|
return searchResults;
|
2022-08-11 13:22:30 +00:00
|
|
|
}
|
|
|
|
|
2022-09-14 10:55:45 +00:00
|
|
|
Future<List<GenericSearchResult>> getHolidaySearchResults(
|
2022-08-15 14:03:42 +00:00
|
|
|
String query,
|
|
|
|
) async {
|
2022-09-14 10:55:45 +00:00
|
|
|
final List<GenericSearchResult> searchResults = [];
|
2022-08-15 14:03:42 +00:00
|
|
|
|
2022-08-17 08:35:16 +00:00
|
|
|
for (var holiday in allHolidays) {
|
2022-09-16 06:13:24 +00:00
|
|
|
if (holiday.name.toLowerCase().contains(query.toLowerCase())) {
|
2022-08-22 07:52:22 +00:00
|
|
|
final matchedFiles =
|
2022-08-16 07:13:21 +00:00
|
|
|
await FilesDB.instance.getFilesCreatedWithinDurations(
|
2022-09-16 12:11:16 +00:00
|
|
|
_getDurationsForCalendarDateInEveryYear(holiday.day, holiday.month),
|
2022-10-31 10:44:20 +00:00
|
|
|
ignoreCollections(),
|
2022-08-22 07:52:22 +00:00
|
|
|
order: 'DESC',
|
2022-08-15 12:02:56 +00:00
|
|
|
);
|
2022-09-14 10:57:47 +00:00
|
|
|
if (matchedFiles.isNotEmpty) {
|
2022-09-14 10:55:45 +00:00
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(ResultType.event, holiday.name, matchedFiles),
|
2022-08-22 07:52:22 +00:00
|
|
|
);
|
|
|
|
}
|
2022-08-12 11:08:26 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-14 10:55:45 +00:00
|
|
|
return searchResults;
|
2022-08-17 10:50:40 +00:00
|
|
|
}
|
|
|
|
|
2022-09-15 04:29:46 +00:00
|
|
|
Future<List<GenericSearchResult>> getFileTypeResults(
|
|
|
|
String query,
|
|
|
|
) async {
|
|
|
|
final List<GenericSearchResult> searchResults = [];
|
|
|
|
final List<File> allFiles = await _getAllFiles();
|
|
|
|
for (var fileType in FileType.values) {
|
|
|
|
final String fileTypeString = getHumanReadableString(fileType);
|
|
|
|
if (fileTypeString.toLowerCase().startsWith(query.toLowerCase())) {
|
|
|
|
final matchedFiles =
|
|
|
|
allFiles.where((e) => e.fileType == fileType).toList();
|
|
|
|
if (matchedFiles.isNotEmpty) {
|
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(
|
|
|
|
ResultType.fileType,
|
|
|
|
fileTypeString,
|
|
|
|
matchedFiles,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return searchResults;
|
|
|
|
}
|
|
|
|
|
2022-12-26 09:21:27 +00:00
|
|
|
Future<List<GenericSearchResult>> getCaptionAndNameResults(
|
2022-11-05 05:52:28 +00:00
|
|
|
String query,
|
|
|
|
) async {
|
|
|
|
final List<GenericSearchResult> searchResults = [];
|
|
|
|
if (query.isEmpty) {
|
|
|
|
return searchResults;
|
|
|
|
}
|
|
|
|
final RegExp pattern = RegExp(query, caseSensitive: false);
|
|
|
|
final List<File> allFiles = await _getAllFiles();
|
2022-12-26 09:21:27 +00:00
|
|
|
final List<File> captionMatch = <File>[];
|
|
|
|
final List<File> displayNameMatch = <File>[];
|
|
|
|
for (File eachFile in allFiles) {
|
2022-12-30 08:03:46 +00:00
|
|
|
if (eachFile.caption != null && pattern.hasMatch(eachFile.caption!)) {
|
2022-12-26 09:21:27 +00:00
|
|
|
captionMatch.add(eachFile);
|
|
|
|
}
|
|
|
|
if (pattern.hasMatch(eachFile.displayName)) {
|
|
|
|
displayNameMatch.add(eachFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (captionMatch.isNotEmpty) {
|
2022-11-05 05:52:28 +00:00
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(
|
|
|
|
ResultType.fileCaption,
|
|
|
|
query,
|
2022-12-26 09:21:27 +00:00
|
|
|
captionMatch,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (displayNameMatch.isNotEmpty) {
|
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(
|
|
|
|
ResultType.file,
|
|
|
|
query,
|
|
|
|
displayNameMatch,
|
2022-11-05 05:52:28 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return searchResults;
|
|
|
|
}
|
|
|
|
|
2022-09-15 04:44:09 +00:00
|
|
|
Future<List<GenericSearchResult>> getFileExtensionResults(
|
|
|
|
String query,
|
|
|
|
) async {
|
|
|
|
final List<GenericSearchResult> searchResults = [];
|
|
|
|
if (!query.startsWith(".")) {
|
|
|
|
return searchResults;
|
|
|
|
}
|
|
|
|
|
|
|
|
final List<File> allFiles = await _getAllFiles();
|
|
|
|
final Map<String, List<File>> resultMap = <String, List<File>>{};
|
|
|
|
|
|
|
|
for (File eachFile in allFiles) {
|
2022-09-23 01:48:25 +00:00
|
|
|
final String fileName = eachFile.displayName;
|
2022-09-16 06:13:24 +00:00
|
|
|
if (fileName.contains(query)) {
|
2022-09-15 04:44:09 +00:00
|
|
|
final String exnType = fileName.split(".").last.toUpperCase();
|
|
|
|
if (!resultMap.containsKey(exnType)) {
|
|
|
|
resultMap[exnType] = <File>[];
|
|
|
|
}
|
2022-12-30 08:03:46 +00:00
|
|
|
resultMap[exnType]!.add(eachFile);
|
2022-09-15 04:44:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for (MapEntry<String, List<File>> entry in resultMap.entries) {
|
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(
|
|
|
|
ResultType.fileExtension,
|
|
|
|
entry.key.toUpperCase(),
|
|
|
|
entry.value,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return searchResults;
|
|
|
|
}
|
|
|
|
|
2022-09-14 10:55:45 +00:00
|
|
|
Future<List<GenericSearchResult>> getMonthSearchResults(String query) async {
|
|
|
|
final List<GenericSearchResult> searchResults = [];
|
2022-09-16 12:11:16 +00:00
|
|
|
for (var month in _getMatchingMonths(query)) {
|
|
|
|
final matchedFiles =
|
|
|
|
await FilesDB.instance.getFilesCreatedWithinDurations(
|
|
|
|
_getDurationsOfMonthInEveryYear(month.monthNumber),
|
2022-10-31 10:44:20 +00:00
|
|
|
ignoreCollections(),
|
2022-09-16 12:11:16 +00:00
|
|
|
order: 'DESC',
|
|
|
|
);
|
|
|
|
if (matchedFiles.isNotEmpty) {
|
|
|
|
searchResults.add(
|
|
|
|
GenericSearchResult(
|
|
|
|
ResultType.month,
|
|
|
|
month.name,
|
|
|
|
matchedFiles,
|
|
|
|
),
|
2022-08-17 10:50:40 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-09-16 12:11:16 +00:00
|
|
|
return searchResults;
|
|
|
|
}
|
2022-08-17 10:50:40 +00:00
|
|
|
|
2022-09-16 12:11:16 +00:00
|
|
|
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;
|
2022-12-30 08:03:46 +00:00
|
|
|
final int? year = potentialDate.item3; // nullable
|
2022-09-16 12:11:16 +00:00
|
|
|
final matchedFiles =
|
|
|
|
await FilesDB.instance.getFilesCreatedWithinDurations(
|
|
|
|
_getDurationsForCalendarDateInEveryYear(day, month, year: year),
|
2022-10-31 10:44:20 +00:00
|
|
|
ignoreCollections(),
|
2022-09-16 12:11:16 +00:00
|
|
|
order: 'DESC',
|
|
|
|
);
|
|
|
|
if (matchedFiles.isNotEmpty) {
|
|
|
|
searchResults.add(
|
2022-09-22 05:26:14 +00:00
|
|
|
GenericSearchResult(
|
|
|
|
ResultType.event,
|
|
|
|
'$day ${potentialDate.item2.name} ${year ?? ''}',
|
|
|
|
matchedFiles,
|
|
|
|
),
|
2022-09-16 12:11:16 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-09-14 10:55:45 +00:00
|
|
|
return searchResults;
|
2022-08-12 11:08:26 +00:00
|
|
|
}
|
|
|
|
|
2022-09-16 12:11:16 +00:00
|
|
|
List<MonthData> _getMatchingMonths(String query) {
|
|
|
|
return allMonths
|
|
|
|
.where(
|
|
|
|
(monthData) =>
|
|
|
|
monthData.name.toLowerCase().startsWith(query.toLowerCase()),
|
|
|
|
)
|
|
|
|
.toList();
|
|
|
|
}
|
|
|
|
|
2022-08-22 05:18:28 +00:00
|
|
|
Future<List<File>> _getFilesInYear(List<int> durationOfYear) async {
|
|
|
|
return await FilesDB.instance.getFilesCreatedWithinDurations(
|
|
|
|
[durationOfYear],
|
2022-10-31 10:44:20 +00:00
|
|
|
ignoreCollections(),
|
2022-08-22 05:18:28 +00:00
|
|
|
order: "DESC",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-16 12:11:16 +00:00
|
|
|
List<List<int>> _getDurationsForCalendarDateInEveryYear(
|
|
|
|
int day,
|
|
|
|
int month, {
|
2022-12-30 08:03:46 +00:00
|
|
|
int? year,
|
2022-09-16 12:11:16 +00:00
|
|
|
}) {
|
2022-08-16 11:49:58 +00:00
|
|
|
final List<List<int>> durationsOfHolidayInEveryYear = [];
|
2022-09-16 12:11:16 +00:00
|
|
|
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,
|
|
|
|
]);
|
|
|
|
}
|
2022-08-15 12:02:56 +00:00
|
|
|
}
|
2022-08-16 11:49:58 +00:00
|
|
|
return durationsOfHolidayInEveryYear;
|
2022-08-15 12:02:56 +00:00
|
|
|
}
|
|
|
|
|
2022-08-17 10:50:40 +00:00
|
|
|
List<List<int>> _getDurationsOfMonthInEveryYear(int month) {
|
|
|
|
final List<List<int>> durationsOfMonthInEveryYear = [];
|
2022-09-16 15:50:00 +00:00
|
|
|
for (var year = searchStartYear; year <= currentYear; year++) {
|
2022-08-17 10:50:40 +00:00
|
|
|
durationsOfMonthInEveryYear.add([
|
|
|
|
DateTime.utc(year, month, 1).microsecondsSinceEpoch,
|
2022-08-17 11:48:25 +00:00
|
|
|
month == 12
|
2022-08-19 05:05:21 +00:00
|
|
|
? DateTime(year + 1, 1, 1).microsecondsSinceEpoch
|
|
|
|
: DateTime(year, month + 1, 1).microsecondsSinceEpoch,
|
2022-08-17 10:50:40 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
return durationsOfMonthInEveryYear;
|
|
|
|
}
|
|
|
|
|
2022-12-30 08:03:46 +00:00
|
|
|
bool _isValidLocation(Location? location) {
|
2022-08-10 13:27:04 +00:00
|
|
|
return location != null &&
|
|
|
|
location.latitude != null &&
|
|
|
|
location.latitude != 0 &&
|
|
|
|
location.longitude != null &&
|
|
|
|
location.longitude != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _isLocationWithinBounds(
|
|
|
|
Location location,
|
|
|
|
LocationDataFromResponse locationData,
|
|
|
|
) {
|
|
|
|
//format returned by the api is [lng,lat,lng,lat] where indexes 0 & 1 are southwest and 2 & 3 northeast
|
2022-12-30 08:03:46 +00:00
|
|
|
return location.longitude! > locationData.bbox[0] &&
|
|
|
|
location.latitude! > locationData.bbox[1] &&
|
|
|
|
location.longitude! < locationData.bbox[2] &&
|
|
|
|
location.latitude! < locationData.bbox[3];
|
2022-08-10 13:27:04 +00:00
|
|
|
}
|
2022-09-16 12:11:16 +00:00
|
|
|
|
2022-12-30 08:03:46 +00:00
|
|
|
List<Tuple3<int, MonthData, int?>> _getPossibleEventDate(String query) {
|
|
|
|
final List<Tuple3<int, MonthData, int?>> possibleEvents = [];
|
2022-09-16 12:11:16 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-12-30 08:03:46 +00:00
|
|
|
final int? day = int.tryParse(result[0]);
|
2022-09-16 12:11:16 +00:00
|
|
|
if (day == null || day < 1 || day > 31) {
|
|
|
|
return possibleEvents;
|
|
|
|
}
|
|
|
|
final List<MonthData> potentialMonth =
|
|
|
|
resultCount > 1 ? _getMatchingMonths(result[1]) : allMonths;
|
2022-12-30 08:03:46 +00:00
|
|
|
final int? parsedYear = resultCount >= 3 ? int.tryParse(result[2]) : null;
|
2022-09-16 12:11:16 +00:00
|
|
|
final List<int> matchingYears = [];
|
|
|
|
if (parsedYear != null) {
|
|
|
|
bool foundMatch = false;
|
2022-09-16 15:50:00 +00:00
|
|
|
for (int i = searchStartYear; i <= currentYear; i++) {
|
2022-09-16 12:11:16 +00:00
|
|
|
if (i.toString().startsWith(parsedYear.toString())) {
|
|
|
|
matchingYears.add(i);
|
|
|
|
foundMatch = foundMatch || (i == parsedYear);
|
|
|
|
}
|
|
|
|
}
|
2022-09-16 15:50:00 +00:00
|
|
|
if (!foundMatch && parsedYear > 1000 && parsedYear <= currentYear) {
|
2022-09-16 12:11:16 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-08-04 06:46:39 +00:00
|
|
|
}
|