diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index f01bba4d95..7c8a1b3351 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -951,6 +951,23 @@ class SearchService { PeoplePage( tagPrefix: "${ResultType.faces.toString()}_${p.data.name}", person: p, + searchResult: GenericSearchResult( + ResultType.faces, + p.data.name, + files, + params: { + kPersonParamID: personID, + kFileID: files.first.uploadedFileID, + }, + hierarchicalSearchFilter: FaceFilter( + personId: p.remoteID, + clusterId: null, + faceName: p.data.name, + faceFile: files.first, + occurrence: kMostRelevantFilter, + matchedUploadedIDs: filesToUploadedFileIDs(files), + ), + ), ), ); }, diff --git a/mobile/lib/ui/viewer/file_details/face_widget.dart b/mobile/lib/ui/viewer/file_details/face_widget.dart index 58c2338812..0b255a2f51 100644 --- a/mobile/lib/ui/viewer/file_details/face_widget.dart +++ b/mobile/lib/ui/viewer/file_details/face_widget.dart @@ -119,6 +119,7 @@ class _FaceWidgetState extends State { MaterialPageRoute( builder: (context) => PeoplePage( person: widget.person!, + searchResult: null, ), ), ); diff --git a/mobile/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart b/mobile/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart index 71039917db..09771ddd2d 100644 --- a/mobile/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart +++ b/mobile/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart @@ -8,6 +8,7 @@ class InheritedSearchFilterData extends InheritedWidget { required super.child, }); + /// Pass null if gallery doesn't need hierarchical search final SearchFilterDataProvider? searchFilterDataProvider; bool get isHierarchicalSearchable => searchFilterDataProvider != null; diff --git a/mobile/lib/ui/viewer/people/cluster_page.dart b/mobile/lib/ui/viewer/people/cluster_page.dart index 34e42bfaac..308b34a975 100644 --- a/mobile/lib/ui/viewer/people/cluster_page.dart +++ b/mobile/lib/ui/viewer/people/cluster_page.dart @@ -200,13 +200,22 @@ class _ClusterPageState extends State { // ignore: unawaited_futures routeToPage( context, - PeoplePage(person: result.$1), + PeoplePage( + person: result.$1, + searchResult: null, + ), ); } else if (result != null && result is PersonEntity) { Navigator.pop(context); // ignore: unawaited_futures - routeToPage(context, PeoplePage(person: result)); + routeToPage( + context, + PeoplePage( + person: result, + searchResult: null, + ), + ); } } else { showShortToast(context, "No personID or clusterID"); diff --git a/mobile/lib/ui/viewer/people/people_app_bar.dart b/mobile/lib/ui/viewer/people/people_app_bar.dart index 76fd73f5e6..6df1ddeb78 100644 --- a/mobile/lib/ui/viewer/people/people_app_bar.dart +++ b/mobile/lib/ui/viewer/people/people_app_bar.dart @@ -15,6 +15,8 @@ import 'package:photos/models/selected_files.dart'; import 'package:photos/services/collections_service.dart'; import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; +import "package:photos/ui/viewer/hierarchicial_search/applied_filters.dart"; +import "package:photos/ui/viewer/hierarchicial_search/recommended_filters.dart"; import "package:photos/ui/viewer/people/add_person_action_sheet.dart"; import "package:photos/ui/viewer/people/people_page.dart"; import "package:photos/ui/viewer/people/person_cluster_suggestion.dart"; @@ -34,8 +36,8 @@ class PeopleAppBar extends StatefulWidget { this.title, this.selectedFiles, this.person, { - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _AppBarWidgetState(); @@ -87,12 +89,39 @@ class _AppBarWidgetState extends State { return AppBar( elevation: 0, centerTitle: false, - title: Text( - _appBarTitle!, - style: - Theme.of(context).textTheme.headlineSmall!.copyWith(fontSize: 16), - maxLines: 2, - overflow: TextOverflow.ellipsis, + // title: Text( + // _appBarTitle!, + // style: + // Theme.of(context).textTheme.headlineSmall!.copyWith(fontSize: 16), + // maxLines: 2, + // overflow: TextOverflow.ellipsis, + // ), + title: Expanded( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text( + _appBarTitle!, + style: Theme.of(context) + .textTheme + .headlineSmall! + .copyWith(fontSize: 16), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox( + width: 200, + height: 50, + child: AppliedFilters(), + ), + ], + ), + ), + bottom: const PreferredSize( + preferredSize: Size.fromHeight(0), + child: Flexible(child: RecommendedFilters()), ), actions: _getDefaultActions(context), ); @@ -294,10 +323,22 @@ class _AppBarWidgetState extends State { Navigator.pop(context); if (result != null && result is (PersonEntity, EnteFile)) { // ignore: unawaited_futures - routeToPage(context, PeoplePage(person: result.$1)); + routeToPage( + context, + PeoplePage( + person: result.$1, + searchResult: null, + ), + ); } else if (result != null && result is PersonEntity) { // ignore: unawaited_futures - routeToPage(context, PeoplePage(person: result)); + routeToPage( + context, + PeoplePage( + person: result, + searchResult: null, + ), + ); } } } diff --git a/mobile/lib/ui/viewer/people/people_page.dart b/mobile/lib/ui/viewer/people/people_page.dart index fd2cce4895..c256d6b45f 100644 --- a/mobile/lib/ui/viewer/people/people_page.dart +++ b/mobile/lib/ui/viewer/people/people_page.dart @@ -11,6 +11,7 @@ import 'package:photos/models/file/file.dart'; import 'package:photos/models/file_load_result.dart'; import 'package:photos/models/gallery_type.dart'; import "package:photos/models/ml/face/person.dart"; +import "package:photos/models/search/search_result.dart"; import 'package:photos/models/selected_files.dart'; import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart"; import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart"; @@ -18,6 +19,8 @@ import "package:photos/services/search_service.dart"; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; import "package:photos/ui/viewer/gallery/state/gallery_files_inherited_widget.dart"; +import "package:photos/ui/viewer/gallery/state/inherited_search_filter_data.dart"; +import "package:photos/ui/viewer/gallery/state/search_filter_data_provider.dart"; import "package:photos/ui/viewer/gallery/state/selection_state.dart"; import "package:photos/ui/viewer/people/people_app_bar.dart"; import "package:photos/ui/viewer/people/people_banner.dart"; @@ -26,6 +29,7 @@ import "package:photos/ui/viewer/people/person_cluster_suggestion.dart"; class PeoplePage extends StatefulWidget { final String tagPrefix; final PersonEntity person; + final SearchResult? searchResult; static const GalleryType appBarType = GalleryType.peopleTag; static const GalleryType overlayType = GalleryType.peopleTag; @@ -33,8 +37,9 @@ class PeoplePage extends StatefulWidget { const PeoplePage({ this.tagPrefix = "", required this.person, - Key? key, - }) : super(key: key); + required this.searchResult, + super.key, + }); @override State createState() => _PeoplePageState(); @@ -112,111 +117,119 @@ class _PeoplePageState extends State { Widget build(BuildContext context) { _logger.info("Building for ${widget.person.data.name}"); return GalleryFilesState( - child: Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(50.0), - child: PeopleAppBar( - GalleryType.peopleTag, - widget.person.data.name, - _selectedFiles, - widget.person, + child: InheritedSearchFilterData( + searchFilterDataProvider: widget.searchResult != null + ? SearchFilterDataProvider( + initialGalleryFilter: + widget.searchResult!.getHierarchicalSearchFilter(), + ) + : null, + child: Scaffold( + appBar: PreferredSize( + preferredSize: const Size.fromHeight(50.0), + child: PeopleAppBar( + GalleryType.peopleTag, + widget.person.data.name, + _selectedFiles, + widget.person, + ), ), - ), - body: FutureBuilder>( - future: filesFuture, - builder: (context, snapshot) { - if (snapshot.hasData) { - final personFiles = snapshot.data as List; - return Column( - children: [ - Expanded( - child: SelectionState( - selectedFiles: _selectedFiles, - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Gallery( - asyncLoader: ( - creationStartTime, - creationEndTime, { - limit, - asc, - }) async { - final result = await loadPersonFiles(); - return Future.value( - FileLoadResult( - result, - false, - ), - ); - }, - reloadEvent: - Bus.instance.on(), - forceReloadEvents: [ - Bus.instance.on(), - ], - removalEventTypes: const { - EventType.deletedFromRemote, - EventType.deletedFromEverywhere, - EventType.hide, - }, - tagPrefix: widget.tagPrefix + widget.tagPrefix, - selectedFiles: _selectedFiles, - initialFiles: personFiles.isNotEmpty - ? [personFiles.first] - : [], - ), - FileSelectionOverlayBar( - PeoplePage.overlayType, - _selectedFiles, - person: widget.person, - ), - ], + body: FutureBuilder>( + future: filesFuture, + builder: (context, snapshot) { + if (snapshot.hasData) { + final personFiles = snapshot.data as List; + return Column( + children: [ + Expanded( + child: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Gallery( + asyncLoader: ( + creationStartTime, + creationEndTime, { + limit, + asc, + }) async { + final result = await loadPersonFiles(); + return Future.value( + FileLoadResult( + result, + false, + ), + ); + }, + reloadEvent: + Bus.instance.on(), + forceReloadEvents: [ + Bus.instance.on(), + ], + removalEventTypes: const { + EventType.deletedFromRemote, + EventType.deletedFromEverywhere, + EventType.hide, + }, + tagPrefix: widget.tagPrefix + widget.tagPrefix, + selectedFiles: _selectedFiles, + initialFiles: personFiles.isNotEmpty + ? [personFiles.first] + : [], + ), + FileSelectionOverlayBar( + PeoplePage.overlayType, + _selectedFiles, + person: widget.person, + ), + ], + ), ), ), - ), - showSuggestionBanner - ? Dismissible( - key: const Key("suggestionBanner"), - direction: DismissDirection.horizontal, - onDismissed: (direction) { - setState(() { - userDismissedSuggestionBanner = true; - }); - }, - child: PeopleBanner( - type: PeopleBannerType.suggestion, - startIcon: Icons.face_retouching_natural, - actionIcon: Icons.search_outlined, - text: "Review suggestions", - subText: "Improve the results", - onTap: () async { - unawaited( - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => - PersonReviewClusterSuggestion( - widget.person, + showSuggestionBanner + ? Dismissible( + key: const Key("suggestionBanner"), + direction: DismissDirection.horizontal, + onDismissed: (direction) { + setState(() { + userDismissedSuggestionBanner = true; + }); + }, + child: PeopleBanner( + type: PeopleBannerType.suggestion, + startIcon: Icons.face_retouching_natural, + actionIcon: Icons.search_outlined, + text: "Review suggestions", + subText: "Improve the results", + onTap: () async { + unawaited( + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + PersonReviewClusterSuggestion( + widget.person, + ), ), ), - ), - ); - }, - ), - ) - : const SizedBox.shrink(), - ], - ); - } else if (snapshot.hasError) { - log("Error: ${snapshot.error} ${snapshot.stackTrace}}"); - //Need to show an error on the UI here - return const SizedBox.shrink(); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, + ); + }, + ), + ) + : const SizedBox.shrink(), + ], + ); + } else if (snapshot.hasError) { + log("Error: ${snapshot.error} ${snapshot.stackTrace}}"); + //Need to show an error on the UI here + return const SizedBox.shrink(); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + ), ), ), ); diff --git a/mobile/lib/ui/viewer/search_tab/people_section.dart b/mobile/lib/ui/viewer/search_tab/people_section.dart index 2d03a50f1d..aa03ebe159 100644 --- a/mobile/lib/ui/viewer/search_tab/people_section.dart +++ b/mobile/lib/ui/viewer/search_tab/people_section.dart @@ -265,10 +265,22 @@ class SearchExample extends StatelessWidget { if (result != null && result is (PersonEntity, EnteFile)) { // ignore: unawaited_futures - routeToPage(context, PeoplePage(person: result.$1)); + routeToPage( + context, + PeoplePage( + person: result.$1, + searchResult: null, + ), + ); } else if (result != null && result is PersonEntity) { // ignore: unawaited_futures - routeToPage(context, PeoplePage(person: result)); + routeToPage( + context, + PeoplePage( + person: result, + searchResult: null, + ), + ); } }, child: Padding(