import 'package:flutter/material.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/services/action.service.dart'; import 'package:immich_mobile/services/timeline.service.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; final actionProvider = NotifierProvider( ActionNotifier.new, dependencies: [ multiSelectProvider, timelineServiceProvider, ], ); class ActionResult { final int count; final bool success; final String? error; const ActionResult({required this.count, required this.success, this.error}); @override String toString() => 'ActionResult(count: $count, success: $success, error: $error)'; } class ActionNotifier extends Notifier { final Logger _logger = Logger('ActionNotifier'); late ActionService _service; ActionNotifier() : super(); @override void build() { _service = ref.watch(actionServiceProvider); } List _getRemoteIdsForSource(ActionSource source) { return _getIdsForSource(source).toIds().toList(); } List _getOwnedRemoteForSource(ActionSource source) { final ownerId = ref.read(currentUserProvider)?.id; return _getIdsForSource(source) .ownedAssets(ownerId) .toIds() .toList(); } Iterable _getIdsForSource(ActionSource source) { final Set assets = switch (source) { ActionSource.timeline => ref.read(multiSelectProvider.select((s) => s.selectedAssets)), ActionSource.viewer => {}, }; return switch (T) { const (RemoteAsset) => assets.whereType(), const (LocalAsset) => assets.whereType(), _ => [], } as Iterable; } Future shareLink( ActionSource source, BuildContext context, ) async { final ids = _getRemoteIdsForSource(source); try { await _service.shareLink(ids, context); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to create shared link for assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future favorite(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.favorite(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to favorite assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future unFavorite(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.unFavorite(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to unfavorite assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future archive(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.archive(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to archive assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future unArchive(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.unArchive(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to unarchive assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future moveToLockFolder(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.moveToLockFolder(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to move assets to lock folder', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future removeFromLockFolder(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.removeFromLockFolder(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to remove assets from lock folder', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future trash(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.trash(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to trash assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future delete(ActionSource source) async { final ids = _getOwnedRemoteForSource(source); try { await _service.delete(ids); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to delete assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } Future editLocation( ActionSource source, BuildContext context, ) async { final ids = _getOwnedRemoteForSource(source); try { final isEdited = await _service.editLocation(ids, context); if (!isEdited) { return null; } return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to edit location for assets', error, stack); return ActionResult( count: ids.length, success: false, error: error.toString(), ); } } } extension on Iterable { Iterable toIds() => map((e) => e.id); Iterable ownedAssets(String? ownerId) { if (ownerId == null) return []; return whereType().where((a) => a.ownerId == ownerId); } }