2025-07-01 23:18:23 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2025-07-01 08:10:25 +05:30
|
|
|
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';
|
2025-06-30 12:21:09 -05:00
|
|
|
import 'package:immich_mobile/services/action.service.dart';
|
2025-07-01 20:53:20 +05:30
|
|
|
import 'package:immich_mobile/services/timeline.service.dart';
|
2025-07-01 08:10:25 +05:30
|
|
|
import 'package:logging/logging.dart';
|
2025-06-30 12:21:09 -05:00
|
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
|
|
|
|
|
|
final actionProvider = NotifierProvider<ActionNotifier, void>(
|
|
|
|
ActionNotifier.new,
|
|
|
|
dependencies: [
|
2025-07-01 20:53:20 +05:30
|
|
|
multiSelectProvider,
|
2025-07-01 08:10:25 +05:30
|
|
|
timelineServiceProvider,
|
2025-06-30 12:21:09 -05:00
|
|
|
],
|
|
|
|
);
|
|
|
|
|
2025-07-01 08:10:25 +05:30
|
|
|
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)';
|
|
|
|
}
|
|
|
|
|
2025-06-30 12:21:09 -05:00
|
|
|
class ActionNotifier extends Notifier<void> {
|
2025-07-01 08:10:25 +05:30
|
|
|
final Logger _logger = Logger('ActionNotifier');
|
2025-07-01 20:53:20 +05:30
|
|
|
late ActionService _service;
|
2025-06-30 12:21:09 -05:00
|
|
|
|
|
|
|
ActionNotifier() : super();
|
|
|
|
|
|
|
|
@override
|
|
|
|
void build() {
|
|
|
|
_service = ref.watch(actionServiceProvider);
|
|
|
|
}
|
|
|
|
|
2025-07-02 09:38:52 +05:30
|
|
|
List<String> _getRemoteIdsForSource(ActionSource source) {
|
|
|
|
return _getIdsForSource<RemoteAsset>(source).toIds().toList();
|
|
|
|
}
|
2025-07-01 08:10:25 +05:30
|
|
|
|
2025-07-02 09:38:52 +05:30
|
|
|
List<String> _getOwnedRemoteForSource(ActionSource source) {
|
|
|
|
final ownerId = ref.read(currentUserProvider)?.id;
|
|
|
|
return _getIdsForSource<RemoteAsset>(source)
|
|
|
|
.ownedAssets(ownerId)
|
|
|
|
.toIds()
|
|
|
|
.toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
Iterable<T> _getIdsForSource<T extends BaseAsset>(ActionSource source) {
|
2025-07-01 08:10:25 +05:30
|
|
|
final Set<BaseAsset> assets = switch (source) {
|
|
|
|
ActionSource.timeline =>
|
|
|
|
ref.read(multiSelectProvider.select((s) => s.selectedAssets)),
|
|
|
|
ActionSource.viewer => {},
|
|
|
|
};
|
|
|
|
|
|
|
|
return switch (T) {
|
2025-07-02 09:38:52 +05:30
|
|
|
const (RemoteAsset) => assets.whereType<RemoteAsset>(),
|
|
|
|
const (LocalAsset) => assets.whereType<LocalAsset>(),
|
|
|
|
_ => <T>[],
|
|
|
|
} as Iterable<T>;
|
2025-07-01 08:10:25 +05:30
|
|
|
}
|
|
|
|
|
2025-07-01 23:18:23 +08:00
|
|
|
Future<ActionResult> shareLink(
|
|
|
|
ActionSource source,
|
|
|
|
BuildContext context,
|
|
|
|
) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getRemoteIdsForSource(source);
|
2025-07-01 23:18:23 +08:00
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-01 08:10:25 +05:30
|
|
|
Future<ActionResult> favorite(ActionSource source) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getOwnedRemoteForSource(source);
|
2025-07-01 08:10:25 +05:30
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
2025-06-30 12:21:09 -05:00
|
|
|
}
|
|
|
|
|
2025-07-01 08:10:25 +05:30
|
|
|
Future<ActionResult> unFavorite(ActionSource source) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getOwnedRemoteForSource(source);
|
2025-07-01 08:10:25 +05:30
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
2025-06-30 12:21:09 -05:00
|
|
|
}
|
2025-07-01 03:38:15 +08:00
|
|
|
|
2025-07-01 08:10:25 +05:30
|
|
|
Future<ActionResult> archive(ActionSource source) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getOwnedRemoteForSource(source);
|
2025-07-01 08:10:25 +05:30
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
2025-07-01 03:38:15 +08:00
|
|
|
}
|
|
|
|
|
2025-07-01 08:10:25 +05:30
|
|
|
Future<ActionResult> unArchive(ActionSource source) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getOwnedRemoteForSource(source);
|
2025-07-01 08:10:25 +05:30
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
2025-07-01 03:38:15 +08:00
|
|
|
}
|
2025-07-01 09:03:45 -05:00
|
|
|
|
|
|
|
Future<ActionResult> moveToLockFolder(ActionSource source) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getOwnedRemoteForSource(source);
|
2025-07-01 09:03:45 -05:00
|
|
|
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<ActionResult> removeFromLockFolder(ActionSource source) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getOwnedRemoteForSource(source);
|
2025-07-01 09:03:45 -05:00
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2025-07-02 00:52:11 +08:00
|
|
|
|
2025-07-03 01:26:07 +08:00
|
|
|
Future<ActionResult> 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<ActionResult> 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(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-02 00:52:11 +08:00
|
|
|
Future<ActionResult?> editLocation(
|
|
|
|
ActionSource source,
|
|
|
|
BuildContext context,
|
|
|
|
) async {
|
2025-07-02 09:38:52 +05:30
|
|
|
final ids = _getOwnedRemoteForSource(source);
|
2025-07-02 00:52:11 +08:00
|
|
|
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(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2025-06-30 12:21:09 -05:00
|
|
|
}
|
2025-07-02 09:38:52 +05:30
|
|
|
|
|
|
|
extension on Iterable<RemoteAsset> {
|
|
|
|
Iterable<String> toIds() => map((e) => e.id);
|
|
|
|
|
|
|
|
Iterable<RemoteAsset> ownedAssets(String? ownerId) {
|
|
|
|
if (ownerId == null) return [];
|
|
|
|
return whereType<RemoteAsset>().where((a) => a.ownerId == ownerId);
|
|
|
|
}
|
|
|
|
}
|