forked from eatvio/eatvio_app
added plan pagination and updated plans creation screen
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class PlansCubit extends Cubit<PlansState> {
|
||||
final FeedRepository feedRepository;
|
||||
int currentPage = 1;
|
||||
late int lastPage;
|
||||
bool isLoading = false;
|
||||
String? _query;
|
||||
|
||||
PlansCubit({
|
||||
required this.feedRepository,
|
||||
}) : super(PlansState.initial());
|
||||
|
||||
loadPlans({
|
||||
String? query,
|
||||
}) async {
|
||||
try {
|
||||
_query = query;
|
||||
final plansPagination = await feedRepository.getPlans(
|
||||
query: query,
|
||||
);
|
||||
lastPage = plansPagination.meta.lastPage;
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: plansPagination.data,
|
||||
errorMessage: null,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
loadNextPage() async {
|
||||
if (currentPage < lastPage && !isLoading) {
|
||||
isLoading = true;
|
||||
|
||||
emit((state.copyWith(isLoading: isLoading, errorMessage: null)));
|
||||
|
||||
try {
|
||||
if (currentPage < lastPage) {
|
||||
currentPage++;
|
||||
final plansPagination = await feedRepository.getPlans(
|
||||
page: currentPage,
|
||||
query: _query,
|
||||
);
|
||||
isLoading = false;
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...state.plans,
|
||||
...plansPagination.data,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'plans_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class PlansState with _$PlansState {
|
||||
const factory PlansState({
|
||||
required bool isLoading,
|
||||
required List<PlanModel> plans,
|
||||
required String? errorMessage,
|
||||
}) = _PlansState;
|
||||
|
||||
factory PlansState.initial() => const _PlansState(
|
||||
isLoading: true,
|
||||
plans: [],
|
||||
errorMessage: null,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'plans_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PlansState {
|
||||
bool get isLoading => throw _privateConstructorUsedError;
|
||||
List<PlanModel> get plans => throw _privateConstructorUsedError;
|
||||
String? get errorMessage => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$PlansStateCopyWith<PlansState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $PlansStateCopyWith<$Res> {
|
||||
factory $PlansStateCopyWith(
|
||||
PlansState value, $Res Function(PlansState) then) =
|
||||
_$PlansStateCopyWithImpl<$Res, PlansState>;
|
||||
@useResult
|
||||
$Res call({bool isLoading, List<PlanModel> plans, String? errorMessage});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$PlansStateCopyWithImpl<$Res, $Val extends PlansState>
|
||||
implements $PlansStateCopyWith<$Res> {
|
||||
_$PlansStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? plans = null,
|
||||
Object? errorMessage = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
plans: null == plans
|
||||
? _value.plans
|
||||
: plans // ignore: cast_nullable_to_non_nullable
|
||||
as List<PlanModel>,
|
||||
errorMessage: freezed == errorMessage
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$PlansStateImplCopyWith<$Res>
|
||||
implements $PlansStateCopyWith<$Res> {
|
||||
factory _$$PlansStateImplCopyWith(
|
||||
_$PlansStateImpl value, $Res Function(_$PlansStateImpl) then) =
|
||||
__$$PlansStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool isLoading, List<PlanModel> plans, String? errorMessage});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$PlansStateImplCopyWithImpl<$Res>
|
||||
extends _$PlansStateCopyWithImpl<$Res, _$PlansStateImpl>
|
||||
implements _$$PlansStateImplCopyWith<$Res> {
|
||||
__$$PlansStateImplCopyWithImpl(
|
||||
_$PlansStateImpl _value, $Res Function(_$PlansStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? plans = null,
|
||||
Object? errorMessage = freezed,
|
||||
}) {
|
||||
return _then(_$PlansStateImpl(
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
plans: null == plans
|
||||
? _value._plans
|
||||
: plans // ignore: cast_nullable_to_non_nullable
|
||||
as List<PlanModel>,
|
||||
errorMessage: freezed == errorMessage
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$PlansStateImpl implements _PlansState {
|
||||
const _$PlansStateImpl(
|
||||
{required this.isLoading,
|
||||
required final List<PlanModel> plans,
|
||||
required this.errorMessage})
|
||||
: _plans = plans;
|
||||
|
||||
@override
|
||||
final bool isLoading;
|
||||
final List<PlanModel> _plans;
|
||||
@override
|
||||
List<PlanModel> get plans {
|
||||
if (_plans is EqualUnmodifiableListView) return _plans;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_plans);
|
||||
}
|
||||
|
||||
@override
|
||||
final String? errorMessage;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlansState(isLoading: $isLoading, plans: $plans, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PlansStateImpl &&
|
||||
(identical(other.isLoading, isLoading) ||
|
||||
other.isLoading == isLoading) &&
|
||||
const DeepCollectionEquality().equals(other._plans, _plans) &&
|
||||
(identical(other.errorMessage, errorMessage) ||
|
||||
other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, isLoading,
|
||||
const DeepCollectionEquality().hash(_plans), errorMessage);
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$PlansStateImplCopyWith<_$PlansStateImpl> get copyWith =>
|
||||
__$$PlansStateImplCopyWithImpl<_$PlansStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _PlansState implements PlansState {
|
||||
const factory _PlansState(
|
||||
{required final bool isLoading,
|
||||
required final List<PlanModel> plans,
|
||||
required final String? errorMessage}) = _$PlansStateImpl;
|
||||
|
||||
@override
|
||||
bool get isLoading;
|
||||
@override
|
||||
List<PlanModel> get plans;
|
||||
@override
|
||||
String? get errorMessage;
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$PlansStateImplCopyWith<_$PlansStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -16,8 +16,8 @@ import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart'
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/recipes_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/bloc/plans_cubit/plans_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -76,7 +76,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
|
||||
_onScrollPlans() {
|
||||
if (plansScrollController.offset >= plansScrollController.position.maxScrollExtent && !plansScrollController.position.outOfRange) {
|
||||
// plansCubit.loadNextPage();
|
||||
plansCubit.loadNextPage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +119,13 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
),
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: NestedScrollView(
|
||||
controller: _selectedChipIndex == 0
|
||||
? blogPostsScrollController
|
||||
: _selectedChipIndex == 1
|
||||
? recipesScrollController
|
||||
: dietsScrollController,
|
||||
controller: switch (_selectedChipIndex) {
|
||||
0 => blogPostsScrollController,
|
||||
1 => recipesScrollController,
|
||||
2 => dietsScrollController,
|
||||
3 => plansScrollController,
|
||||
_ => blogPostsScrollController
|
||||
},
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverAppBar(
|
||||
@@ -317,35 +319,34 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
onRefresh: () => plansCubit.loadPlans(),
|
||||
child: BlocBuilder<PlansCubit, PlansState>(
|
||||
builder: (BuildContext context, PlansState state) {
|
||||
if (state is PlansStateLoaded) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return PlanWidget(planModel: state.plansModel.data[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.plansModel.data.length,
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state is PlansStateError) {
|
||||
} else if (state.errorMessage != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage),
|
||||
child: Text(state.errorMessage ?? ''),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return PlanWidget(planModel: state.plans[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.plans.length,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/features/plans/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
Reference in New Issue
Block a user