diff --git a/mobile/lib/models/search/hierarchical/hierarchical_search_filter.dart b/mobile/lib/models/search/hierarchical/hierarchical_search_filter.dart index 0f30de888b..7e99fe3424 100644 --- a/mobile/lib/models/search/hierarchical/hierarchical_search_filter.dart +++ b/mobile/lib/models/search/hierarchical/hierarchical_search_filter.dart @@ -12,6 +12,7 @@ enum FilterTypeNames { locationFilter, magicFilter, topLevelGenericFilter, + uploaderFilter, onlyThemFilter, } diff --git a/mobile/lib/models/search/hierarchical/uploader_filter.dart b/mobile/lib/models/search/hierarchical/uploader_filter.dart new file mode 100644 index 0000000000..c2849943c1 --- /dev/null +++ b/mobile/lib/models/search/hierarchical/uploader_filter.dart @@ -0,0 +1,44 @@ +import "package:flutter/material.dart"; +import "package:photos/models/file/extensions/file_props.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/models/search/hierarchical/hierarchical_search_filter.dart"; + +class UploaderFilter extends HierarchicalSearchFilter { + final String uploaderName; + final int occurrence; + + UploaderFilter({ + required this.uploaderName, + required this.occurrence, + super.filterTypeName = "uploaderFilter", + super.matchedUploadedIDs, + }); + + @override + String name() { + return uploaderName; + } + + @override + int relevance() { + return occurrence; + } + + @override + bool isMatch(EnteFile file) { + return file.uploaderName == uploaderName; + } + + @override + bool isSameFilter(HierarchicalSearchFilter other) { + if (other is UploaderFilter) { + return other.uploaderName == uploaderName; + } + return false; + } + + @override + IconData? icon() { + return Icons.person_outlined; + } +} diff --git a/mobile/lib/models/search/search_types.dart b/mobile/lib/models/search/search_types.dart index 25ee531ad9..db2c9be797 100644 --- a/mobile/lib/models/search/search_types.dart +++ b/mobile/lib/models/search/search_types.dart @@ -26,6 +26,7 @@ import "package:photos/utils/share_util.dart"; enum ResultType { collection, file, + uploader, location, locationSuggestion, month, diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index b68a597916..1c2e6ed2ab 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -35,6 +35,7 @@ import "package:photos/models/search/hierarchical/hierarchical_search_filter.dar import "package:photos/models/search/hierarchical/location_filter.dart"; import "package:photos/models/search/hierarchical/magic_filter.dart"; import "package:photos/models/search/hierarchical/top_level_generic_filter.dart"; +import "package:photos/models/search/hierarchical/uploader_filter.dart"; import "package:photos/models/search/search_constants.dart"; import "package:photos/models/search/search_types.dart"; import "package:photos/service_locator.dart"; @@ -551,6 +552,7 @@ class SearchService { final List allFiles = await getAllFilesForSearch(); final List captionMatch = []; final List displayNameMatch = []; + final Map> uploaderToFile = {}; for (EnteFile eachFile in allFiles) { if (eachFile.caption != null && pattern.hasMatch(eachFile.caption!)) { captionMatch.add(eachFile); @@ -558,6 +560,13 @@ class SearchService { if (pattern.hasMatch(eachFile.displayName)) { displayNameMatch.add(eachFile); } + if (eachFile.uploaderName != null && + pattern.hasMatch(eachFile.uploaderName!)) { + if (!uploaderToFile.containsKey(eachFile.uploaderName!)) { + uploaderToFile[eachFile.uploaderName!] = []; + } + uploaderToFile[eachFile.uploaderName!]!.add(eachFile); + } } if (captionMatch.isNotEmpty) { searchResults.add( @@ -590,6 +599,22 @@ class SearchService { ), ); } + if (uploaderToFile.isNotEmpty) { + for (MapEntry> entry in uploaderToFile.entries) { + searchResults.add( + GenericSearchResult( + ResultType.uploader, + entry.key, + entry.value, + hierarchicalSearchFilter: UploaderFilter( + uploaderName: entry.key, + occurrence: kMostRelevantFilter, + matchedUploadedIDs: filesToUploadedFileIDs(entry.value), + ), + ), + ); + } + } return searchResults; } diff --git a/mobile/lib/ui/viewer/search/result/search_result_widget.dart b/mobile/lib/ui/viewer/search/result/search_result_widget.dart index 564d77e714..8362b4e604 100644 --- a/mobile/lib/ui/viewer/search/result/search_result_widget.dart +++ b/mobile/lib/ui/viewer/search/result/search_result_widget.dart @@ -164,6 +164,8 @@ class SearchResultWidget extends StatelessWidget { return "Shared"; case ResultType.faces: return "Person"; + case ResultType.uploader: + return "Uploaded by"; } } } diff --git a/mobile/lib/utils/hierarchical_search_util.dart b/mobile/lib/utils/hierarchical_search_util.dart index a3254ef260..dddf48ddbb 100644 --- a/mobile/lib/utils/hierarchical_search_util.dart +++ b/mobile/lib/utils/hierarchical_search_util.dart @@ -5,6 +5,7 @@ import "package:photos/core/constants.dart"; import "package:photos/db/files_db.dart"; import "package:photos/db/ml/db.dart"; import "package:photos/generated/l10n.dart"; +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/location_tag/location_tag.dart"; @@ -18,6 +19,7 @@ import "package:photos/models/search/hierarchical/location_filter.dart"; import "package:photos/models/search/hierarchical/magic_filter.dart"; import "package:photos/models/search/hierarchical/only_them_filter.dart"; import "package:photos/models/search/hierarchical/top_level_generic_filter.dart"; +import "package:photos/models/search/hierarchical/uploader_filter.dart"; import "package:photos/service_locator.dart"; import "package:photos/services/collections_service.dart"; import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart"; @@ -155,6 +157,7 @@ Future curateFilters( files, ); final contactsFilters = _curateContactsFilter(files); + final uploaderFilters = _curateUploaderFilter(files); final faceFilters = await curateFaceFilters(files); final magicFilters = await curateMagicFilters(files, context); final onlyThemFilter = getOnlyThemFilter( @@ -169,6 +172,7 @@ Future curateFilters( ...faceFilters, ...fileTypeFilters, ...contactsFilters, + ...uploaderFilters, ...albumFilters, ...locationFilters, ], @@ -346,6 +350,31 @@ List _curateContactsFilter( return contactsFilters; } +List _curateUploaderFilter( + List files, +) { + final uploaderFilter = []; + final ownerIdToOccurrence = {}; + + for (EnteFile file in files) { + if (file.uploaderName == null) { + continue; + } + ownerIdToOccurrence[file.uploaderName!] = + (ownerIdToOccurrence[file.uploaderName!] ?? 0) + 1; + } + for (String uploader in ownerIdToOccurrence.keys) { + uploaderFilter.add( + UploaderFilter( + uploaderName: uploader, + occurrence: ownerIdToOccurrence[uploader]!, + ), + ); + } + + return uploaderFilter; +} + Future> curateFaceFilters( List files, ) async { @@ -502,6 +531,13 @@ Map> getFiltersForBottomSheet( searchFilterDataProvider.recommendations.whereType(), ); + final uploaderFilters = searchFilterDataProvider.appliedFilters + .whereType() + .toList(); + uploaderFilters.addAll( + searchFilterDataProvider.recommendations.whereType(), + ); + final magicFilters = searchFilterDataProvider.appliedFilters.whereType().toList(); magicFilters.addAll( @@ -518,6 +554,7 @@ Map> getFiltersForBottomSheet( "magicFilters": magicFilters, "locationFilters": locationFilters, "contactsFilters": contactsFilters, + "uploaderFilters": uploaderFilters, "albumFilters": albumFilters, "fileTypeFilters": fileTypeFilters, "topLevelGenericFilter": topLevelGenericFilter, @@ -567,6 +604,7 @@ List getRecommendedFiltersForAppBar( final magicReccos = []; final locationReccos = []; final contactsReccos = []; + final uploaderReccos = []; final albumReccos = []; final fileTypeReccos = []; final onlyThemFilter = []; @@ -582,6 +620,8 @@ List getRecommendedFiltersForAppBar( locationReccos.add(recommendation); } else if (recommendation is ContactsFilter) { contactsReccos.add(recommendation); + } else if (recommendation is UploaderFilter) { + uploaderReccos.add(recommendation); } else if (recommendation is AlbumFilter) { albumReccos.add(recommendation); } else if (recommendation is FileTypeFilter) { @@ -595,6 +635,7 @@ List getRecommendedFiltersForAppBar( ...magicReccos, ...locationReccos, ...contactsReccos, + ...uploaderReccos, ...albumReccos, ...fileTypeReccos, ];