[mob] Clean up

This commit is contained in:
Neeraj Gupta
2024-11-19 18:12:58 +05:30
parent 572f2aff88
commit b0b527d2ea
2 changed files with 24 additions and 321 deletions

View File

@@ -1,56 +1,14 @@
import "dart:async";
import "dart:developer";
import "dart:math" as math;
import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import "package:photos/core/event_bus.dart";
import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/models/file/file.dart";
import "package:photos/models/ml/face/person.dart";
import 'package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart';
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import "package:photos/services/search_service.dart";
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/common/loading_widget.dart';
import 'package:photos/ui/components/bottom_of_title_bar_widget.dart';
import 'package:photos/ui/components/buttons/button_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
import "package:photos/ui/components/text_input_widget.dart";
import 'package:photos/ui/components/title_bar_title_widget.dart';
import "package:photos/ui/viewer/people/person_row_item.dart";
import "package:photos/ui/viewer/people/save_person.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/navigation_util.dart";
import "package:photos/utils/toast_util.dart";
enum PersonActionType {
assignPerson,
}
String _actionName(
BuildContext context,
PersonActionType type,
) {
String text = "";
switch (type) {
case PersonActionType.assignPerson:
text = S.of(context).addNameOrMerge;
break;
}
return text;
}
Future<dynamic> showAssignPersonAction(
BuildContext context, {
required String clusterID,
EnteFile? file,
PersonActionType actionType = PersonActionType.assignPerson,
bool showOptionToAddNewPerson = true,
}) async {
return routeToPage(
@@ -60,279 +18,4 @@ Future<dynamic> showAssignPersonAction(
file: file,
),
);
// return showBarModalBottomSheet(
// context: context,
// builder: (context) {
// return PersonActionSheet(
// actionType: actionType,
// showOptionToCreateNewPerson: showOptionToAddNewPerson,
// cluserID: clusterID,
// );
// },
// shape: const RoundedRectangleBorder(
// side: BorderSide(width: 0),
// borderRadius: BorderRadius.vertical(
// top: Radius.circular(5),
// ),
// ),
// topControl: const SizedBox.shrink(),
// backgroundColor: getEnteColorScheme(context).backgroundElevated,
// barrierColor: backdropFaintDark,
// enableDrag: false,
// );
}
class PersonActionSheet extends StatefulWidget {
final PersonActionType actionType;
final String cluserID;
final bool showOptionToCreateNewPerson;
const PersonActionSheet({
required this.actionType,
required this.cluserID,
required this.showOptionToCreateNewPerson,
super.key,
});
@override
State<PersonActionSheet> createState() => _PersonActionSheetState();
}
class _PersonActionSheetState extends State<PersonActionSheet> {
static const int cancelButtonSize = 80;
String _searchQuery = "";
bool userAlreadyAssigned = false;
@override
Widget build(BuildContext context) {
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
final isKeyboardUp = bottomInset > 100;
return Padding(
padding: EdgeInsets.only(
bottom: isKeyboardUp ? bottomInset - cancelButtonSize : 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: math.min(428, MediaQuery.of(context).size.width),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 32, 0, 8),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Column(
children: [
BottomOfTitleBarWidget(
title: TitleBarTitleWidget(
title: _actionName(context, widget.actionType),
),
// caption: 'Select or create a ',
),
Padding(
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
),
child: TextInputWidget(
hintText: S.of(context).personName,
prefixIcon: Icons.search_rounded,
onChange: (value) {
setState(() {
_searchQuery = value;
});
},
isClearable: true,
shouldUnfocusOnClearOrSubmit: true,
borderRadius: 2,
),
),
_getPersonItems(),
],
),
),
SafeArea(
child: Container(
//inner stroke of 1pt + 15 pts of top padding = 16 pts
padding: const EdgeInsets.fromLTRB(16, 15, 16, 8),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: getEnteColorScheme(context).strokeFaint,
),
),
),
child: ButtonWidget(
buttonType: ButtonType.secondary,
buttonAction: ButtonAction.cancel,
isInAlert: true,
labelText: S.of(context).cancel,
),
),
),
],
),
),
),
],
),
);
}
Flexible _getPersonItems() {
return Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: FutureBuilder<List<(PersonEntity, EnteFile)>>(
future: _getPersonsWithRecentFile(),
builder: (context, snapshot) {
if (snapshot.hasError) {
log("Error: ${snapshot.error} ${snapshot.stackTrace}}");
//Need to show an error on the UI here
if (kDebugMode) {
return Column(
children: [
Text('${snapshot.error}'),
Text('${snapshot.stackTrace}'),
],
);
} else {
return const SizedBox.shrink();
}
} else if (snapshot.hasData) {
final persons = snapshot.data!;
final searchResults = _searchQuery.isNotEmpty
? persons
.where(
(element) => element.$1.data.name
.toLowerCase()
.contains(_searchQuery),
)
.toList()
: persons;
// sort searchResults alphabetically by name
searchResults.sort(
(a, b) => a.$1.data.name.compareTo(b.$1.data.name),
);
return Scrollbar(
thumbVisibility: true,
radius: const Radius.circular(2),
child: Padding(
padding: const EdgeInsets.only(right: 12),
child: ListView.separated(
itemCount: searchResults.length + 1,
itemBuilder: (context, index) {
final person = searchResults[index - 1];
return PersonRowItem(
person: person.$1,
personFile: person.$2,
onTap: () async {
if (userAlreadyAssigned) {
return;
}
userAlreadyAssigned = true;
await MLDataDB.instance.assignClusterToPerson(
personID: person.$1.remoteID,
clusterID: widget.cluserID,
);
Bus.instance.fire(PeopleChangedEvent());
Navigator.pop(context, person);
},
);
},
separatorBuilder: (context, index) {
return const SizedBox(height: 6);
},
),
),
);
} else {
return const EnteLoadingWidget();
}
},
),
),
);
}
Future<void> addNewPerson(
BuildContext context, {
String initValue = '',
required String clusterID,
}) async {
PersonEntity? personEntity;
final result = await showTextInputDialog(
context,
title: S.of(context).newPerson,
submitButtonLabel: S.of(context).add,
hintText: S.of(context).addName,
alwaysShowSuccessState: false,
initialValue: initValue,
textCapitalization: TextCapitalization.words,
onSubmit: (String text) async {
if (userAlreadyAssigned) {
return;
}
// indicates user cancelled the rename request
if (text.trim() == "") {
return;
}
try {
userAlreadyAssigned = true;
personEntity =
await PersonService.instance.addPerson(text, clusterID);
final bool extraPhotosFound =
await ClusterFeedbackService.instance.checkAndDoAutomaticMerges(
personEntity!,
personClusterID: clusterID,
);
if (extraPhotosFound) {
showShortToast(context, S.of(context).extraPhotosFound);
}
} catch (e, s) {
Logger("_PersonActionSheetState")
.severe("Failed to add person", e, s);
rethrow;
}
},
);
if (result is Exception) {
await showGenericErrorDialog(context: context, error: result);
}
if (personEntity != null) {
Bus.instance.fire(PeopleChangedEvent());
Navigator.pop(context, personEntity);
}
}
Future<List<(PersonEntity, EnteFile)>> _getPersonsWithRecentFile({
bool excludeHidden = true,
}) async {
final persons = await PersonService.instance.getPersons();
if (excludeHidden) {
persons.removeWhere((person) => person.data.isIgnored);
}
final List<(PersonEntity, EnteFile)> personAndFileID = [];
for (final person in persons) {
final clustersToFiles =
await SearchService.instance.getClusterFilesForPersonID(
person.remoteID,
);
final files = clustersToFiles.values.expand((e) => e).toList();
if (files.isEmpty) {
debugPrint(
"Person ${kDebugMode ? person.data.name : person.remoteID} has no files",
);
continue;
}
personAndFileID.add((person, files.first));
}
return personAndFileID;
}
}

