mirror of
https://github.com/immich-app/immich.git
synced 2026-05-22 23:52:32 -04:00
feat(mobile): fallback to computed checksum for timeline match
- hash local asset on-demand when checksum missing - search main timeline by localId or checksum before standalone viewer - persist computed hash into local_asset_entity
This commit is contained in:
@@ -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<ViewIntentHandler>(
|
||||
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<bool> _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<String?> _computeChecksum(String localAssetId) async {
|
||||
try {
|
||||
final hashResults = await _nativeSyncApi.hashAssets([localAssetId]);
|
||||
if (hashResults.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return hashResults.first.hash;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user