feat: add people deeplink (#25686)

* Change path prefix from '/memories/' to '/people/'

Updated the AndroidManifest.xml to change the path prefix from '/memories/' to '/people/'.
Memories is anyway wrong and was replaced by /memory
and now the people path completes the known deeplinks.

* Add regex for people deep link handling

Add regex for people deep link handling

* Add deep link handling for 'people' route

* fix: missing person route builder method

---------

Co-authored-by: bwees <brandonwees@gmail.com>
This commit is contained in:
Arne Schwarck 2026-02-13 04:43:04 +01:00 committed by GitHub
parent e5156df4f1
commit 66733eb4c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 40 additions and 5 deletions

View file

@ -117,7 +117,7 @@
android:pathPrefix="/albums/" /> android:pathPrefix="/albums/" />
<data <data
android:host="my.immich.app" android:host="my.immich.app"
android:pathPrefix="/memories/" /> android:pathPrefix="/people/" />
<data <data
android:host="my.immich.app" android:host="my.immich.app"
android:path="/memory" /> android:path="/memory" />

View file

@ -10,6 +10,10 @@ class DriftPeopleService {
const DriftPeopleService(this._repository, this._personApiRepository); const DriftPeopleService(this._repository, this._personApiRepository);
Future<DriftPerson?> get(String personId) {
return _repository.get(personId);
}
Future<List<DriftPerson>> getAssetPeople(String assetId) { Future<List<DriftPerson>> getAssetPeople(String assetId) {
return _repository.getAssetPeople(assetId); return _repository.getAssetPeople(assetId);
} }

View file

@ -7,6 +7,13 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
final Drift _db; final Drift _db;
const DriftPeopleRepository(this._db) : super(_db); const DriftPeopleRepository(this._db) : super(_db);
Future<DriftPerson?> 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<DriftPerson>> getAssetPeople(String assetId) async {
final query = _db.select(_db.assetFaceEntity).join([ final query = _db.select(_db.assetFaceEntity).join([
innerJoin(_db.personEntity, _db.personEntity.id.equalsExp(_db.assetFaceEntity.personId)), innerJoin(_db.personEntity, _db.personEntity.id.equalsExp(_db.assetFaceEntity.personId)),

View file

@ -4,6 +4,7 @@ import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/asset.service.dart' as beta_asset_service; import 'package:immich_mobile/domain/services/asset.service.dart' as beta_asset_service;
import 'package:immich_mobile/domain/services/memory.service.dart'; import 'package:immich_mobile/domain/services/memory.service.dart';
import 'package:immich_mobile/domain/services/people.service.dart';
import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/domain/services/remote_album.service.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
@ -13,6 +14,7 @@ import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart
import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider;
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/infrastructure/people.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
@ -33,6 +35,7 @@ final deepLinkServiceProvider = Provider(
ref.watch(beta_asset_provider.assetServiceProvider), ref.watch(beta_asset_provider.assetServiceProvider),
ref.watch(remoteAlbumServiceProvider), ref.watch(remoteAlbumServiceProvider),
ref.watch(driftMemoryServiceProvider), ref.watch(driftMemoryServiceProvider),
ref.watch(driftPeopleServiceProvider),
ref.watch(currentUserProvider), ref.watch(currentUserProvider),
), ),
); );
@ -49,7 +52,8 @@ class DeepLinkService {
final TimelineFactory _betaTimelineFactory; final TimelineFactory _betaTimelineFactory;
final beta_asset_service.AssetService _betaAssetService; final beta_asset_service.AssetService _betaAssetService;
final RemoteAlbumService _betaRemoteAlbumService; final RemoteAlbumService _betaRemoteAlbumService;
final DriftMemoryService _betaMemoryServiceProvider; final DriftMemoryService _betaMemoryService;
final DriftPeopleService _betaPeopleService;
final UserDto? _currentUser; final UserDto? _currentUser;
@ -62,7 +66,8 @@ class DeepLinkService {
this._betaTimelineFactory, this._betaTimelineFactory,
this._betaAssetService, this._betaAssetService,
this._betaRemoteAlbumService, this._betaRemoteAlbumService,
this._betaMemoryServiceProvider, this._betaMemoryService,
this._betaPeopleService,
this._currentUser, this._currentUser,
); );
@ -84,6 +89,7 @@ class DeepLinkService {
"memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''), "memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''),
"asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref), "asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref),
"album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''), "album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''),
"people" => await _buildPeopleDeepLink(queryParams['id'] ?? ''),
"activity" => await _buildActivityDeepLink(queryParams['albumId'] ?? ''), "activity" => await _buildActivityDeepLink(queryParams['albumId'] ?? ''),
_ => null, _ => null,
}; };
@ -106,6 +112,7 @@ class DeepLinkService {
const uuidRegex = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; const uuidRegex = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';
final assetRegex = RegExp('/photos/($uuidRegex)'); final assetRegex = RegExp('/photos/($uuidRegex)');
final albumRegex = RegExp('/albums/($uuidRegex)'); final albumRegex = RegExp('/albums/($uuidRegex)');
final peopleRegex = RegExp('/people/($uuidRegex)');
PageRouteInfo<dynamic>? deepLinkRoute; PageRouteInfo<dynamic>? deepLinkRoute;
if (assetRegex.hasMatch(path)) { if (assetRegex.hasMatch(path)) {
@ -114,6 +121,9 @@ class DeepLinkService {
} else if (albumRegex.hasMatch(path)) { } else if (albumRegex.hasMatch(path)) {
final albumId = albumRegex.firstMatch(path)?.group(1) ?? ''; final albumId = albumRegex.firstMatch(path)?.group(1) ?? '';
deepLinkRoute = await _buildAlbumDeepLink(albumId); deepLinkRoute = await _buildAlbumDeepLink(albumId);
} else if (peopleRegex.hasMatch(path)) {
final peopleId = peopleRegex.firstMatch(path)?.group(1) ?? '';
deepLinkRoute = await _buildPeopleDeepLink(peopleId);
} else if (path == "/memory") { } else if (path == "/memory") {
deepLinkRoute = await _buildMemoryDeepLink(null); deepLinkRoute = await _buildMemoryDeepLink(null);
} }
@ -136,9 +146,9 @@ class DeepLinkService {
return null; return null;
} }
memories = await _betaMemoryServiceProvider.getMemoryLane(_currentUser.id); memories = await _betaMemoryService.getMemoryLane(_currentUser.id);
} else { } else {
final memory = await _betaMemoryServiceProvider.get(memoryId); final memory = await _betaMemoryService.get(memoryId);
if (memory != null) { if (memory != null) {
memories = [memory]; memories = [memory];
} }
@ -225,4 +235,18 @@ class DeepLinkService {
return DriftActivitiesRoute(album: album); return DriftActivitiesRoute(album: album);
} }
Future<PageRouteInfo?> _buildPeopleDeepLink(String personId) async {
if (Store.isBetaTimelineEnabled == false) {
return null;
}
final person = await _betaPeopleService.get(personId);
if (person == null) {
return null;
}
return DriftPersonRoute(person: person);
}
} }