mirror of
https://github.com/immich-app/immich.git
synced 2025-07-07 18:26:50 -04:00
quick filter for album
This commit is contained in:
parent
2320e7a0d7
commit
2dc73c2987
@ -7,34 +7,30 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_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/album/album_sort_by_options.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/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
||||||
|
|
||||||
|
enum QuickFilterMode {
|
||||||
|
all,
|
||||||
|
sharedWithMe,
|
||||||
|
myAlbums,
|
||||||
|
}
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class AlbumsCollectionPage extends HookConsumerWidget {
|
class AlbumsCollectionPage extends HookConsumerWidget {
|
||||||
const AlbumsCollectionPage({super.key});
|
const AlbumsCollectionPage({super.key});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final albums = ref.watch(albumProvider);
|
final albums = ref.watch(albumProviderV2);
|
||||||
final albumSortOption = ref.watch(albumSortByOptionsProvider);
|
final albumSortOption = ref.watch(albumSortByOptionsProvider);
|
||||||
final albumSortIsReverse = ref.watch(albumSortOrderProvider);
|
final albumSortIsReverse = ref.watch(albumSortOrderProvider);
|
||||||
final remote = albums.where((a) => a.isRemote).toList();
|
final sorted = albumSortOption.sortFn(albums, albumSortIsReverse);
|
||||||
final sorted = albumSortOption.sortFn(remote, albumSortIsReverse);
|
|
||||||
final isGrid = useState(true);
|
final isGrid = useState(true);
|
||||||
final searchController = useTextEditingController();
|
final searchController = useTextEditingController();
|
||||||
final debounceTimer = useRef<Timer?>(null);
|
final debounceTimer = useRef<Timer?>(null);
|
||||||
|
final filterMode = useState(QuickFilterMode.all);
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
|
||||||
ref.read(albumProvider.notifier).getAllAlbums();
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
toggleViewMode() {
|
toggleViewMode() {
|
||||||
isGrid.value = !isGrid.value;
|
isGrid.value = !isGrid.value;
|
||||||
@ -43,15 +39,23 @@ class AlbumsCollectionPage extends HookConsumerWidget {
|
|||||||
onSearch(String value) {
|
onSearch(String value) {
|
||||||
debounceTimer.value?.cancel();
|
debounceTimer.value?.cancel();
|
||||||
debounceTimer.value = Timer(const Duration(milliseconds: 300), () {
|
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(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
searchController.addListener(() {
|
searchController.addListener(() {
|
||||||
onSearch(searchController.text);
|
onSearch(searchController.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () {
|
return () {
|
||||||
searchController.removeListener(() {
|
searchController.removeListener(() {
|
||||||
onSearch(searchController.text);
|
onSearch(searchController.text);
|
||||||
@ -64,7 +68,7 @@ class AlbumsCollectionPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Albums"),
|
title: Text("Albums ${albums.length}"),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
shrinkWrap: true,
|
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(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
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 {
|
class SortButton extends ConsumerWidget {
|
||||||
const SortButton({super.key});
|
const SortButton({super.key});
|
||||||
|
|
||||||
|
119
mobile/lib/providers/album/albumv2.provider.dart
Normal file
119
mobile/lib/providers/album/albumv2.provider.dart
Normal 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),
|
||||||
|
);
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user