quick filter for album

This commit is contained in:
Alex 2024-09-07 14:48:32 -05:00
parent 2320e7a0d7
commit 2dc73c2987
No known key found for this signature in database
GPG Key ID: 53CD082B3A5E1082
2 changed files with 211 additions and 17 deletions

View File

@ -7,34 +7,30 @@ 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/album/albumv2.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
enum QuickFilterMode {
all,
sharedWithMe,
myAlbums,
}
@RoutePage()
class AlbumsCollectionPage extends HookConsumerWidget {
const AlbumsCollectionPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final albums = ref.watch(albumProvider);
final albums = ref.watch(albumProviderV2);
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 sorted = albumSortOption.sortFn(albums, albumSortIsReverse);
final isGrid = useState(true);
final searchController = useTextEditingController();
final debounceTimer = useRef<Timer?>(null);
useEffect(
() {
Future.delayed(const Duration(seconds: 1), () {
ref.read(albumProvider.notifier).getAllAlbums();
});
return null;
},
[],
);
final filterMode = useState(QuickFilterMode.all);
toggleViewMode() {
isGrid.value = !isGrid.value;
@ -43,15 +39,23 @@ class AlbumsCollectionPage extends HookConsumerWidget {
onSearch(String value) {
debounceTimer.value?.cancel();
debounceTimer.value = Timer(const Duration(milliseconds: 300), () {
ref.read(albumProvider.notifier).searchAlbums(value);
filterMode.value = QuickFilterMode.all;
ref.read(albumProviderV2.notifier).searchAlbums(value);
});
}
changeFilter(QuickFilterMode mode) {
filterMode.value = mode;
searchController.clear();
ref.read(albumProviderV2.notifier).filterAlbums(mode);
}
useEffect(
() {
searchController.addListener(() {
onSearch(searchController.text);
});
return () {
searchController.removeListener(() {
onSearch(searchController.text);
@ -64,7 +68,7 @@ class AlbumsCollectionPage extends HookConsumerWidget {
return Scaffold(
appBar: AppBar(
title: const Text("Albums"),
title: Text("Albums ${albums.length}"),
),
body: ListView(
shrinkWrap: true,
@ -93,7 +97,28 @@ class AlbumsCollectionPage extends HookConsumerWidget {
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
Wrap(
spacing: 4,
runSpacing: 4,
children: [
QuickFilterButton(
label: 'All',
isSelected: filterMode.value == QuickFilterMode.all,
onTap: () => changeFilter(QuickFilterMode.all),
),
QuickFilterButton(
label: 'Shared with me',
isSelected: filterMode.value == QuickFilterMode.sharedWithMe,
onTap: () => changeFilter(QuickFilterMode.sharedWithMe),
),
QuickFilterButton(
label: 'My albums',
isSelected: filterMode.value == QuickFilterMode.myAlbums,
onTap: () => changeFilter(QuickFilterMode.myAlbums),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -154,6 +179,56 @@ class AlbumsCollectionPage extends HookConsumerWidget {
}
}
class QuickFilterButton extends StatelessWidget {
const QuickFilterButton({
super.key,
required this.isSelected,
required this.onTap,
required this.label,
});
final bool isSelected;
final VoidCallback onTap;
final String label;
@override
Widget build(BuildContext context) {
return TextButton.icon(
onPressed: onTap,
icon: isSelected
? Icon(
Icons.check_rounded,
color: context.colorScheme.onPrimary,
size: 18,
)
: const SizedBox.shrink(),
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
isSelected ? context.colorScheme.primary : Colors.transparent,
),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: context.colorScheme.onSurface.withAlpha(25),
width: 1,
),
),
),
),
label: Text(
label,
style: TextStyle(
color: isSelected
? context.colorScheme.onPrimary
: context.colorScheme.onSurface,
fontSize: 14,
),
),
);
}
}
class SortButton extends ConsumerWidget {
const SortButton({super.key});

View File

@ -0,0 +1,119 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/pages/collections/albums/albums_collection.page.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:isar/isar.dart';
class AlbumNotifierV2 extends StateNotifier<List<Album>> {
AlbumNotifierV2(this._albumService, this.db) : super([]) {
final query = db.albums.filter().remoteIdIsNotNull();
query.findAll().then((value) {
if (mounted) {
state = value;
}
});
_streamSub = query.watch().listen((data) => state = data);
}
final AlbumService _albumService;
final Isar db;
late final StreamSubscription<List<Album>> _streamSub;
Future<void> getAllAlbums() {
return Future.wait([
_albumService.refreshDeviceAlbums(),
_albumService.refreshRemoteAlbums(isShared: true),
]);
}
Future<void> getDeviceAlbums() {
return _albumService.refreshDeviceAlbums();
}
Future<bool> deleteAlbum(Album album) {
return _albumService.deleteAlbum(album);
}
Future<Album?> createAlbum(
String albumTitle,
Set<Asset> assets,
) {
return _albumService.createAlbum(albumTitle, assets, []);
}
Future<Album?> getAlbumByName(String albumName, {bool remoteOnly = false}) {
return _albumService.getAlbumByName(albumName, remoteOnly);
}
/// Create an album on the server with the same name as the selected album for backup
/// First this will check if the album already exists on the server with name
/// If it does not exist, it will create the album on the server
Future<void> createSyncAlbum(
String albumName,
) async {
final album = await getAlbumByName(albumName, remoteOnly: true);
if (album != null) {
return;
}
await createAlbum(albumName, {});
}
void searchAlbums(String value) async {
final query = db.albums
.filter()
.remoteIdIsNotNull()
.nameContains(value, caseSensitive: false);
final albums = await query.findAll();
state = albums;
}
void filterAlbums(QuickFilterMode mode) async {
switch (mode) {
case QuickFilterMode.all:
state = await db.albums.filter().remoteIdIsNotNull().findAll();
return;
case QuickFilterMode.sharedWithMe:
state = await db.albums
.filter()
.remoteIdIsNotNull()
.owner(
(q) =>
q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
)
.findAll();
return;
case QuickFilterMode.myAlbums:
state = await db.albums
.filter()
.remoteIdIsNotNull()
.owner(
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
)
.findAll();
return;
}
}
@override
void dispose() {
_streamSub.cancel();
super.dispose();
}
}
final albumProviderV2 =
StateNotifierProvider.autoDispose<AlbumNotifierV2, List<Album>>((ref) {
return AlbumNotifierV2(
ref.watch(albumServiceProvider),
ref.watch(dbProvider),
);
});