feat: action buttons place holder (#19561)

* feat: action buttons place holder

* lint

* Update base_action_button.widget.dart

Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>

---------

Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
This commit is contained in:
Alex 2025-06-27 08:33:46 -05:00 committed by GitHub
parent 72a53f43c8
commit 97aabe466e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 341 additions and 26 deletions

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class ArchiveActionButton extends ConsumerWidget {
const ArchiveActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.archive_outlined,
label: "archive".t(context: context),
);
}
}

View File

@ -8,19 +8,24 @@ class BaseActionButton extends StatelessWidget {
required this.iconData,
this.onPressed,
this.onLongPressed,
this.maxWidth = 90.0,
});
final String label;
final IconData iconData;
final double maxWidth;
final void Function()? onPressed;
final void Function()? onLongPressed;
@override
Widget build(BuildContext context) {
final minWidth =
context.isMobile ? MediaQuery.sizeOf(context).width / 4.5 : 75.0;
final minWidth = context.isMobile ? context.width / 4.5 : 75.0;
return MaterialButton(
return ConstrainedBox(
constraints: BoxConstraints(
maxWidth: maxWidth,
),
child: MaterialButton(
padding: const EdgeInsets.all(10),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
@ -36,12 +41,14 @@ class BaseActionButton extends StatelessWidget {
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400),
style:
const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400),
maxLines: 3,
textAlign: TextAlign.center,
),
],
),
),
);
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class DeletePermanentActionButton extends ConsumerWidget {
const DeletePermanentActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
maxWidth: 110.0,
iconData: Icons.delete_forever,
label: "delete_dialog_title".t(context: context),
);
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class DeleteLocalActionButton extends ConsumerWidget {
const DeleteLocalActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
maxWidth: 95.0,
iconData: Icons.no_cell_outlined,
label: "control_bottom_app_bar_delete_from_local".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class DownloadActionButton extends ConsumerWidget {
const DownloadActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.download,
label: "download".t(context: context),
);
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class EditDateTimeActionButton extends ConsumerWidget {
const EditDateTimeActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
maxWidth: 95.0,
iconData: Icons.edit_calendar_outlined,
label: "control_bottom_app_bar_edit_time".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class EditLocationActionButton extends ConsumerWidget {
const EditLocationActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.edit_location_alt_outlined,
label: "control_bottom_app_bar_edit_location".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class FavoriteActionButton extends ConsumerWidget {
const FavoriteActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.favorite_border_rounded,
label: "favorite".t(context: context),
);
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class MoveToLockFolderActionButton extends ConsumerWidget {
const MoveToLockFolderActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
maxWidth: 100.0,
iconData: Icons.lock_outline_rounded,
label: "move_to_locked_folder".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class RemoveFromAlbumActionButton extends ConsumerWidget {
const RemoveFromAlbumActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.remove_circle_outline,
label: "remove_from_album".t(context: context),
);
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class RemoveFromLockFolderActionButton extends ConsumerWidget {
const RemoveFromLockFolderActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
maxWidth: 100.0,
iconData: Icons.lock_open_rounded,
label: "remove_from_locked_folder".t(context: context),
);
}
}

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class ShareActionButton extends ConsumerWidget {
@ -13,7 +13,7 @@ class ShareActionButton extends ConsumerWidget {
return BaseActionButton(
iconData:
Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded,
label: context.tr('share'),
label: 'share'.t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class ShareLinkActionButton extends ConsumerWidget {
const ShareLinkActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.link_rounded,
label: "share_link".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class StackActionButton extends ConsumerWidget {
const StackActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.filter_none_rounded,
label: "stack".t(context: context),
);
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class TrashActionButton extends ConsumerWidget {
const TrashActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
maxWidth: 85.0,
iconData: Icons.delete_outline_rounded,
label: "control_bottom_app_bar_trash_from_immich".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class UnarchiveActionButton extends ConsumerWidget {
const UnarchiveActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.unarchive_outlined,
label: "unarchive".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class UnFavoriteActionButton extends ConsumerWidget {
const UnFavoriteActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.favorite_rounded,
label: "unfavorite".t(context: context),
);
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class UploadActionButton extends ConsumerWidget {
const UploadActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.backup_outlined,
label: "upload".t(context: context),
);
}
}

View File

@ -1,16 +1,54 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
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_buton.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_app_bar/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
class HomeBottomAppBar extends ConsumerWidget {
const HomeBottomAppBar({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return const BaseBottomSheet(
final multiselect = ref.watch(multiSelectProvider);
final isTrashEnable = ref.watch(
serverInfoProvider.select((state) => state.serverFeatures.trash),
);
return BaseBottomSheet(
initialChildSize: 0.25,
minChildSize: 0.22,
actions: [
ShareActionButton(),
const ShareActionButton(),
if (multiselect.hasRemote) ...[
const ShareLinkActionButton(),
const ArchiveActionButton(),
const FavoriteActionButton(),
const DownloadActionButton(),
isTrashEnable
? const TrashActionButton()
: const DeletePermanentActionButton(),
const EditDateTimeActionButton(),
const EditLocationActionButton(),
const MoveToLockFolderActionButton(),
const StackActionButton(),
],
if (multiselect.hasLocal) ...[
const DeleteLocalActionButton(),
const UploadActionButton(),
],
],
);
}

View File

@ -19,6 +19,14 @@ class MultiSelectState {
});
bool get isEnabled => selectedAssets.isNotEmpty;
bool get hasRemote => selectedAssets.any(
(asset) =>
asset.storage == AssetState.remote ||
asset.storage == AssetState.merged,
);
bool get hasLocal => selectedAssets.any(
(asset) => asset.storage == AssetState.local,
);
MultiSelectState copyWith({
Set<BaseAsset>? selectedAssets,