diff --git a/mobile/lib/pages/collections/albums/albums_collection.page.dart b/mobile/lib/pages/collections/albums/albums_collection.page.dart index a55ccf5fb8d59..e05c68f7ec77d 100644 --- a/mobile/lib/pages/collections/albums/albums_collection.page.dart +++ b/mobile/lib/pages/collections/albums/albums_collection.page.dart @@ -1,18 +1,198 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart'; +import 'package:immich_mobile/widgets/common/immich_app_bar.dart'; @RoutePage() class AlbumsCollectionPage extends HookConsumerWidget { const AlbumsCollectionPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final albums = ref.watch(albumProvider); + final albumSortOption = ref.watch(albumSortByOptionsProvider); + final albumSortIsReverse = ref.watch(albumSortOrderProvider); + final remote = albums.where((a) => a.isRemote).toList(); + final sorted = albumSortOption.sortFn(remote, albumSortIsReverse); + final local = albums.where((a) => a.isLocal).toList(); + + useEffect( + () { + ref.read(albumProvider.notifier).getAllAlbums(); + return null; + }, + [], + ); + return Scaffold( appBar: AppBar( - title: const Text('albums_collection_page_title'), + title: const Text("Albums"), ), - body: const Center( - child: Text('albums_collection_page_content'), + body: CustomScrollView( + slivers: [ + const SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only( + top: 12.0, + left: 12.0, + right: 12.0, + bottom: 20.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SortButton(), + ], + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12.0), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 250, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + childAspectRatio: .7, + ), + delegate: SliverChildBuilderDelegate( + childCount: sorted.length, + (context, index) { + return AlbumThumbnailCard( + album: sorted[index], + onTap: () => context.pushRoute( + AlbumViewerRoute( + albumId: sorted[index].id, + ), + ), + ); + }, + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only( + top: 12.0, + left: 12.0, + right: 12.0, + bottom: 20.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'library_page_device_albums', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + ), + ).tr(), + ], + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12.0), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 250, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + childAspectRatio: .7, + ), + delegate: SliverChildBuilderDelegate( + childCount: local.length, + (context, index) => AlbumThumbnailCard( + album: local[index], + onTap: () => context.pushRoute( + AlbumViewerRoute( + albumId: local[index].id, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class SortButton extends ConsumerWidget { + const SortButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumSortOption = ref.watch(albumSortByOptionsProvider); + final albumSortIsReverse = ref.watch(albumSortOrderProvider); + + return PopupMenuButton( + position: PopupMenuPosition.over, + itemBuilder: (BuildContext context) { + return AlbumSortMode.values + .map>((option) { + final selected = albumSortOption == option; + return PopupMenuItem( + value: option, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 12.0), + child: Icon( + Icons.check, + color: selected ? context.primaryColor : Colors.transparent, + ), + ), + Text( + option.label.tr(), + style: TextStyle( + color: selected ? context.primaryColor : null, + fontSize: 14.0, + ), + ), + ], + ), + ); + }).toList(); + }, + onSelected: (AlbumSortMode value) { + final selected = albumSortOption == value; + // Switch direction + if (selected) { + ref + .read(albumSortOrderProvider.notifier) + .changeSortDirection(!albumSortIsReverse); + } else { + ref.read(albumSortByOptionsProvider.notifier).changeSortMode(value); + } + }, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 5), + child: Icon( + albumSortIsReverse + ? Icons.arrow_downward_rounded + : Icons.arrow_upward_rounded, + size: 14, + color: context.primaryColor, + ), + ), + Text( + albumSortOption.label.tr(), + style: context.textTheme.labelLarge?.copyWith( + color: context.primaryColor, + ), + ), + ], ), ); } diff --git a/mobile/lib/pages/collections/collections.page.dart b/mobile/lib/pages/collections/collections.page.dart index 91492386ccbd4..1210b5bb2706f 100644 --- a/mobile/lib/pages/collections/collections.page.dart +++ b/mobile/lib/pages/collections/collections.page.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -15,10 +16,13 @@ import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @RoutePage() -class CollectionsPage extends StatelessWidget { +class CollectionsPage extends ConsumerWidget { const CollectionsPage({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final trashEnabled = + ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); + return Scaffold( appBar: const ImmichAppBar( showUploadButton: false, @@ -53,11 +57,13 @@ class CollectionsPage extends StatelessWidget { label: 'Shared links', ), const SizedBox(width: 8), - ActionButton( - onPressed: () => context.pushRoute(const TrashRoute()), - icon: Icons.delete_outline_rounded, - label: 'Trash', - ), + trashEnabled + ? ActionButton( + onPressed: () => context.pushRoute(const TrashRoute()), + icon: Icons.delete_outline_rounded, + label: 'Trash', + ) + : const SizedBox.shrink(), ], ), const SizedBox(height: 24), @@ -141,9 +147,11 @@ class AlbumsCollectionCard extends ConsumerWidget { : ref.watch(albumProvider).where((album) => album.isRemote); final size = MediaQuery.of(context).size.width * 0.5 - 20; return GestureDetector( - onTap: () => context.pushRoute(isLocal - ? const LocalAlbumsCollectionRoute() - : const AlbumsCollectionRoute()), + onTap: () => context.pushRoute( + isLocal + ? const LocalAlbumsCollectionRoute() + : const AlbumsCollectionRoute(), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 81ada3b106d9f..c840c0c0e2175 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -145,7 +145,11 @@ class AppRouter extends RootStackRouter { ), AutoRoute(page: EditImageRoute.page), AutoRoute(page: CropImageRoute.page), - AutoRoute(page: FavoritesRoute.page, guards: [_authGuard, _duplicateGuard]), + CustomRoute( + page: FavoritesRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), AutoRoute(page: AllVideosRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute( page: AllMotionPhotosRoute.page, @@ -191,8 +195,16 @@ class AppRouter extends RootStackRouter { AutoRoute(page: SettingsSubRoute.page, guards: [_duplicateGuard]), AutoRoute(page: AppLogRoute.page, guards: [_duplicateGuard]), AutoRoute(page: AppLogDetailRoute.page, guards: [_duplicateGuard]), - AutoRoute(page: ArchiveRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: PartnerRoute.page, guards: [_authGuard, _duplicateGuard]), + CustomRoute( + page: ArchiveRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + CustomRoute( + page: PartnerRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), AutoRoute( page: PartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard], @@ -208,10 +220,15 @@ class AppRouter extends RootStackRouter { page: AlbumOptionsRoute.page, guards: [_authGuard, _duplicateGuard], ), - AutoRoute(page: TrashRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute( + CustomRoute( + page: TrashRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + CustomRoute( page: SharedLinkRoute.page, guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, ), AutoRoute( page: SharedLinkEditRoute.page,