diff --git a/mobile/lib/presentation/pages/dev/drift_partner_detail.page.dart b/mobile/lib/presentation/pages/dev/drift_partner_detail.page.dart deleted file mode 100644 index 4b62387cb4..0000000000 --- a/mobile/lib/presentation/pages/dev/drift_partner_detail.page.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/widgets.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; - -@RoutePage() -class DriftPartnerDetailPage extends StatelessWidget { - final String partnerId; - - const DriftPartnerDetailPage({super.key, required this.partnerId}); - - @override - Widget build(BuildContext context) { - return ProviderScope( - overrides: [ - timelineServiceProvider.overrideWith( - (ref) { - final timelineService = - ref.watch(timelineFactoryProvider).remoteAssets(partnerId); - ref.onDispose(timelineService.dispose); - return timelineService; - }, - ), - ], - child: const Timeline(), - ); - } -} diff --git a/mobile/lib/presentation/pages/dev/media_stat.page.dart b/mobile/lib/presentation/pages/dev/media_stat.page.dart index 0a77d9dfe8..e61dcdf90d 100644 --- a/mobile/lib/presentation/pages/dev/media_stat.page.dart +++ b/mobile/lib/presentation/pages/dev/media_stat.page.dart @@ -226,7 +226,7 @@ class RemoteMediaSummaryPage extends StatelessWidget { name: album.name, countFuture: countFuture, onTap: () => context.router.push( - RemoteTimelineRoute(album: album), + RemoteAlbumRoute(album: album), ), ); }, diff --git a/mobile/lib/presentation/pages/drift_album.page.dart b/mobile/lib/presentation/pages/drift_album.page.dart index 8ae3c79138..e6d3d796a4 100644 --- a/mobile/lib/presentation/pages/drift_album.page.dart +++ b/mobile/lib/presentation/pages/drift_album.page.dart @@ -568,7 +568,7 @@ class _AlbumList extends StatelessWidget { ), ), onTap: () => context.router.push( - RemoteTimelineRoute(album: album), + RemoteAlbumRoute(album: album), ), leadingPadding: const EdgeInsets.only( right: 16, @@ -705,7 +705,7 @@ class _GridAlbumCard extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: () => context.router.push( - RemoteTimelineRoute(album: album), + RemoteAlbumRoute(album: album), ), child: Card( elevation: 0, diff --git a/mobile/lib/presentation/pages/dev/drift_archive.page.dart b/mobile/lib/presentation/pages/drift_archive.page.dart similarity index 90% rename from mobile/lib/presentation/pages/dev/drift_archive.page.dart rename to mobile/lib/presentation/pages/drift_archive.page.dart index fa847c74b5..90b8dcb646 100644 --- a/mobile/lib/presentation/pages/dev/drift_archive.page.dart +++ b/mobile/lib/presentation/pages/drift_archive.page.dart @@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.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/bottom_sheet/archive_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -34,6 +35,7 @@ class DriftArchivePage extends StatelessWidget { title: 'archive'.t(context: context), icon: Icons.archive_outlined, ), + bottomSheet: const ArchiveBottomSheet(), ), ); } diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart index 93dfb265c6..e06321413e 100644 --- a/mobile/lib/presentation/pages/drift_create_album.page.dart +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -203,7 +203,7 @@ class _DriftCreateAlbumPageState extends ConsumerState { if (album != null) { context.replaceRoute( - RemoteTimelineRoute(album: album), + RemoteAlbumRoute(album: album), ); } } diff --git a/mobile/lib/presentation/pages/dev/drift_favorite.page.dart b/mobile/lib/presentation/pages/drift_favorite.page.dart similarity index 90% rename from mobile/lib/presentation/pages/dev/drift_favorite.page.dart rename to mobile/lib/presentation/pages/drift_favorite.page.dart index 43f270574b..90a273f93b 100644 --- a/mobile/lib/presentation/pages/dev/drift_favorite.page.dart +++ b/mobile/lib/presentation/pages/drift_favorite.page.dart @@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.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/bottom_sheet/favorite_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -34,6 +35,7 @@ class DriftFavoritePage extends StatelessWidget { title: 'favorites'.t(context: context), icon: Icons.favorite_outline, ), + bottomSheet: const FavoriteBottomSheet(), ), ); } diff --git a/mobile/lib/presentation/pages/drift_library.page.dart b/mobile/lib/presentation/pages/drift_library.page.dart index 2b0ec191e2..1e8975dcfa 100644 --- a/mobile/lib/presentation/pages/drift_library.page.dart +++ b/mobile/lib/presentation/pages/drift_library.page.dart @@ -512,8 +512,9 @@ class _PartnerList extends StatelessWidget { fontWeight: FontWeight.w500, ), ).t(context: context, args: {'user': partner.name}), - onTap: () => - context.pushRoute(DriftPartnerDetailRoute(partnerId: partner.id)), + onTap: () => context.pushRoute( + DriftPartnerDetailRoute(partner: partner), + ), ); }, ); diff --git a/mobile/lib/presentation/pages/dev/drift_local_album.page.dart b/mobile/lib/presentation/pages/drift_local_album.page.dart similarity index 100% rename from mobile/lib/presentation/pages/dev/drift_local_album.page.dart rename to mobile/lib/presentation/pages/drift_local_album.page.dart diff --git a/mobile/lib/presentation/pages/dev/drift_locked_folder.page.dart b/mobile/lib/presentation/pages/drift_locked_folder.page.dart similarity index 70% rename from mobile/lib/presentation/pages/dev/drift_locked_folder.page.dart rename to mobile/lib/presentation/pages/drift_locked_folder.page.dart index 5ab7c71347..9b42cdb103 100644 --- a/mobile/lib/presentation/pages/dev/drift_locked_folder.page.dart +++ b/mobile/lib/presentation/pages/drift_locked_folder.page.dart @@ -1,9 +1,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; @RoutePage() class DriftLockedFolderPage extends StatelessWidget { @@ -27,7 +30,12 @@ class DriftLockedFolderPage extends StatelessWidget { }, ), ], - child: const Timeline(), + child: Timeline( + appBar: MesmerizingSliverAppBar( + title: 'locked_folder'.t(context: context), + ), + bottomSheet: const LockedFolderBottomSheet(), + ), ); } } diff --git a/mobile/lib/presentation/pages/drift_partner_detail.page.dart b/mobile/lib/presentation/pages/drift_partner_detail.page.dart new file mode 100644 index 0000000000..baae893d39 --- /dev/null +++ b/mobile/lib/presentation/pages/drift_partner_detail.page.dart @@ -0,0 +1,109 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart'; +import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; + +@RoutePage() +class DriftPartnerDetailPage extends StatelessWidget { + final UserDto partner; + + const DriftPartnerDetailPage({ + super.key, + required this.partner, + }); + + @override + Widget build(BuildContext context) { + return ProviderScope( + overrides: [ + timelineServiceProvider.overrideWith( + (ref) { + final timelineService = + ref.watch(timelineFactoryProvider).remoteAssets(partner.id); + ref.onDispose(timelineService.dispose); + return timelineService; + }, + ), + ], + child: Timeline( + appBar: MesmerizingSliverAppBar( + title: partner.name, + icon: Icons.person_outline, + ), + topSliverWidget: _InfoBox( + onTap: () => { + // TODO: Create DriftUserProvider/DriftUserService to handle this action + }, + inTimeline: partner.inTimeline, + ), + topSliverWidgetHeight: 110, + bottomSheet: const PartnerDetailBottomSheet(), + ), + ); + } +} + +class _InfoBox extends StatelessWidget { + final VoidCallback onTap; + final bool inTimeline; + + const _InfoBox({ + required this.onTap, + required this.inTimeline, + }); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: SizedBox( + height: 110, + child: Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 16.0), + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: context.colorScheme.onSurface.withAlpha(10), + width: 1, + ), + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + gradient: LinearGradient( + colors: [ + context.colorScheme.primary.withAlpha(10), + context.colorScheme.primary.withAlpha(15), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ListTile( + title: Text( + "Show in timeline", + style: context.textTheme.titleSmall?.copyWith( + color: context.colorScheme.primary, + ), + ), + subtitle: Text( + "Show photos and videos from this user in your timeline", + style: context.textTheme.bodyMedium, + ), + trailing: Switch( + value: inTimeline, + onChanged: (_) => onTap(), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/pages/dev/drift_recently_taken.page.dart b/mobile/lib/presentation/pages/drift_recently_taken.page.dart similarity index 100% rename from mobile/lib/presentation/pages/dev/drift_recently_taken.page.dart rename to mobile/lib/presentation/pages/drift_recently_taken.page.dart diff --git a/mobile/lib/presentation/pages/dev/remote_timeline.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart similarity index 78% rename from mobile/lib/presentation/pages/dev/remote_timeline.page.dart rename to mobile/lib/presentation/pages/drift_remote_album.page.dart index 2930a3c3d8..bbfe6ddc74 100644 --- a/mobile/lib/presentation/pages/dev/remote_timeline.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -2,15 +2,16 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; @RoutePage() -class RemoteTimelinePage extends StatelessWidget { +class RemoteAlbumPage extends StatelessWidget { final RemoteAlbum album; - const RemoteTimelinePage({super.key, required this.album}); + const RemoteAlbumPage({super.key, required this.album}); @override Widget build(BuildContext context) { @@ -31,6 +32,9 @@ class RemoteTimelinePage extends StatelessWidget { title: album.name, icon: Icons.photo_album_outlined, ), + bottomSheet: RemoteAlbumBottomSheet( + album: album, + ), ), ); } diff --git a/mobile/lib/presentation/pages/dev/drift_trash.page.dart b/mobile/lib/presentation/pages/drift_trash.page.dart similarity index 100% rename from mobile/lib/presentation/pages/dev/drift_trash.page.dart rename to mobile/lib/presentation/pages/drift_trash.page.dart diff --git a/mobile/lib/presentation/pages/dev/drift_video.page.dart b/mobile/lib/presentation/pages/drift_video.page.dart similarity index 100% rename from mobile/lib/presentation/pages/dev/drift_video.page.dart rename to mobile/lib/presentation/pages/drift_video.page.dart diff --git a/mobile/lib/presentation/pages/dev/local_timeline.page.dart b/mobile/lib/presentation/pages/local_timeline.page.dart similarity index 87% rename from mobile/lib/presentation/pages/dev/local_timeline.page.dart rename to mobile/lib/presentation/pages/local_timeline.page.dart index f966109289..b4df0f64e2 100644 --- a/mobile/lib/presentation/pages/dev/local_timeline.page.dart +++ b/mobile/lib/presentation/pages/local_timeline.page.dart @@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; @@ -28,6 +29,7 @@ class LocalTimelinePage extends StatelessWidget { ], child: Timeline( appBar: MesmerizingSliverAppBar(title: album.name), + bottomSheet: const LocalAlbumBottomSheet(), ), ); } 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 index a58d9f1ee1..e44500144c 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart @@ -8,10 +8,10 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; -class UnarchiveActionButton extends ConsumerWidget { +class UnArchiveActionButton extends ConsumerWidget { final ActionSource source; - const UnarchiveActionButton({super.key, required this.source}); + const UnArchiveActionButton({super.key, required this.source}); void _onTap(BuildContext context, WidgetRef ref) async { if (!context.mounted) { diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart index d0bdc28d10..a7a2a57ce5 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart @@ -16,7 +16,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/location_details.widget.dart'; -import 'package:immich_mobile/presentation/widgets/bottom_app_bar/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; diff --git a/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart new file mode 100644 index 0000000000..c23f268465 --- /dev/null +++ b/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.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_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; + +class ArchiveBottomSheet extends ConsumerWidget { + const ArchiveBottomSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final multiselect = ref.watch(multiSelectProvider); + final isTrashEnable = ref.watch( + serverInfoProvider.select((state) => state.serverFeatures.trash), + ); + + return BaseBottomSheet( + initialChildSize: 0.25, + maxChildSize: 0.4, + shouldCloseOnMinExtent: false, + actions: [ + const ShareActionButton(), + if (multiselect.hasRemote) ...[ + const ShareLinkActionButton(source: ActionSource.timeline), + const UnArchiveActionButton(source: ActionSource.timeline), + const FavoriteActionButton(source: ActionSource.timeline), + const DownloadActionButton(), + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton( + source: ActionSource.timeline, + ), + const EditDateTimeActionButton(), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton( + source: ActionSource.timeline, + ), + const StackActionButton(), + ], + if (multiselect.hasLocal) ...[ + const DeleteLocalActionButton(), + const UploadActionButton(), + ], + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/bottom_app_bar/base_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart similarity index 100% rename from mobile/lib/presentation/widgets/bottom_app_bar/base_bottom_sheet.widget.dart rename to mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart diff --git a/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart new file mode 100644 index 0000000000..2f8208a80b --- /dev/null +++ b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.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/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_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; + +class FavoriteBottomSheet extends ConsumerWidget { + const FavoriteBottomSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final multiselect = ref.watch(multiSelectProvider); + final isTrashEnable = ref.watch( + serverInfoProvider.select((state) => state.serverFeatures.trash), + ); + + return BaseBottomSheet( + initialChildSize: 0.25, + maxChildSize: 0.4, + shouldCloseOnMinExtent: false, + actions: [ + const ShareActionButton(), + if (multiselect.hasRemote) ...[ + const ShareLinkActionButton(source: ActionSource.timeline), + const UnFavoriteActionButton(source: ActionSource.timeline), + const ArchiveActionButton(source: ActionSource.timeline), + const DownloadActionButton(), + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton( + source: ActionSource.timeline, + ), + const EditDateTimeActionButton(), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton( + source: ActionSource.timeline, + ), + const StackActionButton(), + ], + if (multiselect.hasLocal) ...[ + const DeleteLocalActionButton(), + const UploadActionButton(), + ], + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart similarity index 92% rename from mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart rename to mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart index f5ba759130..900adefd0b 100644 --- a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart @@ -14,12 +14,12 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.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_sheet/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}); +class GeneralBottomSheet extends ConsumerWidget { + const GeneralBottomSheet({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,9 +30,10 @@ class HomeBottomAppBar extends ConsumerWidget { return BaseBottomSheet( initialChildSize: 0.25, + maxChildSize: 0.4, shouldCloseOnMinExtent: false, actions: [ - if (multiselect.isEnabled) const ShareActionButton(), + const ShareActionButton(), if (multiselect.hasRemote) ...[ const ShareLinkActionButton(source: ActionSource.timeline), const ArchiveActionButton(source: ActionSource.timeline), diff --git a/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart new file mode 100644 index 0000000000..3fd717f516 --- /dev/null +++ b/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_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/upload_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; + +class LocalAlbumBottomSheet extends ConsumerWidget { + const LocalAlbumBottomSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return const BaseBottomSheet( + initialChildSize: 0.25, + maxChildSize: 0.4, + shouldCloseOnMinExtent: false, + actions: [ + ShareActionButton(), + DeleteLocalActionButton(), + UploadActionButton(), + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart new file mode 100644 index 0000000000..97b2646f32 --- /dev/null +++ b/mobile/lib/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_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/remove_from_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/bottom_sheet/base_bottom_sheet.widget.dart'; + +class LockedFolderBottomSheet extends ConsumerWidget { + const LockedFolderBottomSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return const BaseBottomSheet( + initialChildSize: 0.25, + maxChildSize: 0.4, + shouldCloseOnMinExtent: false, + actions: [ + ShareActionButton(), + DownloadActionButton(), + DeletePermanentActionButton(source: ActionSource.timeline), + RemoveFromLockFolderActionButton(source: ActionSource.timeline), + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart new file mode 100644 index 0000000000..7af8ab7c86 --- /dev/null +++ b/mobile/lib/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; + +class PartnerDetailBottomSheet extends ConsumerWidget { + const PartnerDetailBottomSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return const BaseBottomSheet( + initialChildSize: 0.25, + maxChildSize: 0.4, + shouldCloseOnMinExtent: false, + actions: [ + ShareActionButton(), + DownloadActionButton(), + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart new file mode 100644 index 0000000000..0268a2b386 --- /dev/null +++ b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart @@ -0,0 +1,68 @@ +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/album/album.model.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/remove_from_album_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_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; + +class RemoteAlbumBottomSheet extends ConsumerWidget { + final RemoteAlbum album; + const RemoteAlbumBottomSheet({super.key, required this.album}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final multiselect = ref.watch(multiSelectProvider); + final isTrashEnable = ref.watch( + serverInfoProvider.select((state) => state.serverFeatures.trash), + ); + + return BaseBottomSheet( + initialChildSize: 0.25, + maxChildSize: 0.4, + shouldCloseOnMinExtent: false, + actions: [ + const ShareActionButton(), + if (multiselect.hasRemote) ...[ + const ShareLinkActionButton(source: ActionSource.timeline), + const ArchiveActionButton(source: ActionSource.timeline), + const FavoriteActionButton(source: ActionSource.timeline), + const DownloadActionButton(), + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton( + source: ActionSource.timeline, + ), + const EditDateTimeActionButton(), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton( + source: ActionSource.timeline, + ), + const StackActionButton(), + ], + if (multiselect.hasLocal) ...[ + const DeleteLocalActionButton(), + const UploadActionButton(), + ], + RemoveFromAlbumActionButton( + source: ActionSource.timeline, + albumId: album.id, + ), + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index bc1b044044..490f2bcff2 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -10,7 +10,7 @@ import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; @@ -28,13 +28,14 @@ class Timeline extends StatelessWidget { this.topSliverWidgetHeight, this.showStorageIndicator = false, this.appBar, + this.bottomSheet = const GeneralBottomSheet(), }); final Widget? topSliverWidget; final double? topSliverWidgetHeight; final bool showStorageIndicator; final Widget? appBar; - + final Widget? bottomSheet; @override Widget build(BuildContext context) { return Scaffold( @@ -56,6 +57,7 @@ class Timeline extends StatelessWidget { topSliverWidget: topSliverWidget, topSliverWidgetHeight: topSliverWidgetHeight, appBar: appBar, + bottomSheet: bottomSheet, ), ), ), @@ -68,11 +70,13 @@ class _SliverTimeline extends ConsumerStatefulWidget { this.topSliverWidget, this.topSliverWidgetHeight, this.appBar, + this.bottomSheet, }); final Widget? topSliverWidget; final double? topSliverWidgetHeight; final Widget? appBar; + final Widget? bottomSheet; @override ConsumerState createState() => _SliverTimelineState(); @@ -197,7 +201,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { } return const SizedBox.shrink(); }, - child: const HomeBottomAppBar(), + child: widget.bottomSheet, ), ], ], diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 63dc194ac6..5fcd060b0c 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -69,19 +69,19 @@ import 'package:immich_mobile/pages/search/person_result.page.dart'; import 'package:immich_mobile/pages/search/recently_taken.page.dart'; import 'package:immich_mobile/pages/search/search.page.dart'; import 'package:immich_mobile/pages/share_intent/share_intent.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_favorite.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_partner_detail.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_local_album.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_recently_taken.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_video.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_trash.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/drift_locked_folder.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_favorite.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_partner_detail.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_local_album.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_video.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_trash.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_archive.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_locked_folder.page.dart'; import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/local_timeline.page.dart'; +import 'package:immich_mobile/presentation/pages/local_timeline.page.dart'; import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart'; import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/remote_timeline.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart'; import 'package:immich_mobile/presentation/pages/drift_album.page.dart'; import 'package:immich_mobile/presentation/pages/drift_library.page.dart'; import 'package:immich_mobile/presentation/pages/drift_asset_selection_timeline.page.dart'; @@ -390,7 +390,7 @@ class AppRouter extends RootStackRouter { guards: [_authGuard, _duplicateGuard], ), AutoRoute( - page: RemoteTimelineRoute.page, + page: RemoteAlbumRoute.page, guards: [_authGuard, _duplicateGuard], ), AutoRoute( diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 51d171c582..bd2b148455 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -821,11 +821,11 @@ class DriftPartnerDetailRoute extends PageRouteInfo { DriftPartnerDetailRoute({ Key? key, - required String partnerId, + required UserDto partner, List? children, }) : super( DriftPartnerDetailRoute.name, - args: DriftPartnerDetailRouteArgs(key: key, partnerId: partnerId), + args: DriftPartnerDetailRouteArgs(key: key, partner: partner), initialChildren: children, ); @@ -835,21 +835,21 @@ class DriftPartnerDetailRoute name, builder: (data) { final args = data.argsAs(); - return DriftPartnerDetailPage(key: args.key, partnerId: args.partnerId); + return DriftPartnerDetailPage(key: args.key, partner: args.partner); }, ); } class DriftPartnerDetailRouteArgs { - const DriftPartnerDetailRouteArgs({this.key, required this.partnerId}); + const DriftPartnerDetailRouteArgs({this.key, required this.partner}); final Key? key; - final String partnerId; + final UserDto partner; @override String toString() { - return 'DriftPartnerDetailRouteArgs{key: $key, partnerId: $partnerId}'; + return 'DriftPartnerDetailRouteArgs{key: $key, partner: $partner}'; } } @@ -1760,6 +1760,43 @@ class RecentlyTakenRoute extends PageRouteInfo { ); } +/// generated route for +/// [RemoteAlbumPage] +class RemoteAlbumRoute extends PageRouteInfo { + RemoteAlbumRoute({ + Key? key, + required RemoteAlbum album, + List? children, + }) : super( + RemoteAlbumRoute.name, + args: RemoteAlbumRouteArgs(key: key, album: album), + initialChildren: children, + ); + + static const String name = 'RemoteAlbumRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return RemoteAlbumPage(key: args.key, album: args.album); + }, + ); +} + +class RemoteAlbumRouteArgs { + const RemoteAlbumRouteArgs({this.key, required this.album}); + + final Key? key; + + final RemoteAlbum album; + + @override + String toString() { + return 'RemoteAlbumRouteArgs{key: $key, album: $album}'; + } +} + /// generated route for /// [RemoteMediaSummaryPage] class RemoteMediaSummaryRoute extends PageRouteInfo { @@ -1776,43 +1813,6 @@ class RemoteMediaSummaryRoute extends PageRouteInfo { ); } -/// generated route for -/// [RemoteTimelinePage] -class RemoteTimelineRoute extends PageRouteInfo { - RemoteTimelineRoute({ - Key? key, - required RemoteAlbum album, - List? children, - }) : super( - RemoteTimelineRoute.name, - args: RemoteTimelineRouteArgs(key: key, album: album), - initialChildren: children, - ); - - static const String name = 'RemoteTimelineRoute'; - - static PageInfo page = PageInfo( - name, - builder: (data) { - final args = data.argsAs(); - return RemoteTimelinePage(key: args.key, album: args.album); - }, - ); -} - -class RemoteTimelineRouteArgs { - const RemoteTimelineRouteArgs({this.key, required this.album}); - - final Key? key; - - final RemoteAlbum album; - - @override - String toString() { - return 'RemoteTimelineRouteArgs{key: $key, album: $album}'; - } -} - /// generated route for /// [SearchPage] class SearchRoute extends PageRouteInfo {