feat(mobile): unify partner assets on timeline (#4974)

* feat(mobile): unify partner assets on timeline

* skip non-owned assets in bulk actions

* add message when trying to delete partner assets
This commit is contained in:
Fynn Petersen-Frey 2023-11-13 16:54:41 +01:00 committed by GitHub
parent 0c482960ce
commit 9fa9ad05b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 274 additions and 138 deletions

View file

@ -44,6 +44,7 @@ class HomePage extends HookConsumerWidget {
final sharedAlbums = ref.watch(sharedAlbumProvider);
final albumService = ref.watch(albumServiceProvider);
final currentUser = ref.watch(currentUserProvider);
final timelineUsers = ref.watch(timelineUsersIdsProvider);
final trashEnabled =
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
@ -55,6 +56,7 @@ class HomePage extends HookConsumerWidget {
() {
ref.read(websocketProvider.notifier).connect();
Future(() => ref.read(assetProvider.notifier).getAllAsset());
ref.read(assetProvider.notifier).getPartnerAssets();
ref.read(albumProvider.notifier).getAllAlbums();
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
ref.read(serverInfoProvider.notifier).getServerInfo();
@ -84,20 +86,49 @@ class HomePage extends HookConsumerWidget {
SelectionAssetState.fromSelection(selectedAssets);
}
List<Asset> remoteOnlySelection({String? localErrorMessage}) {
final Set<Asset> assets = selection.value;
errorBuilder(String? msg) => msg != null && msg.isNotEmpty
? () => ImmichToast.show(
context: context,
msg: msg,
gravity: ToastGravity.BOTTOM,
)
: null;
Iterable<Asset> remoteOnly(
Iterable<Asset> assets, {
void Function()? errorCallback,
}) {
final bool onlyRemote = assets.every((e) => e.isRemote);
if (!onlyRemote) {
if (localErrorMessage != null && localErrorMessage.isNotEmpty) {
ImmichToast.show(
context: context,
msg: localErrorMessage,
gravity: ToastGravity.BOTTOM,
);
}
return assets.where((a) => a.isRemote).toList();
if (errorCallback != null) errorCallback();
return assets.where((a) => a.isRemote);
}
return assets.toList();
return assets;
}
Iterable<Asset> ownedOnly(
Iterable<Asset> assets, {
void Function()? errorCallback,
}) {
if (currentUser == null) return [];
final userId = currentUser.isarId;
final bool onlyOwned = assets.every((e) => e.ownerId == userId);
if (!onlyOwned) {
if (errorCallback != null) errorCallback();
return assets.where((a) => a.ownerId == userId);
}
return assets;
}
Iterable<Asset> ownedRemoteSelection({
String? localErrorMessage,
String? ownerErrorMessage,
}) {
final assets = selection.value;
return remoteOnly(
ownedOnly(assets, errorCallback: errorBuilder(ownerErrorMessage)),
errorCallback: errorBuilder(localErrorMessage),
);
}
void onShareAssets(bool shareLocal) {
@ -105,7 +136,7 @@ class HomePage extends HookConsumerWidget {
if (shareLocal) {
handleShareAssets(ref, context, selection.value.toList());
} else {
final ids = remoteOnlySelection().map((e) => e.remoteId!);
final ids = ownedRemoteSelection().map((e) => e.remoteId!);
context.autoPush(SharedLinkEditRoute(assetsList: ids.toList()));
}
processing.value = false;
@ -115,11 +146,12 @@ class HomePage extends HookConsumerWidget {
void onFavoriteAssets() async {
processing.value = true;
try {
final remoteAssets = remoteOnlySelection(
final remoteAssets = ownedRemoteSelection(
localErrorMessage: 'home_page_favorite_err_local'.tr(),
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
);
if (remoteAssets.isNotEmpty) {
await handleFavoriteAssets(ref, context, remoteAssets);
await handleFavoriteAssets(ref, context, remoteAssets.toList());
}
} finally {
processing.value = false;
@ -130,10 +162,11 @@ class HomePage extends HookConsumerWidget {
void onArchiveAsset() async {
processing.value = true;
try {
final remoteAssets = remoteOnlySelection(
final remoteAssets = ownedRemoteSelection(
localErrorMessage: 'home_page_archive_err_local'.tr(),
ownerErrorMessage: 'home_page_archive_err_partner'.tr(),
);
await handleArchiveAssets(ref, context, remoteAssets);
await handleArchiveAssets(ref, context, remoteAssets.toList());
} finally {
processing.value = false;
selectionEnabledHook.value = false;
@ -143,12 +176,16 @@ class HomePage extends HookConsumerWidget {
void onDelete() async {
processing.value = true;
try {
final toDelete = ownedOnly(
selection.value,
errorCallback: errorBuilder('home_page_delete_err_partner'.tr()),
).toList();
await ref
.read(assetProvider.notifier)
.deleteAssets(selection.value, force: !trashEnabled);
.deleteAssets(toDelete, force: !trashEnabled);
final hasRemote = selection.value.any((a) => a.isRemote);
final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset';
final hasRemote = toDelete.any((a) => a.isRemote);
final assetOrAssets = toDelete.length > 1 ? 'assets' : 'asset';
final trashOrRemoved =
!trashEnabled ? 'deleted permanently' : 'trashed';
if (hasRemote) {
@ -180,8 +217,9 @@ class HomePage extends HookConsumerWidget {
void onAddToAlbum(Album album) async {
processing.value = true;
try {
final Iterable<Asset> assets = remoteOnlySelection(
final Iterable<Asset> assets = ownedRemoteSelection(
localErrorMessage: "home_page_add_to_album_err_local".tr(),
ownerErrorMessage: "home_page_album_err_partner".tr(),
);
if (assets.isEmpty) {
return;
@ -228,8 +266,9 @@ class HomePage extends HookConsumerWidget {
void onCreateNewAlbum() async {
processing.value = true;
try {
final Iterable<Asset> assets = remoteOnlySelection(
final Iterable<Asset> assets = ownedRemoteSelection(
localErrorMessage: "home_page_add_to_album_err_local".tr(),
ownerErrorMessage: "home_page_album_err_partner".tr(),
);
if (assets.isEmpty) {
return;
@ -270,6 +309,9 @@ class HomePage extends HookConsumerWidget {
Future<void> refreshAssets() async {
final fullRefresh = refreshCount.value > 0;
await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh);
if (timelineUsers.length > 1) {
await ref.read(assetProvider.notifier).getPartnerAssets();
}
if (fullRefresh) {
// refresh was forced: user requested another refresh within 2 seconds
refreshCount.value = 0;
@ -330,7 +372,13 @@ class HomePage extends HookConsumerWidget {
bottom: false,
child: Stack(
children: [
ref.watch(assetsProvider(currentUser?.isarId)).when(
ref
.watch(
timelineUsers.length > 1
? multiUserAssetsProvider(timelineUsers)
: assetsProvider(currentUser?.isarId),
)
.when(
data: (data) => data.isEmpty
? buildLoadingIndicator()
: ImmichAssetGrid(
@ -338,11 +386,10 @@ class HomePage extends HookConsumerWidget {
listener: selectionListener,
selectionActive: selectionEnabledHook.value,
onRefresh: refreshAssets,
topWidget: (currentUser != null &&
currentUser.memoryEnabled != null &&
currentUser.memoryEnabled!)
? const MemoryLane()
: const SizedBox(),
topWidget:
(currentUser != null && currentUser.memoryEnabled)
? const MemoryLane()
: const SizedBox(),
showStack: true,
),
error: (error, _) => Center(child: Text(error.toString())),