feat(mobile): lazy loading of assets (#2413)

This commit is contained in:
Fynn Petersen-Frey
2023-05-17 19:36:02 +02:00
committed by GitHub
parent 93863b0629
commit 0dde76bbbc
54 changed files with 1494 additions and 2328 deletions

View File

@@ -1,68 +1,25 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.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/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:isar/isar.dart';
class FavoriteSelectionNotifier extends StateNotifier<Set<int>> {
FavoriteSelectionNotifier(this.assetsState, this.assetNotifier) : super({}) {
state = assetsState.allAssets
.where((asset) => asset.isFavorite)
.map((asset) => asset.id)
.toSet();
final favoriteAssetsProvider = StreamProvider<RenderList>((ref) async* {
final query = ref
.watch(dbProvider)
.assets
.filter()
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
.isFavoriteEqualTo(true)
.sortByFileCreatedAt();
final settings = ref.watch(appSettingsServiceProvider);
final groupBy =
GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
yield await RenderList.fromQuery(query, groupBy);
await for (final _ in query.watchLazy()) {
yield await RenderList.fromQuery(query, groupBy);
}
final AssetsState assetsState;
final AssetNotifier assetNotifier;
void _setFavoriteForAssetId(int id, bool favorite) {
if (!favorite) {
state = state.difference({id});
} else {
state = state.union({id});
}
}
bool _isFavorite(int id) {
return state.contains(id);
}
Future<void> toggleFavorite(Asset asset) async {
// TODO support local favorite assets
if (asset.storage == AssetState.local) return;
_setFavoriteForAssetId(asset.id, !_isFavorite(asset.id));
await assetNotifier.toggleFavorite(
asset,
state.contains(asset.id),
);
}
Future<void> addToFavorites(Iterable<Asset> assets) {
state = state.union(assets.map((a) => a.id).toSet());
final futures = assets.map(
(a) => assetNotifier.toggleFavorite(
a,
true,
),
);
return Future.wait(futures);
}
}
final favoriteProvider =
StateNotifierProvider<FavoriteSelectionNotifier, Set<int>>((ref) {
return FavoriteSelectionNotifier(
ref.watch(assetProvider),
ref.watch(assetProvider.notifier),
);
});
final favoriteAssetProvider = StateProvider((ref) {
final favorites = ref.watch(favoriteProvider);
return ref
.watch(assetProvider)
.allAssets
.where((element) => favorites.contains(element.id))
.toList();
});

View File

@@ -1,36 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
class FavoriteImage extends HookConsumerWidget {
final Asset asset;
final List<Asset> assets;
const FavoriteImage(this.asset, this.assets, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
void viewAsset() {
AutoRouter.of(context).push(
GalleryViewerRoute(
asset: asset,
assetList: assets,
),
);
}
return GestureDetector(
onTap: viewAsset,
child: ImmichImage(
asset,
width: 300,
height: 300,
),
);
}
}

View File

@@ -1,15 +1,32 @@
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:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
class FavoritesPage extends HookConsumerWidget {
const FavoritesPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectionEnabledHook = useState(false);
final selection = useState(<Asset>{});
final processing = useState(false);
void selectionListener(
bool multiselect,
Set<Asset> selectedAssets,
) {
selectionEnabledHook.value = multiselect;
selection.value = selectedAssets;
}
AppBar buildAppBar() {
return AppBar(
leading: IconButton(
@@ -24,15 +41,77 @@ class FavoritesPage extends HookConsumerWidget {
);
}
void unfavorite() async {
try {
if (selection.value.isNotEmpty) {
await ref.watch(assetProvider.notifier).toggleFavorite(
selection.value.toList(),
false,
);
final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset';
ImmichToast.show(
context: context,
msg:
'Removed ${selection.value.length} $assetOrAssets from favorites',
gravity: ToastGravity.CENTER,
);
}
} finally {
processing.value = false;
selectionEnabledHook.value = false;
}
}
Widget buildBottomBar() {
return SafeArea(
child: Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
height: 64,
child: Card(
child: Column(
children: [
ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
leading: const Icon(
Icons.star_border,
),
title: const Text(
"Unfavorite",
style: TextStyle(fontSize: 14),
),
onTap: processing.value ? null : unfavorite,
)
],
),
),
),
),
);
}
return Scaffold(
appBar: buildAppBar(),
body: ref.watch(favoriteAssetProvider).isEmpty
? Center(
child: Text('favorites_page_no_favorites'.tr()),
)
: ImmichAssetGrid(
assets: ref.watch(favoriteAssetProvider),
),
body: ref.watch(favoriteAssetsProvider).when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stackTrace) => Center(child: Text(error.toString())),
data: (data) => data.isEmpty
? Center(
child: Text('favorites_page_no_favorites'.tr()),
)
: Stack(
children: [
ImmichAssetGrid(
renderList: data,
selectionActive: selectionEnabledHook.value,
listener: selectionListener,
),
if (selectionEnabledHook.value) buildBottomBar()
],
),
),
);
}
}