From c1dccf438bac5879d7467211b755c7aafcb31a69 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 12 Feb 2025 10:16:46 +0530 Subject: [PATCH] [mob][photos] Simplify with BaseLocation --- mobile/lib/models/base_location.dart | 51 ++++++++++++++++++++++++ mobile/lib/services/search_service.dart | 53 ++++++++++++------------- 2 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 mobile/lib/models/base_location.dart diff --git a/mobile/lib/models/base_location.dart b/mobile/lib/models/base_location.dart new file mode 100644 index 0000000000..38b7847c64 --- /dev/null +++ b/mobile/lib/models/base_location.dart @@ -0,0 +1,51 @@ +import "package:photos/models/file/file.dart"; +import "package:photos/models/location/location.dart"; + +class BaseLocation { + final List files; + int? firstCreationTime; + int? lastCreationTime; + final Location location; + final bool isCurrentBase; + + BaseLocation( + this.files, + this.location, + this.isCurrentBase, { + this.firstCreationTime, + this.lastCreationTime, + }); + + int averageCreationTime() { + if (firstCreationTime != null && lastCreationTime != null) { + return (firstCreationTime! + lastCreationTime!) ~/ 2; + } + final List creationTimes = files + .where((file) => file.creationTime != null) + .map((file) => file.creationTime!) + .toList(); + if (creationTimes.length < 2) { + return creationTimes.isEmpty ? 0 : creationTimes.first; + } + creationTimes.sort(); + firstCreationTime ??= creationTimes.first; + lastCreationTime ??= creationTimes.last; + return (firstCreationTime! + lastCreationTime!) ~/ 2; + } + + BaseLocation copyWith({ + List? files, + int? firstCreationTime, + int? lastCreationTime, + Location? location, + bool? isCurrentBase, + }) { + return BaseLocation( + files ?? this.files, + location ?? this.location, + isCurrentBase ?? this.isCurrentBase, + firstCreationTime: firstCreationTime ?? this.firstCreationTime, + lastCreationTime: lastCreationTime ?? this.lastCreationTime, + ); + } +} diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index fb6b6f7373..a87e6e7be0 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -16,6 +16,7 @@ import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/extensions/user_extension.dart"; import "package:photos/models/api/collection/user.dart"; import "package:photos/models/base/id.dart"; +import "package:photos/models/base_location.dart"; import 'package:photos/models/collection/collection.dart'; import 'package:photos/models/collection/collection_items.dart'; import "package:photos/models/file/extensions/file_props.dart"; @@ -1207,15 +1208,15 @@ class SearchService { for (int i = 0; i < locationTagEntities.length; i++) { tagToItemsMap[locationTagEntities.elementAt(i)] = []; } - final Map, Location)> smallRadiusClusters = {}; + final List<(List, Location)> smallRadiusClusters = []; final Map, Location)> wideRadiusClusters = {}; // Go through all files and cluster the ones not inside any location tag for (EnteFile file in allFiles) { if (!file.hasLocation || file.uploadedFileID == null || !file.isOwner || - file.creationTime! > cutOffTime.microsecondsSinceEpoch || - file.creationTime == null) { + file.creationTime == null || + file.creationTime! > cutOffTime.microsecondsSinceEpoch) { continue; } // Check if the file is inside any location tag @@ -1234,25 +1235,20 @@ class SearchService { if (!hasLocationTag) { // Small radius clustering for base locations bool foundSmallCluster = false; - for (final clusterID in smallRadiusClusters.keys) { - final clusterLocation = smallRadiusClusters[clusterID]!.$2; + for (final cluster in smallRadiusClusters) { + final clusterLocation = cluster.$2; if (isFileInsideLocationTag( clusterLocation, file.location!, 0.6, )) { - smallRadiusClusters[clusterID]!.$1.add(file); + cluster.$1.add(file); foundSmallCluster = true; break; } } if (!foundSmallCluster) { - smallRadiusClusters[newAutoLocationID()] = ( - [ - file, - ], - file.location! - ); + smallRadiusClusters.add(([file], file.location!)); } // Wide radius clustering for trip locations bool foundWideCluster = false; @@ -1280,10 +1276,10 @@ class SearchService { } // Identify base locations - final Map, Location, bool)> baseLocations = {}; - for (final clusterID in smallRadiusClusters.keys) { - final files = smallRadiusClusters[clusterID]!.$1; - final location = smallRadiusClusters[clusterID]!.$2; + final List baseLocations = []; + for (final cluster in smallRadiusClusters) { + final files = cluster.$1; + final location = cluster.$2; // Check that the photos are distributed over a longer time range (3+ months) final creationTimes = []; final Set uniqueDays = {}; @@ -1309,12 +1305,12 @@ class SearchService { final daysRange = lastCreationTime.difference(firstCreationTime).inDays; if (uniqueDays.length < daysRange * 0.1) continue; // Check if it's a current or old base location - final bool current = lastCreationTime.isAfter( + final bool isCurrent = lastCreationTime.isAfter( DateTime.now().subtract( const Duration(days: 90), ), ); - baseLocations[clusterID] = (files, location, current); + baseLocations.add(BaseLocation(files, location, isCurrent)); } // Identify trip locations @@ -1326,9 +1322,9 @@ class SearchService { // Check that it's at least 10km away from any base or tag location bool tooClose = false; - for (final baseLocation in baseLocations.values) { + for (final baseLocation in baseLocations) { if (isFileInsideLocationTag( - baseLocation.$2, + baseLocation.location, location, 10.0, )) { @@ -1442,20 +1438,18 @@ class SearchService { } // For now for testing let's just surface all base locations - for (final baseLocation in baseLocations.values) { - final files = baseLocation.$1; - final current = baseLocation.$3; - final name = "Base (${current ? 'current' : 'old'})"; + for (final baseLocation in baseLocations) { + final name = "Base (${baseLocation.isCurrentBase ? 'current' : 'old'})"; searchResults.add( GenericSearchResult( ResultType.event, name, - files, + baseLocation.files, hierarchicalSearchFilter: TopLevelGenericFilter( filterName: name, occurrence: kMostRelevantFilter, filterResultType: ResultType.event, - matchedUploadedIDs: filesToUploadedFileIDs(files), + matchedUploadedIDs: filesToUploadedFileIDs(baseLocation.files), filterIcon: Icons.event_outlined, ), ), @@ -1512,12 +1506,15 @@ class SearchService { ), ), ); - if (limit != null && searchResults.length >= limit) return searchResults; + if (limit != null && searchResults.length >= limit) { + return searchResults; + } } } // Otherwise, if no trips happened in the current month, // look for the earliest upcoming trip in another month that has 3+ trips. - else { // TODO lau: make sure the same upcoming trip isn't shown multiple times over multiple months + else { + // TODO lau: make sure the same upcoming trip isn't shown multiple times over multiple months final sortedUpcomingMonths = List.generate(12, (i) => ((currentMonth + i) % 12) + 1); checkUpcomingMonths: