ente/mobile/lib/ui/viewer/file_details/face_widget.dart
2024-03-20 14:34:12 +05:30

169 lines
5.3 KiB
Dart

import "dart:developer" show log;
import "dart:typed_data";
import "package:flutter/material.dart";
import "package:photos/face/db.dart";
import "package:photos/face/model/face.dart";
import "package:photos/face/model/person.dart";
import 'package:photos/models/file/file.dart';
import "package:photos/services/search_service.dart";
import "package:photos/ui/viewer/file/no_thumbnail_widget.dart";
import "package:photos/ui/viewer/people/cluster_page.dart";
import "package:photos/ui/viewer/people/people_page.dart";
import "package:photos/utils/face/face_box_crop.dart";
import "package:photos/utils/thumbnail_util.dart";
class FaceWidget extends StatelessWidget {
final EnteFile file;
final Face face;
final Person? person;
final int? clusterID;
const FaceWidget(
this.file,
this.face, {
this.person,
this.clusterID,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FutureBuilder<Uint8List?>(
future: getFaceCrop(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final ImageProvider imageProvider = MemoryImage(snapshot.data!);
return GestureDetector(
onTap: () async {
log(
"FaceWidget is tapped, with person $person and clusterID $clusterID",
name: "FaceWidget",
);
if (person == null && clusterID == null) {
return;
}
if (person != null) {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PeoplePage(
person: person!,
),
),
);
} else if (clusterID != null) {
final fileIdsToClusterIds =
await FaceMLDataDB.instance.getFileIdToClusterIds();
final files = await SearchService.instance.getAllFiles();
final clusterFiles = files
.where(
(file) =>
fileIdsToClusterIds[file.uploadedFileID]
?.contains(clusterID) ??
false,
)
.toList();
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ClusterPage(
clusterFiles,
cluserID: clusterID!,
),
),
);
}
},
child: Column(
children: [
ClipRRect(
borderRadius:
const BorderRadius.all(Radius.elliptical(16, 12)),
child: SizedBox(
width: 60,
height: 60,
child: Image(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
const SizedBox(height: 8),
Text(
(face.score).toStringAsFixed(2),
style: Theme.of(context).textTheme.bodySmall,
),
if (person != null)
Text(
person!.attr.name.trim(),
style: Theme.of(context).textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
);
} else {
if (snapshot.connectionState == ConnectionState.waiting) {
return const ClipRRect(
borderRadius: BorderRadius.all(Radius.elliptical(16, 12)),
child: SizedBox(
width: 60, // Ensure consistent sizing
height: 60,
child: CircularProgressIndicator(),
),
);
}
if (snapshot.hasError) {
log('Error getting face: ${snapshot.error}');
}
return const ClipRRect(
borderRadius: BorderRadius.all(Radius.elliptical(16, 12)),
child: SizedBox(
width: 60, // Ensure consistent sizing
height: 60,
child: NoThumbnailWidget(),
),
);
}
},
);
}
Future<Uint8List?> getFaceCrop() async {
try {
final Uint8List? cachedFace = faceCropCache.get(face.faceID);
if (cachedFace != null) {
return cachedFace;
}
final faceCropCacheFile = cachedFaceCropPath(face.faceID);
if ((await faceCropCacheFile.exists())) {
final data = await faceCropCacheFile.readAsBytes();
faceCropCache.put(face.faceID, data);
return data;
}
final result = await pool.withResource(
() async => await getFaceCrops(
file,
{
face.faceID: face.detection.box,
},
),
);
final Uint8List? computedCrop = result?[face.faceID];
if (computedCrop != null) {
faceCropCache.put(face.faceID, computedCrop);
faceCropCacheFile.writeAsBytes(computedCrop).ignore();
}
return computedCrop;
} catch (e, s) {
log(
"Error getting face for faceID: ${face.faceID}",
error: e,
stackTrace: s,
);
return null;
}
}
}