face_widget.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import "dart:developer" show log;
  2. import "dart:typed_data";
  3. import "package:flutter/material.dart";
  4. import "package:photos/face/db.dart";
  5. import "package:photos/face/model/face.dart";
  6. import "package:photos/face/model/person.dart";
  7. import 'package:photos/models/file/file.dart';
  8. import "package:photos/services/search_service.dart";
  9. import "package:photos/ui/viewer/file/no_thumbnail_widget.dart";
  10. import "package:photos/ui/viewer/people/cluster_page.dart";
  11. import "package:photos/ui/viewer/people/people_page.dart";
  12. import "package:photos/utils/face/face_box_crop.dart";
  13. import "package:photos/utils/thumbnail_util.dart";
  14. class FaceWidget extends StatelessWidget {
  15. final EnteFile file;
  16. final Face face;
  17. final Person? person;
  18. final int? clusterID;
  19. const FaceWidget(
  20. this.file,
  21. this.face, {
  22. this.person,
  23. this.clusterID,
  24. Key? key,
  25. }) : super(key: key);
  26. @override
  27. Widget build(BuildContext context) {
  28. return FutureBuilder<Uint8List?>(
  29. future: getFaceCrop(),
  30. builder: (context, snapshot) {
  31. if (snapshot.hasData) {
  32. final ImageProvider imageProvider = MemoryImage(snapshot.data!);
  33. return GestureDetector(
  34. onTap: () async {
  35. log(
  36. "FaceWidget is tapped, with person $person and clusterID $clusterID",
  37. name: "FaceWidget",
  38. );
  39. if (person == null && clusterID == null) {
  40. return;
  41. }
  42. if (person != null) {
  43. await Navigator.of(context).push(
  44. MaterialPageRoute(
  45. builder: (context) => PeoplePage(
  46. person: person!,
  47. ),
  48. ),
  49. );
  50. } else if (clusterID != null) {
  51. final fileIdsToClusterIds =
  52. await FaceMLDataDB.instance.getFileIdToClusterIds();
  53. final files = await SearchService.instance.getAllFiles();
  54. final clusterFiles = files
  55. .where(
  56. (file) =>
  57. fileIdsToClusterIds[file.uploadedFileID]
  58. ?.contains(clusterID) ??
  59. false,
  60. )
  61. .toList();
  62. await Navigator.of(context).push(
  63. MaterialPageRoute(
  64. builder: (context) => ClusterPage(
  65. clusterFiles,
  66. cluserID: clusterID!,
  67. ),
  68. ),
  69. );
  70. }
  71. },
  72. child: Column(
  73. children: [
  74. ClipRRect(
  75. borderRadius:
  76. const BorderRadius.all(Radius.elliptical(16, 12)),
  77. child: SizedBox(
  78. width: 60,
  79. height: 60,
  80. child: Image(
  81. image: imageProvider,
  82. fit: BoxFit.cover,
  83. ),
  84. ),
  85. ),
  86. const SizedBox(height: 8),
  87. Text(
  88. (face.score).toStringAsFixed(2),
  89. style: Theme.of(context).textTheme.bodySmall,
  90. ),
  91. if (person != null)
  92. Text(
  93. person!.attr.name.trim(),
  94. style: Theme.of(context).textTheme.bodySmall,
  95. overflow: TextOverflow.ellipsis,
  96. maxLines: 1,
  97. ),
  98. ],
  99. ),
  100. );
  101. } else {
  102. if (snapshot.connectionState == ConnectionState.waiting) {
  103. return const ClipRRect(
  104. borderRadius: BorderRadius.all(Radius.elliptical(16, 12)),
  105. child: SizedBox(
  106. width: 60, // Ensure consistent sizing
  107. height: 60,
  108. child: CircularProgressIndicator(),
  109. ),
  110. );
  111. }
  112. if (snapshot.hasError) {
  113. log('Error getting face: ${snapshot.error}');
  114. }
  115. return const ClipRRect(
  116. borderRadius: BorderRadius.all(Radius.elliptical(16, 12)),
  117. child: SizedBox(
  118. width: 60, // Ensure consistent sizing
  119. height: 60,
  120. child: NoThumbnailWidget(),
  121. ),
  122. );
  123. }
  124. },
  125. );
  126. }
  127. Future<Uint8List?> getFaceCrop() async {
  128. try {
  129. final Uint8List? cachedFace = faceCropCache.get(face.faceID);
  130. if (cachedFace != null) {
  131. return cachedFace;
  132. }
  133. final faceCropCacheFile = cachedFaceCropPath(face.faceID);
  134. if ((await faceCropCacheFile.exists())) {
  135. final data = await faceCropCacheFile.readAsBytes();
  136. faceCropCache.put(face.faceID, data);
  137. return data;
  138. }
  139. final result = await pool.withResource(
  140. () async => await getFaceCrops(
  141. file,
  142. {
  143. face.faceID: face.detection.box,
  144. },
  145. ),
  146. );
  147. final Uint8List? computedCrop = result?[face.faceID];
  148. if (computedCrop != null) {
  149. faceCropCache.put(face.faceID, computedCrop);
  150. faceCropCacheFile.writeAsBytes(computedCrop).ignore();
  151. }
  152. return computedCrop;
  153. } catch (e, s) {
  154. log(
  155. "Error getting face for faceID: ${face.faceID}",
  156. error: e,
  157. stackTrace: s,
  158. );
  159. return null;
  160. }
  161. }
  162. }