View File

@@ -1,4 +1,5 @@
import "dart:developer";
import 'dart:async';
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
@@ -45,6 +46,14 @@ class _SavePersonState extends State<SavePerson> {
String _inputName = "";
bool userAlreadyAssigned = false;
late final Logger _logger = Logger("_SavePersonState");
Timer? _debounce;
List<(PersonEntity, EnteFile)> _cachedPersons = [];
@override
void dispose() {
_debounce?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
@@ -85,8 +94,11 @@ class _SavePersonState extends State<SavePerson> {
const SizedBox(height: 36),
TextFormField(
onChanged: (value) {
setState(() {
_inputName = value;
if (_debounce?.isActive ?? false) _debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 300), () {
setState(() {
_inputName = value;
});
});
},
decoration: InputDecoration(
@@ -141,8 +153,8 @@ class _SavePersonState extends State<SavePerson> {
Widget _getPersonItems() {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 12, 4, 0),
child: FutureBuilder<List<(PersonEntity, EnteFile)>>(
future: _getPersonsWithRecentFile(),
child: StreamBuilder<List<(PersonEntity, EnteFile)>>(
stream: _getPersonsWithRecentFileStream(),
builder: (context, snapshot) {
if (snapshot.hasError) {
log("Error: ${snapshot.error} ${snapshot.stackTrace}}");
@@ -232,6 +244,14 @@ class _SavePersonState extends State<SavePerson> {
);
}
Stream<List<(PersonEntity, EnteFile)>>
_getPersonsWithRecentFileStream() async* {
if (_cachedPersons.isEmpty) {
_cachedPersons = await _getPersonsWithRecentFile();
}
yield _cachedPersons;
}
Future<void> addNewPerson(
BuildContext context, {
String text = '',