From adf7064b2c6903632ca183242adc73d3a1ddc95b Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 4 Jul 2025 15:25:34 -0500 Subject: [PATCH] feat: new create album page --- .../lib/domain/models/album/album.model.dart | 6 +- .../domain/services/remote_album.service.dart | 32 +- .../repositories/remote_album.repository.dart | 24 +- .../presentation/pages/drift_album.page.dart | 21 +- .../pages/drift_create_album.page.dart | 280 ++++++++++++++++++ .../infrastructure/album.provider.dart | 6 +- .../infrastructure/remote_album.provider.dart | 39 ++- .../repositories/album_api.repository.dart | 39 +++ mobile/lib/routing/router.dart | 6 + mobile/lib/routing/router.gr.dart | 39 +++ mobile/lib/utils/remote_album.utils.dart | 35 ++- 11 files changed, 496 insertions(+), 31 deletions(-) create mode 100644 mobile/lib/presentation/pages/drift_create_album.page.dart diff --git a/mobile/lib/domain/models/album/album.model.dart b/mobile/lib/domain/models/album/album.model.dart index 29f75f29ef..7cafca9116 100644 --- a/mobile/lib/domain/models/album/album.model.dart +++ b/mobile/lib/domain/models/album/album.model.dart @@ -11,7 +11,7 @@ enum AlbumUserRole { } // Model for an album stored in the server -class Album { +class RemoteAlbum { final String id; final String name; final String ownerId; @@ -24,7 +24,7 @@ class Album { final int assetCount; final String ownerName; - const Album({ + const RemoteAlbum({ required this.id, required this.name, required this.ownerId, @@ -57,7 +57,7 @@ class Album { @override bool operator ==(Object other) { - if (other is! Album) return false; + if (other is! RemoteAlbum) return false; if (identical(this, other)) return true; return id == other.id && name == other.name && diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index f3106470d2..fde57629e1 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -1,33 +1,35 @@ import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; +import 'package:immich_mobile/repositories/album_api.repository.dart'; import 'package:immich_mobile/utils/remote_album.utils.dart'; class RemoteAlbumService { final DriftRemoteAlbumRepository _repository; + final AlbumApiRepository _albumApiRepository; - const RemoteAlbumService(this._repository); + const RemoteAlbumService(this._repository, this._albumApiRepository); - Future> getAll() { + Future> getAll() { return _repository.getAll(); } - List sortAlbums( - List albums, + List sortAlbums( + List albums, RemoteAlbumSortMode sortMode, { bool isReverse = false, }) { return sortMode.sortFn(albums, isReverse); } - List searchAlbums( - List albums, + List searchAlbums( + List albums, String query, String? userId, [ QuickFilterMode filterMode = QuickFilterMode.all, ]) { final lowerQuery = query.toLowerCase(); - List filtered = albums; + List filtered = albums; // Apply text search filter if (query.isNotEmpty) { @@ -57,4 +59,20 @@ class RemoteAlbumService { return filtered; } + + Future createAlbum({ + required String title, + required List assetIds, + String? description, + }) async { + final album = await _albumApiRepository.createDriftAlbum( + title, + description: description, + assetIds: assetIds, + ); + + await _repository.create(album); + + return album; + } } diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 0e49b45bc5..b7465db513 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -9,7 +9,9 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { final Drift _db; const DriftRemoteAlbumRepository(this._db) : super(_db); - Future> getAll({Set sortBy = const {}}) { + Future> getAll({ + Set sortBy = const {}, + }) { final assetCount = _db.remoteAlbumAssetEntity.assetId.count(); final query = _db.remoteAlbumEntity.select().join([ @@ -48,11 +50,27 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { ) .get(); } + + Future create(RemoteAlbum album) { + final entity = RemoteAlbumEntityCompanion( + id: Value(album.id), + name: Value(album.name), + ownerId: Value(album.ownerId), + createdAt: Value(album.createdAt), + updatedAt: Value(album.updatedAt), + description: Value(album.description), + thumbnailAssetId: Value(album.thumbnailAssetId), + isActivityEnabled: Value(album.isActivityEnabled), + order: Value(album.order), + ); + + return _db.into(_db.remoteAlbumEntity).insert(entity); + } } extension on RemoteAlbumEntityData { - Album toDto({int assetCount = 0, required String ownerName}) { - return Album( + RemoteAlbum toDto({int assetCount = 0, required String ownerName}) { + return RemoteAlbum( id: id, name: name, ownerId: ownerId, diff --git a/mobile/lib/presentation/pages/drift_album.page.dart b/mobile/lib/presentation/pages/drift_album.page.dart index 3298f12b65..1efb63e26f 100644 --- a/mobile/lib/presentation/pages/drift_album.page.dart +++ b/mobile/lib/presentation/pages/drift_album.page.dart @@ -97,7 +97,20 @@ class _DriftAlbumsPageState extends ConsumerState { onRefresh: onRefresh, child: CustomScrollView( slivers: [ - const ImmichSliverAppBar(), + ImmichSliverAppBar( + actions: [ + IconButton( + icon: const Icon( + Icons.add_rounded, + size: 28, + ), + onPressed: () => context.pushRoute( + DriftCreateAlbumRoute(), + ), + ), + ], + showUploadButton: false, + ), _SearchBar( searchController: searchController, searchFocusNode: searchFocusNode, @@ -475,7 +488,7 @@ class _AlbumList extends StatelessWidget { final bool isLoading; final String? error; - final List albums; + final List albums; final String? userId; @override @@ -599,7 +612,7 @@ class _AlbumGrid extends StatelessWidget { required this.error, }); - final List albums; + final List albums; final String? userId; final bool isLoading; final String? error; @@ -674,7 +687,7 @@ class _GridAlbumCard extends StatelessWidget { required this.userId, }); - final Album album; + final RemoteAlbum album; final String? userId; @override diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart new file mode 100644 index 0000000000..dbedfc792c --- /dev/null +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -0,0 +1,280 @@ +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/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/album/album_title.provider.dart'; +import 'package:immich_mobile/providers/album/album_viewer.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/album/album_action_filled_button.dart'; +import 'package:immich_mobile/widgets/album/album_title_text_field.dart'; +import 'package:immich_mobile/widgets/album/album_viewer_editable_description.dart'; +import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart'; + +@RoutePage() +class DriftCreateAlbumPage extends HookConsumerWidget { + final List? assets; + + const DriftCreateAlbumPage({super.key, this.assets}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumTitleController = + useTextEditingController.fromValue(TextEditingValue.empty); + final albumTitleTextFieldFocusNode = useFocusNode(); + final albumDescriptionTextFieldFocusNode = useFocusNode(); + final isAlbumTitleTextFieldFocus = useState(false); + final isAlbumTitleEmpty = useState(true); + final selectedAssets = useState>( + assets != null ? Set.from(assets!) : const {}, + ); + + void onBackgroundTapped() { + albumTitleTextFieldFocusNode.unfocus(); + albumDescriptionTextFieldFocusNode.unfocus(); + isAlbumTitleTextFieldFocus.value = false; + + if (albumTitleController.text.isEmpty) { + albumTitleController.text = 'create_album_page_untitled'.tr(); + isAlbumTitleEmpty.value = false; + ref + .watch(albumTitleProvider.notifier) + .setAlbumTitle('create_album_page_untitled'.tr()); + } + } + + onSelectPhotosButtonPressed() async { + // AssetSelectionPageResult? selectedAsset = + // await context.pushRoute( + // AlbumAssetSelectionRoute( + // existingAssets: selectedAssets.value, + // canDeselect: true, + // ), + // ); + // if (selectedAsset == null) { + // selectedAssets.value = const {}; + // } else { + // selectedAssets.value = selectedAsset.selectedAssets; + // } + } + + buildTitleInputField() { + return Padding( + padding: const EdgeInsets.only( + right: 10, + left: 10, + ), + child: AlbumTitleTextField( + isAlbumTitleEmpty: isAlbumTitleEmpty, + albumTitleTextFieldFocusNode: albumTitleTextFieldFocusNode, + albumTitleController: albumTitleController, + isAlbumTitleTextFieldFocus: isAlbumTitleTextFieldFocus, + ), + ); + } + + buildDescriptionInputField() { + return Padding( + padding: const EdgeInsets.only( + right: 10, + left: 10, + ), + child: AlbumViewerEditableDescription( + albumDescription: '', + descriptionFocusNode: albumDescriptionTextFieldFocusNode, + ), + ); + } + + buildTitle() { + if (selectedAssets.value.isEmpty) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: 200, left: 18), + child: Text( + 'create_shared_album_page_share_add_assets', + style: context.textTheme.labelLarge, + ).tr(), + ), + ); + } + + return const SliverToBoxAdapter(); + } + + buildSelectPhotosButton() { + if (selectedAssets.value.isEmpty) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: 16, left: 16, right: 16), + child: FilledButton.icon( + style: FilledButton.styleFrom( + alignment: Alignment.centerLeft, + padding: + const EdgeInsets.symmetric(vertical: 24, horizontal: 16), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(10), + ), + ), + backgroundColor: context.colorScheme.surfaceContainerHigh, + ), + onPressed: onSelectPhotosButtonPressed, + icon: Icon( + Icons.add_rounded, + color: context.primaryColor, + ), + label: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + 'create_shared_album_page_share_select_photos', + style: context.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.primaryColor, + ), + ).tr(), + ), + ), + ), + ); + } + + return const SliverToBoxAdapter(); + } + + buildControlButton() { + return Padding( + padding: const EdgeInsets.only(left: 12.0, top: 16, bottom: 16), + child: SizedBox( + height: 42, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + AlbumActionFilledButton( + iconData: Icons.add_photo_alternate_outlined, + onPressed: onSelectPhotosButtonPressed, + labelText: "add_photos".tr(), + ), + ], + ), + ), + ); + } + + buildSelectedImageGrid() { + // if (selectedAssets.value.isNotEmpty) { + // return SliverPadding( + // padding: const EdgeInsets.only(top: 16), + // sliver: SliverGrid( + // gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + // crossAxisCount: 3, + // crossAxisSpacing: 5.0, + // mainAxisSpacing: 5, + // ), + // delegate: SliverChildBuilderDelegate( + // (BuildContext context, int index) { + // return GestureDetector( + // onTap: onBackgroundTapped, + // child: SharedAlbumThumbnailImage( + // asset: selectedAssets.value.elementAt(index), + // ), + // ); + // }, + // childCount: selectedAssets.value.length, + // ), + // ), + // ); + // } + + return const SliverToBoxAdapter(); + } + + Future createAlbum() async { + onBackgroundTapped(); + + final description = + ref.read(albumViewerProvider.select((s) => s.editDescriptionText)); + + final newAlbum = + await ref.watch(remoteAlbumProvider.notifier).createAlbum( + title: ref.read(albumTitleProvider), + description: description, + // selectedAssets.value.map((e) => e.id).toList(), + ); + + if (newAlbum != null) { + ref.read(albumProvider.notifier).refreshRemoteAlbums(); + selectedAssets.value = {}; + ref.read(albumTitleProvider.notifier).clearAlbumTitle(); + ref.read(albumViewerProvider.notifier).disableEditAlbum(); + // context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id)); + } + } + + return Scaffold( + appBar: AppBar( + elevation: 0, + centerTitle: false, + backgroundColor: context.scaffoldBackgroundColor, + leading: IconButton( + onPressed: () { + selectedAssets.value = {}; + context.maybePop(); + }, + icon: const Icon(Icons.close_rounded), + ), + title: const Text( + 'create_album', + ).tr(), + actions: [ + TextButton( + onPressed: + albumTitleController.text.isNotEmpty ? createAlbum : null, + child: Text( + 'create'.tr(), + style: TextStyle( + fontWeight: FontWeight.bold, + color: albumTitleController.text.isNotEmpty + ? context.primaryColor + : context.themeData.disabledColor, + ), + ), + ), + ], + ), + body: GestureDetector( + onTap: onBackgroundTapped, + child: CustomScrollView( + slivers: [ + SliverAppBar( + backgroundColor: context.scaffoldBackgroundColor, + elevation: 5, + automaticallyImplyLeading: false, + pinned: true, + floating: false, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(125.0), + child: Column( + children: [ + buildTitleInputField(), + buildDescriptionInputField(), + if (selectedAssets.value.isNotEmpty) buildControlButton(), + ], + ), + ), + ), + buildTitle(), + buildSelectPhotosButton(), + buildSelectedImageGrid(), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/providers/infrastructure/album.provider.dart b/mobile/lib/providers/infrastructure/album.provider.dart index f222e20f50..7cd345cdbf 100644 --- a/mobile/lib/providers/infrastructure/album.provider.dart +++ b/mobile/lib/providers/infrastructure/album.provider.dart @@ -4,6 +4,7 @@ import 'package:immich_mobile/infrastructure/repositories/local_album.repository import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; +import 'package:immich_mobile/repositories/album_api.repository.dart'; final localAlbumRepository = Provider( (ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)), @@ -14,7 +15,10 @@ final remoteAlbumRepository = Provider( ); final remoteAlbumServiceProvider = Provider( - (ref) => RemoteAlbumService(ref.watch(remoteAlbumRepository)), + (ref) => RemoteAlbumService( + ref.watch(remoteAlbumRepository), + ref.watch(albumApiRepositoryProvider), + ), dependencies: [remoteAlbumRepository], ); diff --git a/mobile/lib/providers/infrastructure/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index 2e5a475b9c..84db53ab9f 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -8,21 +8,21 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'album.provider.dart'; class RemoteAlbumState { - final List albums; - final List filteredAlbums; + final List albums; + final List filteredAlbums; final bool isLoading; final String? error; const RemoteAlbumState({ required this.albums, - List? filteredAlbums, + List? filteredAlbums, this.isLoading = false, this.error, }) : filteredAlbums = filteredAlbums ?? albums; RemoteAlbumState copyWith({ - List? albums, - List? filteredAlbums, + List? albums, + List? filteredAlbums, bool? isLoading, String? error, }) { @@ -66,7 +66,7 @@ class RemoteAlbumNotifier extends Notifier { return const RemoteAlbumState(albums: [], filteredAlbums: []); } - Future> getAll() async { + Future> getAll() async { state = state.copyWith(isLoading: true, error: null); try { @@ -118,4 +118,31 @@ class RemoteAlbumNotifier extends Notifier { .sortAlbums(state.filteredAlbums, sortMode, isReverse: isReverse); state = state.copyWith(filteredAlbums: sortedAlbums); } + + Future createAlbum({ + required String title, + String? description, + List assetIds = const [], + }) async { + state = state.copyWith(isLoading: true, error: null); + + try { + final album = await _remoteAlbumService.createAlbum( + title: title, + description: description, + assetIds: assetIds, + ); + + state = state.copyWith( + albums: [...state.albums, album], + filteredAlbums: [...state.filteredAlbums, album], + ); + + state = state.copyWith(isLoading: false); + return album; + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + rethrow; + } + } } diff --git a/mobile/lib/repositories/album_api.repository.dart b/mobile/lib/repositories/album_api.repository.dart index 019e4dc63c..20365534c2 100644 --- a/mobile/lib/repositories/album_api.repository.dart +++ b/mobile/lib/repositories/album_api.repository.dart @@ -1,5 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart' + show AlbumAssetOrder, RemoteAlbum; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart' @@ -50,6 +52,25 @@ class AlbumApiRepository extends ApiRepository { return _toAlbum(responseDto); } + // TODO: Change name after removing old method + Future createDriftAlbum( + String name, { + required Iterable assetIds, + String? description, + }) async { + final responseDto = await checkNull( + _api.createAlbum( + CreateAlbumDto( + albumName: name, + description: description, + assetIds: assetIds.toList(), + ), + ), + ); + + return _toRemoteAlbum(responseDto); + } + Future update( String albumId, { String? name, @@ -170,4 +191,22 @@ class AlbumApiRepository extends ApiRepository { return album; } + + static RemoteAlbum _toRemoteAlbum(AlbumResponseDto dto) { + return RemoteAlbum( + id: dto.id, + name: dto.albumName, + ownerId: dto.owner.id, + description: dto.description, + createdAt: dto.createdAt, + updatedAt: dto.updatedAt, + thumbnailAssetId: dto.albumThumbnailAssetId, + isActivityEnabled: dto.isActivityEnabled, + order: dto.order == AssetOrder.asc + ? AlbumAssetOrder.asc + : AlbumAssetOrder.desc, + assetCount: dto.assetCount, + ownerName: dto.owner.name, + ); + } } diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 56e1aa0f96..1d7771c155 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -1,6 +1,7 @@ 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/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; @@ -72,6 +73,7 @@ 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_album.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_create_album.page.dart'; import 'package:immich_mobile/presentation/pages/drift_memory.page.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart'; import 'package:immich_mobile/providers/api.provider.dart'; @@ -392,6 +394,10 @@ class AppRouter extends RootStackRouter { page: DriftMemoryRoute.page, guards: [_authGuard, _duplicateGuard], ), + AutoRoute( + page: DriftCreateAlbumRoute.page, + guards: [_authGuard, _duplicateGuard], + ), // required to handle all deeplinks in deep_link.service.dart // auto_route_library#1722 diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index f8f37970f9..38f648eb8c 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -618,6 +618,45 @@ class DriftAlbumsRoute extends PageRouteInfo { ); } +/// generated route for +/// [DriftCreateAlbumPage] +class DriftCreateAlbumRoute extends PageRouteInfo { + DriftCreateAlbumRoute({ + Key? key, + List? assets, + List? children, + }) : super( + DriftCreateAlbumRoute.name, + args: DriftCreateAlbumRouteArgs(key: key, assets: assets), + initialChildren: children, + ); + + static const String name = 'DriftCreateAlbumRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const DriftCreateAlbumRouteArgs(), + ); + return DriftCreateAlbumPage(key: args.key, assets: args.assets); + }, + ); +} + +class DriftCreateAlbumRouteArgs { + const DriftCreateAlbumRouteArgs({this.key, this.assets}); + + final Key? key; + + final List? assets; + + @override + String toString() { + return 'DriftCreateAlbumRouteArgs{key: $key, assets: $assets}'; + } +} + /// generated route for /// [DriftMemoryPage] class DriftMemoryRoute extends PageRouteInfo { diff --git a/mobile/lib/utils/remote_album.utils.dart b/mobile/lib/utils/remote_album.utils.dart index 4fc7ba5f74..04184ee367 100644 --- a/mobile/lib/utils/remote_album.utils.dart +++ b/mobile/lib/utils/remote_album.utils.dart @@ -1,38 +1,56 @@ import 'package:collection/collection.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; -typedef AlbumSortFn = List Function(List albums, bool isReverse); +typedef AlbumSortFn = List Function( + List albums, + bool isReverse, +); class _RemoteAlbumSortHandlers { const _RemoteAlbumSortHandlers._(); static const AlbumSortFn created = _sortByCreated; - static List _sortByCreated(List albums, bool isReverse) { + static List _sortByCreated( + List albums, + bool isReverse, + ) { final sorted = albums.sortedBy((album) => album.createdAt); return (isReverse ? sorted.reversed : sorted).toList(); } static const AlbumSortFn title = _sortByTitle; - static List _sortByTitle(List albums, bool isReverse) { + static List _sortByTitle( + List albums, + bool isReverse, + ) { final sorted = albums.sortedBy((album) => album.name); return (isReverse ? sorted.reversed : sorted).toList(); } static const AlbumSortFn lastModified = _sortByLastModified; - static List _sortByLastModified(List albums, bool isReverse) { + static List _sortByLastModified( + List albums, + bool isReverse, + ) { final sorted = albums.sortedBy((album) => album.updatedAt); return (isReverse ? sorted.reversed : sorted).toList(); } static const AlbumSortFn assetCount = _sortByAssetCount; - static List _sortByAssetCount(List albums, bool isReverse) { + static List _sortByAssetCount( + List albums, + bool isReverse, + ) { final sorted = albums.sorted((a, b) => a.assetCount.compareTo(b.assetCount)); return (isReverse ? sorted.reversed : sorted).toList(); } static const AlbumSortFn mostRecent = _sortByMostRecent; - static List _sortByMostRecent(List albums, bool isReverse) { + static List _sortByMostRecent( + List albums, + bool isReverse, + ) { final sorted = albums.sorted((a, b) { // For most recent, we sort by updatedAt in descending order return b.updatedAt.compareTo(a.updatedAt); @@ -41,7 +59,10 @@ class _RemoteAlbumSortHandlers { } static const AlbumSortFn mostOldest = _sortByMostOldest; - static List _sortByMostOldest(List albums, bool isReverse) { + static List _sortByMostOldest( + List albums, + bool isReverse, + ) { final sorted = albums.sorted((a, b) { // For oldest, we sort by createdAt in ascending order return a.createdAt.compareTo(b.createdAt);