diff --git a/mobile/lib/providers/asset_viewer/view_intent_handler.provider.dart b/mobile/lib/providers/asset_viewer/view_intent_handler.provider.dart index 7b5104980c..92056c039d 100644 --- a/mobile/lib/providers/asset_viewer/view_intent_handler.provider.dart +++ b/mobile/lib/providers/asset_viewer/view_intent_handler.provider.dart @@ -1,15 +1,18 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/models/view_intent/view_intent_attachment.model.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -21,6 +24,7 @@ final viewIntentHandlerProvider = Provider( ref.read(viewIntentServiceProvider), ref.watch(appRouterProvider), ref.read(localAssetRepository), + ref.read(nativeSyncApiProvider), ref.read(timelineFactoryProvider), ), ); @@ -30,6 +34,7 @@ class ViewIntentHandler { final ViewIntentService _viewIntentService; final AppRouter _router; final DriftLocalAssetRepository _localAssetRepository; + final NativeSyncApi _nativeSyncApi; final TimelineFactory _timelineFactory; const ViewIntentHandler( @@ -37,6 +42,7 @@ class ViewIntentHandler { this._viewIntentService, this._router, this._localAssetRepository, + this._nativeSyncApi, this._timelineFactory, ); @@ -63,7 +69,19 @@ class ViewIntentHandler { if (localAssetId != null) { final localAsset = await _localAssetRepository.getById(localAssetId); if (localAsset != null) { - _openAssetViewer(localAsset); + var checksum = localAsset.checksum; + if (checksum == null) { + checksum = await _computeChecksum(localAssetId); + if (checksum != null) { + await _localAssetRepository.updateHashes({localAssetId: checksum}); + } + } + //todo clarify logic for assets not presented into MainTimeline (locked folder, deleted etc) + final timelineMatch = await _openFromMainTimeline(localAssetId, checksum: checksum); + if (timelineMatch) { + return; + } + _openAssetViewer(localAsset, _timelineFactory.fromAssets([localAsset], TimelineOrigin.deepLink), 0); return; } } @@ -71,7 +89,44 @@ class ViewIntentHandler { await _router.push(ExternalMediaViewerRoute(attachment: attachment)); } - void _openAssetViewer(LocalAsset asset) { + Future _openFromMainTimeline(String localAssetId, {String? checksum}) async { + final timelineService = _ref.read(timelineServiceProvider); + if (timelineService.totalAssets == 0) { + try { + await timelineService.watchBuckets().first.timeout(const Duration(seconds: 2)); + } catch (_) { + // Ignore and fallback. + } + } + + final totalAssets = timelineService.totalAssets; + if (totalAssets == 0) { + return false; + } + + final batchSize = kTimelineAssetLoadBatchSize; + for (var offset = 0; offset < totalAssets; offset += batchSize) { + final count = (offset + batchSize > totalAssets) ? totalAssets - offset : batchSize; + final assets = await timelineService.loadAssets(offset, count); + final indexInBatch = assets.indexWhere((asset) { + if (asset.localId == localAssetId) { + return true; + } + if (checksum != null && asset.checksum == checksum) { + return true; + } + return false; + }); + if (indexInBatch >= 0) { + final asset = assets[indexInBatch]; + _openAssetViewer(asset, timelineService, offset + indexInBatch); + return true; + } + } + return false; + } + + void _openAssetViewer(BaseAsset asset, TimelineService timelineService, int initialIndex) { _ref.read(assetViewerProvider.notifier).reset(); _ref.read(assetViewerProvider.notifier).setAsset(asset); _ref.read(currentAssetNotifier.notifier).setAsset(asset); @@ -83,11 +138,18 @@ class ViewIntentHandler { _ref.read(assetViewerProvider.notifier).setControls(false); } - _router.push( - AssetViewerRoute( - initialIndex: 0, - timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.deepLink), - ), - ); + _router.push(AssetViewerRoute(initialIndex: initialIndex, timelineService: timelineService)); + } + + Future _computeChecksum(String localAssetId) async { + try { + final hashResults = await _nativeSyncApi.hashAssets([localAssetId]); + if (hashResults.isEmpty) { + return null; + } + return hashResults.first.hash; + } catch (_) { + return null; + } } }