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,39 +8,46 @@ class BaseActionButton extends StatelessWidget {
required this.iconData, required this.iconData,
this.onPressed, this.onPressed,
this.onLongPressed, this.onLongPressed,
this.maxWidth = 90.0,
}); });
final String label; final String label;
final IconData iconData; final IconData iconData;
final double maxWidth;
final void Function()? onPressed; final void Function()? onPressed;
final void Function()? onLongPressed; final void Function()? onLongPressed;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final minWidth = final minWidth = context.isMobile ? context.width / 4.5 : 75.0;
context.isMobile ? MediaQuery.sizeOf(context).width / 4.5 : 75.0;
return MaterialButton( return ConstrainedBox(
padding: const EdgeInsets.all(10), constraints: BoxConstraints(
shape: const RoundedRectangleBorder( maxWidth: maxWidth,
borderRadius: BorderRadius.all(Radius.circular(20)),
), ),
onPressed: onPressed, child: MaterialButton(
onLongPress: onLongPressed, padding: const EdgeInsets.all(10),
minWidth: minWidth, shape: const RoundedRectangleBorder(
child: Column( borderRadius: BorderRadius.all(Radius.circular(20)),
mainAxisAlignment: MainAxisAlignment.start, ),
crossAxisAlignment: CrossAxisAlignment.center, onPressed: onPressed,
children: [ onLongPress: onLongPressed,
Icon(iconData, size: 24), minWidth: minWidth,
const SizedBox(height: 8), child: Column(
Text( mainAxisAlignment: MainAxisAlignment.start,
label, crossAxisAlignment: CrossAxisAlignment.center,
style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400), children: [
maxLines: 3, Icon(iconData, size: 24),
textAlign: TextAlign.center, const SizedBox(height: 8),
), Text(
], label,
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 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
class ShareActionButton extends ConsumerWidget { class ShareActionButton extends ConsumerWidget {
@ -13,7 +13,7 @@ class ShareActionButton extends ConsumerWidget {
return BaseActionButton( return BaseActionButton(
iconData: iconData:
Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded, 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:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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_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/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 { class HomeBottomAppBar extends ConsumerWidget {
const HomeBottomAppBar({super.key}); const HomeBottomAppBar({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { 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: [ 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 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({ MultiSelectState copyWith({
Set<BaseAsset>? selectedAssets, Set<BaseAsset>? selectedAssets,