face_widget.dart 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. ClipOval(
  75. child: SizedBox(
  76. width: 60,
  77. height: 60,
  78. child: Image(
  79. image: imageProvider,
  80. fit: BoxFit.cover,
  81. ),
  82. ),
  83. ),
  84. const SizedBox(height: 8),
  85. if (person != null)
  86. Text(
  87. person!.attr.name.trim(),
  88. style: Theme.of(context).textTheme.bodySmall,
  89. overflow: TextOverflow.ellipsis,
  90. maxLines: 1,
  91. ),
  92. ],
  93. ),
  94. );
  95. } else {
  96. if (snapshot.connectionState == ConnectionState.waiting) {
  97. return const ClipOval(
  98. child: SizedBox(
  99. width: 60, // Ensure consistent sizing
  100. height: 60,
  101. child: CircularProgressIndicator(),
  102. ),
  103. );
  104. }
  105. if (snapshot.hasError) {
  106. log('Error getting face: ${snapshot.error}');
  107. }
  108. return const ClipOval(
  109. child: SizedBox(
  110. width: 60, // Ensure consistent sizing
  111. height: 60,
  112. child: NoThumbnailWidget(),
  113. ),
  114. );
  115. }
  116. },
  117. );
  118. }
  119. Future<Uint8List?> getFaceCrop() async {
  120. try {
  121. final Uint8List? cachedFace = faceCropCache.get(face.faceID);
  122. if (cachedFace != null) {
  123. return cachedFace;
  124. }
  125. final faceCropCacheFile = cachedFaceCropPath(face.faceID);
  126. if ((await faceCropCacheFile.exists())) {
  127. final data = await faceCropCacheFile.readAsBytes();
  128. faceCropCache.put(face.faceID, data);
  129. return data;
  130. }
  131. final result = await pool.withResource(
  132. () async => await getFaceCrops(
  133. file,
  134. {
  135. face.faceID: face.detection.box,
  136. },
  137. ),
  138. );
  139. final Uint8List? computedCrop = result?[face.faceID];
  140. if (computedCrop != null) {
  141. faceCropCache.put(face.faceID, computedCrop);
  142. faceCropCacheFile.writeAsBytes(computedCrop).ignore();
  143. }
  144. return computedCrop;
  145. } catch (e, s) {
  146. log(
  147. "Error getting face for faceID: ${face.faceID}",
  148. error: e,
  149. stackTrace: s,
  150. );
  151. return null;
  152. }
  153. }
  154. }