Bläddra i källkod

Perf improvement in city locatio search + suggestions for untagged location clusters (#1720)

Neeraj Gupta 1 år sedan
förälder
incheckning
6ff97a8fa5

+ 4 - 2
lib/models/search/search_types.dart

@@ -9,6 +9,7 @@ import "package:photos/events/location_tag_updated_event.dart";
 import "package:photos/generated/l10n.dart";
 import "package:photos/models/collection/collection.dart";
 import "package:photos/models/collection/collection_items.dart";
+import "package:photos/models/search/generic_search_result.dart";
 import "package:photos/models/search/search_result.dart";
 import "package:photos/models/typedefs.dart";
 import "package:photos/services/collections_service.dart";
@@ -24,6 +25,7 @@ enum ResultType {
   collection,
   file,
   location,
+  locationSuggestion,
   month,
   year,
   fileType,
@@ -243,10 +245,10 @@ extension SectionTypeExtensions on SectionType {
   }) {
     switch (this) {
       case SectionType.face:
-        return SearchService.instance.getAllLocationTags(limit);
+        return Future.value(List<GenericSearchResult>.empty());
 
       case SectionType.content:
-        return SearchService.instance.getAllLocationTags(limit);
+        return Future.value(List<GenericSearchResult>.empty());
 
       case SectionType.moment:
         return SearchService.instance.getRandomMomentsSearchResults(context);

+ 18 - 20
lib/services/location_service.dart

@@ -242,31 +242,29 @@ Future<List<City>> parseCities(Map args) async {
 
 Map<City, List<EnteFile>> getCityResults(Map args) {
   final query = (args["query"] as String).toLowerCase();
-  final cities = args["cities"] as List<City>;
-  final files = args["files"] as List<EnteFile>;
+  final List<City> cities = args["cities"] as List<City>;
+  final List<EnteFile> files = args["files"] as List<EnteFile>;
 
-  final matchingCities = cities.where(
-    (city) => city.city.toLowerCase().contains(query),
-  );
+  final matchingCities = cities
+      .where(
+        (city) => city.city.toLowerCase().contains(query),
+      )
+      .toList();
 
   final Map<City, List<EnteFile>> results = {};
-  for (final city in matchingCities) {
-    final List<EnteFile> matchingFiles = [];
-    final cityLocation = Location(latitude: city.lat, longitude: city.lng);
-    for (final file in files) {
-      if (file.hasLocation) {
-        if (isFileInsideLocationTag(
-          cityLocation,
-          file.location!,
-          defaultCityRadius,
-        )) {
-          matchingFiles.add(file);
-        }
+  for (final file in files) {
+    if (!file.hasLocation) continue; // Skip files without location
+    for (final city in matchingCities) {
+      final cityLocation = Location(latitude: city.lat, longitude: city.lng);
+      if (isFileInsideLocationTag(
+        cityLocation,
+        file.location!,
+        defaultCityRadius,
+      )) {
+        results.putIfAbsent(city, () => []).add(file);
+        break; // Stop searching once a file is matched with a city
       }
     }
-    if (matchingFiles.isNotEmpty) {
-      results[city] = matchingFiles;
-    }
   }
   return results;
 }

+ 35 - 0
lib/services/search_service.dart

@@ -3,6 +3,7 @@ import "dart:math";
 import "package:flutter/cupertino.dart";
 import "package:intl/intl.dart";
 import 'package:logging/logging.dart';
+import "package:photos/core/constants.dart";
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/data/holidays.dart';
 import 'package:photos/data/months.dart';
@@ -17,6 +18,7 @@ import "package:photos/models/file/extensions/file_props.dart";
 import 'package:photos/models/file/file.dart';
 import 'package:photos/models/file/file_type.dart';
 import "package:photos/models/local_entity_data.dart";
+import "package:photos/models/location/location.dart";
 import "package:photos/models/location_tag/location_tag.dart";
 import 'package:photos/models/search/album_search_result.dart';
 import 'package:photos/models/search/generic_search_result.dart';
@@ -25,6 +27,7 @@ import 'package:photos/services/collections_service.dart';
 import "package:photos/services/location_service.dart";
 import 'package:photos/services/semantic_search/semantic_search_service.dart';
 import "package:photos/states/location_screen_state.dart";
+import "package:photos/ui/viewer/location/add_location_sheet.dart";
 import "package:photos/ui/viewer/location/location_screen.dart";
 import 'package:photos/utils/date_time_util.dart';
 import "package:photos/utils/navigation_util.dart";
@@ -708,6 +711,7 @@ class SearchService {
       final locationTagEntities =
           (await LocationService.instance.getLocationTags());
       final allFiles = await getAllFiles();
+      final List<EnteFile> filesWithNoLocTag = [];
 
       for (int i = 0; i < locationTagEntities.length; i++) {
         if (limit != null && i >= limit) break;
@@ -716,15 +720,20 @@ class SearchService {
 
       for (EnteFile file in allFiles) {
         if (file.hasLocation) {
+          bool hasLocationTag = false;
           for (LocalEntity<LocationTag> tag in tagToItemsMap.keys) {
             if (isFileInsideLocationTag(
               tag.item.centerPoint,
               file.location!,
               tag.item.radius,
             )) {
+              hasLocationTag = true;
               tagToItemsMap[tag]!.add(file);
             }
           }
+          if (!hasLocationTag) {
+            filesWithNoLocTag.add(file);
+          }
         }
       }
 
@@ -753,6 +762,32 @@ class SearchService {
           );
         }
       }
+      if (limit == null || tagSearchResults.length < limit) {
+        final results = await LocationService.instance
+            .getFilesInCity(filesWithNoLocTag, '');
+        final List<City> sortedByResultCount = results.keys.toList()
+          ..sort((a, b) => results[b]!.length.compareTo(results[a]!.length));
+        for (final city in sortedByResultCount) {
+          if (results[city]!.length <= 1) continue;
+          // If the location tag already exists for a city, don't add it again
+          tagSearchResults.add(
+            GenericSearchResult(
+              ResultType.locationSuggestion,
+              city.city,
+              results[city]!,
+              onResultTap: (ctx) {
+                Navigator.of(ctx).pop();
+                showAddLocationSheet(
+                  ctx,
+                  Location(latitude: city.lat, longitude: city.lng),
+                  name: city.city,
+                  radius: defaultCityRadius,
+                );
+              },
+            ),
+          );
+        }
+      }
       return tagSearchResults;
     } catch (e) {
       _logger.severe("Error in getAllLocationTags", e);

+ 8 - 2
lib/states/location_state.dart

@@ -14,11 +14,14 @@ import "package:photos/utils/debouncer.dart";
 class LocationTagStateProvider extends StatefulWidget {
   final LocalEntity<LocationTag>? locationTagEntity;
   final Location? centerPoint;
+  final double? radius;
   final Widget child;
   const LocationTagStateProvider(
     this.child, {
     this.centerPoint,
     this.locationTagEntity,
+    // if the locationTagEntity is null, we use the centerPoint and radius
+    this.radius,
     super.key,
   });
 
@@ -47,9 +50,12 @@ class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
     ///If the location tag has a custom radius value, we add the custom radius
     ///value to the list of default radius values only for this location tag and
     ///keep it in the state of this widget.
-    _radiusValues = _getRadiusValuesOfLocTag(_locationTagEntity?.item.radius);
+    _radiusValues = _getRadiusValuesOfLocTag(
+      _locationTagEntity?.item.radius ?? widget.radius,
+    );
 
-    _selectedRadius = _locationTagEntity?.item.radius ?? defaultRadiusValue;
+    _selectedRadius =
+        _locationTagEntity?.item.radius ?? widget.radius ?? defaultRadiusValue;
 
     _locTagEntityListener =
         Bus.instance.on<LocationTagUpdatedEvent>().listen((event) {

+ 20 - 6
lib/ui/viewer/location/add_location_sheet.dart

@@ -22,14 +22,20 @@ import "package:photos/ui/viewer/location/radius_picker_widget.dart";
 
 showAddLocationSheet(
   BuildContext context,
-  Location coordinates,
-) {
+  Location coordinates, {
+  String name = '',
+  double radius = defaultRadiusValue,
+}) {
   showBarModalBottomSheet(
     context: context,
     builder: (context) {
       return LocationTagStateProvider(
         centerPoint: coordinates,
-        const AddLocationSheet(),
+        AddLocationSheet(
+          radius: radius,
+          name: name,
+        ),
+        radius: radius,
       );
     },
     shape: const RoundedRectangleBorder(
@@ -45,7 +51,13 @@ showAddLocationSheet(
 }
 
 class AddLocationSheet extends StatefulWidget {
-  const AddLocationSheet({super.key});
+  final double radius;
+  final String name;
+  const AddLocationSheet({
+    super.key,
+    this.radius = defaultRadiusValue,
+    this.name = '',
+  });
 
   @override
   State<AddLocationSheet> createState() => _AddLocationSheetState();
@@ -61,8 +73,7 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
   final ValueNotifier<bool> _submitNotifer = ValueNotifier(false);
 
   final ValueNotifier<bool> _cancelNotifier = ValueNotifier(false);
-  final ValueNotifier<double> _selectedRadiusNotifier =
-      ValueNotifier(defaultRadiusValue);
+  late ValueNotifier<double> _selectedRadiusNotifier;
   final _focusNode = FocusNode();
   final _textEditingController = TextEditingController();
   final _isEmptyNotifier = ValueNotifier(true);
@@ -70,8 +81,11 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
 
   @override
   void initState() {
+    _textEditingController.text = widget.name;
     _focusNode.addListener(_focusNodeListener);
+    _selectedRadiusNotifier = ValueNotifier(widget.radius);
     _selectedRadiusNotifier.addListener(_selectedRadiusListener);
+
     super.initState();
   }
 

+ 2 - 0
lib/ui/viewer/search/result/search_result_widget.dart

@@ -131,6 +131,8 @@ class SearchResultWidget extends StatelessWidget {
         return "Day";
       case ResultType.location:
         return "Location";
+      case ResultType.locationSuggestion:
+        return "Add Location";
       case ResultType.fileType:
         return "Type";
       case ResultType.fileExtension:

+ 10 - 3
lib/ui/viewer/search/result/search_section_all_page.dart

@@ -3,6 +3,7 @@ import "dart:async";
 import "package:flutter/material.dart";
 import "package:flutter_animate/flutter_animate.dart";
 import "package:photos/events/event.dart";
+import "package:photos/extensions/list.dart";
 import "package:photos/models/search/album_search_result.dart";
 import "package:photos/models/search/generic_search_result.dart";
 import "package:photos/models/search/recent_searches.dart";
@@ -83,8 +84,6 @@ class _SearchSectionAllPageState extends State<SearchSectionAllPage> {
                   builder: (context, snapshot) {
                     if (snapshot.hasData) {
                       final sectionResults = snapshot.data!;
-                      sectionResults
-                          .sort((a, b) => a.name().compareTo(b.name()));
                       return Text(sectionResults.length.toString())
                           .animate()
                           .fadeIn(
@@ -109,7 +108,15 @@ class _SearchSectionAllPageState extends State<SearchSectionAllPage> {
                 future: sectionData,
                 builder: (context, snapshot) {
                   if (snapshot.hasData) {
-                    final sectionResults = snapshot.data!;
+                    List<SearchResult> sectionResults = snapshot.data!;
+                    sectionResults.sort((a, b) => a.name().compareTo(b.name()));
+                    if (widget.sectionType == SectionType.location) {
+                      final result = sectionResults.splitMatch(
+                        (e) => e.type() == ResultType.location,
+                      );
+                      sectionResults = result.matched;
+                      sectionResults.addAll(result.unmatched);
+                    }
                     return ListView.separated(
                       itemBuilder: (context, index) {
                         if (sectionResults.length == index) {

+ 4 - 1
lib/ui/viewer/search/result/searchable_item.dart

@@ -77,7 +77,10 @@ class SearchableItemWidget extends StatelessWidget {
                         children: [
                           Text(
                             searchResult.name(),
-                            style: textTheme.body,
+                            style: searchResult.type() ==
+                                    ResultType.locationSuggestion
+                                ? textTheme.bodyFaint
+                                : textTheme.body,
                             overflow: TextOverflow.ellipsis,
                           ),
                           const SizedBox(

+ 1 - 1
pubspec.yaml

@@ -12,7 +12,7 @@ description: ente photos application
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 
-version: 0.8.58+578
+version: 0.8.59+579
 
 environment:
   sdk: ">=3.0.0 <4.0.0"