feat(mobile): add unstack button (#21869)
* fix: add unstack button * feat: allow unstacking inside of asset viewer * chore: update tests * chore: rework unstacking in asset viewer --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: bwees <brandonwees@gmail.com>
This commit is contained in:
@@ -36,7 +36,7 @@ class UnStackActionButton extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.filter_none_rounded,
|
||||
iconData: Icons.layers_clear_outlined,
|
||||
label: "unstack".t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
|
||||
@@ -51,6 +51,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
isArchived: isArchived,
|
||||
isTrashEnabled: isTrashEnable,
|
||||
isInLockedView: isInLockedView,
|
||||
isStacked: asset.hasRemote && (asset as RemoteAsset).stackId != null,
|
||||
currentAlbum: currentAlbum,
|
||||
advancedTroubleshooting: advancedTroubleshooting,
|
||||
source: ActionSource.viewer,
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
@@ -44,6 +45,7 @@ class ArchiveBottomSheet extends ConsumerWidget {
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
@@ -44,6 +45,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_b
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
@@ -117,6 +118,7 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
const DeleteActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_b
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
@@ -102,6 +103,7 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -3,7 +3,10 @@ import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/services/asset.service.dart';
|
||||
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
@@ -36,6 +39,7 @@ class ActionNotifier extends Notifier<void> {
|
||||
late ActionService _service;
|
||||
late UploadService _uploadService;
|
||||
late DownloadService _downloadService;
|
||||
late AssetService _assetService;
|
||||
|
||||
ActionNotifier() : super();
|
||||
|
||||
@@ -43,6 +47,7 @@ class ActionNotifier extends Notifier<void> {
|
||||
void build() {
|
||||
_uploadService = ref.watch(uploadServiceProvider);
|
||||
_service = ref.watch(actionServiceProvider);
|
||||
_assetService = ref.watch(assetServiceProvider);
|
||||
_downloadService = ref.watch(downloadServiceProvider);
|
||||
_downloadService.onImageDownloadStatus = _downloadImageCallback;
|
||||
_downloadService.onVideoDownloadStatus = _downloadVideoCallback;
|
||||
@@ -335,6 +340,14 @@ class ActionNotifier extends Notifier<void> {
|
||||
final assets = _getOwnedRemoteAssetsForSource(source);
|
||||
try {
|
||||
await _service.unStack(assets.map((e) => e.stackId).nonNulls.toList());
|
||||
if (source == ActionSource.viewer) {
|
||||
final updatedParent = await _assetService.getRemoteAsset(assets.first.id);
|
||||
if (updatedParent != null) {
|
||||
ref.read(currentAssetNotifier.notifier).setAsset(updatedParent);
|
||||
ref.read(assetViewerProvider.notifier).setAsset(updatedParent);
|
||||
}
|
||||
}
|
||||
|
||||
return ActionResult(count: assets.length, success: true);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Failed to unstack assets', error, stack);
|
||||
|
||||
@@ -28,6 +28,8 @@ class MultiSelectState {
|
||||
bool get hasRemote =>
|
||||
selectedAssets.any((asset) => asset.storage == AssetState.remote || asset.storage == AssetState.merged);
|
||||
|
||||
bool get hasStacked => selectedAssets.any((asset) => asset is RemoteAsset && asset.stackId != null);
|
||||
|
||||
bool get hasLocal => selectedAssets.any((asset) => asset.storage == AssetState.local);
|
||||
|
||||
bool get hasMerged => selectedAssets.any((asset) => asset.storage == AssetState.merged);
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_b
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
|
||||
class ActionButtonContext {
|
||||
@@ -24,6 +25,7 @@ class ActionButtonContext {
|
||||
final bool isArchived;
|
||||
final bool isTrashEnabled;
|
||||
final bool isInLockedView;
|
||||
final bool isStacked;
|
||||
final RemoteAlbum? currentAlbum;
|
||||
final bool advancedTroubleshooting;
|
||||
final ActionSource source;
|
||||
@@ -33,6 +35,7 @@ class ActionButtonContext {
|
||||
required this.isOwner,
|
||||
required this.isArchived,
|
||||
required this.isTrashEnabled,
|
||||
required this.isStacked,
|
||||
required this.isInLockedView,
|
||||
required this.currentAlbum,
|
||||
required this.advancedTroubleshooting,
|
||||
@@ -55,6 +58,7 @@ enum ActionButtonType {
|
||||
deleteLocal,
|
||||
upload,
|
||||
removeFromAlbum,
|
||||
unstack,
|
||||
likeActivity;
|
||||
|
||||
bool shouldShow(ActionButtonContext context) {
|
||||
@@ -110,6 +114,10 @@ enum ActionButtonType {
|
||||
context.isOwner && //
|
||||
!context.isInLockedView && //
|
||||
context.currentAlbum != null,
|
||||
ActionButtonType.unstack =>
|
||||
context.isOwner && //
|
||||
!context.isInLockedView && //
|
||||
context.isStacked,
|
||||
ActionButtonType.likeActivity =>
|
||||
!context.isInLockedView &&
|
||||
context.currentAlbum != null &&
|
||||
@@ -138,28 +146,13 @@ enum ActionButtonType {
|
||||
source: context.source,
|
||||
),
|
||||
ActionButtonType.likeActivity => const LikeActivityActionButton(),
|
||||
ActionButtonType.unstack => UnStackActionButton(source: context.source),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ActionButtonBuilder {
|
||||
static const List<ActionButtonType> _actionTypes = [
|
||||
ActionButtonType.advancedInfo,
|
||||
ActionButtonType.share,
|
||||
ActionButtonType.shareLink,
|
||||
ActionButtonType.likeActivity,
|
||||
ActionButtonType.archive,
|
||||
ActionButtonType.unarchive,
|
||||
ActionButtonType.download,
|
||||
ActionButtonType.trash,
|
||||
ActionButtonType.deletePermanent,
|
||||
ActionButtonType.delete,
|
||||
ActionButtonType.moveToLockFolder,
|
||||
ActionButtonType.removeFromLockFolder,
|
||||
ActionButtonType.deleteLocal,
|
||||
ActionButtonType.upload,
|
||||
ActionButtonType.removeFromAlbum,
|
||||
];
|
||||
static const List<ActionButtonType> _actionTypes = ActionButtonType.values;
|
||||
|
||||
static List<Widget> build(ActionButtonContext context) {
|
||||
return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList();
|
||||
|
||||
Reference in New Issue
Block a user