feat(mobile): move buttons in the bottom sheet to the kebabu menu (#24175)

* refactor: remove bottom sheet buttons

* feat: add iconOnly and menuItem parameters to action buttons

* feat: enhance action button context and kebab menu integration

* feat: use ActionButtonContext

* fix: add missing options in some cases

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
idubnori
2025-12-16 07:44:27 +09:00
committed by GitHub
parent 77926383db
commit b10a8baf53
19 changed files with 274 additions and 158 deletions

View File

@@ -42,14 +42,17 @@ class ViewerBottomBar extends ConsumerWidget {
final actions = <Widget>[
const ShareActionButton(source: ActionSource.viewer),
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
if (asset.type == AssetType.image) const EditImageActionButton(),
if (asset.hasRemote) AddActionButton(originalTheme: originalTheme),
if (isOwner) ...[
asset.isLocalOnly
? const DeleteLocalActionButton(source: ActionSource.viewer)
: const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true),
if (!isInLockedView) ...[
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
if (asset.type == AssetType.image) const EditImageActionButton(),
if (asset.hasRemote) AddActionButton(originalTheme: originalTheme),
if (isOwner) ...[
asset.isLocalOnly
? const DeleteLocalActionButton(source: ActionSource.viewer)
: const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true),
],
],
];
@@ -76,7 +79,7 @@ class ViewerBottomBar extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (asset.isVideo) const VideoControls(),
if (!isInLockedView && !isReadonlyModeEnabled)
if (!isReadonlyModeEnabled)
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: actions),
],
),

View File

@@ -8,7 +8,6 @@ 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/domain/models/exif.model.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/duration_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
@@ -21,14 +20,9 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_shee
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
import 'package:immich_mobile/utils/bytes_units.dart';
import 'package:immich_mobile/utils/timezone.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
@@ -48,29 +42,8 @@ class AssetDetailBottomSheet extends ConsumerWidget {
return const SizedBox.shrink();
}
final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash));
final isOwner = asset is RemoteAsset && asset.ownerId == ref.watch(currentUserProvider)?.id;
final isInLockedView = ref.watch(inLockedViewProvider);
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
final buttonContext = ActionButtonContext(
asset: asset,
isOwner: isOwner,
isArchived: isArchived,
isTrashEnabled: isTrashEnable,
isInLockedView: isInLockedView,
isStacked: asset is RemoteAsset && asset.stackId != null,
currentAlbum: currentAlbum,
advancedTroubleshooting: advancedTroubleshooting,
source: ActionSource.viewer,
);
final actions = ActionButtonBuilder.build(buttonContext);
return BaseBottomSheet(
actions: actions,
actions: [],
slivers: const [_AssetDetailBottomSheet()],
controller: controller,
initialChildSize: initialChildSize,

View File

@@ -1,10 +1,16 @@
import 'package:flutter/material.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/domain/models/setting.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
@@ -24,16 +30,28 @@ class ViewerKebabMenu extends ConsumerWidget {
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
final timelineOrigin = ref.read(timelineServiceProvider).origin;
final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash));
final isInLockedView = ref.watch(inLockedViewProvider);
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
final kebabContext = ViewerKebabMenuButtonContext(
final actionContext = ActionButtonContext(
asset: asset,
isOwner: isOwner,
isArchived: isArchived,
isTrashEnabled: isTrashEnable,
isStacked: asset is RemoteAsset && asset.stackId != null,
isInLockedView: isInLockedView,
currentAlbum: currentAlbum,
advancedTroubleshooting: advancedTroubleshooting,
source: ActionSource.viewer,
isCasting: isCasting,
timelineOrigin: timelineOrigin,
originalTheme: originalTheme,
);
final menuChildren = ViewerKebabMenuButtonBuilder.build(kebabContext, context, ref);
final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context, ref);
return MenuAnchor(
consumeOutsideTap: true,