Perf improvement in city locatio search + suggestions for untagged location clusters (#1720)
This commit is contained in:
commit
6ff97a8fa5
9 changed files with 102 additions and 35 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue