From 9c5716bd5a13937e7a61ebee3c0b1377b1b211e6 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 2 Oct 2024 20:01:22 +0300 Subject: [PATCH] feat(feed): add lazy loading --- .../bloc/diets_cubit/diets_cubit.dart | 47 +++++++-- .../bloc/diets_cubit/diets_state.dart | 3 +- .../bloc/diets_cubit/diets_state.freezed.dart | 97 ++++++++++--------- .../bloc/recipes_cubit/recipes_cubit.dart | 10 +- .../feed/presentation/pages/feed_screen.dart | 33 +++++-- 5 files changed, 124 insertions(+), 66 deletions(-) diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart index 23d1c657..fa44eafb 100644 --- a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart @@ -7,7 +7,7 @@ class DietsCubit extends Cubit { int currentPage = 1; late int lastPage; bool isLoading = false; - // String? _query; + String? _query; DietsCubit({required this.feedRepository}) : super(const DietsState.loading()); @@ -15,14 +15,49 @@ class DietsCubit extends Cubit { String? query, }) async { try { - // _query = query; - final diets = await feedRepository.getDiets( + _query = query; + final dietsPagination = await feedRepository.getDiets( query: query, ); - lastPage = diets.meta.lastPage; - emit(DietsState.loaded(diets: diets)); + lastPage = dietsPagination.meta.lastPage; + emit(DietsState.loaded( + dietsList: dietsPagination.data, + isLoading: isLoading, + )); } catch (e) { - emit(DietsState.error(errorMessage: e.toString())); + emit(DietsState.error( + errorMessage: e.toString(), + )); + } + } + + loadNextPage() async { + if (currentPage < lastPage && !isLoading) { + isLoading = true; + + emit((state as DietsStateLoaded).copyWith(isLoading: isLoading)); + + try { + if (currentPage < lastPage) { + currentPage++; + final dietPagination = await feedRepository.getDiets( + page: currentPage, + query: _query, + ); + isLoading = false; + emit( + DietsState.loaded( + dietsList: [ + ...(state as DietsStateLoaded).dietsList, + ...dietPagination.data, + ], + isLoading: isLoading, + ), + ); + } + } catch (e) { + emit(DietsState.error(errorMessage: e.toString())); + } } } } diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart index 3cd342e8..bc5bbba1 100644 --- a/lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart @@ -10,7 +10,8 @@ abstract class DietsState with _$DietsState { const factory DietsState.loading() = DietsStateLoading; const factory DietsState.loaded({ - required DietsPagination diets, + required List dietsList, + required bool isLoading, }) = DietsStateLoaded; const factory DietsState.error({ diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart index dd30f73d..8edde15b 100644 --- a/lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart @@ -20,7 +20,7 @@ mixin _$DietsState { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(DietsPagination diets) loaded, + required TResult Function(List dietsList, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) => throw _privateConstructorUsedError; @@ -28,7 +28,7 @@ mixin _$DietsState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(DietsPagination diets)? loaded, + TResult? Function(List dietsList, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) => throw _privateConstructorUsedError; @@ -36,7 +36,7 @@ mixin _$DietsState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(DietsPagination diets)? loaded, + TResult Function(List dietsList, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) => @@ -132,7 +132,7 @@ class _$DietsStateImpl implements _DietsState { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(DietsPagination diets) loaded, + required TResult Function(List dietsList, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { return $default(); @@ -143,7 +143,7 @@ class _$DietsStateImpl implements _DietsState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(DietsPagination diets)? loaded, + TResult? Function(List dietsList, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { return $default?.call(); @@ -154,7 +154,7 @@ class _$DietsStateImpl implements _DietsState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(DietsPagination diets)? loaded, + TResult Function(List dietsList, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { @@ -249,7 +249,7 @@ class _$DietsStateLoadingImpl implements DietsStateLoading { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(DietsPagination diets) loaded, + required TResult Function(List dietsList, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { return loading(); @@ -260,7 +260,7 @@ class _$DietsStateLoadingImpl implements DietsStateLoading { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(DietsPagination diets)? loaded, + TResult? Function(List dietsList, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { return loading?.call(); @@ -271,7 +271,7 @@ class _$DietsStateLoadingImpl implements DietsStateLoading { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(DietsPagination diets)? loaded, + TResult Function(List dietsList, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { @@ -329,9 +329,7 @@ abstract class _$$DietsStateLoadedImplCopyWith<$Res> { $Res Function(_$DietsStateLoadedImpl) then) = __$$DietsStateLoadedImplCopyWithImpl<$Res>; @useResult - $Res call({DietsPagination diets}); - - $DietsPaginationCopyWith<$Res> get diets; + $Res call({List dietsList, bool isLoading}); } /// @nodoc @@ -347,38 +345,43 @@ class __$$DietsStateLoadedImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? diets = null, + Object? dietsList = null, + Object? isLoading = null, }) { return _then(_$DietsStateLoadedImpl( - diets: null == diets - ? _value.diets - : diets // ignore: cast_nullable_to_non_nullable - as DietsPagination, + dietsList: null == dietsList + ? _value._dietsList + : dietsList // ignore: cast_nullable_to_non_nullable + as List, + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, )); } - - /// Create a copy of DietsState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $DietsPaginationCopyWith<$Res> get diets { - return $DietsPaginationCopyWith<$Res>(_value.diets, (value) { - return _then(_value.copyWith(diets: value)); - }); - } } /// @nodoc class _$DietsStateLoadedImpl implements DietsStateLoaded { - const _$DietsStateLoadedImpl({required this.diets}); + const _$DietsStateLoadedImpl( + {required final List dietsList, required this.isLoading}) + : _dietsList = dietsList; + + final List _dietsList; + @override + List get dietsList { + if (_dietsList is EqualUnmodifiableListView) return _dietsList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_dietsList); + } @override - final DietsPagination diets; + final bool isLoading; @override String toString() { - return 'DietsState.loaded(diets: $diets)'; + return 'DietsState.loaded(dietsList: $dietsList, isLoading: $isLoading)'; } @override @@ -386,11 +389,15 @@ class _$DietsStateLoadedImpl implements DietsStateLoaded { return identical(this, other) || (other.runtimeType == runtimeType && other is _$DietsStateLoadedImpl && - (identical(other.diets, diets) || other.diets == diets)); + const DeepCollectionEquality() + .equals(other._dietsList, _dietsList) && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading)); } @override - int get hashCode => Object.hash(runtimeType, diets); + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_dietsList), isLoading); /// Create a copy of DietsState /// with the given fields replaced by the non-null parameter values. @@ -406,10 +413,10 @@ class _$DietsStateLoadedImpl implements DietsStateLoaded { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(DietsPagination diets) loaded, + required TResult Function(List dietsList, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { - return loaded(diets); + return loaded(dietsList, isLoading); } @override @@ -417,10 +424,10 @@ class _$DietsStateLoadedImpl implements DietsStateLoaded { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(DietsPagination diets)? loaded, + TResult? Function(List dietsList, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { - return loaded?.call(diets); + return loaded?.call(dietsList, isLoading); } @override @@ -428,12 +435,12 @@ class _$DietsStateLoadedImpl implements DietsStateLoaded { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(DietsPagination diets)? loaded, + TResult Function(List dietsList, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { if (loaded != null) { - return loaded(diets); + return loaded(dietsList, isLoading); } return orElse(); } @@ -477,10 +484,12 @@ class _$DietsStateLoadedImpl implements DietsStateLoaded { } abstract class DietsStateLoaded implements DietsState { - const factory DietsStateLoaded({required final DietsPagination diets}) = - _$DietsStateLoadedImpl; + const factory DietsStateLoaded( + {required final List dietsList, + required final bool isLoading}) = _$DietsStateLoadedImpl; - DietsPagination get diets; + List get dietsList; + bool get isLoading; /// Create a copy of DietsState /// with the given fields replaced by the non-null parameter values. @@ -561,7 +570,7 @@ class _$DietsStateErrorImpl implements DietsStateError { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(DietsPagination diets) loaded, + required TResult Function(List dietsList, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { return error(errorMessage); @@ -572,7 +581,7 @@ class _$DietsStateErrorImpl implements DietsStateError { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(DietsPagination diets)? loaded, + TResult? Function(List dietsList, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { return error?.call(errorMessage); @@ -583,7 +592,7 @@ class _$DietsStateErrorImpl implements DietsStateError { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(DietsPagination diets)? loaded, + TResult Function(List dietsList, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { diff --git a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart index 621880fe..93bcf399 100644 --- a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart +++ b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart @@ -13,6 +13,11 @@ class RecipesCubit extends Cubit { bool isLoading = false; String? _query; + RecipesCubit({ + required this.feedRepository, + required this.recipeRepository, + }) : super(const RecipesState.loading()); + loadRecipesList({ String? query, }) async { @@ -35,11 +40,6 @@ class RecipesCubit extends Cubit { } } - RecipesCubit({ - required this.feedRepository, - required this.recipeRepository, - }) : super(const RecipesState.loading()); - loadNextPage() async { if (currentPage < lastPage && !isLoading) { isLoading = true; diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index 115f25f3..0abbb6c2 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -57,11 +57,23 @@ class _FeedScreenState extends State { } } - _onScrollRecipes() {} + _onScrollRecipes() { + if (recipesScrollController.offset >= recipesScrollController.position.maxScrollExtent && !recipesScrollController.position.outOfRange) { + recipesCubit.loadNextPage(); + } + } - _onScrollDiets() {} + _onScrollDiets() { + if (dietsScrollController.offset >= dietsScrollController.position.maxScrollExtent && !dietsScrollController.position.outOfRange) { + dietsCubit.loadNextPage(); + } + } - _onScrollPlans() {} + _onScrollPlans() { + if (plansScrollController.offset >= plansScrollController.position.maxScrollExtent && !plansScrollController.position.outOfRange) { + // plansCubit.loadNextPage(); + } + } @override void dispose() { @@ -133,16 +145,17 @@ class _FeedScreenState extends State { }, body: Builder( builder: (BuildContext context) { - if (_selectedChipIndex == 0) + if (_selectedChipIndex == 0) { return _buildAllBlogPosts(); - else if (_selectedChipIndex == 1) + } else if (_selectedChipIndex == 1) { return _buildAllRecipes(); - else if (_selectedChipIndex == 2) + } else if (_selectedChipIndex == 2) { return _buildDiets(); - else if (_selectedChipIndex == 3) + } else if (_selectedChipIndex == 3) { return _buildPlans(); - else + } else { return Center(child: Text(chipsLabelList[_selectedChipIndex])); + } }, ), ), @@ -274,12 +287,12 @@ class _FeedScreenState extends State { shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { - return DietsWidget(diet: state.diets.data[index]); + return DietsWidget(diet: state.dietsList[index]); }, separatorBuilder: (context, index) { return SizedBox(height: 8); }, - itemCount: state.diets.data.length, + itemCount: state.dietsList.length, ); } else if (state is DietsStateError) { return Scaffold(