Compare commits

...

14 commits

Author SHA1 Message Date
Alex Tran
4643b4894e
Merge branch 'main' of github.com:immich-app/immich into feat/mobile-delete-local-only 2023-12-02 09:28:35 -06:00
shalong-tanwen
eb7c7ca6bf Merge branch 'main' into feat/mobile-delete-local-only 2023-11-30 07:12:58 +05:30
shalong-tanwen
6efa87dc31 chore: pull main 2023-11-19 11:03:56 +05:30
shalong-tanwen
b7ac861f47 mobile: delete local only button to dialog 2023-11-11 00:29:12 +05:30
shalong-tanwen
d2669fc906 chore: pull main 2023-11-10 23:16:52 +05:30
shalong-tanwen
46587f8dd8 mobile: change content color for local only 2023-11-05 23:14:37 +05:30
shalong-tanwen
1270086b43 remove toggle inside alert and show different content 2023-11-05 19:07:27 +05:30
shalong-tanwen
5039d68dd1 Merge branch 'main' into feat/mobile-delete-local-only 2023-11-05 18:40:52 +05:30
Alex Tran
7321ec08ce Merge branch 'main' of github.com:immich-app/immich into feat/mobile-delete-local-only 2023-10-31 05:34:04 -05:00
shalong-tanwen
844eaaa0f4 chore: pull main 2023-10-23 02:35:25 +05:30
shalong-tanwen
16646d9946 chore: pull main 2023-10-22 21:05:50 +05:30
shalong-tanwen
5219f55b6a mobile: add backed up only toggle for delete device only 2023-10-21 03:13:24 +05:30
shalong-tanwen
3d7c13b30f mobile: update toast asset count 2023-10-18 04:03:59 +05:30
shalong-tanwen
1679973caf feat(mobile): delete assets from device only 2023-10-17 07:55:14 +05:30
6 changed files with 179 additions and 24 deletions

View file

@ -138,6 +138,7 @@
"control_bottom_app_bar_archive": "Archive", "control_bottom_app_bar_archive": "Archive",
"control_bottom_app_bar_create_new_album": "Create new album", "control_bottom_app_bar_create_new_album": "Create new album",
"control_bottom_app_bar_delete": "Delete", "control_bottom_app_bar_delete": "Delete",
"control_bottom_app_bar_delete_from_local": "Delete from device",
"control_bottom_app_bar_favorite": "Favorite", "control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_share": "Share", "control_bottom_app_bar_share": "Share",
"control_bottom_app_bar_share_to": "Share To", "control_bottom_app_bar_share_to": "Share To",
@ -155,8 +156,12 @@
"daily_title_text_date_year": "E, MMM dd, yyyy", "daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a", "date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
"delete_dialog_cancel": "Cancel", "delete_dialog_cancel": "Cancel",
"delete_dialog_ok": "Delete", "delete_dialog_ok": "Delete",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_dialog_title": "Delete Permanently", "delete_dialog_title": "Delete Permanently",
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
"delete_shared_link_dialog_title": "Delete Shared Link", "delete_shared_link_dialog_title": "Delete Shared Link",

View file

