immich/mobile/lib/presentation/actions/edit_asset.action.dart
shenlong 652ef8a427
refactor: server capabilities (#30663)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-08-14 07:53:12 -05:00

82 lines
3.3 KiB
Dart

import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
import 'package:immich_mobile/domain/models/server_capability.model.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/presentation/pages/edit/editor.provider.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/error_handler.dart';
final _stateProvider = Provider.family.autoDispose<RemoteAsset?, ActionSource>((ref, source) {
final isSupported = ref.watch(serverInfoProvider.select((state) => state.serverVersion.supports(.assetEdits)));
if (!isSupported) {
return null;
}
final assets = ref.watch(ownedAssetsActionProvider(source));
return assets.where((asset) => asset.isEditable).singleOrNull;
}, dependencies: [ownedAssetsActionProvider]);
class EditAssetAction extends AssetActionBuilder {
const EditAssetAction({required super.source});
@override
ActionItem? create(BuildContext context, WidgetRef ref) {
if (!ref.watch(_stateProvider(source).select((asset) => asset != null))) {
return null;
}
return .new(icon: Icons.tune, label: context.t.edit, onAction: () => _edit(context, ref));
}
Future<void> _edit(BuildContext context, WidgetRef ref) async {
final asset = ref.read(_stateProvider(source));
if (asset == null) {
return;
}
try {
// TODO(shenlong): Move all EXIF and Apply Edits logic onto the Route
final repository = ref.read(remoteAssetRepositoryProvider);
final (edits, exif) = await (repository.getAssetEdits(asset.id), repository.getExif(asset.id)).wait;
if (exif == null || !context.mounted) {
return;
}
ref.read(editorStateProvider.notifier).init(edits, exif);
unawaited(
context.pushRoute(
DriftEditImageRoute(
image: Image(image: getFullImageProvider(asset, edited: false)),
applyEdits: (newEdits) => applyEdits(ref, asset.id, newEdits),
),
),
);
} catch (error, stack) {
handleError(error, stack: stack, description: "Failed to open the editor for the asset");
}
}
}
@visibleForTesting
Future<void> applyEdits(WidgetRef ref, String remoteId, List<AssetEdit> edits) async {
final websocket = ref.read(websocketProvider.notifier);
bool isCurrentId(dynamic data) => data is Map && (data['asset'] as Map?)?['id'] == remoteId;
await ref.read(assetServiceProvider).applyEdits(remoteId, edits);
await Future.any([
websocket.waitForEvent('AssetEditReadyV1', isCurrentId, const .new(seconds: 10)),
websocket.waitForEvent('AssetEditReadyV2', isCurrentId, const .new(seconds: 10)),
]).catchError((_) {});
}