Refactor to simplify

This commit is contained in:
laurenspriem
2025-05-28 11:22:21 +05:30
parent 015321fa0d
commit 903366a42c
2 changed files with 118 additions and 185 deletions

View File

@@ -1,8 +1,6 @@
import "dart:async";
import "dart:developer" show log;
import "dart:typed_data";
import "package:flutter/cupertino.dart";
import "package:flutter/foundation.dart" show kDebugMode;
import "package:flutter/material.dart";
import "package:logging/logging.dart";
@@ -14,7 +12,6 @@ import "package:photos/models/ml/face/face.dart";
import "package:photos/models/ml/face/person.dart";
import "package:photos/services/machine_learning/face_ml/face_detection/detection.dart";
import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart";
import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart";
import "package:photos/services/machine_learning/ml_service.dart";
import "package:photos/services/search_service.dart";
import "package:photos/theme/ente_theme.dart";
@@ -31,7 +28,6 @@ class FileInfoFaceWidget extends StatefulWidget {
final PersonEntity? person;
final String? clusterID;
final bool highlight;
final bool editMode;
const FileInfoFaceWidget(
this.file,
@@ -40,7 +36,6 @@ class FileInfoFaceWidget extends StatefulWidget {
this.person,
this.clusterID,
this.highlight = false,
this.editMode = false,
super.key,
});
@@ -56,11 +51,6 @@ class _FileInfoFaceWidgetState extends State<FileInfoFaceWidget> {
@override
Widget build(BuildContext context) {
final bool givenFaces = widget.faceCrops != null;
return _buildFaceImageGenerated(givenFaces);
}
Widget _buildFaceImageGenerated(bool givenFaces) {
late final mlDataDB = MLDataDB.instance;
return FutureBuilder<Map<String, Uint8List>?>(
future: givenFaces
? widget.faceCrops
@@ -71,97 +61,7 @@ class _FileInfoFaceWidgetState extends State<FileInfoFaceWidget> {
MemoryImage(snapshot.data![widget.face.faceID]!);
return GestureDetector(
onTap: () async {
if (widget.editMode) return;
log(
"FaceWidget is tapped, with person ${widget.person?.data.name} and clusterID ${widget.clusterID}",
name: "FaceWidget",
);
if (widget.person == null && widget.clusterID == null) {
// Double check that it doesn't belong to an existing clusterID.
final existingClusterID =
await mlDataDB.getClusterIDForFaceID(widget.face.faceID);
if (existingClusterID != null) {
final fileIdsToClusterIds =
await mlDataDB.getFileIdToClusterIds();
final files =
await SearchService.instance.getAllFilesForSearch();
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;
}
if (widget.face.score <= kMinimumQualityFaceScore) {
// The face score is too low for automatic clustering,
// assigning a manual new clusterID so that the user can cluster it manually
final String clusterID = newClusterID();
await mlDataDB.updateFaceIdToClusterId(
{widget.face.faceID: clusterID},
);
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ClusterPage(
[widget.file],
clusterID: clusterID,
),
),
);
return;
}
showShortToast(
context,
S.of(context).faceNotClusteredYet,
);
unawaited(MLService.instance.clusterAllImages(force: true));
return;
}
if (widget.person != null) {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PeoplePage(
person: widget.person!,
searchResult: null,
),
),
);
} else if (widget.clusterID != null) {
final fileIdsToClusterIds =
await mlDataDB.getFileIdToClusterIds();
final files =
await SearchService.instance.getAllFilesForSearch();
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!,
),
),
);
}
},
onTap: _onTap,
child: Column(
children: [
Stack(
@@ -195,66 +95,10 @@ class _FileInfoFaceWidgetState extends State<FileInfoFaceWidget> {
),
),
),
// 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
? '(' + S.of(context).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)} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
if (kDebugMode)
Text(
'B: ${widget.face.blur.toStringAsFixed(0)} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
if (kDebugMode)
Text(
'D: ${widget.face.detection.getFaceDirection().toDirectionString()} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
if (kDebugMode)
Text(
'Sideways: ${widget.face.detection.faceIsSideways().toString()} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
if (kDebugMode && widget.face.score < kMinimumFaceShowScore)
Text(
'Not visible to user (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
..._buildFaceInfo(),
],
),
);
@@ -287,24 +131,120 @@ class _FileInfoFaceWidgetState extends State<FileInfoFaceWidget> {
);
}
void _cornerIconPressed() async {
log('face widget (file info) corner icon is pressed');
try {
if (isJustRemoved) {
await ClusterFeedbackService.instance
.addFacesToCluster([widget.face.faceID], widget.clusterID!);
} else {
await ClusterFeedbackService.instance
.removeFilesFromCluster([widget.file], widget.clusterID!);
}
setState(() {
isJustRemoved = !isJustRemoved;
});
} catch (e, s) {
_logger.severe(
"removing face/file from cluster from file info widget failed: $e, \n $s",
List<Widget> _buildFaceInfo() {
final List<Widget> faceInfo = [];
if (widget.person != null) {
faceInfo.add(
Text(
widget.person!.data.isIgnored
? '(' + S.of(context).ignored + ')'
: widget.person!.data.name.trim(),
style: Theme.of(context).textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
);
}
if (kDebugMode) {
faceInfo.add(
Text(
'S: ${widget.face.score.toStringAsFixed(3)} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
);
faceInfo.add(
Text(
'B: ${widget.face.blur.toStringAsFixed(0)} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
);
faceInfo.add(
Text(
'D: ${widget.face.detection.getFaceDirection().toDirectionString()} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
);
faceInfo.add(
Text(
'Sideways: ${widget.face.detection.faceIsSideways().toString()} (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
);
if (widget.face.score < kMinimumFaceShowScore) {
faceInfo.add(
Text(
'Not visible to user (I)',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
),
);
}
}
return faceInfo;
}
Future<void> _onTap() async {
final mlDataDB = MLDataDB.instance;
if (widget.person != null) {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PeoplePage(
person: widget.person!,
searchResult: null,
),
),
);
return;
}
final String? clusterID = widget.clusterID ??
await mlDataDB.getClusterIDForFaceID(widget.face.faceID);
if (clusterID != null) {
final fileIdsToClusterIds = await mlDataDB.getFileIdToClusterIds();
final files = await SearchService.instance.getAllFilesForSearch();
final clusterFiles = files
.where(
(file) =>
fileIdsToClusterIds[file.uploadedFileID]?.contains(clusterID) ??
false,
)
.toList();
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ClusterPage(
clusterFiles,
clusterID: clusterID,
),
),
);
return;
}
if (widget.face.score <= kMinimumQualityFaceScore) {
// The face score is too low for automatic clustering,
// assigning a manual new clusterID so that the user can cluster it manually
final String clusterID = newClusterID();
await mlDataDB.updateFaceIdToClusterId(
{widget.face.faceID: clusterID},
);
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ClusterPage(
[widget.file],
clusterID: clusterID,
),
),
);
return;
}
showShortToast(
context,
S.of(context).faceNotClusteredYet,
);
unawaited(MLService.instance.clusterAllImages(force: true));
return;
}
}

View File

@@ -25,8 +25,6 @@ class FacesItemWidget extends StatefulWidget {
}
class _FacesItemWidgetState extends State<FacesItemWidget> {
bool editMode = false;
@override
void initState() {
super.initState();
@@ -38,18 +36,14 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
return InfoItemWidget(
key: const ValueKey("Faces"),
leadingIcon: Icons.face_retouching_natural_outlined,
subtitleSection: _faceWidgets(context, widget.file, editMode),
subtitleSection: _faceWidgets(context, widget.file),
hasChipButtons: true,
biggerSpinner: true,
);
}
Future<List<Widget>> _faceWidgets(
BuildContext context,
EnteFile file,
bool editMode,
) async {
late final mlDataDB = MLDataDB.instance;
Future<List<Widget>> _faceWidgets(BuildContext context, EnteFile file) async {
final mlDataDB = MLDataDB.instance;
try {
if (file.uploadedFileID == null) {
return [const NoFaceChipButtonWidget(NoFacesReason.fileNotUploaded)];
@@ -151,7 +145,6 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
clusterID: clusterID,
person: person,
highlight: highlight,
editMode: highlight ? editMode : false,
),
);
}