@ -15,6 +15,7 @@ class ControlBottomAppBar extends ConsumerWidget {
final void Function() onFavorite; final void Function() onFavorite;
final void Function() onArchive; final void Function() onArchive;
final void Function() onDelete; final void Function() onDelete;
final void Function(bool onlyMerged) onDeleteLocal;
final Function(Album album) onAddToAlbum; final Function(Album album) onAddToAlbum;
final void Function() onCreateNewAlbum; final void Function() onCreateNewAlbum;
final void Function() onUpload; final void Function() onUpload;
@ -31,6 +32,7 @@ class ControlBottomAppBar extends ConsumerWidget {
required this.onFavorite, required this.onFavorite,
required this.onArchive, required this.onArchive,
required this.onDelete, required this.onDelete,
required this.onDeleteLocal,
required this.sharedAlbums, required this.sharedAlbums,
required this.albums, required this.albums,
required this.onAddToAlbum, required this.onAddToAlbum,
@ -45,7 +47,8 @@ class ControlBottomAppBar extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
var hasRemote = var hasRemote =
selectionAssetState.hasRemote || selectionAssetState.hasMerged; selectionAssetState.hasRemote || selectionAssetState.hasMerged;
var hasLocal = selectionAssetState.hasLocal; var hasLocal =
selectionAssetState.hasLocal || selectionAssetState.hasMerged;
final trashEnabled = final trashEnabled =
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
@ -74,26 +77,48 @@ class ControlBottomAppBar extends ConsumerWidget {
label: "control_bottom_app_bar_favorite".tr(), label: "control_bottom_app_bar_favorite".tr(),
onPressed: enabled ? onFavorite : null, onPressed: enabled ? onFavorite : null,
), ),
ControlBoxButton( if (hasRemote)
iconData: Icons.delete_outline_rounded, ControlBoxButton(
label: "control_bottom_app_bar_delete".tr(), iconData: Icons.delete_outline_rounded,
onPressed: enabled label: "control_bottom_app_bar_delete".tr(),
? () { onPressed: enabled
if (!trashEnabled) { ? () {
showDialog( if (!trashEnabled) {
context: context, showDialog(
builder: (BuildContext context) { context: context,
return DeleteDialog( builder: (BuildContext context) {
onDelete: onDelete, return DeleteDialog(
); onDelete: onDelete,
}, );
); },
} else { );
onDelete(); } else {
onDelete();
}
} }
} : null,
: null, ),
), if (hasLocal)
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: ControlBoxButton(
iconData: Icons.no_cell_rounded,
label: "control_bottom_app_bar_delete_from_local".tr(),
onPressed: enabled
? () {
showDialog(
context: context,
builder: (BuildContext context) {
return DeleteLocalOnlyDialog(
showWarning: selectionAssetState.hasLocal,
onDeleteLocal: onDeleteLocal,
);
},
);
}
: null,
),
),
if (!hasLocal && selectionAssetState.selectedCount > 1) if (!hasLocal && selectionAssetState.selectedCount > 1)
ControlBoxButton( ControlBoxButton(
iconData: Icons.filter_none_rounded, iconData: Icons.filter_none_rounded,
@ -148,7 +173,7 @@ class ControlBottomAppBar extends ConsumerWidget {
const CustomDraggingHandle(), const CustomDraggingHandle(),
const SizedBox(height: 12), const SizedBox(height: 12),
SizedBox( SizedBox(
height: 70, height: hasLocal ? 90 : 75,
child: ListView( child: ListView(
shrinkWrap: true, shrinkWrap: true,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
@ -186,6 +211,69 @@ class ControlBottomAppBar extends ConsumerWidget {
} }
} }
class DeleteLocalOnlyDialog extends StatelessWidget {
final bool showWarning;
final void Function(bool onlyMerged) onDeleteLocal;
const DeleteLocalOnlyDialog({
super.key,
this.showWarning = false,
required this.onDeleteLocal,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
title: const Text("delete_dialog_title").tr(),
content: Text(
showWarning
? "delete_dialog_alert_local_non_backed_up"
: "delete_dialog_alert_local",
).tr(),
actions: [
TextButton(
onPressed: () => context.pop(false),
child: Text(
"delete_dialog_cancel",
style: TextStyle(
color: context.primaryColor,
fontWeight: FontWeight.bold,
),
).tr(),
),
if (showWarning)
TextButton(
onPressed: () {
context.pop(true);
onDeleteLocal(true);
},
child: Text(
"delete_local_dialog_ok_backed_up_only",
style: TextStyle(
color: showWarning ? null : Colors.red[400],
fontWeight: FontWeight.bold,
),
).tr(),
),
TextButton(
onPressed: () {
context.pop(true);
onDeleteLocal(false);
},
child: Text(
showWarning ? "delete_local_dialog_ok_force" : "delete_dialog_ok",
style: TextStyle(
color: Colors.red[400],
fontWeight: FontWeight.bold,
),
).tr(),
),
],
);
}
}
class AddToAlbumTitleRow extends StatelessWidget { class AddToAlbumTitleRow extends StatelessWidget {
const AddToAlbumTitleRow({ const AddToAlbumTitleRow({
super.key, super.key,

View file

@ -209,6 +209,29 @@ class HomePage extends HookConsumerWidget {
} }
} }
void onDeleteLocal(bool onlyMerged) async {
processing.value = true;
try {
final localIds = selection.value.where((a) => a.isLocal).toList();
final isDeleted = await ref
.read(assetProvider.notifier)
.deleteLocalAssets(localIds, onlyMerged: onlyMerged);
if (isDeleted) {
final assetOrAssets = localIds.length > 1 ? 'assets' : 'asset';
ImmichToast.show(
context: context,
msg:
'${localIds.length} $assetOrAssets removed permanently from your device',
gravity: ToastGravity.BOTTOM,
);
}
selectionEnabledHook.value = false;
} finally {
processing.value = false;
}
}
void onUpload() { void onUpload() {
processing.value = true; processing.value = true;
selectionEnabledHook.value = false; selectionEnabledHook.value = false;
@ -403,6 +426,7 @@ class HomePage extends HookConsumerWidget {
onFavorite: onFavoriteAssets, onFavorite: onFavoriteAssets,
onArchive: onArchiveAsset, onArchive: onArchiveAsset,
onDelete: onDelete, onDelete: onDelete,
onDeleteLocal: onDeleteLocal,
onAddToAlbum: onAddToAlbum, onAddToAlbum: onAddToAlbum,
albums: albums, albums: albums,
sharedAlbums: sharedAlbums, sharedAlbums: sharedAlbums,

View file

@ -91,6 +91,43 @@ class AssetNotifier extends StateNotifier<bool> {
await _syncService.syncNewAssetToDb(newAsset); await _syncService.syncNewAssetToDb(newAsset);
} }
Future<bool> deleteLocalAssets(
Iterable<Asset> deleteAssets, {
bool onlyMerged = false,
}) async {
_deleteInProgress = true;
state = true;
try {
final assets = onlyMerged
? deleteAssets.where((e) => e.storage == AssetState.merged)
: deleteAssets;
final localDeleted = await _deleteLocalAssets(assets);
if (localDeleted.isNotEmpty) {
final localOnlyIds = deleteAssets
.where((e) => e.storage == AssetState.local)
.map((e) => e.id)
.toList();
final mergedAssets =
deleteAssets.where((e) => e.storage == AssetState.merged).map((e) {
e.localId = null;
return e;
}).toList();
await _db.writeTxn(() async {
if (mergedAssets.isNotEmpty) {
await _db.assets.putAll(mergedAssets);
}
await _db.exifInfos.deleteAll(localOnlyIds);
await _db.assets.deleteAll(localOnlyIds);
});
return true;
}
} finally {
_deleteInProgress = false;
state = false;
}
return false;
}
Future<bool> deleteAssets( Future<bool> deleteAssets(
Iterable<Asset> deleteAssets, { Iterable<Asset> deleteAssets, {
bool force = false, bool force = false,

View file

@ -1,9 +1,8 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
class ConfirmDialog extends ConsumerWidget { class ConfirmDialog extends StatelessWidget {
final Function onOk; final Function onOk;
final String title; final String title;
final String content; final String content;
@ -20,7 +19,7 @@ class ConfirmDialog extends ConsumerWidget {
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
title: Text(title).tr(), title: Text(title).tr(),

View file

@ -44,6 +44,8 @@ class ControlBoxButton extends StatelessWidget {
Text( Text(
label, label,
style: const TextStyle(fontSize: 12.0), style: const TextStyle(fontSize: 12.0),
maxLines: 2,
textAlign: TextAlign.center,
), ),
], ],
), ),