forked from eatvio/eatvio_app
plans filters ready
This commit is contained in:
@@ -25,7 +25,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
'name:desc',
|
||||
];
|
||||
static const List<int?> _durationValues = [null, ...durationValues];
|
||||
static const List<String?> _types = [null, ...typeValues];
|
||||
static const List<String?> _types = [null, ...dietTypeValues];
|
||||
late final StreamSubscription likesStateSubscription;
|
||||
late final StreamSubscription favoriteStateSubscription;
|
||||
late final StreamSubscription commentsStateSubscription;
|
||||
@@ -54,17 +54,35 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
|
||||
void changeTypeFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(typeIndex: index), errorMessage: null));
|
||||
loadDiets(query: _query);
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _query,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void changeSortingType(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(sortingIndex: index), errorMessage: null));
|
||||
loadDiets(query: _query);
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _query,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void changeDurationFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null));
|
||||
loadDiets(query: _query);
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _query,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
loadDiets({
|
||||
@@ -82,9 +100,8 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
sorting: sorting,
|
||||
);
|
||||
lastPage = dietsPagination.meta.lastPage;
|
||||
|
||||
emit(
|
||||
state.copyWith(dietsList: dietsPagination.data, isLoading: isLoading, errorMessage: null),
|
||||
state.copyWith(dietsList: dietsPagination.data, isLoading: isLoading, errorMessage: null,),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
@@ -181,7 +198,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _changeDietLikeState(DietLikesState likesState) {
|
||||
void _changeDietLikeState(LikesState likesState) {
|
||||
final currentDiets = state.dietsList;
|
||||
final dietsForModification = [...currentDiets];
|
||||
final currentDiet = currentDiets[likesState.index];
|
||||
@@ -201,7 +218,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _changeDietFavoriteState(DietFavoriteState favoriteState) {
|
||||
void _changeDietFavoriteState(FavoriteState favoriteState) {
|
||||
final currentDiets = state.dietsList;
|
||||
final dietsForModification = [...currentDiets];
|
||||
final currentDiet = currentDiets[favoriteState.index];
|
||||
@@ -220,7 +237,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _changeDietCommentsState(DietCommentsState commentsState) {
|
||||
void _changeDietCommentsState(CommentsState commentsState) {
|
||||
final currentDiets = state.dietsList;
|
||||
final dietsForModification = [...currentDiets];
|
||||
final currentDiet = currentDiets[commentsState.index];
|
||||
@@ -261,7 +278,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
Duration(milliseconds: 200),
|
||||
() async {
|
||||
try {
|
||||
await dietsRepository.addToFavorite(id);
|
||||
await dietsRepository.addToFavorite(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
@@ -301,7 +318,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
Duration(milliseconds: 200),
|
||||
() async {
|
||||
try {
|
||||
await dietsRepository.removeFromFavorite(id);
|
||||
await dietsRepository.removeFromFavorite(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
|
||||
@@ -1,41 +1,176 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||
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:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class PlansCubit extends Cubit<PlansState> {
|
||||
final FeedRepository feedRepository;
|
||||
final PlanRepository planRepository;
|
||||
|
||||
int currentPage = 1;
|
||||
late int lastPage;
|
||||
bool isLoading = false;
|
||||
String? _query;
|
||||
static const List<int?> _durationValues = [null, ...durationValues];
|
||||
static const List<String?> _types = [null, ...planTypeValues];
|
||||
|
||||
late final StreamSubscription likesStateSubscription;
|
||||
late final StreamSubscription favoriteStateSubscription;
|
||||
late final StreamSubscription commentsStateSubscription;
|
||||
|
||||
PlansCubit({
|
||||
required this.feedRepository,
|
||||
}) : super(PlansState.initial());
|
||||
required this.planRepository,
|
||||
}) : super(PlansState.initial()) {
|
||||
likesStateSubscription = planRepository.planLikeState.listen((likesState) {
|
||||
_changePlanLikeState(likesState);
|
||||
});
|
||||
favoriteStateSubscription = planRepository.planFavoriteState.listen((favoriteState) {
|
||||
_changePlanFavoriteState(favoriteState);
|
||||
});
|
||||
commentsStateSubscription = planRepository.planCommentsState.listen((commentsState) {
|
||||
_changePlanCommentsState(commentsState);
|
||||
});
|
||||
}
|
||||
|
||||
void changeDurationFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: 1),
|
||||
() => loadPlans(
|
||||
query: _query,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void changeTypeFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(typeIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: 1),
|
||||
() => loadPlans(
|
||||
query: _query,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void changePriceFilter(int minPrice, int maxPrice, {bool disableDebounce = false}) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(priceFilter: (minPrice, maxPrice)), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: disableDebounce ? 0 : 1),
|
||||
() => loadPlans(
|
||||
query: _query,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _changePlanLikeState(LikesState likesState) {
|
||||
final currentPlans = state.plans;
|
||||
final plansForModification = [...currentPlans];
|
||||
final currentPlan = currentPlans[likesState.index];
|
||||
|
||||
plansForModification[likesState.index] = currentPlan.copyWith(
|
||||
totalLikes: likesState.totalLikes,
|
||||
wasLikedByMe: likesState.wasLikedByMe,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...plansForModification,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _changePlanFavoriteState(FavoriteState favoriteState) {
|
||||
final currentPlans = state.plans;
|
||||
final plansForModification = [...currentPlans];
|
||||
final currentPlan = currentPlans[favoriteState.index];
|
||||
|
||||
plansForModification[favoriteState.index] = currentPlan.copyWith(
|
||||
isFavorite: favoriteState.isFavorite,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...plansForModification,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _changePlanCommentsState(CommentsState commentsState) {
|
||||
final currentPlans = state.plans;
|
||||
final plansForModification = [...currentPlans];
|
||||
final currentPlan = currentPlans[commentsState.index];
|
||||
|
||||
plansForModification[commentsState.index] = currentPlan.copyWith(
|
||||
totalComments: commentsState.totalComments,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...plansForModification,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
loadPlans({
|
||||
String? query,
|
||||
}) async {
|
||||
try {
|
||||
_query = query;
|
||||
final type = _types[state.filterState.typeIndex];
|
||||
final duration = _durationValues[state.filterState.durationIndex];
|
||||
final minPrice = state.filterState.priceFilter.$1;
|
||||
final maxPrice = state.filterState.priceFilter.$2;
|
||||
final plansPagination = await feedRepository.getPlans(
|
||||
query: query,
|
||||
duration: duration,
|
||||
maxPrice: maxPrice,
|
||||
minPrice: minPrice,
|
||||
types: type,
|
||||
);
|
||||
lastPage = plansPagination.meta.lastPage;
|
||||
final minPlanPrice = plansPagination.meta.totalMinPrice ?? 0;
|
||||
final maxPlanPrice = plansPagination.meta.totalMaxPrice ?? 0;
|
||||
if (state.filterState.priceFilter.$1 == null || state.filterState.priceFilter.$2 == null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
filterState: state.filterState.copyWith(priceFilter: (minPlanPrice, maxPlanPrice)),
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: plansPagination.data,
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
isLoading: false,
|
||||
minPlanPrice: minPlanPrice,
|
||||
maxPlanPrice: maxPlanPrice,
|
||||
),
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
log(e.toString());
|
||||
log(stackTrace.toString());
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
emit(state.copyWith(isLoading: isLoading, errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,4 +204,84 @@ class PlansCubit extends Cubit<PlansState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addPlanToFavorite(String slug, int index) async {
|
||||
final currentPlans = state.plans;
|
||||
final plansForModification = [...currentPlans];
|
||||
final currentPlan = currentPlans[index];
|
||||
|
||||
plansForModification[index] = currentPlan.copyWith(
|
||||
isFavorite: true,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...plansForModification,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
EasyDebounce.debounce(
|
||||
'add_or_remove_plan_from_favorite',
|
||||
Duration(milliseconds: 200),
|
||||
() async {
|
||||
try {
|
||||
await planRepository.addToFavorite(slug);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...currentPlans,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removePlanFromFavorite(String slug, int index) async {
|
||||
final currentPlans = state.plans;
|
||||
final plansForModification = [...currentPlans];
|
||||
final currentPlan = currentPlans[index];
|
||||
|
||||
plansForModification[index] = currentPlan.copyWith(
|
||||
isFavorite: false,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...plansForModification,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
EasyDebounce.debounce(
|
||||
'add_or_remove_plan_from_favorite',
|
||||
Duration(milliseconds: 200),
|
||||
() async {
|
||||
try {
|
||||
await planRepository.addToFavorite(slug);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...currentPlans,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,28 @@ abstract class PlansState with _$PlansState {
|
||||
required bool isLoading,
|
||||
required List<PlanModel> plans,
|
||||
required String? errorMessage,
|
||||
required PlansFilterState filterState,
|
||||
@Default(0) int minPlanPrice,
|
||||
@Default(0) int maxPlanPrice,
|
||||
}) = _PlansState;
|
||||
|
||||
factory PlansState.initial() => const _PlansState(
|
||||
factory PlansState.initial() => const PlansState(
|
||||
isLoading: true,
|
||||
plans: [],
|
||||
errorMessage: null,
|
||||
);
|
||||
filterState: PlansFilterState(
|
||||
priceFilter: (null, null),
|
||||
durationIndex: 0,
|
||||
typeIndex: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class PlansFilterState with _$PlansFilterState {
|
||||
const factory PlansFilterState({
|
||||
required (int?, int?) priceFilter,
|
||||
required int durationIndex,
|
||||
required int typeIndex,
|
||||
}) = _PlansFilterState;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ mixin _$PlansState {
|
||||
bool get isLoading => throw _privateConstructorUsedError;
|
||||
List<PlanModel> get plans => throw _privateConstructorUsedError;
|
||||
String? get errorMessage => throw _privateConstructorUsedError;
|
||||
PlansFilterState get filterState => throw _privateConstructorUsedError;
|
||||
int get minPlanPrice => throw _privateConstructorUsedError;
|
||||
int get maxPlanPrice => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -33,7 +36,15 @@ abstract class $PlansStateCopyWith<$Res> {
|
||||
PlansState value, $Res Function(PlansState) then) =
|
||||
_$PlansStateCopyWithImpl<$Res, PlansState>;
|
||||
@useResult
|
||||
$Res call({bool isLoading, List<PlanModel> plans, String? errorMessage});
|
||||
$Res call(
|
||||
{bool isLoading,
|
||||
List<PlanModel> plans,
|
||||
String? errorMessage,
|
||||
PlansFilterState filterState,
|
||||
int minPlanPrice,
|
||||
int maxPlanPrice});
|
||||
|
||||
$PlansFilterStateCopyWith<$Res> get filterState;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -54,6 +65,9 @@ class _$PlansStateCopyWithImpl<$Res, $Val extends PlansState>
|
||||
Object? isLoading = null,
|
||||
Object? plans = null,
|
||||
Object? errorMessage = freezed,
|
||||
Object? filterState = null,
|
||||
Object? minPlanPrice = null,
|
||||
Object? maxPlanPrice = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isLoading: null == isLoading
|
||||
@@ -68,8 +82,30 @@ class _$PlansStateCopyWithImpl<$Res, $Val extends PlansState>
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
filterState: null == filterState
|
||||
? _value.filterState
|
||||
: filterState // ignore: cast_nullable_to_non_nullable
|
||||
as PlansFilterState,
|
||||
minPlanPrice: null == minPlanPrice
|
||||
? _value.minPlanPrice
|
||||
: minPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
maxPlanPrice: null == maxPlanPrice
|
||||
? _value.maxPlanPrice
|
||||
: maxPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$PlansFilterStateCopyWith<$Res> get filterState {
|
||||
return $PlansFilterStateCopyWith<$Res>(_value.filterState, (value) {
|
||||
return _then(_value.copyWith(filterState: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -80,7 +116,16 @@ abstract class _$$PlansStateImplCopyWith<$Res>
|
||||
__$$PlansStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool isLoading, List<PlanModel> plans, String? errorMessage});
|
||||
$Res call(
|
||||
{bool isLoading,
|
||||
List<PlanModel> plans,
|
||||
String? errorMessage,
|
||||
PlansFilterState filterState,
|
||||
int minPlanPrice,
|
||||
int maxPlanPrice});
|
||||
|
||||
@override
|
||||
$PlansFilterStateCopyWith<$Res> get filterState;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -99,6 +144,9 @@ class __$$PlansStateImplCopyWithImpl<$Res>
|
||||
Object? isLoading = null,
|
||||
Object? plans = null,
|
||||
Object? errorMessage = freezed,
|
||||
Object? filterState = null,
|
||||
Object? minPlanPrice = null,
|
||||
Object? maxPlanPrice = null,
|
||||
}) {
|
||||
return _then(_$PlansStateImpl(
|
||||
isLoading: null == isLoading
|
||||
@@ -113,6 +161,18 @@ class __$$PlansStateImplCopyWithImpl<$Res>
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
filterState: null == filterState
|
||||
? _value.filterState
|
||||
: filterState // ignore: cast_nullable_to_non_nullable
|
||||
as PlansFilterState,
|
||||
minPlanPrice: null == minPlanPrice
|
||||
? _value.minPlanPrice
|
||||
: minPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
maxPlanPrice: null == maxPlanPrice
|
||||
? _value.maxPlanPrice
|
||||
: maxPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -123,7 +183,10 @@ class _$PlansStateImpl implements _PlansState {
|
||||
const _$PlansStateImpl(
|
||||
{required this.isLoading,
|
||||
required final List<PlanModel> plans,
|
||||
required this.errorMessage})
|
||||
required this.errorMessage,
|
||||
required this.filterState,
|
||||
this.minPlanPrice = 0,
|
||||
this.maxPlanPrice = 0})
|
||||
: _plans = plans;
|
||||
|
||||
@override
|
||||
@@ -138,10 +201,18 @@ class _$PlansStateImpl implements _PlansState {
|
||||
|
||||
@override
|
||||
final String? errorMessage;
|
||||
@override
|
||||
final PlansFilterState filterState;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int minPlanPrice;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int maxPlanPrice;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlansState(isLoading: $isLoading, plans: $plans, errorMessage: $errorMessage)';
|
||||
return 'PlansState(isLoading: $isLoading, plans: $plans, errorMessage: $errorMessage, filterState: $filterState, minPlanPrice: $minPlanPrice, maxPlanPrice: $maxPlanPrice)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -153,12 +224,24 @@ class _$PlansStateImpl implements _PlansState {
|
||||
other.isLoading == isLoading) &&
|
||||
const DeepCollectionEquality().equals(other._plans, _plans) &&
|
||||
(identical(other.errorMessage, errorMessage) ||
|
||||
other.errorMessage == errorMessage));
|
||||
other.errorMessage == errorMessage) &&
|
||||
(identical(other.filterState, filterState) ||
|
||||
other.filterState == filterState) &&
|
||||
(identical(other.minPlanPrice, minPlanPrice) ||
|
||||
other.minPlanPrice == minPlanPrice) &&
|
||||
(identical(other.maxPlanPrice, maxPlanPrice) ||
|
||||
other.maxPlanPrice == maxPlanPrice));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, isLoading,
|
||||
const DeepCollectionEquality().hash(_plans), errorMessage);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
isLoading,
|
||||
const DeepCollectionEquality().hash(_plans),
|
||||
errorMessage,
|
||||
filterState,
|
||||
minPlanPrice,
|
||||
maxPlanPrice);
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -173,7 +256,10 @@ abstract class _PlansState implements PlansState {
|
||||
const factory _PlansState(
|
||||
{required final bool isLoading,
|
||||
required final List<PlanModel> plans,
|
||||
required final String? errorMessage}) = _$PlansStateImpl;
|
||||
required final String? errorMessage,
|
||||
required final PlansFilterState filterState,
|
||||
final int minPlanPrice,
|
||||
final int maxPlanPrice}) = _$PlansStateImpl;
|
||||
|
||||
@override
|
||||
bool get isLoading;
|
||||
@@ -181,6 +267,12 @@ abstract class _PlansState implements PlansState {
|
||||
List<PlanModel> get plans;
|
||||
@override
|
||||
String? get errorMessage;
|
||||
@override
|
||||
PlansFilterState get filterState;
|
||||
@override
|
||||
int get minPlanPrice;
|
||||
@override
|
||||
int get maxPlanPrice;
|
||||
|
||||
/// Create a copy of PlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -189,3 +281,174 @@ abstract class _PlansState implements PlansState {
|
||||
_$$PlansStateImplCopyWith<_$PlansStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PlansFilterState {
|
||||
(int?, int?) get priceFilter => throw _privateConstructorUsedError;
|
||||
int get durationIndex => throw _privateConstructorUsedError;
|
||||
int get typeIndex => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of PlansFilterState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$PlansFilterStateCopyWith<PlansFilterState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $PlansFilterStateCopyWith<$Res> {
|
||||
factory $PlansFilterStateCopyWith(
|
||||
PlansFilterState value, $Res Function(PlansFilterState) then) =
|
||||
_$PlansFilterStateCopyWithImpl<$Res, PlansFilterState>;
|
||||
@useResult
|
||||
$Res call({(int?, int?) priceFilter, int durationIndex, int typeIndex});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$PlansFilterStateCopyWithImpl<$Res, $Val extends PlansFilterState>
|
||||
implements $PlansFilterStateCopyWith<$Res> {
|
||||
_$PlansFilterStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of PlansFilterState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? priceFilter = null,
|
||||
Object? durationIndex = null,
|
||||
Object? typeIndex = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
priceFilter: null == priceFilter
|
||||
? _value.priceFilter
|
||||
: priceFilter // ignore: cast_nullable_to_non_nullable
|
||||
as (int?, int?),
|
||||
durationIndex: null == durationIndex
|
||||
? _value.durationIndex
|
||||
: durationIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
typeIndex: null == typeIndex
|
||||
? _value.typeIndex
|
||||
: typeIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$PlansFilterStateImplCopyWith<$Res>
|
||||
implements $PlansFilterStateCopyWith<$Res> {
|
||||
factory _$$PlansFilterStateImplCopyWith(_$PlansFilterStateImpl value,
|
||||
$Res Function(_$PlansFilterStateImpl) then) =
|
||||
__$$PlansFilterStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({(int?, int?) priceFilter, int durationIndex, int typeIndex});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$PlansFilterStateImplCopyWithImpl<$Res>
|
||||
extends _$PlansFilterStateCopyWithImpl<$Res, _$PlansFilterStateImpl>
|
||||
implements _$$PlansFilterStateImplCopyWith<$Res> {
|
||||
__$$PlansFilterStateImplCopyWithImpl(_$PlansFilterStateImpl _value,
|
||||
$Res Function(_$PlansFilterStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of PlansFilterState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? priceFilter = null,
|
||||
Object? durationIndex = null,
|
||||
Object? typeIndex = null,
|
||||
}) {
|
||||
return _then(_$PlansFilterStateImpl(
|
||||
priceFilter: null == priceFilter
|
||||
? _value.priceFilter
|
||||
: priceFilter // ignore: cast_nullable_to_non_nullable
|
||||
as (int?, int?),
|
||||
durationIndex: null == durationIndex
|
||||
? _value.durationIndex
|
||||
: durationIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
typeIndex: null == typeIndex
|
||||
? _value.typeIndex
|
||||
: typeIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$PlansFilterStateImpl implements _PlansFilterState {
|
||||
const _$PlansFilterStateImpl(
|
||||
{required this.priceFilter,
|
||||
required this.durationIndex,
|
||||
required this.typeIndex});
|
||||
|
||||
@override
|
||||
final (int?, int?) priceFilter;
|
||||
@override
|
||||
final int durationIndex;
|
||||
@override
|
||||
final int typeIndex;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlansFilterState(priceFilter: $priceFilter, durationIndex: $durationIndex, typeIndex: $typeIndex)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PlansFilterStateImpl &&
|
||||
(identical(other.priceFilter, priceFilter) ||
|
||||
other.priceFilter == priceFilter) &&
|
||||
(identical(other.durationIndex, durationIndex) ||
|
||||
other.durationIndex == durationIndex) &&
|
||||
(identical(other.typeIndex, typeIndex) ||
|
||||
other.typeIndex == typeIndex));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, priceFilter, durationIndex, typeIndex);
|
||||
|
||||
/// Create a copy of PlansFilterState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$PlansFilterStateImplCopyWith<_$PlansFilterStateImpl> get copyWith =>
|
||||
__$$PlansFilterStateImplCopyWithImpl<_$PlansFilterStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _PlansFilterState implements PlansFilterState {
|
||||
const factory _PlansFilterState(
|
||||
{required final (int?, int?) priceFilter,
|
||||
required final int durationIndex,
|
||||
required final int typeIndex}) = _$PlansFilterStateImpl;
|
||||
|
||||
@override
|
||||
(int?, int?) get priceFilter;
|
||||
@override
|
||||
int get durationIndex;
|
||||
@override
|
||||
int get typeIndex;
|
||||
|
||||
/// Create a copy of PlansFilterState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$PlansFilterStateImplCopyWith<_$PlansFilterStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:eatvio_app/features/feed/presentation/widgets/diet_filter_bottom
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/diets_widget.dart';
|
||||
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/plans_filter_bottom_sheet.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/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
@@ -94,8 +95,21 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<FeedProfileCubit>()..getProfile(),
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<FeedProfileCubit>(
|
||||
create: (context) => sl<FeedProfileCubit>()..getProfile(),
|
||||
),
|
||||
BlocProvider<RecipesCubit>(
|
||||
create: (context) => sl<RecipesCubit>()..loadRecipesList(),
|
||||
),
|
||||
BlocProvider<DietsCubit>(
|
||||
create: (context) => sl<DietsCubit>()..loadDiets(),
|
||||
),
|
||||
BlocProvider<PlansCubit>(
|
||||
create: (context) => sl<PlansCubit>()..loadPlans(),
|
||||
),
|
||||
],
|
||||
child: Scaffold(
|
||||
floatingActionButton: _selectedChipIndex == 1
|
||||
? FloatingActionButton.small(
|
||||
@@ -188,185 +202,193 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
}
|
||||
|
||||
_buildAllBlogPosts() {
|
||||
return BlocProvider<FeedCubit>(
|
||||
create: (context) => feedCubit,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => feedCubit.loadFeedBlogPostList(),
|
||||
child: BlocBuilder<FeedCubit, FeedState>(
|
||||
builder: (BuildContext context, FeedState state) {
|
||||
if (state is FeedStateLoaded) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == state.feedBlogPostList.length) {
|
||||
return const Loader();
|
||||
}
|
||||
return BlogPostWidget(index: index);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
|
||||
);
|
||||
} else if (state is FeedStateError) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildAllRecipes() {
|
||||
return BlocProvider<RecipesCubit>(
|
||||
create: (context) => recipesCubit,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => recipesCubit.loadRecipesList(),
|
||||
child: BlocBuilder<RecipesCubit, RecipesState>(
|
||||
builder: (BuildContext context, RecipesState state) {
|
||||
if (state is RecipesStateLoaded) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return RecipesWidget(recipes: state.recipesList[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.recipesList.length,
|
||||
);
|
||||
} else if (state is RecipesStateError) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildDiets() {
|
||||
return BlocProvider<DietsCubit>(
|
||||
create: (context) => dietsCubit,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => dietsCubit.loadDiets(),
|
||||
child: BlocBuilder<DietsCubit, DietsState>(
|
||||
builder: (BuildContext context, DietsState state) {
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state.errorMessage != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage ?? ''),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Stack(
|
||||
children: [
|
||||
SizedBox.expand(),
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return DietsWidget(diet: state.dietsList[index], index: index);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.dietsList.length,
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 74,
|
||||
child: FilterBadge(
|
||||
filterBottomSheet: DietFilterBottomSheet(
|
||||
dietsCubit: dietsCubit,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildPlans() {
|
||||
return BlocProvider<PlansCubit>(
|
||||
create: (context) => plansCubit,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => plansCubit.loadPlans(),
|
||||
child: BlocBuilder<PlansCubit, PlansState>(
|
||||
builder: (BuildContext context, PlansState state) {
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state.errorMessage != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage ?? ''),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => feedCubit.loadFeedBlogPostList(),
|
||||
child: BlocBuilder<FeedCubit, FeedState>(
|
||||
builder: (BuildContext context, FeedState state) {
|
||||
if (state is FeedStateLoaded) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return PlanWidget(planModel: state.plans[index]);
|
||||
if (index == state.feedBlogPostList.length) {
|
||||
return const Loader();
|
||||
}
|
||||
return BlogPostWidget(index: index);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.plans.length,
|
||||
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
|
||||
);
|
||||
},
|
||||
),
|
||||
} else if (state is FeedStateError) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildAllRecipes() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => recipesCubit.loadRecipesList(),
|
||||
child: BlocBuilder<RecipesCubit, RecipesState>(
|
||||
bloc: recipesCubit,
|
||||
builder: (BuildContext context, RecipesState state) {
|
||||
if (state is RecipesStateLoaded) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return RecipesWidget(recipes: state.recipesList[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.recipesList.length,
|
||||
);
|
||||
} else if (state is RecipesStateError) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildDiets() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => dietsCubit.loadDiets(),
|
||||
child: BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: dietsCubit,
|
||||
builder: (BuildContext context, DietsState state) {
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state.errorMessage != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage ?? ''),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Stack(
|
||||
children: [
|
||||
SizedBox.expand(),
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return DietsWidget(diet: state.dietsList[index], index: index);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.dietsList.length,
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 74,
|
||||
child: FilterBadge(
|
||||
filterBottomSheet: DietFilterBottomSheet(
|
||||
dietsCubit: dietsCubit,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildPlans() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => plansCubit.loadPlans(),
|
||||
child: BlocBuilder<PlansCubit, PlansState>(
|
||||
bloc: plansCubit,
|
||||
builder: (BuildContext context, PlansState state) {
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state.errorMessage != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage ?? ''),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Stack(
|
||||
children: [
|
||||
SizedBox.expand(),
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return PlanWidget(
|
||||
plan: state.plans[index],
|
||||
index: index,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.plans.length,
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 74,
|
||||
child: FilterBadge(
|
||||
filterBottomSheet: PlansFilterBottomSheet(
|
||||
plansCubit: plansCubit,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -500,6 +522,9 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
case 2:
|
||||
dietsCubit.loadDiets(query: query);
|
||||
break;
|
||||
case 3:
|
||||
plansCubit.loadPlans(query: query);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -90,7 +90,8 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
if (!(state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe ?? false) && !state.feedBlogPostList[widget.index].author.authorIsMe)
|
||||
if (!(state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe ?? false) &&
|
||||
!state.feedBlogPostList[widget.index].author.authorIsMe)
|
||||
SubscribeButtonWidget(
|
||||
slug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||
),
|
||||
|
||||
@@ -31,22 +31,9 @@ class DietFilterBottomSheet extends StatelessWidget {
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
SortingDropDownFilter(cubit: dietsCubit,),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
SortingDropDownFilter(
|
||||
cubit: dietsCubit,
|
||||
),
|
||||
CustomDivider(),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
s.diet_time,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
TypeChoiceFilterWidget(cubit: dietsCubit,),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
@@ -61,7 +48,26 @@ class DietFilterBottomSheet extends StatelessWidget {
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DurationChoiceFilterWidget(cubit: dietsCubit,),
|
||||
TypeChoiceFilterWidget(
|
||||
dietsCubit: dietsCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
CustomDivider(),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
s.time,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DurationChoiceFilterWidget(
|
||||
dietCubit: dietsCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
|
||||
@@ -2,21 +2,23 @@ import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_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/features/feed/presentation/widgets/choice_chip_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class DurationChoiceFilterWidget extends StatefulWidget {
|
||||
const DurationChoiceFilterWidget({super.key, required this.cubit});
|
||||
final DietsCubit cubit;
|
||||
const DurationChoiceFilterWidget({super.key, this.dietCubit, this.plansCubit});
|
||||
final DietsCubit? dietCubit;
|
||||
final PlansCubit? plansCubit;
|
||||
@override
|
||||
State<DurationChoiceFilterWidget> createState() => _DurationChoiceFilterWidgetState();
|
||||
}
|
||||
|
||||
class _DurationChoiceFilterWidgetState extends State<DurationChoiceFilterWidget> {
|
||||
final List<String> chipsLabelList = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final s = S.of(context);
|
||||
@@ -31,55 +33,108 @@ class _DurationChoiceFilterWidgetState extends State<DurationChoiceFilterWidget>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: widget.cubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeDurationFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeDurationFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeDurationFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeDurationFilter(3);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (widget.dietCubit != null) {
|
||||
return BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: widget.dietCubit!,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(3);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return BlocBuilder<PlansCubit, PlansState>(
|
||||
bloc: widget.plansCubit!,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(3);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ class FilterBadge extends StatelessWidget {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => filterBottomSheet,
|
||||
useRootNavigator: true,
|
||||
barrierColor: Colors.transparent,
|
||||
backgroundColor: appTheme.whiteA700,);
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => filterBottomSheet,
|
||||
useRootNavigator: true,
|
||||
barrierColor: Colors.transparent,
|
||||
backgroundColor: appTheme.whiteA700,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 12, bottom: 12, right: 24),
|
||||
|
||||
@@ -1,174 +1,172 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/diet_info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class PlanWidget extends StatelessWidget {
|
||||
final PlanModel planModel;
|
||||
|
||||
class PlanWidget extends StatefulWidget {
|
||||
final PlanModel plan;
|
||||
final int index;
|
||||
const PlanWidget({
|
||||
super.key,
|
||||
required this.planModel,
|
||||
required this.plan,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PlanWidget> createState() => _PlanWidgetState();
|
||||
}
|
||||
|
||||
class _PlanWidgetState extends State<PlanWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'aboutPlanScreen',
|
||||
extra: {
|
||||
'plan': planModel,
|
||||
'navigateToComments': false,
|
||||
},
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () =>
|
||||
GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'index': widget.index, 'navigateToComments': false}),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: baseUrl + '/storage/' + planModel.image,
|
||||
imageUrl: baseUrl + '/storage/' + widget.plan.image,
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 8,
|
||||
bottom: 8,
|
||||
child: Wrap(
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_iconButton(Icons.timelapse_rounded, '${planModel.duration} дней'),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
right: 14,
|
||||
child: BookmarkButton(
|
||||
onTap: () async {
|
||||
if (widget.plan.isFavorite) {
|
||||
await context.read<PlansCubit>().removePlanFromFavorite(widget.plan.slug, widget.index);
|
||||
} else {
|
||||
await context.read<PlansCubit>().addPlanToFavorite(widget.plan.slug, widget.index);
|
||||
}
|
||||
},
|
||||
color: appTheme.whiteA700,
|
||||
isFavorite: widget.plan.isFavorite,
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
|
||||
width: 40,
|
||||
height: 44,
|
||||
size: 19,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: InfoRow(
|
||||
plan: widget.plan,
|
||||
itemsBackgroundColor: appTheme.whiteA700,
|
||||
disableDescriptionOfTypes: true,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 8,
|
||||
top: 8,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.favorite_rounded,
|
||||
size: 16,
|
||||
color: Colors.black,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'0',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
widget.plan.name,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Positioned(
|
||||
right: 8,
|
||||
top: 8,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.visibility_rounded,
|
||||
size: 16,
|
||||
color: Colors.black,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'0',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.plan.details.isNotEmpty) ...[
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Html(
|
||||
data: widget.plan.details.length > 200 ? widget.plan.details.substring(0, 200) + '...' : widget.plan.details,
|
||||
style: {
|
||||
"*": Style(
|
||||
margin: Margins.zero,
|
||||
color: appTheme.black,
|
||||
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
|
||||
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
planModel.name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Html(
|
||||
data: planModel.details,
|
||||
style: {
|
||||
"*": Style(
|
||||
color: Colors.black87,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_iconButton(IconData icon, [String? text]) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
height: 32,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: Colors.black,
|
||||
],
|
||||
),
|
||||
if (text != null) SizedBox(width: 8),
|
||||
if (text != null)
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
FeedIconButton(
|
||||
icon: widget.plan.wasLikedByMe ? Icons.favorite_rounded : Icons.favorite_border_rounded,
|
||||
text: widget.plan.totalLikes.toString(),
|
||||
iconColor: widget.plan.wasLikedByMe ? appTheme.blueA400 : null,
|
||||
backgroundColor: widget.plan.wasLikedByMe ? appTheme.transparentBlue : null,
|
||||
onTap: () async {
|
||||
await context.read<DietsCubit>().likeDiet(widget.plan.id, widget.index);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
FeedIconButton(
|
||||
icon: Icons.chat_bubble_outline,
|
||||
text: widget.plan.totalComments.toString(),
|
||||
onTap: () =>
|
||||
GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'index': widget.index, 'navigateToComments': true}),
|
||||
),
|
||||
SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
FeedIconButton(
|
||||
svgIcon: SvgPicture.asset('assets/icons/repost.svg'),
|
||||
onTap: () async {
|
||||
final result = await Share.shareUri(
|
||||
Uri.parse(
|
||||
baseUrl + '/ru/diet/' + widget.plan.id.toString(),
|
||||
),
|
||||
);
|
||||
switch (result.status) {
|
||||
case ShareResultStatus.success:
|
||||
case ShareResultStatus.dismissed:
|
||||
case ShareResultStatus.unavailable:
|
||||
}
|
||||
},
|
||||
),
|
||||
Spacer(),
|
||||
FeedIconButton(
|
||||
svgIcon: SvgPicture.asset('assets/icons/eye2.svg'),
|
||||
text: widget.plan.totalViews.toString(),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/duration_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/price_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/type_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlansFilterBottomSheet extends StatelessWidget {
|
||||
const PlansFilterBottomSheet({super.key, required this.plansCubit});
|
||||
final PlansCubit plansCubit;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Center(child: BottomSheetHandlerBar()),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
s.price,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
PriceChoiceFilterWidget(plansCubit: plansCubit),
|
||||
SizedBox(height: 16),
|
||||
CustomDivider(),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
s.time,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DurationChoiceFilterWidget(
|
||||
plansCubit: plansCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
CustomDivider(),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
s.plan_type,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
TypeChoiceFilterWidget(
|
||||
plansCubit: plansCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class PriceChoiceFilterWidget extends StatefulWidget {
|
||||
PriceChoiceFilterWidget({super.key, required this.plansCubit});
|
||||
final PlansCubit plansCubit;
|
||||
|
||||
@override
|
||||
State<PriceChoiceFilterWidget> createState() => _PriceChoiceFilterWidgetState();
|
||||
}
|
||||
|
||||
class _PriceChoiceFilterWidgetState extends State<PriceChoiceFilterWidget> {
|
||||
final minPriceController = TextEditingController()..text = '₽';
|
||||
final maxPriceController = TextEditingController()..text = '₽';
|
||||
int? prevMinPrice;
|
||||
int? prevMaxPrice;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
minPriceController.addListener(() {
|
||||
if (!minPriceController.text.endsWith('₽')) {
|
||||
final textWithoutCurrency = minPriceController.text.replaceAll('₽', '');
|
||||
minPriceController.value = TextEditingValue(
|
||||
text: "$textWithoutCurrency₽",
|
||||
selection: TextSelection.collapsed(offset: textWithoutCurrency.length),
|
||||
);
|
||||
}
|
||||
});
|
||||
maxPriceController.addListener(() {
|
||||
if (!maxPriceController.text.endsWith('₽')) {
|
||||
final textWithoutCurrency = maxPriceController.text.replaceAll('₽', '');
|
||||
maxPriceController.value = TextEditingValue(
|
||||
text: "$textWithoutCurrency₽",
|
||||
selection: TextSelection.collapsed(offset: textWithoutCurrency.length),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<PlansCubit, PlansState>(
|
||||
bloc: widget.plansCubit,
|
||||
builder: (context, state) {
|
||||
prevMinPrice ??= state.minPlanPrice;
|
||||
prevMaxPrice ??= state.maxPlanPrice;
|
||||
|
||||
minPriceController.text = state.filterState.priceFilter.$1?.toStringAsFixed(0) ?? '₽';
|
||||
maxPriceController.text = state.filterState.priceFilter.$2?.toStringAsFixed(0) ?? '₽';
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
keyboardType: TextInputType.number,
|
||||
controller: minPriceController,
|
||||
onChanged: (value) {
|
||||
final price = int.tryParse(value.replaceAll('₽', ''));
|
||||
if (price == null) {
|
||||
EasyDebounce.cancel('changeMinPriceFilter');
|
||||
minPriceController.value = TextEditingValue(
|
||||
text: '₽',
|
||||
selection: TextSelection.collapsed(offset: 0),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price < state.minPlanPrice) {
|
||||
minPriceController.value = TextEditingValue(
|
||||
text: '${prevMinPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMinPrice.toString().length),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price > (state.filterState.priceFilter.$2 ?? state.maxPlanPrice)) {
|
||||
minPriceController.value = TextEditingValue(
|
||||
text: '${prevMinPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMinPrice.toString().length),
|
||||
);
|
||||
EasyDebounce.cancel('changeMinPriceFilter');
|
||||
widget.plansCubit.changePriceFilter(
|
||||
prevMinPrice ?? state.minPlanPrice,
|
||||
state.filterState.priceFilter.$2 ?? state.maxPlanPrice,
|
||||
);
|
||||
return;
|
||||
}
|
||||
prevMinPrice = price;
|
||||
EasyDebounce.debounce(
|
||||
'changeMinPriceFilter',
|
||||
Duration(seconds: 2),
|
||||
() => widget.plansCubit.changePriceFilter(
|
||||
price,
|
||||
state.filterState.priceFilter.$2 ?? state.maxPlanPrice,
|
||||
disableDebounce: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
onChanged: (value) {
|
||||
final price = int.tryParse(value.replaceAll('₽', ''));
|
||||
if (price == null) {
|
||||
EasyDebounce.cancel('changeMaxPriceFilter');
|
||||
maxPriceController.value = TextEditingValue(
|
||||
text: '₽',
|
||||
selection: TextSelection.collapsed(offset: 0),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price > state.maxPlanPrice) {
|
||||
maxPriceController.value = TextEditingValue(
|
||||
text: '${prevMaxPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMaxPrice.toString().length),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price < (state.filterState.priceFilter.$1 ?? state.minPlanPrice)) {
|
||||
maxPriceController.value = TextEditingValue(
|
||||
text: '${prevMaxPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMaxPrice.toString().length),
|
||||
);
|
||||
EasyDebounce.cancel('changeMaxPriceFilter');
|
||||
widget.plansCubit.changePriceFilter(
|
||||
state.filterState.priceFilter.$1 ?? state.minPlanPrice,
|
||||
prevMaxPrice ?? state.maxPlanPrice,
|
||||
);
|
||||
return;
|
||||
}
|
||||
prevMaxPrice = price;
|
||||
EasyDebounce.debounce(
|
||||
'changeMaxPriceFilter',
|
||||
Duration(seconds: 2),
|
||||
() => widget.plansCubit.changePriceFilter(
|
||||
state.filterState.priceFilter.$1 ?? state.minPlanPrice,
|
||||
price,
|
||||
disableDebounce: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
keyboardType: TextInputType.number,
|
||||
controller: maxPriceController,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
sliderTheme: SliderThemeData(
|
||||
overlayShape: SliderComponentShape.noOverlay,
|
||||
),
|
||||
),
|
||||
child: RangeSlider(
|
||||
activeColor: appTheme.blueA400,
|
||||
inactiveColor: appTheme.blueA400,
|
||||
min: state.minPlanPrice.toDouble(),
|
||||
max: state.maxPlanPrice.toDouble(),
|
||||
onChanged: (values) {
|
||||
if (values.start <= values.end) {
|
||||
widget.plansCubit.changePriceFilter(values.start.toInt(), values.end.toInt());
|
||||
}
|
||||
},
|
||||
values: RangeValues(
|
||||
state.filterState.priceFilter.$1?.toDouble() ?? state.minPlanPrice.toDouble(),
|
||||
state.filterState.priceFilter.$2?.toDouble() ?? state.maxPlanPrice.toDouble(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_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/features/feed/presentation/widgets/choice_chip_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class TypeChoiceFilterWidget extends StatefulWidget {
|
||||
const TypeChoiceFilterWidget({super.key, required this.cubit});
|
||||
final DietsCubit cubit;
|
||||
const TypeChoiceFilterWidget({
|
||||
super.key,
|
||||
this.dietsCubit,
|
||||
this.plansCubit,
|
||||
});
|
||||
final DietsCubit? dietsCubit;
|
||||
final PlansCubit? plansCubit;
|
||||
@override
|
||||
State<TypeChoiceFilterWidget> createState() => _TypeChoiceFilterWidgetState();
|
||||
}
|
||||
@@ -18,77 +25,151 @@ class _TypeChoiceFilterWidgetState extends State<TypeChoiceFilterWidget> {
|
||||
void didChangeDependencies() {
|
||||
final s = S.of(context);
|
||||
super.didChangeDependencies();
|
||||
if (chipsLabelList.isEmpty) {
|
||||
chipsLabelList.add(s.all);
|
||||
chipsLabelList.add(s.vegan_type);
|
||||
chipsLabelList.add(s.light_type);
|
||||
chipsLabelList.add(s.carb_type);
|
||||
chipsLabelList.add(s.meat_type);
|
||||
if (widget.dietsCubit != null) {
|
||||
if (chipsLabelList.isEmpty) {
|
||||
chipsLabelList.add(s.all);
|
||||
chipsLabelList.add(s.vegan_type);
|
||||
chipsLabelList.add(s.light_type);
|
||||
chipsLabelList.add(s.carb_type);
|
||||
chipsLabelList.add(s.meat_type);
|
||||
}
|
||||
} else {
|
||||
if (chipsLabelList.isEmpty) {
|
||||
chipsLabelList.add(s.all);
|
||||
chipsLabelList.add(s.fit_type);
|
||||
chipsLabelList.add(s.slim_type);
|
||||
chipsLabelList.add(s.wellness_type);
|
||||
chipsLabelList.add(s.eating_behavior_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: widget.cubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeTypeFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeTypeFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeTypeFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeTypeFilter(3);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 4,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.cubit.changeTypeFilter(4);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (widget.plansCubit != null) {
|
||||
return BlocBuilder<PlansCubit, PlansState>(
|
||||
bloc: widget.plansCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(3);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 4,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(4);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: widget.dietsCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(3);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 4,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(4);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user