diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart new file mode 100644 index 0000000000..90209b171d --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart index 8ea43b8781..6c60b47535 100644 --- a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart @@ -8,39 +8,46 @@ 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( - padding: const EdgeInsets.all(10), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20)), + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: maxWidth, ), - onPressed: onPressed, - onLongPress: onLongPressed, - minWidth: minWidth, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(iconData, size: 24), - const SizedBox(height: 8), - Text( - label, - style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400), - maxLines: 3, - textAlign: TextAlign.center, - ), - ], + child: MaterialButton( + padding: const EdgeInsets.all(10), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + onPressed: onPressed, + onLongPress: onLongPressed, + minWidth: minWidth, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(iconData, size: 24), + const SizedBox(height: 8), + Text( + label, + style: + const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400), + maxLines: 3, + textAlign: TextAlign.center, + ), + ], + ), ), ); } diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart new file mode 100644 index 0000000000..ff77e99041 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart new file mode 100644 index 0000000000..dfc84b4190 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart new file mode 100644 index 0000000000..53ea5d4946 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart new file mode 100644 index 0000000000..3db3dde44d --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart new file mode 100644 index 0000000000..e8a6b633c8 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart new file mode 100644 index 0000000000..5c093c8fa0 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart new file mode 100644 index 0000000000..ba93c86109 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart new file mode 100644 index 0000000000..b18e10ebba --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart new file mode 100644 index 0000000000..24c84281d4 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart index 4e6b6e2af4..6a8864c14f 100644 --- a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart @@ -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), ); } } diff --git a/mobile/lib/presentation/widgets/action_buttons/share_link_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/share_link_action_button.widget.dart new file mode 100644 index 0000000000..c227df1b33 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/share_link_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart new file mode 100644 index 0000000000..dc42eb96f1 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/trash_action_buton.widget.dart b/mobile/lib/presentation/widgets/action_buttons/trash_action_buton.widget.dart new file mode 100644 index 0000000000..ccaaf314fc --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/trash_action_buton.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart new file mode 100644 index 0000000000..7fa0f8513a --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart new file mode 100644 index 0000000000..420821ef3f --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart new file mode 100644 index 0000000000..66467e44a3 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart @@ -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), + ); + } +} diff --git a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart index c29149df85..0b5efc4bd1 100644 --- a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart @@ -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(), + ], ], ); } diff --git a/mobile/lib/providers/timeline/multiselect.provider.dart b/mobile/lib/providers/timeline/multiselect.provider.dart index cb9ddc6d7c..1a4c0e4c65 100644 --- a/mobile/lib/providers/timeline/multiselect.provider.dart +++ b/mobile/lib/providers/timeline/multiselect.provider.dart @@ -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? selectedAssets,