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

@@ -9,8 +9,10 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
class AdvancedInfoActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const AdvancedInfoActionButton({super.key, required this.source});
const AdvancedInfoActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -26,6 +28,8 @@ class AdvancedInfoActionButton extends ConsumerWidget {
maxWidth: 115.0,
iconData: Icons.help_outline_rounded,
label: "troubleshoot".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -35,8 +35,10 @@ Future<void> performArchiveAction(BuildContext context, WidgetRef ref, {required
class ArchiveActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const ArchiveActionButton({super.key, required this.source});
const ArchiveActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performArchiveAction(context, ref, source: source);
@@ -47,6 +49,8 @@ class ArchiveActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.archive_outlined,
label: "to_archive".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -18,7 +18,15 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class DeleteActionButton extends ConsumerWidget {
final ActionSource source;
final bool showConfirmation;
const DeleteActionButton({super.key, required this.source, this.showConfirmation = false});
final bool iconOnly;
final bool menuItem;
const DeleteActionButton({
super.key,
required this.source,
this.showConfirmation = false,
this.iconOnly = false,
this.menuItem = false,
});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -74,6 +82,8 @@ class DeleteActionButton extends ConsumerWidget {
maxWidth: 110.0,
iconData: Icons.delete_sweep_outlined,
label: "delete".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -14,8 +14,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
/// - Prompt to delete the asset locally
class DeleteLocalActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const DeleteLocalActionButton({super.key, required this.source});
const DeleteLocalActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -55,6 +57,8 @@ class DeleteLocalActionButton extends ConsumerWidget {
maxWidth: 95.0,
iconData: Icons.no_cell_outlined,
label: "control_bottom_app_bar_delete_from_local".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -15,8 +15,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
/// - Prompt to delete the asset locally
class DeletePermanentActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const DeletePermanentActionButton({super.key, required this.source});
const DeletePermanentActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -51,6 +53,8 @@ class DeletePermanentActionButton extends ConsumerWidget {
maxWidth: 110.0,
iconData: Icons.delete_forever,
label: "delete_permanently".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -38,8 +38,10 @@ Future<void> performMoveToLockFolderAction(BuildContext context, WidgetRef ref,
class MoveToLockFolderActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const MoveToLockFolderActionButton({super.key, required this.source});
const MoveToLockFolderActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performMoveToLockFolderAction(context, ref, source: source);
@@ -51,6 +53,8 @@ class MoveToLockFolderActionButton extends ConsumerWidget {
maxWidth: 115.0,
iconData: Icons.lock_outline_rounded,
label: "move_to_locked_folder".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
@@ -11,8 +13,16 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class RemoveFromAlbumActionButton extends ConsumerWidget {
final String albumId;
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const RemoveFromAlbumActionButton({super.key, required this.albumId, required this.source});
const RemoveFromAlbumActionButton({
super.key,
required this.albumId,
required this.source,
this.iconOnly = false,
this.menuItem = false,
});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -22,6 +32,10 @@ class RemoveFromAlbumActionButton extends ConsumerWidget {
final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'remove_from_album_action_prompt'.t(
context: context,
args: {'count': result.count.toString()},
@@ -42,6 +56,8 @@ class RemoveFromAlbumActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.remove_circle_outline,
label: "remove_from_album".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
maxWidth: 100,
);

View File

@@ -10,8 +10,15 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class RemoveFromLockFolderActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const RemoveFromLockFolderActionButton({super.key, required this.source});
const RemoveFromLockFolderActionButton({
super.key,
required this.source,
this.iconOnly = false,
this.menuItem = false,
});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -42,6 +49,8 @@ class RemoveFromLockFolderActionButton extends ConsumerWidget {
maxWidth: 100.0,
iconData: Icons.lock_open_rounded,
label: "remove_from_locked_folder".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -31,8 +31,10 @@ class _SharePreparingDialog extends StatelessWidget {
class ShareActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const ShareActionButton({super.key, required this.source});
const ShareActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -74,6 +76,8 @@ class ShareActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded,
label: 'share'.t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -7,8 +7,10 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
class ShareLinkActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const ShareLinkActionButton({super.key, required this.source});
const ShareLinkActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
_onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -23,6 +25,8 @@ class ShareLinkActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.link_rounded,
label: "share_link".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -13,8 +13,10 @@ import 'package:immich_mobile/routing/router.dart';
class SimilarPhotosActionButton extends ConsumerWidget {
final String assetId;
final bool iconOnly;
final bool menuItem;
const SimilarPhotosActionButton({super.key, required this.assetId});
const SimilarPhotosActionButton({super.key, required this.assetId, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -44,6 +46,8 @@ class SimilarPhotosActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.compare,
label: "view_similar_photos".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
maxWidth: 100,
);

View File

@@ -15,8 +15,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
/// which will be permanently deleted after the number of days configure by the admin
class TrashActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const TrashActionButton({super.key, required this.source});
const TrashActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -48,6 +50,8 @@ class TrashActionButton extends ConsumerWidget {
maxWidth: 85.0,
iconData: Icons.delete_outline_rounded,
label: "control_bottom_app_bar_trash_from_immich".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -37,8 +37,10 @@ Future<void> performUnArchiveAction(BuildContext context, WidgetRef ref, {requir
class UnArchiveActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const UnArchiveActionButton({super.key, required this.source});
const UnArchiveActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performUnArchiveAction(context, ref, source: source);
@@ -49,6 +51,8 @@ class UnArchiveActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.unarchive_outlined,
label: "unarchive".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -10,8 +10,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class UnStackActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const UnStackActionButton({super.key, required this.source});
const UnStackActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -38,6 +40,8 @@ class UnStackActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.layers_clear_outlined,
label: "unstack".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View File

@@ -10,8 +10,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class UploadActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const UploadActionButton({super.key, required this.source});
const UploadActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -39,6 +41,8 @@ class UploadActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.backup_outlined,
label: "upload".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}