|
@@ -54,233 +54,24 @@ class _FaceWidgetState extends State<FaceWidget> {
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
final bool givenFaces = widget.faceCrops != null;
|
|
final bool givenFaces = widget.faceCrops != null;
|
|
if (useGeneratedFaceCrops) {
|
|
if (useGeneratedFaceCrops) {
|
|
- return FutureBuilder<Map<String, Uint8List>?>(
|
|
|
|
- future: givenFaces ? widget.faceCrops : getFaceCrop(),
|
|
|
|
- builder: (context, snapshot) {
|
|
|
|
- if (snapshot.hasData) {
|
|
|
|
- final ImageProvider imageProvider =
|
|
|
|
- MemoryImage(snapshot.data![widget.face.faceID]!);
|
|
|
|
-
|
|
|
|
- return GestureDetector(
|
|
|
|
- onTap: () async {
|
|
|
|
- if (widget.editMode) return;
|
|
|
|
-
|
|
|
|
- log(
|
|
|
|
- "FaceWidget is tapped, with person ${widget.person} and clusterID ${widget.clusterID}",
|
|
|
|
- name: "FaceWidget",
|
|
|
|
- );
|
|
|
|
- if (widget.person == null && widget.clusterID == null) {
|
|
|
|
- // Get faceID and double check that it doesn't belong to an existing clusterID. If it does, push that cluster page
|
|
|
|
- final w = (kDebugMode ? EnteWatch('FaceWidget') : null)
|
|
|
|
- ?..start();
|
|
|
|
- final existingClusterID = await FaceMLDataDB.instance
|
|
|
|
- .getClusterIDForFaceID(widget.face.faceID);
|
|
|
|
- w?.log('getting existing clusterID for faceID');
|
|
|
|
- if (existingClusterID != null) {
|
|
|
|
- final fileIdsToClusterIds =
|
|
|
|
- await FaceMLDataDB.instance.getFileIdToClusterIds();
|
|
|
|
- final files = await SearchService.instance.getAllFiles();
|
|
|
|
- final clusterFiles = files
|
|
|
|
- .where(
|
|
|
|
- (file) =>
|
|
|
|
- fileIdsToClusterIds[file.uploadedFileID]
|
|
|
|
- ?.contains(existingClusterID) ??
|
|
|
|
- false,
|
|
|
|
- )
|
|
|
|
- .toList();
|
|
|
|
- await Navigator.of(context).push(
|
|
|
|
- MaterialPageRoute(
|
|
|
|
- builder: (context) => ClusterPage(
|
|
|
|
- clusterFiles,
|
|
|
|
- clusterID: existingClusterID,
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
|
|
+ return _buildFaceImageGenerated(givenFaces);
|
|
|
|
+ } else {
|
|
|
|
+ return _buildFaceImageFlutterZoom();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- // Create new clusterID for the faceID and update DB to assign the faceID to the new clusterID
|
|
|
|
- final int newClusterID =
|
|
|
|
- DateTime.now().microsecondsSinceEpoch;
|
|
|
|
- await FaceMLDataDB.instance.updateFaceIdToClusterId(
|
|
|
|
- {widget.face.faceID: newClusterID},
|
|
|
|
- );
|
|
|
|
|
|
+ Widget _buildFaceImageGenerated(bool givenFaces) {
|
|
|
|
+ return FutureBuilder<Map<String, Uint8List>?>(
|
|
|
|
+ future: givenFaces ? widget.faceCrops : getFaceCrop(),
|
|
|
|
+ builder: (context, snapshot) {
|
|
|
|
+ if (snapshot.hasData) {
|
|
|
|
+ final ImageProvider imageProvider =
|
|
|
|
+ MemoryImage(snapshot.data![widget.face.faceID]!);
|
|
|
|
|
|
- // Push page for the new cluster
|
|
|
|
- await Navigator.of(context).push(
|
|
|
|
- MaterialPageRoute(
|
|
|
|
- builder: (context) => ClusterPage(
|
|
|
|
- [widget.file],
|
|
|
|
- clusterID: newClusterID,
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- if (widget.person != null) {
|
|
|
|
- await Navigator.of(context).push(
|
|
|
|
- MaterialPageRoute(
|
|
|
|
- builder: (context) => PeoplePage(
|
|
|
|
- person: widget.person!,
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- } else if (widget.clusterID != null) {
|
|
|
|
- final fileIdsToClusterIds =
|
|
|
|
- await FaceMLDataDB.instance.getFileIdToClusterIds();
|
|
|
|
- final files = await SearchService.instance.getAllFiles();
|
|
|
|
- final clusterFiles = files
|
|
|
|
- .where(
|
|
|
|
- (file) =>
|
|
|
|
- fileIdsToClusterIds[file.uploadedFileID]
|
|
|
|
- ?.contains(widget.clusterID) ??
|
|
|
|
- false,
|
|
|
|
- )
|
|
|
|
- .toList();
|
|
|
|
- await Navigator.of(context).push(
|
|
|
|
- MaterialPageRoute(
|
|
|
|
- builder: (context) => ClusterPage(
|
|
|
|
- clusterFiles,
|
|
|
|
- clusterID: widget.clusterID!,
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- child: Column(
|
|
|
|
- children: [
|
|
|
|
- Stack(
|
|
|
|
- children: [
|
|
|
|
- Container(
|
|
|
|
- height: 60,
|
|
|
|
- width: 60,
|
|
|
|
- decoration: ShapeDecoration(
|
|
|
|
- shape: RoundedRectangleBorder(
|
|
|
|
- borderRadius: const BorderRadius.all(
|
|
|
|
- Radius.elliptical(16, 12),
|
|
|
|
- ),
|
|
|
|
- side: widget.highlight
|
|
|
|
- ? BorderSide(
|
|
|
|
- color:
|
|
|
|
- getEnteColorScheme(context).primary700,
|
|
|
|
- width: 1.0,
|
|
|
|
- )
|
|
|
|
- : BorderSide.none,
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- child: ClipRRect(
|
|
|
|
- borderRadius:
|
|
|
|
- const BorderRadius.all(Radius.elliptical(16, 12)),
|
|
|
|
- child: SizedBox(
|
|
|
|
- width: 60,
|
|
|
|
- height: 60,
|
|
|
|
- child: Image(
|
|
|
|
- image: imageProvider,
|
|
|
|
- fit: BoxFit.cover,
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- // TODO: the edges of the green line are still not properly rounded around ClipRRect
|
|
|
|
- if (widget.editMode)
|
|
|
|
- Positioned(
|
|
|
|
- right: 0,
|
|
|
|
- top: 0,
|
|
|
|
- child: GestureDetector(
|
|
|
|
- onTap: _cornerIconPressed,
|
|
|
|
- child: isJustRemoved
|
|
|
|
- ? const Icon(
|
|
|
|
- CupertinoIcons.add_circled_solid,
|
|
|
|
- color: Colors.green,
|
|
|
|
- )
|
|
|
|
- : const Icon(
|
|
|
|
- Icons.cancel,
|
|
|
|
- color: Colors.red,
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- ),
|
|
|
|
- ],
|
|
|
|
- ),
|
|
|
|
- const SizedBox(height: 8),
|
|
|
|
- if (widget.person != null)
|
|
|
|
- Text(
|
|
|
|
- widget.person!.data.isIgnored
|
|
|
|
- ? '(ignored)'
|
|
|
|
- : widget.person!.data.name.trim(),
|
|
|
|
- style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
- overflow: TextOverflow.ellipsis,
|
|
|
|
- maxLines: 1,
|
|
|
|
- ),
|
|
|
|
- if (kDebugMode)
|
|
|
|
- Text(
|
|
|
|
- 'S: ${widget.face.score.toStringAsFixed(3)}',
|
|
|
|
- style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
- maxLines: 1,
|
|
|
|
- ),
|
|
|
|
- if (kDebugMode)
|
|
|
|
- Text(
|
|
|
|
- 'B: ${widget.face.blur.toStringAsFixed(0)}',
|
|
|
|
- style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
- maxLines: 1,
|
|
|
|
- ),
|
|
|
|
- if (kDebugMode)
|
|
|
|
- Text(
|
|
|
|
- 'D: ${widget.face.detection.getFaceDirection().toDirectionString()}',
|
|
|
|
- style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
- maxLines: 1,
|
|
|
|
- ),
|
|
|
|
- if (kDebugMode)
|
|
|
|
- Text(
|
|
|
|
- 'Sideways: ${widget.face.detection.faceIsSideways().toString()}',
|
|
|
|
- style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
- maxLines: 1,
|
|
|
|
- ),
|
|
|
|
- if (kDebugMode && widget.face.score < 0.75)
|
|
|
|
- Text(
|
|
|
|
- '[Debug only]',
|
|
|
|
- style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
- maxLines: 1,
|
|
|
|
- ),
|
|
|
|
- // if (kDebugMode)
|
|
|
|
- // if (highlight)
|
|
|
|
- // const Text(
|
|
|
|
- // "Highlighted",
|
|
|
|
- // style: TextStyle(
|
|
|
|
- // color: Colors.red,
|
|
|
|
- // fontSize: 12,
|
|
|
|
- // ),
|
|
|
|
- // ),
|
|
|
|
- ],
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- } 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(),
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- );
|
|
|
|
- } else {
|
|
|
|
- return Builder(
|
|
|
|
- builder: (context) {
|
|
|
|
return GestureDetector(
|
|
return GestureDetector(
|
|
onTap: () async {
|
|
onTap: () async {
|
|
|
|
+ if (widget.editMode) return;
|
|
|
|
+
|
|
log(
|
|
log(
|
|
"FaceWidget is tapped, with person ${widget.person} and clusterID ${widget.clusterID}",
|
|
"FaceWidget is tapped, with person ${widget.person} and clusterID ${widget.clusterID}",
|
|
name: "FaceWidget",
|
|
name: "FaceWidget",
|
|
@@ -386,9 +177,9 @@ class _FaceWidgetState extends State<FaceWidget> {
|
|
child: SizedBox(
|
|
child: SizedBox(
|
|
width: 60,
|
|
width: 60,
|
|
height: 60,
|
|
height: 60,
|
|
- child: CroppedFaceImageView(
|
|
|
|
- enteFile: widget.file,
|
|
|
|
- face: widget.face,
|
|
|
|
|
|
+ child: Image(
|
|
|
|
+ image: imageProvider,
|
|
|
|
+ fit: BoxFit.cover,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
@@ -416,7 +207,9 @@ class _FaceWidgetState extends State<FaceWidget> {
|
|
const SizedBox(height: 8),
|
|
const SizedBox(height: 8),
|
|
if (widget.person != null)
|
|
if (widget.person != null)
|
|
Text(
|
|
Text(
|
|
- widget.person!.data.name.trim(),
|
|
|
|
|
|
+ widget.person!.data.isIgnored
|
|
|
|
+ ? '(ignored)'
|
|
|
|
+ : widget.person!.data.name.trim(),
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
overflow: TextOverflow.ellipsis,
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
maxLines: 1,
|
|
@@ -445,12 +238,40 @@ class _FaceWidgetState extends State<FaceWidget> {
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
maxLines: 1,
|
|
maxLines: 1,
|
|
),
|
|
),
|
|
|
|
+ if (kDebugMode && widget.face.score < 0.75)
|
|
|
|
+ Text(
|
|
|
|
+ '[Debug only]',
|
|
|
|
+ style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
+ maxLines: 1,
|
|
|
|
+ ),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
);
|
|
- },
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
|
|
+ } else {
|
|
|
|
+ if (snapshot.connectionState == ConnectionState.waiting) {
|
|
|
|
+ return const ClipRRect(
|
|
|
|
+ borderRadius: BorderRadius.all(Radius.elliptical(16, 12)),
|
|
|
|
+ child: SizedBox(
|
|
|
|
+ width: 60,
|
|
|
|
+ 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,
|
|
|
|
+ height: 60,
|
|
|
|
+ child: NoThumbnailWidget(),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
void _cornerIconPressed() async {
|
|
void _cornerIconPressed() async {
|
|
@@ -508,4 +329,178 @@ class _FaceWidgetState extends State<FaceWidget> {
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ Widget _buildFaceImageFlutterZoom() {
|
|
|
|
+ return Builder(
|
|
|
|
+ builder: (context) {
|
|
|
|
+ return GestureDetector(
|
|
|
|
+ onTap: () async {
|
|
|
|
+ log(
|
|
|
|
+ "FaceWidget is tapped, with person ${widget.person} and clusterID ${widget.clusterID}",
|
|
|
|
+ name: "FaceWidget",
|
|
|
|
+ );
|
|
|
|
+ if (widget.person == null && widget.clusterID == null) {
|
|
|
|
+ // Get faceID and double check that it doesn't belong to an existing clusterID. If it does, push that cluster page
|
|
|
|
+ final w = (kDebugMode ? EnteWatch('FaceWidget') : null)?..start();
|
|
|
|
+ final existingClusterID = await FaceMLDataDB.instance
|
|
|
|
+ .getClusterIDForFaceID(widget.face.faceID);
|
|
|
|
+ w?.log('getting existing clusterID for faceID');
|
|
|
|
+ if (existingClusterID != null) {
|
|
|
|
+ final fileIdsToClusterIds =
|
|
|
|
+ await FaceMLDataDB.instance.getFileIdToClusterIds();
|
|
|
|
+ final files = await SearchService.instance.getAllFiles();
|
|
|
|
+ final clusterFiles = files
|
|
|
|
+ .where(
|
|
|
|
+ (file) =>
|
|
|
|
+ fileIdsToClusterIds[file.uploadedFileID]
|
|
|
|
+ ?.contains(existingClusterID) ??
|
|
|
|
+ false,
|
|
|
|
+ )
|
|
|
|
+ .toList();
|
|
|
|
+ await Navigator.of(context).push(
|
|
|
|
+ MaterialPageRoute(
|
|
|
|
+ builder: (context) => ClusterPage(
|
|
|
|
+ clusterFiles,
|
|
|
|
+ clusterID: existingClusterID,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Create new clusterID for the faceID and update DB to assign the faceID to the new clusterID
|
|
|
|
+ final int newClusterID = DateTime.now().microsecondsSinceEpoch;
|
|
|
|
+ await FaceMLDataDB.instance.updateFaceIdToClusterId(
|
|
|
|
+ {widget.face.faceID: newClusterID},
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // Push page for the new cluster
|
|
|
|
+ await Navigator.of(context).push(
|
|
|
|
+ MaterialPageRoute(
|
|
|
|
+ builder: (context) => ClusterPage(
|
|
|
|
+ [widget.file],
|
|
|
|
+ clusterID: newClusterID,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ if (widget.person != null) {
|
|
|
|
+ await Navigator.of(context).push(
|
|
|
|
+ MaterialPageRoute(
|
|
|
|
+ builder: (context) => PeoplePage(
|
|
|
|
+ person: widget.person!,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ } else if (widget.clusterID != null) {
|
|
|
|
+ final fileIdsToClusterIds =
|
|
|
|
+ await FaceMLDataDB.instance.getFileIdToClusterIds();
|
|
|
|
+ final files = await SearchService.instance.getAllFiles();
|
|
|
|
+ final clusterFiles = files
|
|
|
|
+ .where(
|
|
|
|
+ (file) =>
|
|
|
|
+ fileIdsToClusterIds[file.uploadedFileID]
|
|
|
|
+ ?.contains(widget.clusterID) ??
|
|
|
|
+ false,
|
|
|
|
+ )
|
|
|
|
+ .toList();
|
|
|
|
+ await Navigator.of(context).push(
|
|
|
|
+ MaterialPageRoute(
|
|
|
|
+ builder: (context) => ClusterPage(
|
|
|
|
+ clusterFiles,
|
|
|
|
+ clusterID: widget.clusterID!,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ child: Column(
|
|
|
|
+ children: [
|
|
|
|
+ Stack(
|
|
|
|
+ children: [
|
|
|
|
+ Container(
|
|
|
|
+ height: 60,
|
|
|
|
+ width: 60,
|
|
|
|
+ decoration: ShapeDecoration(
|
|
|
|
+ shape: RoundedRectangleBorder(
|
|
|
|
+ borderRadius: const BorderRadius.all(
|
|
|
|
+ Radius.elliptical(16, 12),
|
|
|
|
+ ),
|
|
|
|
+ side: widget.highlight
|
|
|
|
+ ? BorderSide(
|
|
|
|
+ color: getEnteColorScheme(context).primary700,
|
|
|
|
+ width: 1.0,
|
|
|
|
+ )
|
|
|
|
+ : BorderSide.none,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ child: ClipRRect(
|
|
|
|
+ borderRadius:
|
|
|
|
+ const BorderRadius.all(Radius.elliptical(16, 12)),
|
|
|
|
+ child: SizedBox(
|
|
|
|
+ width: 60,
|
|
|
|
+ height: 60,
|
|
|
|
+ child: CroppedFaceImageView(
|
|
|
|
+ enteFile: widget.file,
|
|
|
|
+ face: widget.face,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ if (widget.editMode)
|
|
|
|
+ Positioned(
|
|
|
|
+ right: 0,
|
|
|
|
+ top: 0,
|
|
|
|
+ child: GestureDetector(
|
|
|
|
+ onTap: _cornerIconPressed,
|
|
|
|
+ child: isJustRemoved
|
|
|
|
+ ? const Icon(
|
|
|
|
+ CupertinoIcons.add_circled_solid,
|
|
|
|
+ color: Colors.green,
|
|
|
|
+ )
|
|
|
|
+ : const Icon(
|
|
|
|
+ Icons.cancel,
|
|
|
|
+ color: Colors.red,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ const SizedBox(height: 8),
|
|
|
|
+ if (widget.person != null)
|
|
|
|
+ Text(
|
|
|
|
+ widget.person!.data.name.trim(),
|
|
|
|
+ style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
|
+ maxLines: 1,
|
|
|
|
+ ),
|
|
|
|
+ if (kDebugMode)
|
|
|
|
+ Text(
|
|
|
|
+ 'S: ${widget.face.score.toStringAsFixed(3)}',
|
|
|
|
+ style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
+ maxLines: 1,
|
|
|
|
+ ),
|
|
|
|
+ if (kDebugMode)
|
|
|
|
+ Text(
|
|
|
|
+ 'B: ${widget.face.blur.toStringAsFixed(0)}',
|
|
|
|
+ style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
+ maxLines: 1,
|
|
|
|
+ ),
|
|
|
|
+ if (kDebugMode)
|
|
|
|
+ Text(
|
|
|
|
+ 'D: ${widget.face.detection.getFaceDirection().toDirectionString()}',
|
|
|
|
+ style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
+ maxLines: 1,
|
|
|
|
+ ),
|
|
|
|
+ if (kDebugMode)
|
|
|
|
+ Text(
|
|
|
|
+ 'Sideways: ${widget.face.detection.faceIsSideways().toString()}',
|
|
|
|
+ style: Theme.of(context).textTheme.bodySmall,
|
|
|
|
+ maxLines: 1,
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
+ );
|
|
|
|
+ }
|
|
}
|
|
}
|