added plan pagination and updated plans creation screen

This commit is contained in:
Username1548
2024-12-04 22:03:31 +07:00
parent 4a57c72297
commit bc0b3829c9
50 changed files with 1504 additions and 959 deletions

View File

@@ -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()));
}
}
}
}

View File

@@ -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,
);
}

View File

@@ -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;
}

View File

@@ -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,
);
},
),
),

View File

@@ -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';