Compare commits
14 commits
main
...
feat/mobil
Author | SHA1 | Date | |
---|---|---|---|
|
4643b4894e | ||
|
eb7c7ca6bf | ||
|
6efa87dc31 | ||
|
b7ac861f47 | ||
|
d2669fc906 | ||
|
46587f8dd8 | ||
|
1270086b43 | ||
|
5039d68dd1 | ||
|
7321ec08ce | ||
|
844eaaa0f4 | ||
|
16646d9946 | ||
|
5219f55b6a | ||
|
3d7c13b30f | ||
|
1679973caf |
6 changed files with 179 additions and 24 deletions
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue