diff --git a/mobile/lib/domain/services/asset.service.dart b/mobile/lib/domain/services/asset.service.dart index 26d6948468..5006e2d45c 100644 --- a/mobile/lib/domain/services/asset.service.dart +++ b/mobile/lib/domain/services/asset.service.dart @@ -22,6 +22,10 @@ class AssetService { return asset is LocalAsset ? _localAssetRepository.watchAsset(id) : _remoteAssetRepository.watchAsset(id); } + Future getRemoteAsset(String id) { + return _remoteAssetRepository.get(id); + } + Future> getStack(RemoteAsset asset) async { if (asset.stackId == null) { return []; diff --git a/mobile/lib/domain/services/memory.service.dart b/mobile/lib/domain/services/memory.service.dart index 50ed1e0f75..ead613370f 100644 --- a/mobile/lib/domain/services/memory.service.dart +++ b/mobile/lib/domain/services/memory.service.dart @@ -13,6 +13,10 @@ class DriftMemoryService { return _repository.getAll(ownerId); } + Future get(String memoryId) { + return _repository.get(memoryId); + } + Future getCount() { return _repository.getCount(); } diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index a30d6f410f..6c3b2e32af 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -22,6 +22,10 @@ class RemoteAlbumService { return _repository.getAll(); } + Future get(String albumId) { + return _repository.get(albumId); + } + List sortAlbums( List albums, RemoteAlbumSortMode sortMode, { diff --git a/mobile/lib/infrastructure/repositories/memory.repository.dart b/mobile/lib/infrastructure/repositories/memory.repository.dart index b964557952..3663b75bf3 100644 --- a/mobile/lib/infrastructure/repositories/memory.repository.dart +++ b/mobile/lib/infrastructure/repositories/memory.repository.dart @@ -58,6 +58,43 @@ class DriftMemoryRepository extends DriftDatabaseRepository { return memoriesMap.values.toList(); } + Future get(String memoryId) async { + final query = _db.select(_db.memoryEntity).join([ + leftOuterJoin( + _db.memoryAssetEntity, + _db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id), + ), + leftOuterJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) & + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline), + ), + ]) + ..where(_db.memoryEntity.id.equals(memoryId)) + ..where(_db.memoryEntity.deletedAt.isNull()) + ..orderBy([ + OrderingTerm.desc(_db.memoryEntity.memoryAt), + OrderingTerm.asc(_db.remoteAssetEntity.createdAt), + ]); + + final rows = await query.get(); + + if (rows.isEmpty) { + return null; + } + + final memory = rows.first.readTable(_db.memoryEntity); + final assets = []; + + for (final row in rows) { + final asset = row.readTable(_db.remoteAssetEntity); + assets.add(asset.toDto()); + } + + return memory.toDto().copyWith(assets: assets); + } + Future getCount() { return _db.managers.memoryEntity.count(); } diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 2d7bbe2370..f60caceb47 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -67,6 +67,41 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .get(); } + Future get(String albumId) { + final assetCount = _db.remoteAlbumAssetEntity.assetId.count(); + + final query = _db.remoteAlbumEntity.select().join([ + leftOuterJoin( + _db.remoteAlbumAssetEntity, + _db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), + useColumns: false, + ), + leftOuterJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), + useColumns: false, + ), + leftOuterJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), + useColumns: false, + ), + ]) + ..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull()) + ..addColumns([assetCount]) + ..addColumns([_db.userEntity.name]) + ..groupBy([_db.remoteAlbumEntity.id]); + + return query + .map( + (row) => row.readTable(_db.remoteAlbumEntity).toDto( + assetCount: row.read(assetCount) ?? 0, + ownerName: row.read(_db.userEntity.name)!, + ), + ) + .getSingleOrNull(); + } + Future create( RemoteAlbum album, List assetIds, diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index af1249dc03..212b9b7f34 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -29,6 +29,33 @@ class RemoteAssetRepository extends DriftDatabaseRepository { return query.map((row) => row.toDto()).get(); } + SingleOrNullSelectable _assetSelectable(String id) { + final query = _db.remoteAssetEntity.select().addColumns([ + _db.localAssetEntity.id, + ]).join([ + leftOuterJoin( + _db.localAssetEntity, + _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), + useColumns: false, + ), + ]) + ..where(_db.remoteAssetEntity.id.equals(id)) + ..limit(1); + + return query.map((row) { + final asset = row.readTable(_db.remoteAssetEntity).toDto(); + return asset.copyWith(localId: row.read(_db.localAssetEntity.id)); + }); + } + + Stream watch(String id) { + return _assetSelectable(id).watchSingleOrNull(); + } + + Future get(String id) { + return _assetSelectable(id).getSingleOrNull(); + } + Stream watchAsset(String id) { final query = _db.remoteAssetEntity.select().addColumns([ _db.localAssetEntity.id, diff --git a/mobile/lib/services/deep_link.service.dart b/mobile/lib/services/deep_link.service.dart index caa20918c3..c08eacd0e3 100644 --- a/mobile/lib/services/deep_link.service.dart +++ b/mobile/lib/services/deep_link.service.dart @@ -1,6 +1,16 @@ import 'package:auto_route/auto_route.dart'; +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/remote_album.service.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/asset.service.dart'; @@ -15,24 +25,53 @@ final deepLinkServiceProvider = Provider( ref.watch(albumServiceProvider), ref.watch(currentAssetProvider.notifier), ref.watch(currentAlbumProvider.notifier), + // Below is used for beta timeline + ref.watch(timelineFactoryProvider), + ref.watch(beta_asset_provider.assetServiceProvider), + ref.watch(currentRemoteAlbumProvider.notifier), + ref.watch(remoteAlbumServiceProvider), + ref.watch(driftMemoryServiceProvider), ), ); class DeepLinkService { + /// TODO: Remove this when beta is default final MemoryService _memoryService; final AssetService _assetService; final AlbumService _albumService; final CurrentAsset _currentAsset; final CurrentAlbum _currentAlbum; + /// Used for beta timeline + final TimelineFactory _betaTimelineFactory; + final beta_asset_service.AssetService _betaAssetService; + final CurrentAlbumNotifier _betaCurrentAlbumNotifier; + final RemoteAlbumService _betaRemoteAlbumService; + final DriftMemoryService _betaMemoryServiceProvider; + const DeepLinkService( this._memoryService, this._assetService, this._albumService, this._currentAsset, this._currentAlbum, + this._betaTimelineFactory, + this._betaAssetService, + this._betaCurrentAlbumNotifier, + this._betaRemoteAlbumService, + this._betaMemoryServiceProvider, ); + DeepLink _handleColdStart(PageRouteInfo route, bool isColdStart) { + return DeepLink([ + // we need something to segue back to if the app was cold started + // TODO: use MainTimelineRoute this when beta is default + + if (isColdStart) (Store.isBetaTimelineEnabled) ? const MainTimelineRoute() : const PhotosRoute(), + route, + ]); + } + Future handleScheme(PlatformDeepLink link, bool isColdStart) async { // get everything after the scheme, since Uri cannot parse path final intent = link.uri.host; @@ -54,11 +93,7 @@ class DeepLinkService { return DeepLink.none; } - return DeepLink([ - // we need something to segue back to if the app was cold started - if (isColdStart) const PhotosRoute(), - deepLinkRoute, - ]); + return _handleColdStart(deepLinkRoute, isColdStart); } Future handleMyImmichApp( @@ -86,49 +121,80 @@ class DeepLinkService { return DeepLink.none; } - return DeepLink([ - // we need something to segue back to if the app was cold started - if (isColdStart) const PhotosRoute(), - deepLinkRoute, - ]); + return _handleColdStart(deepLinkRoute, isColdStart); } Future _buildMemoryDeepLink(String memoryId) async { - final memory = await _memoryService.getMemoryById(memoryId); + if (Store.isBetaTimelineEnabled) { + final memory = await _betaMemoryServiceProvider.get(memoryId); - if (memory == null) { - return null; + if (memory == null) { + return null; + } + + return DriftMemoryRoute(memories: [memory], memoryIndex: 0); + } else { + // TODO: Remove this when beta is default + final memory = await _memoryService.getMemoryById(memoryId); + + if (memory == null) { + return null; + } + + return MemoryRoute(memories: [memory], memoryIndex: 0); } - - return MemoryRoute(memories: [memory], memoryIndex: 0); } Future _buildAssetDeepLink(String assetId) async { - final asset = await _assetService.getAssetByRemoteId(assetId); - if (asset == null) { - return null; + if (Store.isBetaTimelineEnabled) { + final asset = await _betaAssetService.getRemoteAsset(assetId); + if (asset == null) { + return null; + } + + return AssetViewerRoute( + initialIndex: 0, + timelineService: _betaTimelineFactory.fromAssets([asset]), + ); + } else { + // TODO: Remove this when beta is default + final asset = await _assetService.getAssetByRemoteId(assetId); + if (asset == null) { + return null; + } + + _currentAsset.set(asset); + final renderList = await RenderList.fromAssets([asset], GroupAssetsBy.auto); + + return GalleryViewerRoute( + renderList: renderList, + initialIndex: 0, + heroOffset: 0, + showStack: true, + ); } - - _currentAsset.set(asset); - final renderList = await RenderList.fromAssets([asset], GroupAssetsBy.auto); - - return GalleryViewerRoute( - renderList: renderList, - initialIndex: 0, - heroOffset: 0, - showStack: true, - ); } Future _buildAlbumDeepLink(String albumId) async { - final album = await _albumService.getAlbumByRemoteId(albumId); + if (Store.isBetaTimelineEnabled) { + final album = await _betaRemoteAlbumService.get(albumId); - if (album == null) { - return null; + if (album == null) { + return null; + } + + _betaCurrentAlbumNotifier.setAlbum(album); + return RemoteAlbumRoute(album: album); + } else { + // TODO: Remove this when beta is default + final album = await _albumService.getAlbumByRemoteId(albumId); + + if (album == null) { + return null; + } + + _currentAlbum.set(album); + return AlbumViewerRoute(albumId: album.id); } - - _currentAlbum.set(album); - - return AlbumViewerRoute(albumId: album.id); } }