refactor: unify person model (#30659)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2026-08-12 21:12:11 +05:30 committed by GitHub
parent 943c11c019
commit 52edcc0c74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 34 additions and 78 deletions

View file

@ -3,32 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'person.model.freezed.dart';
// TODO: Remove PersonDto once Isar is removed
@freezed
abstract class PersonDto with _$PersonDto {
const factory PersonDto({
required String id,
DateTime? birthDate,
required bool isHidden,
required String name,
required String thumbnailPath,
DateTime? updatedAt,
}) = _PersonDto;
}
// Model for a person stored in the server
@freezed
abstract class DriftPerson with _$DriftPerson {
const factory DriftPerson({
required String id,
required DateTime createdAt,
required DateTime updatedAt,
required String ownerId,
required String name,
String? faceAssetId,
required bool isFavorite,
required bool isHidden,
required String? color,
DateTime? birthDate,
}) = _DriftPerson;
abstract class Person with _$Person {
const factory Person({required String id, required String name, DateTime? updatedAt, DateTime? birthDate}) = _Person;
}

View file

@ -10,15 +10,15 @@ class DriftPeopleService {
const DriftPeopleService(this._repository, this._personApiRepository);
Future<DriftPerson?> get(String personId) {
Future<Person?> get(String personId) {
return _repository.get(personId);
}
Future<List<DriftPerson>> getAssetPeople(String assetId) {
Future<List<Person>> getAssetPeople(String assetId) {
return _repository.getAssetPeople(assetId);
}
Future<List<DriftPerson>> getAllPeople({int minFaces = 3}) {
Future<List<Person>> getAllPeople({int minFaces = 3}) {
return _repository.getAllPeople(minFaces: minFaces);
}

View file

@ -8,14 +8,14 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftPeopleRepository(this._db) : super(_db);
Future<DriftPerson?> get(String personId) async {
Future<Person?> get(String personId) async {
final query = _db.select(_db.personEntity)..where((row) => row.id.equals(personId));
final result = await query.getSingleOrNull();
return result?.toDto();
}
Future<List<DriftPerson>> getAssetPeople(String assetId) async {
Future<List<Person>> getAssetPeople(String assetId) async {
// An asset can have multiple face records for the same person (e.g., metadata
// imports alongside ML detections). Use a subquery instead of a join so each
// person is returned once, regardless of how many of their faces are on the asset
@ -33,7 +33,7 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
return query.map((row) => row.toDto()).get();
}
Future<List<DriftPerson>> getAllPeople({int minFaces = 3}) async {
Future<List<Person>> getAllPeople({int minFaces = 3}) async {
final people = _db.personEntity;
final faces = _db.assetFaceEntity;
final assets = _db.remoteAssetEntity;
@ -76,18 +76,5 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
}
extension on PersonEntityData {
DriftPerson toDto() {
return DriftPerson(
id: id,
createdAt: createdAt,
updatedAt: updatedAt,
ownerId: ownerId,
name: name,
faceAssetId: faceAssetId,
isFavorite: isFavorite,
isHidden: isHidden,
color: color,
birthDate: birthDate,
);
}
Person toDto() => Person(id: id, updatedAt: updatedAt, name: name, birthDate: birthDate);
}

View file

@ -194,7 +194,7 @@ class SearchFilter {
String? language;
String? assetId;
List<String>? tagIds;
Set<PersonDto> people;
Set<Person> people;
SearchLocationFilter location;
SearchCameraFilter camera;
SearchDateFilter date;
@ -250,7 +250,7 @@ class SearchFilter {
String? language,
String? ocr,
String? assetId,
Set<PersonDto>? people,
Set<Person>? people,
List<String>? tagIds,
SearchLocationFilter? location,
SearchCameraFilter? camera,

View file

@ -14,7 +14,7 @@ import 'package:immich_mobile/widgets/common/person_sliver_app_bar.dart';
@RoutePage()
class DriftPersonPage extends ConsumerStatefulWidget {
final DriftPerson person;
final Person person;
const DriftPersonPage({super.key, required this.person});
@ -23,7 +23,7 @@ class DriftPersonPage extends ConsumerStatefulWidget {
}
class _DriftPersonPageState extends ConsumerState<DriftPersonPage> {
late DriftPerson _person;
late Person _person;
@override
void initState() {

View file

@ -129,7 +129,7 @@ class DriftSearchPage extends HookConsumerWidget {
void showPeoplePicker() {
var people = filter.value.people;
void handleOnSelect(Set<PersonDto> value) {
void handleOnSelect(Set<Person> value) {
people = value;
}

View file

@ -30,7 +30,7 @@ class PeopleDetails extends ConsumerWidget {
final peopleFuture = ref.watch(driftPeopleAssetProvider(asset.id));
Future<void> showNameEditModal(DriftPerson person) async {
Future<void> showNameEditModal(Person person) async {
await showDialog(
context: context,
useRootNavigator: false,
@ -96,7 +96,7 @@ class PeopleDetails extends ConsumerWidget {
}
class _Avatar extends StatelessWidget {
final DriftPerson person;
final Person person;
final DateTime assetFileCreatedAt;
final VoidCallback? onTap;
final VoidCallback? onNameTap;

View file

@ -11,7 +11,7 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:scroll_date_picker/scroll_date_picker.dart';
class DriftPersonBirthdayEditForm extends ConsumerStatefulWidget {
final DriftPerson person;
final Person person;
const DriftPersonBirthdayEditForm({super.key, required this.person});

View file

@ -9,7 +9,7 @@ import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class DriftPersonNameEditForm extends ConsumerStatefulWidget {
final DriftPerson person;
final Person person;
const DriftPersonNameEditForm({super.key, required this.person});

View file

@ -14,12 +14,12 @@ final driftPeopleServiceProvider = Provider<DriftPeopleService>(
(ref) => DriftPeopleService(ref.watch(driftPeopleRepositoryProvider), ref.watch(personApiRepositoryProvider)),
);
final driftPeopleAssetProvider = FutureProvider.family<List<DriftPerson>, String>((ref, assetId) async {
final driftPeopleAssetProvider = FutureProvider.family<List<Person>, String>((ref, assetId) async {
final service = ref.watch(driftPeopleServiceProvider);
return service.getAssetPeople(assetId);
});
final driftGetAllPeopleProvider = FutureProvider<List<DriftPerson>>((ref) async {
final driftGetAllPeopleProvider = FutureProvider<List<Person>>((ref) async {
final service = ref.watch(driftPeopleServiceProvider);
final prefs = await ref.watch(userMetadataPreferencesProvider.future);
return service.getAllPeople(minFaces: prefs?.minimumFaces ?? 3);

View file

@ -2,7 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/services/person.service.dart';
final getAllPeopleProvider = FutureProvider.autoDispose<List<PersonDto>>((ref) async {
final getAllPeopleProvider = FutureProvider.autoDispose<List<Person>>((ref) async {
final PersonService personService = ref.read(personServiceProvider);
final people = await personService.getAllPeople();

View file

@ -11,12 +11,12 @@ class PersonApiRepository extends ApiRepository {
PersonApiRepository(this._api);
Future<List<PersonDto>> getAll() async {
Future<List<Person>> getAll() async {
final dto = await checkNull(_api.getAllPeople());
return dto.people.map(_toPerson).toList();
}
Future<PersonDto> update(String id, {String? name, DateTime? birthday}) async {
Future<Person> update(String id, {String? name, DateTime? birthday}) async {
final birthdayUtc = birthday == null ? null : DateTime.utc(birthday.year, birthday.month, birthday.day);
final dto = PersonUpdateDto(
name: name == null ? const Optional.absent() : Optional.present(name),
@ -26,12 +26,6 @@ class PersonApiRepository extends ApiRepository {
return _toPerson(response);
}
static PersonDto _toPerson(PersonResponseDto dto) => PersonDto(
birthDate: dto.birthDate,
id: dto.id,
isHidden: dto.isHidden,
name: dto.name,
thumbnailPath: dto.thumbnailPath,
updatedAt: dto.updatedAt.orElse(null),
);
static Person _toPerson(PersonResponseDto dto) =>
.new(birthDate: dto.birthDate, id: dto.id, name: dto.name, updatedAt: dto.updatedAt.value);
}

View file

@ -12,7 +12,7 @@ class PersonService {
final PersonApiRepository _personApiRepository;
PersonService(this._personApiRepository);
Future<List<PersonDto>> getAllPeople() async {
Future<List<Person>> getAllPeople() async {
try {
return await _personApiRepository.getAll();
} catch (error, stack) {
@ -21,7 +21,7 @@ class PersonService {
}
}
Future<PersonDto?> updateName(String id, String name) async {
Future<Person?> updateName(String id, String name) async {
try {
return await _personApiRepository.update(id, name: name);
} catch (error, stack) {

View file

@ -34,7 +34,7 @@ int _calculateAgeInMonths(DateTime birthDate, DateTime referenceDate) {
(referenceDate.day < birthDate.day ? 1 : 0);
}
Future<String?> showNameEditModal(BuildContext context, DriftPerson person) {
Future<String?> showNameEditModal(BuildContext context, Person person) {
return showDialog<String?>(
context: context,
useRootNavigator: false,
@ -44,7 +44,7 @@ Future<String?> showNameEditModal(BuildContext context, DriftPerson person) {
);
}
Future<DateTime?> showBirthdayEditModal(BuildContext context, DriftPerson person) {
Future<DateTime?> showBirthdayEditModal(BuildContext context, Person person) {
return showDialog<DateTime?>(
context: context,
useRootNavigator: false,

View file

@ -28,7 +28,7 @@ class PersonSliverAppBar extends ConsumerStatefulWidget {
required this.onBirthdayTap,
});
final DriftPerson person;
final Person person;
final VoidCallback onNameTap;
final VoidCallback onBirthdayTap;
final VoidCallback onShowOptions;
@ -137,7 +137,7 @@ class _MesmerizingSliverAppBarState extends ConsumerState<PersonSliverAppBar> {
class _ExpandedBackground extends ConsumerStatefulWidget {
final double scrollProgress;
final DriftPerson person;
final Person person;
final VoidCallback onNameTap;
final VoidCallback onBirthdayTap;

View file

@ -15,8 +15,8 @@ import 'package:immich_mobile/widgets/common/search_field.dart';
class PeoplePicker extends HookConsumerWidget {
const PeoplePicker({super.key, required this.onSelect, this.filter});
final Function(Set<PersonDto>) onSelect;
final Set<PersonDto>? filter;
final Function(Set<Person>) onSelect;
final Set<Person>? filter;
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -24,7 +24,7 @@ class PeoplePicker extends HookConsumerWidget {
const imageSize = 60.0;
final searchQuery = useState('');
final people = ref.watch(getAllPeopleProvider);
final selectedPeople = useState<Set<PersonDto>>(filter ?? {});
final selectedPeople = useState<Set<Person>>(filter ?? {});
return Column(
children: [