refactor: migrate album sort option to provider
This commit is contained in:
parent
086a957a2b
commit
a753832c04
6 changed files with 219 additions and 75 deletions
|
@ -196,9 +196,11 @@
|
|||
"library_page_favorites": "Favorites",
|
||||
"library_page_new_album": "New album",
|
||||
"library_page_sharing": "Sharing",
|
||||
"library_page_sort_created": "Most recently created",
|
||||
"library_page_sort_created": "Created date",
|
||||
"library_page_sort_last_modified": "Last modified",
|
||||
"library_page_sort_most_recent_photo": "Most recent photo",
|
||||
"library_page_sort_most_oldest_photo": "Oldest photo",
|
||||
"library_page_sort_asset_count": "Number of assets",
|
||||
"library_page_sort_title": "Album title",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'album_sort_options.provider.g.dart';
|
||||
|
||||
typedef AlbumSortFn = List<Album> Function(List<Album> albums, bool isReverse);
|
||||
|
||||
class _AlbumSortHandlers {
|
||||
const _AlbumSortHandlers._();
|
||||
|
||||
static const AlbumSortFn created = _sortByCreated;
|
||||
static List<Album> _sortByCreated(List<Album> albums, bool isReverse) {
|
||||
final sorted =
|
||||
albums.where((a) => a.isRemote).sortedBy((album) => album.createdAt);
|
||||
return (isReverse ? sorted.reversed : sorted).toList();
|
||||
}
|
||||
|
||||
static const AlbumSortFn title = _sortByTitle;
|
||||
static List<Album> _sortByTitle(List<Album> albums, bool isReverse) {
|
||||
final sorted =
|
||||
albums.where((a) => a.isRemote).sortedBy((album) => album.name);
|
||||
return (isReverse ? sorted.reversed : sorted).toList();
|
||||
}
|
||||
|
||||
static const AlbumSortFn lastModified = _sortByLastModified;
|
||||
static List<Album> _sortByLastModified(List<Album> albums, bool isReverse) {
|
||||
final sorted =
|
||||
albums.where((a) => a.isRemote).sortedBy((album) => album.modifiedAt);
|
||||
return (isReverse ? sorted.reversed : sorted).toList();
|
||||
}
|
||||
|
||||
static const AlbumSortFn assetCount = _sortByAssetCount;
|
||||
static List<Album> _sortByAssetCount(List<Album> albums, bool isReverse) {
|
||||
final sorted = albums
|
||||
.where((a) => a.isRemote)
|
||||
.sorted((a, b) => a.assetCount.compareTo(b.assetCount));
|
||||
return (isReverse ? sorted.reversed : sorted).toList();
|
||||
}
|
||||
|
||||
static const AlbumSortFn mostRecent = _sortByMostRecent;
|
||||
static List<Album> _sortByMostRecent(List<Album> albums, bool isReverse) {
|
||||
final sorted = albums
|
||||
.where((a) => a.isRemote)
|
||||
.sortedBy((album) => album.endDate ?? DateTime(1000))
|
||||
.sorted((a, b) {
|
||||
if (a.endDate == null) return 1;
|
||||
if (b.endDate == null) return -1;
|
||||
return 0;
|
||||
});
|
||||
return (isReverse ? sorted.reversed : sorted).toList();
|
||||
}
|
||||
|
||||
static const AlbumSortFn mostOldest = _sortByMostOldest;
|
||||
static List<Album> _sortByMostOldest(List<Album> albums, bool isReverse) {
|
||||
final sorted = albums
|
||||
.where((a) => a.isRemote)
|
||||
.sortedBy((album) => album.startDate ?? DateTime.now())
|
||||
.sorted((a, b) {
|
||||
if (a.startDate == null) return 1;
|
||||
if (b.startDate == null) return -1;
|
||||
return 0;
|
||||
});
|
||||
return (isReverse ? sorted.reversed : sorted).toList();
|
||||
}
|
||||
}
|
||||
|
||||
// Store index allows us to re-arrange the values without affecting the saved prefs
|
||||
enum AlbumSortMode {
|
||||
title(1, "library_page_sort_title", _AlbumSortHandlers.title),
|
||||
assetCount(4, "library_page_sort_asset_count", _AlbumSortHandlers.assetCount),
|
||||
lastModified(
|
||||
3,
|
||||
"library_page_sort_last_modified",
|
||||
_AlbumSortHandlers.lastModified,
|
||||
),
|
||||
created(0, "library_page_sort_created", _AlbumSortHandlers.created),
|
||||
mostRecent(
|
||||
2,
|
||||
"library_page_sort_most_recent_photo",
|
||||
_AlbumSortHandlers.mostRecent,
|
||||
),
|
||||
mostOldest(
|
||||
5,
|
||||
"library_page_sort_most_oldest_photo",
|
||||
_AlbumSortHandlers.mostOldest,
|
||||
);
|
||||
|
||||
final int storeIndex;
|
||||
final String label;
|
||||
final AlbumSortFn sortFn;
|
||||
|
||||
const AlbumSortMode(this.storeIndex, this.label, this.sortFn);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class AlbumSortFunction extends _$AlbumSortFunction {
|
||||
@override
|
||||
AlbumSortMode build() {
|
||||
final sortOpt = ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.selectedAlbumSortOrder);
|
||||
return sortOpt < AlbumSortMode.values.length && sortOpt >= 0
|
||||
? AlbumSortMode.values.firstWhere((e) => e.index == sortOpt)
|
||||
: AlbumSortMode.title;
|
||||
}
|
||||
|
||||
void changeSortMode(AlbumSortMode sortOption) {
|
||||
state = sortOption;
|
||||
ref.watch(appSettingsServiceProvider).setSetting(
|
||||
AppSettingsEnum.selectedAlbumSortOrder,
|
||||
sortOption.storeIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class AlbumSortOrder extends _$AlbumSortOrder {
|
||||
@override
|
||||
bool build() {
|
||||
return ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.selectedAlbumSortReverse);
|
||||
}
|
||||
|
||||
void changeSortDirection(bool isReverse) {
|
||||
state = isReverse;
|
||||
ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.setSetting(AppSettingsEnum.selectedAlbumSortReverse, isReverse);
|
||||
}
|
||||
}
|
42
mobile/lib/modules/album/providers/album_sort_options.provider.g.dart
generated
Normal file
42
mobile/lib/modules/album/providers/album_sort_options.provider.g.dart
generated
Normal file
|
@ -0,0 +1,42 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'album_sort_options.provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$albumSortFunctionHash() => r'19a199c30181c19bb8f834f3105b27e6b0fb92f4';
|
||||
|
||||
/// See also [AlbumSortFunction].
|
||||
@ProviderFor(AlbumSortFunction)
|
||||
final albumSortFunctionProvider =
|
||||
AutoDisposeNotifierProvider<AlbumSortFunction, AlbumSortMode>.internal(
|
||||
AlbumSortFunction.new,
|
||||
name: r'albumSortFunctionProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$albumSortFunctionHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AlbumSortFunction = AutoDisposeNotifier<AlbumSortMode>;
|
||||
String _$albumSortOrderHash() => r'573dea45b4519e69386fc7104c72522e35713440';
|
||||
|
||||
/// See also [AlbumSortOrder].
|
||||
@ProviderFor(AlbumSortOrder)
|
||||
final albumSortOrderProvider =
|
||||
AutoDisposeNotifierProvider<AlbumSortOrder, bool>.internal(
|
||||
AlbumSortOrder.new,
|
||||
name: r'albumSortOrderProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$albumSortOrderHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AlbumSortOrder = AutoDisposeNotifier<bool>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
|
@ -1,15 +1,12 @@
|
|||
import 'package:collection/collection.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/modules/album/providers/album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album_sort_options.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_app_bar.dart';
|
||||
|
||||
|
@ -21,8 +18,9 @@ class LibraryPage extends HookConsumerWidget {
|
|||
final trashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
final albums = ref.watch(albumProvider);
|
||||
var isDarkTheme = context.isDarkTheme;
|
||||
var settings = ref.watch(appSettingsServiceProvider);
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
final albumSortOption = ref.watch(albumSortFunctionProvider);
|
||||
final albumSortIsReverse = ref.watch(albumSortOrderProvider);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
|
@ -32,64 +30,15 @@ class LibraryPage extends HookConsumerWidget {
|
|||
[],
|
||||
);
|
||||
|
||||
final selectedAlbumSortOrder =
|
||||
useState(settings.getSetting(AppSettingsEnum.selectedAlbumSortOrder));
|
||||
|
||||
List<Album> sortedAlbums() {
|
||||
// Created.
|
||||
if (selectedAlbumSortOrder.value == 0) {
|
||||
return albums
|
||||
.where((a) => a.isRemote)
|
||||
.sortedBy((album) => album.createdAt)
|
||||
.reversed
|
||||
.toList();
|
||||
}
|
||||
// Album title.
|
||||
if (selectedAlbumSortOrder.value == 1) {
|
||||
return albums.where((a) => a.isRemote).sortedBy((album) => album.name);
|
||||
}
|
||||
// Most recent photo, if unset (e.g. empty album, use modifiedAt / updatedAt).
|
||||
if (selectedAlbumSortOrder.value == 2) {
|
||||
return albums
|
||||
.where((a) => a.isRemote)
|
||||
.sorted(
|
||||
(a, b) => a.lastModifiedAssetTimestamp != null &&
|
||||
b.lastModifiedAssetTimestamp != null
|
||||
? a.lastModifiedAssetTimestamp!
|
||||
.compareTo(b.lastModifiedAssetTimestamp!)
|
||||
: a.modifiedAt.compareTo(b.modifiedAt),
|
||||
)
|
||||
.reversed
|
||||
.toList();
|
||||
}
|
||||
// Last modified.
|
||||
if (selectedAlbumSortOrder.value == 3) {
|
||||
return albums
|
||||
.where((a) => a.isRemote)
|
||||
.sortedBy((album) => album.modifiedAt)
|
||||
.reversed
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Fallback: Album title.
|
||||
return albums.where((a) => a.isRemote).sortedBy((album) => album.name);
|
||||
}
|
||||
|
||||
Widget buildSortButton() {
|
||||
final options = [
|
||||
"library_page_sort_created".tr(),
|
||||
"library_page_sort_title".tr(),
|
||||
"library_page_sort_most_recent_photo".tr(),
|
||||
"library_page_sort_last_modified".tr(),
|
||||
];
|
||||
|
||||
return PopupMenuButton(
|
||||
position: PopupMenuPosition.over,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return options.mapIndexed<PopupMenuEntry<int>>((index, option) {
|
||||
final selected = selectedAlbumSortOrder.value == index;
|
||||
return AlbumSortMode.values
|
||||
.map<PopupMenuEntry<AlbumSortMode>>((option) {
|
||||
final selected = albumSortOption == option;
|
||||
return PopupMenuItem(
|
||||
value: index,
|
||||
value: option,
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
|
@ -101,7 +50,7 @@ class LibraryPage extends HookConsumerWidget {
|
|||
),
|
||||
),
|
||||
Text(
|
||||
option,
|
||||
option.label.tr(),
|
||||
style: TextStyle(
|
||||
color: selected ? context.primaryColor : null,
|
||||
fontSize: 12.0,
|
||||
|
@ -112,19 +61,31 @@ class LibraryPage extends HookConsumerWidget {
|
|||
);
|
||||
}).toList();
|
||||
},
|
||||
onSelected: (int value) {
|
||||
selectedAlbumSortOrder.value = value;
|
||||
settings.setSetting(AppSettingsEnum.selectedAlbumSortOrder, value);
|
||||
onSelected: (AlbumSortMode value) {
|
||||
final selected = albumSortOption == value;
|
||||
// Switch direction
|
||||
if (selected) {
|
||||
ref
|
||||
.read(albumSortOrderProvider.notifier)
|
||||
.changeSortDirection(!albumSortIsReverse);
|
||||
} else {
|
||||
ref.read(albumSortFunctionProvider.notifier).changeSortMode(value);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.swap_vert_rounded,
|
||||
size: 18,
|
||||
color: context.primaryColor,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Icon(
|
||||
albumSortIsReverse
|
||||
? Icons.arrow_downward_rounded
|
||||
: Icons.arrow_upward_rounded,
|
||||
size: 14,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
options[selectedAlbumSortOrder.value],
|
||||
albumSortOption.label.tr(),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
|
@ -140,9 +101,8 @@ class LibraryPage extends HookConsumerWidget {
|
|||
var cardSize = constraints.maxWidth;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.autoPush(CreateAlbumRoute(isSharedAlbum: false));
|
||||
},
|
||||
onTap: () =>
|
||||
context.autoPush(CreateAlbumRoute(isSharedAlbum: false)),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(bottom: 32), // Adjust padding to suit
|
||||
|
@ -160,7 +120,7 @@ class LibraryPage extends HookConsumerWidget {
|
|||
: const Color.fromARGB(255, 203, 203, 203),
|
||||
),
|
||||
color: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
|
@ -223,7 +183,7 @@ class LibraryPage extends HookConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final sorted = sortedAlbums();
|
||||
final sorted = albumSortOption.sortFn(albums, albumSortIsReverse);
|
||||
|
||||
final local = albums.where((a) => a.isLocal).toList();
|
||||
|
||||
|
@ -231,7 +191,7 @@ class LibraryPage extends HookConsumerWidget {
|
|||
return trashEnabled
|
||||
? InkWell(
|
||||
onTap: () => context.autoPush(const TrashRoute()),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
child: const Icon(
|
||||
Icons.delete_rounded,
|
||||
size: 25,
|
||||
|
|
|
@ -51,6 +51,11 @@ enum AppSettingsEnum<T> {
|
|||
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
||||
mapRelativeDate<int>(StoreKey.mapRelativeDate, null, 0),
|
||||
allowSelfSignedSSLCert<bool>(StoreKey.selfSignedCert, null, false),
|
||||
selectedAlbumSortReverse<bool>(
|
||||
StoreKey.selectedAlbumSortReverse,
|
||||
null,
|
||||
false,
|
||||
),
|
||||
;
|
||||
|
||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||
|
|
|
@ -182,6 +182,7 @@ enum StoreKey<T> {
|
|||
mapRelativeDate<int>(119, type: int),
|
||||
selfSignedCert<bool>(120, type: bool),
|
||||
mapIncludeArchived<bool>(121, type: bool),
|
||||
selectedAlbumSortReverse<bool>(122, type: bool),
|
||||
;
|
||||
|
||||
const StoreKey(
|
||||
|
|
Loading…
Reference in a new issue