mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
feat: favorite action (#19623)
This commit is contained in:
parent
fa5f30d9ca
commit
4c3fcdc745
@ -983,6 +983,7 @@
|
|||||||
"failed_to_load_assets": "Failed to load assets",
|
"failed_to_load_assets": "Failed to load assets",
|
||||||
"failed_to_load_folder": "Failed to load folder",
|
"failed_to_load_folder": "Failed to load folder",
|
||||||
"favorite": "Favorite",
|
"favorite": "Favorite",
|
||||||
|
"favorite_action_prompt": "{count} added to Favorites",
|
||||||
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
|
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
|
||||||
"favorites": "Favorites",
|
"favorites": "Favorites",
|
||||||
"favorites_page_no_favorites": "No favorite assets found",
|
"favorites_page_no_favorites": "No favorite assets found",
|
||||||
|
@ -12,3 +12,5 @@ enum TextSearchType {
|
|||||||
enum AssetVisibilityEnum { timeline, hidden, archive, locked }
|
enum AssetVisibilityEnum { timeline, hidden, archive, locked }
|
||||||
|
|
||||||
enum SortUserBy { id }
|
enum SortUserBy { id }
|
||||||
|
|
||||||
|
enum ActionSource { timeline, viewer }
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
part 'asset.model.dart';
|
part 'remote_asset.model.dart';
|
||||||
part 'local_asset.model.dart';
|
part 'local_asset.model.dart';
|
||||||
|
|
||||||
enum AssetType {
|
enum AssetType {
|
||||||
|
@ -8,16 +8,18 @@ enum AssetVisibility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Model for an asset stored in the server
|
// Model for an asset stored in the server
|
||||||
class Asset extends BaseAsset {
|
class RemoteAsset extends BaseAsset {
|
||||||
final String id;
|
final String id;
|
||||||
final String? localId;
|
final String? localId;
|
||||||
final String? thumbHash;
|
final String? thumbHash;
|
||||||
final AssetVisibility visibility;
|
final AssetVisibility visibility;
|
||||||
|
final String ownerId;
|
||||||
|
|
||||||
const Asset({
|
const RemoteAsset({
|
||||||
required this.id,
|
required this.id,
|
||||||
this.localId,
|
this.localId,
|
||||||
required super.name,
|
required super.name,
|
||||||
|
required this.ownerId,
|
||||||
required super.checksum,
|
required super.checksum,
|
||||||
required super.type,
|
required super.type,
|
||||||
required super.createdAt,
|
required super.createdAt,
|
||||||
@ -37,16 +39,17 @@ class Asset extends BaseAsset {
|
|||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return '''Asset {
|
return '''Asset {
|
||||||
id: $id,
|
id: $id,
|
||||||
name: $name,
|
name: $name,
|
||||||
type: $type,
|
ownerId: $ownerId,
|
||||||
createdAt: $createdAt,
|
type: $type,
|
||||||
updatedAt: $updatedAt,
|
createdAt: $createdAt,
|
||||||
width: ${width ?? "<NA>"},
|
updatedAt: $updatedAt,
|
||||||
height: ${height ?? "<NA>"},
|
width: ${width ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
localId: ${localId ?? "<NA>"},
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
isFavorite: $isFavorite,
|
localId: ${localId ?? "<NA>"},
|
||||||
|
isFavorite: $isFavorite,
|
||||||
thumbHash: ${thumbHash ?? "<NA>"},
|
thumbHash: ${thumbHash ?? "<NA>"},
|
||||||
visibility: $visibility,
|
visibility: $visibility,
|
||||||
}''';
|
}''';
|
||||||
@ -54,10 +57,11 @@ class Asset extends BaseAsset {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! Asset) return false;
|
if (other is! RemoteAsset) return false;
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return super == other &&
|
return super == other &&
|
||||||
id == other.id &&
|
id == other.id &&
|
||||||
|
ownerId == other.ownerId &&
|
||||||
localId == other.localId &&
|
localId == other.localId &&
|
||||||
thumbHash == other.thumbHash &&
|
thumbHash == other.thumbHash &&
|
||||||
visibility == other.visibility;
|
visibility == other.visibility;
|
||||||
@ -67,6 +71,7 @@ class Asset extends BaseAsset {
|
|||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
super.hashCode ^
|
super.hashCode ^
|
||||||
id.hashCode ^
|
id.hashCode ^
|
||||||
|
ownerId.hashCode ^
|
||||||
localId.hashCode ^
|
localId.hashCode ^
|
||||||
thumbHash.hashCode ^
|
thumbHash.hashCode ^
|
||||||
visibility.hashCode;
|
visibility.hashCode;
|
@ -37,9 +37,10 @@ class RemoteAssetEntity extends Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
||||||
Asset toDto() => Asset(
|
RemoteAsset toDto() => RemoteAsset(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
|
ownerId: ownerId,
|
||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
type: type,
|
type: type,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
|
final remoteAssetRepositoryProvider = Provider<RemoteAssetRepository>(
|
||||||
|
(ref) => RemoteAssetRepository(ref.watch(driftProvider)),
|
||||||
|
);
|
||||||
|
|
||||||
|
class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||||
|
final Drift _db;
|
||||||
|
const RemoteAssetRepository(this._db) : super(_db);
|
||||||
|
|
||||||
|
Future<void> updateFavorite(List<String> ids, bool isFavorite) {
|
||||||
|
return _db.batch((batch) async {
|
||||||
|
for (final id in ids) {
|
||||||
|
batch.update(
|
||||||
|
_db.remoteAssetEntity,
|
||||||
|
RemoteAssetEntityCompanion(isFavorite: Value(isFavorite)),
|
||||||
|
where: (e) => e.id.equals(id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -70,36 +70,38 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
return _db.mergedAssetDrift
|
return _db.mergedAssetDrift
|
||||||
.mergedAsset(userIds, limit: Limit(count, offset))
|
.mergedAsset(userIds, limit: Limit(count, offset))
|
||||||
.map(
|
.map(
|
||||||
(row) => row.remoteId != null
|
(row) {
|
||||||
? Asset(
|
return row.remoteId != null && row.ownerId != null
|
||||||
id: row.remoteId!,
|
? RemoteAsset(
|
||||||
localId: row.localId,
|
id: row.remoteId!,
|
||||||
name: row.name,
|
localId: row.localId,
|
||||||
checksum: row.checksum,
|
name: row.name,
|
||||||
type: row.type,
|
ownerId: row.ownerId!,
|
||||||
createdAt: row.createdAt,
|
checksum: row.checksum,
|
||||||
updatedAt: row.updatedAt,
|
type: row.type,
|
||||||
thumbHash: row.thumbHash,
|
createdAt: row.createdAt,
|
||||||
width: row.width,
|
updatedAt: row.updatedAt,
|
||||||
height: row.height,
|
thumbHash: row.thumbHash,
|
||||||
isFavorite: row.isFavorite,
|
width: row.width,
|
||||||
durationInSeconds: row.durationInSeconds,
|
height: row.height,
|
||||||
)
|
isFavorite: row.isFavorite,
|
||||||
: LocalAsset(
|
durationInSeconds: row.durationInSeconds,
|
||||||
id: row.localId!,
|
)
|
||||||
remoteId: row.remoteId,
|
: LocalAsset(
|
||||||
name: row.name,
|
id: row.localId!,
|
||||||
checksum: row.checksum,
|
remoteId: row.remoteId,
|
||||||
type: row.type,
|
name: row.name,
|
||||||
createdAt: row.createdAt,
|
checksum: row.checksum,
|
||||||
updatedAt: row.updatedAt,
|
type: row.type,
|
||||||
width: row.width,
|
createdAt: row.createdAt,
|
||||||
height: row.height,
|
updatedAt: row.updatedAt,
|
||||||
isFavorite: row.isFavorite,
|
width: row.width,
|
||||||
durationInSeconds: row.durationInSeconds,
|
height: row.height,
|
||||||
),
|
isFavorite: row.isFavorite,
|
||||||
)
|
durationInSeconds: row.durationInSeconds,
|
||||||
.get();
|
);
|
||||||
|
},
|
||||||
|
).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Bucket>> watchLocalBucket(
|
Stream<List<Bucket>> watchLocalBucket(
|
||||||
|
@ -1,16 +1,73 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
|
||||||
class FavoriteActionButton extends ConsumerWidget {
|
class FavoriteActionButton extends ConsumerWidget {
|
||||||
const FavoriteActionButton({super.key});
|
final ActionSource source;
|
||||||
|
|
||||||
|
const FavoriteActionButton({super.key, required this.source});
|
||||||
|
|
||||||
|
onAction(BuildContext context, WidgetRef ref) {
|
||||||
|
switch (source) {
|
||||||
|
case ActionSource.timeline:
|
||||||
|
timelineAction(context, ref);
|
||||||
|
case ActionSource.viewer:
|
||||||
|
viewerAction(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void timelineAction(BuildContext context, WidgetRef ref) {
|
||||||
|
final user = ref.read(currentUserProvider);
|
||||||
|
if (user == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ids = ref
|
||||||
|
.read(multiSelectProvider.select((value) => value.selectedAssets))
|
||||||
|
.whereType<RemoteAsset>()
|
||||||
|
.where((asset) => asset.ownerId == user.id)
|
||||||
|
.map((asset) => asset.id)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (ids.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.read(actionProvider.notifier).favorite(ids);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
|
final toastMessage = 'favorite_action_prompt'.t(
|
||||||
|
context: context,
|
||||||
|
args: {'count': ids.length.toString()},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: toastMessage,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void viewerAction(WidgetRef _) {
|
||||||
|
UnimplementedError("Viewer action for favorite is not implemented yet.");
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return BaseActionButton(
|
return BaseActionButton(
|
||||||
iconData: Icons.favorite_border_rounded,
|
iconData: Icons.favorite_border_rounded,
|
||||||
label: "favorite".t(context: context),
|
label: "favorite".t(context: context),
|
||||||
|
onPressed: () => onAction(context, ref),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
|
||||||
@ -35,7 +36,7 @@ class HomeBottomAppBar extends ConsumerWidget {
|
|||||||
if (multiselect.hasRemote) ...[
|
if (multiselect.hasRemote) ...[
|
||||||
const ShareLinkActionButton(),
|
const ShareLinkActionButton(),
|
||||||
const ArchiveActionButton(),
|
const ArchiveActionButton(),
|
||||||
const FavoriteActionButton(),
|
const FavoriteActionButton(source: ActionSource.timeline),
|
||||||
const DownloadActionButton(),
|
const DownloadActionButton(),
|
||||||
isTrashEnable
|
isTrashEnable
|
||||||
? const TrashActionButton()
|
? const TrashActionButton()
|
||||||
|
@ -32,7 +32,7 @@ class Thumbnail extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asset is Asset) {
|
if (asset is RemoteAsset) {
|
||||||
return RemoteThumbProvider(
|
return RemoteThumbProvider(
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
@ -45,7 +45,8 @@ class Thumbnail extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final thumbHash = asset is Asset ? (asset as Asset).thumbHash : null;
|
final thumbHash =
|
||||||
|
asset is RemoteAsset ? (asset as RemoteAsset).thumbHash : null;
|
||||||
final provider = imageProvider(asset: asset, size: size);
|
final provider = imageProvider(asset: asset, size: size);
|
||||||
|
|
||||||
return OctoImage.fromSet(
|
return OctoImage.fromSet(
|
||||||
|
@ -30,9 +30,8 @@ class ThumbnailTile extends ConsumerWidget {
|
|||||||
? context.primaryColor.darken(amount: 0.6)
|
? context.primaryColor.darken(amount: 0.6)
|
||||||
: context.primaryColor.lighten(amount: 0.8);
|
: context.primaryColor.lighten(amount: 0.8);
|
||||||
|
|
||||||
final isSelected = ref
|
final multiselect = ref.watch(multiSelectProvider);
|
||||||
.watch(multiSelectProvider.select((state) => state.selectedAssets))
|
final isSelected = multiselect.selectedAssets.contains(asset);
|
||||||
.contains(asset);
|
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
|
@ -185,7 +185,7 @@ class FixedSegment extends Segment {
|
|||||||
/// and prevents duplicate keys even when assets have the same name/timestamp
|
/// and prevents duplicate keys even when assets have the same name/timestamp
|
||||||
String _generateUniqueKey(BaseAsset asset, int assetIndex) {
|
String _generateUniqueKey(BaseAsset asset, int assetIndex) {
|
||||||
// Try to get the most unique identifier based on asset type
|
// Try to get the most unique identifier based on asset type
|
||||||
if (asset is Asset) {
|
if (asset is RemoteAsset) {
|
||||||
// For remote/merged assets, use the remote ID which is globally unique
|
// For remote/merged assets, use the remote ID which is globally unique
|
||||||
return 'asset_${asset.id}';
|
return 'asset_${asset.id}';
|
||||||
} else if (asset is LocalAsset) {
|
} else if (asset is LocalAsset) {
|
||||||
|
28
mobile/lib/providers/infrastructure/action.provider.dart
Normal file
28
mobile/lib/providers/infrastructure/action.provider.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:immich_mobile/services/action.service.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
final actionProvider = NotifierProvider<ActionNotifier, void>(
|
||||||
|
ActionNotifier.new,
|
||||||
|
dependencies: [
|
||||||
|
actionServiceProvider,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
class ActionNotifier extends Notifier<void> {
|
||||||
|
late final ActionService _service;
|
||||||
|
|
||||||
|
ActionNotifier() : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {
|
||||||
|
_service = ref.watch(actionServiceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> favorite(List<String> ids) async {
|
||||||
|
await _service.favorite(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unFavorite(List<String> ids) async {
|
||||||
|
await _service.unFavorite(ids);
|
||||||
|
}
|
||||||
|
}
|
@ -13,9 +13,11 @@ final multiSelectProvider =
|
|||||||
|
|
||||||
class MultiSelectState {
|
class MultiSelectState {
|
||||||
final Set<BaseAsset> selectedAssets;
|
final Set<BaseAsset> selectedAssets;
|
||||||
|
final int lastUpdatedTime;
|
||||||
|
|
||||||
const MultiSelectState({
|
const MultiSelectState({
|
||||||
required this.selectedAssets,
|
required this.selectedAssets,
|
||||||
|
required this.lastUpdatedTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool get isEnabled => selectedAssets.isNotEmpty;
|
bool get isEnabled => selectedAssets.isNotEmpty;
|
||||||
@ -30,25 +32,29 @@ class MultiSelectState {
|
|||||||
|
|
||||||
MultiSelectState copyWith({
|
MultiSelectState copyWith({
|
||||||
Set<BaseAsset>? selectedAssets,
|
Set<BaseAsset>? selectedAssets,
|
||||||
|
int? lastUpdatedTime,
|
||||||
}) {
|
}) {
|
||||||
return MultiSelectState(
|
return MultiSelectState(
|
||||||
selectedAssets: selectedAssets ?? this.selectedAssets,
|
selectedAssets: selectedAssets ?? this.selectedAssets,
|
||||||
|
lastUpdatedTime: lastUpdatedTime ?? this.lastUpdatedTime,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'MultiSelectState(selectedAssets: $selectedAssets)';
|
String toString() =>
|
||||||
|
'MultiSelectState(selectedAssets: $selectedAssets, lastUpdatedTime: $lastUpdatedTime)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(covariant MultiSelectState other) {
|
bool operator ==(covariant MultiSelectState other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
final listEquals = const DeepCollectionEquality().equals;
|
final listEquals = const DeepCollectionEquality().equals;
|
||||||
|
|
||||||
return listEquals(other.selectedAssets, selectedAssets);
|
return listEquals(other.selectedAssets, selectedAssets) &&
|
||||||
|
other.lastUpdatedTime == lastUpdatedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => selectedAssets.hashCode;
|
int get hashCode => selectedAssets.hashCode ^ lastUpdatedTime.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
||||||
@ -60,6 +66,7 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
|||||||
|
|
||||||
return const MultiSelectState(
|
return const MultiSelectState(
|
||||||
selectedAssets: {},
|
selectedAssets: {},
|
||||||
|
lastUpdatedTime: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +104,13 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
state = MultiSelectState(
|
||||||
|
selectedAssets: {},
|
||||||
|
lastUpdatedTime: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Bucket bulk operations
|
/// Bucket bulk operations
|
||||||
void selectBucket(int offset, int bucketCount) async {
|
void selectBucket(int offset, int bucketCount) async {
|
||||||
final assets = await _timelineService.loadAssets(offset, bucketCount);
|
final assets = await _timelineService.loadAssets(offset, bucketCount);
|
||||||
|
@ -56,6 +56,15 @@ class AssetApiRepository extends ApiRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateFavorite(
|
||||||
|
List<String> ids,
|
||||||
|
bool isFavorite,
|
||||||
|
) async {
|
||||||
|
return _api.updateAssets(
|
||||||
|
AssetBulkUpdateDto(ids: ids, isFavorite: isFavorite),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_mapVisibility(AssetVisibilityEnum visibility) => switch (visibility) {
|
_mapVisibility(AssetVisibilityEnum visibility) => switch (visibility) {
|
||||||
AssetVisibilityEnum.timeline => AssetVisibility.timeline,
|
AssetVisibilityEnum.timeline => AssetVisibility.timeline,
|
||||||
AssetVisibilityEnum.hidden => AssetVisibility.hidden,
|
AssetVisibilityEnum.hidden => AssetVisibility.hidden,
|
||||||
|
36
mobile/lib/services/action.service.dart
Normal file
36
mobile/lib/services/action.service.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||||
|
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
final actionServiceProvider = Provider<ActionService>(
|
||||||
|
(ref) => ActionService(
|
||||||
|
ref.watch(assetApiRepositoryProvider),
|
||||||
|
ref.watch(remoteAssetRepositoryProvider),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
class ActionService {
|
||||||
|
final AssetApiRepository _assetApiRepository;
|
||||||
|
final RemoteAssetRepository _remoteAssetRepository;
|
||||||
|
|
||||||
|
const ActionService(this._assetApiRepository, this._remoteAssetRepository);
|
||||||
|
|
||||||
|
Future<void> favorite(List<String> remoteIds) async {
|
||||||
|
try {
|
||||||
|
await _assetApiRepository.updateFavorite(remoteIds, true);
|
||||||
|
await _remoteAssetRepository.updateFavorite(remoteIds, true);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error favoriting assets: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unFavorite(List<String> remoteIds) async {
|
||||||
|
try {
|
||||||
|
await _assetApiRepository.updateFavorite(remoteIds, false);
|
||||||
|
await _remoteAssetRepository.updateFavorite(remoteIds, false);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error unfavoriting assets: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user