diff --git a/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart b/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart index 36203c314..39c4a1840 100644 --- a/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart +++ b/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart @@ -1,5 +1,6 @@ import "dart:async" show StreamSubscription, unawaited; import "dart:math"; +import "dart:typed_data"; import "package:flutter/foundation.dart" show kDebugMode; import "package:flutter/material.dart"; @@ -93,7 +94,7 @@ class _PersonClustersState extends State { final bool usingMean = currentSuggestion.usedOnlyMeanForSuggestion; final List files = currentSuggestion.filesInCluster; - final Future generateFaceThumbnails = _generateFaceThumbnails( + final Future> generateFacedThumbnails = _generateFaceThumbnails( files.sublist(0, min(files.length, 8)), clusterID, ); @@ -139,7 +140,7 @@ class _PersonClustersState extends State { files, numberOfDifferentSuggestions, allSuggestions, - generateFaceThumbnails, + generateFacedThumbnails, ), ), ); @@ -214,7 +215,7 @@ class _PersonClustersState extends State { List files, int numberOfSuggestions, List allSuggestions, - Future generateFaceThumbnails, + Future> generateFaceThumbnails, ) { final widgetToReturn = Column( key: ValueKey("cluster_id-$clusterID-files-${files.length}"), @@ -315,15 +316,16 @@ class _PersonClustersState extends State { Widget _buildThumbnailWidget( List files, int clusterID, - Future generateFaceThumbnails, + Future> generateFaceThumbnails, ) { return SizedBox( height: MediaQuery.of(context).size.height * 0.4, - child: FutureBuilder( + child: FutureBuilder>( key: futureBuilderKeyFaceThumbnails, future: generateFaceThumbnails, builder: (context, snapshot) { if (snapshot.hasData) { + final faceThumbnails = snapshot.data!; return Column( children: [ Row( @@ -331,6 +333,7 @@ class _PersonClustersState extends State { children: _buildThumbnailWidgetsRow( files, clusterID, + faceThumbnails, ), ), if (files.length > 4) const SizedBox(height: 24), @@ -340,6 +343,7 @@ class _PersonClustersState extends State { children: _buildThumbnailWidgetsRow( files, clusterID, + faceThumbnails, start: 4, ), ), @@ -363,7 +367,8 @@ class _PersonClustersState extends State { List _buildThumbnailWidgetsRow( List files, - int cluserId, { + int cluserId, + Map faceThumbnails, { int start = 0, }) { return List.generate( @@ -379,6 +384,7 @@ class _PersonClustersState extends State { clusterID: cluserId, useFullFile: false, thumbnailFallback: false, + faceCrop: faceThumbnails[files[start + index].uploadedFileID!], ), ), ), @@ -386,11 +392,11 @@ class _PersonClustersState extends State { ); } - Future _generateFaceThumbnails( + Future> _generateFaceThumbnails( List files, int clusterID, ) async { - final futures = >[]; + final futures = >[]; for (final file in files) { futures.add( PersonFaceWidget.precomputeNextFaceCrops( @@ -400,7 +406,11 @@ class _PersonClustersState extends State { ), ); } - await Future.wait(futures); - return true; + final faceCropsList = await Future.wait(futures); + final faceCrops = {}; + for (var i = 0; i < faceCropsList.length; i++) { + faceCrops[files[i].uploadedFileID!] = faceCropsList[i]; + } + return faceCrops; } } diff --git a/mobile/lib/ui/viewer/search/result/person_face_widget.dart b/mobile/lib/ui/viewer/search/result/person_face_widget.dart index b2e000508..6b4c922ce 100644 --- a/mobile/lib/ui/viewer/search/result/person_face_widget.dart +++ b/mobile/lib/ui/viewer/search/result/person_face_widget.dart @@ -22,6 +22,7 @@ class PersonFaceWidget extends StatelessWidget { final int? clusterID; final bool useFullFile; final bool thumbnailFallback; + final Uint8List? faceCrop; // PersonFaceWidget constructor checks that both personId and clusterID are not null // and that the file is not null @@ -31,6 +32,7 @@ class PersonFaceWidget extends StatelessWidget { this.clusterID, this.useFullFile = true, this.thumbnailFallback = true, + this.faceCrop, Key? key, }) : assert( personId != null || clusterID != null, @@ -40,6 +42,17 @@ class PersonFaceWidget extends StatelessWidget { @override Widget build(BuildContext context) { + if (faceCrop != null) { + return Stack( + fit: StackFit.expand, + children: [ + Image( + image: MemoryImage(faceCrop!), + fit: BoxFit.cover, + ), + ], + ); + } if (useGeneratedFaceCrops) { return FutureBuilder( future: getFaceCrop(), @@ -171,7 +184,7 @@ class PersonFaceWidget extends StatelessWidget { } } - static Future precomputeNextFaceCrops( + static Future precomputeNextFaceCrops( file, clusterID, { required bool useFullFile, @@ -185,23 +198,23 @@ class PersonFaceWidget extends StatelessWidget { debugPrint( "No cover face for cluster $clusterID and recentFile ${file.uploadedFileID}", ); - return; + return null; } final Uint8List? cachedFace = faceCropCache.get(face.faceID); if (cachedFace != null) { - return; + return cachedFace; } final faceCropCacheFile = cachedFaceCropPath(face.faceID); if ((await faceCropCacheFile.exists())) { final data = await faceCropCacheFile.readAsBytes(); faceCropCache.put(face.faceID, data); - return; + return data; } if (!useFullFile) { final Uint8List? cachedFaceThumbnail = faceCropThumbnailCache.get(face.faceID); if (cachedFaceThumbnail != null) { - return; + return cachedFaceThumbnail; } } EnteFile? fileForFaceCrop = file; @@ -210,7 +223,7 @@ class PersonFaceWidget extends StatelessWidget { await FilesDB.instance.getAnyUploadedFile(face.fileID); } if (fileForFaceCrop == null) { - return; + return null; } final result = await pool.withResource( @@ -231,14 +244,14 @@ class PersonFaceWidget extends StatelessWidget { faceCropThumbnailCache.put(face.faceID, computedCrop); } } - return; + return computedCrop; } catch (e, s) { log( "Error getting cover face for cluster $clusterID", error: e, stackTrace: s, ); - return; + return null; } } }