mirror of
https://github.com/immich-app/immich.git
synced 2026-06-06 14:55:17 -04:00
9d4a6614b1
* feat(mobile): handle Android ACTION_VIEW intent - add ViewIntent Pigeon API and generated bindings - implement Android ViewIntentPlugin + iOS no-op host - route ExternalMediaViewer by ViewIntentAttachment - buffer pending view intents and flush on user ready/resume * 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 * fix(mobile): proper handling is user authenticated * feat(mobile): open ACTION_VIEW fallback in AssetViewer drop ExternalMediaViewer route * feat(mobile): add logger * test(mobile): add unit tests for view intent pending/flush flow * fix(mobile): fix format * fix(mobile): remove redundant iOS code update code related to LocalAsset model and asset viewer * refactor(mobile): simplify view intent flow and support file-backed ACTION_VIEW assets remove redundant view intent model/repository layer handle transient ACTION_VIEW files in viewer/upload flow clean up managed temp files for fallback assets * refactor(mobile): extract MediaStore utils and resolve view intents via merged assets * refactor(mobile): move deferred view intents into providers, split view-intent providers, and clean up ACTION_VIEW handling * refactor(mobile): resolve merge conflicts use NativeSyncApi for hash files instead method from removed BackgroundServicePlugin.kt * style(mobile): format files * style(mobile): format files #2 * refactor(mobile): lazily materialize view-intent files and clean up temp-file handling * fix(mobile): flush pending view intents after login navigation * refactor(mobile): split view intent handler by platform and trigger it from app events * refactor(mobile): move view intent handling behind platform-specific factories * refactor(mobile): simplify code * fix(mobile): hand off deep-link viewer to main timeline after upload Add MainTimelineHandoffCoordinator to switch the asset viewer to the main timeline once a view-intent asset is uploaded and becomes available, and guard viewer reload/navigation transitions to avoid race conditions and crashes. * refactor(mobile): use remote asset ids for view intent handoff and simplify resolver * refactor(mobile): resolve merge conflicts * style(mobile): reformat code * style(mobile): reformat code #2 * fix(mobile): stabilize Android view intent asset resolution and fallback viewer * refactor(mobile): share AssetViewer pre-navigation state preparation * fix(mobile): wait for main timeline before deferred view intent handoff * refactor(mobile): decouple view intent asset resolver from providers * fix(mobile): avoid double pop when canceling upload dialog * fix(mobile): resolve view intent MIME type with fallbacks * docs(mobile): clarify view intent fallback asset TODO * fix(mobile): resolve merge conflicts * cleanup * lint --------- Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com> Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
66 lines
2.6 KiB
Dart
66 lines
2.6 KiB
Dart
import 'package:hooks_riverpod/hooks_riverpod.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_payload.extension.dart';
|
|
import 'package:immich_mobile/platform/view_intent_api.g.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
|
import 'package:logging/logging.dart';
|
|
|
|
class ViewIntentResolvedAsset {
|
|
final BaseAsset asset;
|
|
final TimelineService timelineService;
|
|
|
|
final String? viewIntentFilePath;
|
|
|
|
const ViewIntentResolvedAsset({required this.asset, required this.timelineService, this.viewIntentFilePath});
|
|
}
|
|
|
|
final viewIntentAssetResolverProvider = Provider<ViewIntentAssetResolver>(
|
|
(ref) => ViewIntentAssetResolver(
|
|
localAssetRepository: ref.read(localAssetRepository),
|
|
timelineFactory: ref.read(timelineFactoryProvider),
|
|
),
|
|
);
|
|
|
|
class ViewIntentAssetResolver {
|
|
final DriftLocalAssetRepository _localAssetRepository;
|
|
final TimelineFactory _timelineFactory;
|
|
static final Logger _logger = Logger('ViewIntentAssetResolver');
|
|
|
|
const ViewIntentAssetResolver({required this._localAssetRepository, required this._timelineFactory});
|
|
|
|
Future<ViewIntentResolvedAsset> resolve(ViewIntentPayload attachment) async {
|
|
final localAssetId = attachment.localAssetId;
|
|
final path = attachment.path;
|
|
_logger.fine('resolve start, localAssetId=$localAssetId, path=$path, mimeType=${attachment.mimeType}');
|
|
|
|
if (localAssetId == null && path == null) {
|
|
throw StateError('ViewIntent resolution requires either a localAssetId or a materialized file path.');
|
|
}
|
|
|
|
final localAsset = localAssetId != null ? await _localAssetRepository.getById(localAssetId) : null;
|
|
final asset = localAsset ?? _toTransientAsset(attachment);
|
|
|
|
return ViewIntentResolvedAsset(
|
|
asset: asset,
|
|
timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.deepLink),
|
|
viewIntentFilePath: localAsset == null ? path : null,
|
|
);
|
|
}
|
|
|
|
LocalAsset _toTransientAsset(ViewIntentPayload attachment) {
|
|
final now = DateTime.now();
|
|
return LocalAsset(
|
|
id: attachment.localAssetId ?? '-${attachment.path!.hashCode.abs()}',
|
|
name: attachment.fileName,
|
|
type: attachment.isVideo ? AssetType.video : AssetType.image,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
isEdited: false,
|
|
playbackStyle: attachment.playbackStyle,
|
|
);
|
|
}
|
|
}
|