forked from eatvio/eatvio_app
feat(plans): plan creating has been completed
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||
import 'package:eatvio_app/core/widgets/success_published_diet_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/widgets/option_widget.dart';
|
||||
@@ -15,6 +19,7 @@ import 'package:eatvio_app/features/create_plan/presentation/widgets/select_exac
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class CreatePlanScreen extends StatelessWidget {
|
||||
static const name = 'createPlanScreen';
|
||||
@@ -36,177 +41,229 @@ class CreatePlanScreenContent extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
s.adding_plan,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
MainBackgroundBouble(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.label,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: s.plan_label_desc,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setName(value);
|
||||
},
|
||||
maxLength: 150,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
s.plan_duration,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const PlanDurationDropDownWidget(),
|
||||
const SizedBox(height: 20),
|
||||
const PlanImagePickerWidget(),
|
||||
const SizedBox(height: 16),
|
||||
const PlanTypeChoiceWidget(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.plan_desc,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
height: 132,
|
||||
child: CustomTextFormField(
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
maxLength: 1000,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
expands: true,
|
||||
hintText: s.plan_desc_example,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setDetails(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
s.services,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionsCountButton(),
|
||||
const SizedBox(height: 20),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.selectedOptionIndex != current.selectedOptionIndex,
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
'${s.option} ${(state.selectedOptionIndex ?? 0) + 1}',
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionWidget(),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
CustomOutlinedButton(
|
||||
onPressed: () {},
|
||||
text: s.to_draft,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {},
|
||||
text: s.preview,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () {},
|
||||
text: s.publish,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.planCreated != current.planCreated || previous.isLoading != current.isLoading,
|
||||
builder: (context, state) {
|
||||
if (state.planCreated) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(
|
||||
title: state.planData.name ?? 'План',
|
||||
isPlan: true,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
s.adding_plan,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: 24.0),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SelectExactOptionWidget(),
|
||||
body: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
MainBackgroundBouble(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.label,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.validationState != current.validationState,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
CustomTextFormField(
|
||||
hintText: s.plan_label_desc,
|
||||
hasError: state.validationState.titleHasError,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setName(value);
|
||||
},
|
||||
maxLength: 150,
|
||||
),
|
||||
if (state.validationState.titleHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
s.plan_duration,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const PlanDurationDropDownWidget(),
|
||||
const SizedBox(height: 20),
|
||||
const PlanImagePickerWidget(),
|
||||
const SizedBox(height: 16),
|
||||
const PlanTypeChoiceWidget(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.plan_desc,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.validationState != current.validationState,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 132,
|
||||
child: CustomTextFormField(
|
||||
hasError: state.validationState.descriptionHasError,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
maxLength: 1000,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
expands: true,
|
||||
hintText: s.plan_desc_example,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setDetails(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (state.validationState.descriptionHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
s.services,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionsCountButton(),
|
||||
const SizedBox(height: 20),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.selectedOptionIndex != current.selectedOptionIndex,
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
'${s.option} ${(state.selectedOptionIndex ?? 0) + 1}',
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionWidget(),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// CustomOutlinedButton(
|
||||
// onPressed: () {},
|
||||
// text: s.to_draft,
|
||||
// height: 52,
|
||||
// buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
// buttonStyle: ButtonStyle(
|
||||
// side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
// backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 12),
|
||||
// CustomOutlinedButton(
|
||||
// onPressed: () async {},
|
||||
// text: s.preview,
|
||||
// height: 52,
|
||||
// buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
// buttonStyle: ButtonStyle(
|
||||
// side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
// backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 12),
|
||||
state.isLoading
|
||||
? const Loader()
|
||||
: CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
await context.read<CreatePlanCubit>().publishPlan();
|
||||
},
|
||||
text: s.publish,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: 24.0),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SelectExactOptionWidget(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,65 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
|
||||
class CreatePlanCubit extends Cubit<CreatePlanState> {
|
||||
CreatePlanCubit() : super(CreatePlanState.initial());
|
||||
final CreatePlanRepository createPlanRepository;
|
||||
CreatePlanCubit({required this.createPlanRepository}) : super(CreatePlanState.initial());
|
||||
|
||||
void setDetails(String text) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(details: text)));
|
||||
validateDetails();
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(name: name)));
|
||||
validateName();
|
||||
}
|
||||
|
||||
void setOptionTitle(String title) {
|
||||
final options = [...state.planData.options];
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(short: title);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(short: title);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionTitle();
|
||||
}
|
||||
|
||||
void setPrice(double price){
|
||||
void setPrice(double price) {
|
||||
final selectedOptionIndex = state.selectedOptionIndex ?? 0;
|
||||
final options = [...state.planData.options];
|
||||
options[selectedOptionIndex] = options[selectedOptionIndex].copyWith(price: price);
|
||||
final options = [...state.planData.options];
|
||||
options[selectedOptionIndex] = options[selectedOptionIndex].copyWith(price: price);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionPrice();
|
||||
}
|
||||
|
||||
void setOptionDescription(String description) {
|
||||
final options = [...state.planData.options];
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(description: description);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(description: description);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionDescription();
|
||||
}
|
||||
|
||||
void setOptionContent(String content, index) {
|
||||
final options = [...state.planData.options];
|
||||
void setOptionContent(String content, index) {
|
||||
final options = [...state.planData.options];
|
||||
final items = [...options[state.selectedOptionIndex ?? 0].items];
|
||||
if (items.length <= index) {
|
||||
items.add(content);
|
||||
}else {
|
||||
} else {
|
||||
items[index] = content;
|
||||
}
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: items);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: items);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionItems();
|
||||
}
|
||||
|
||||
void removeOptionContentItem(int index) {
|
||||
final options = [...state.planData.options];
|
||||
final updatedItems = [...options[state.selectedOptionIndex ?? 0].items];
|
||||
updatedItems.removeAt(index);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: updatedItems);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: updatedItems);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionItems();
|
||||
}
|
||||
|
||||
void setDuration(int index) {
|
||||
@@ -62,10 +71,12 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
|
||||
),
|
||||
),
|
||||
);
|
||||
validateDuration();
|
||||
}
|
||||
|
||||
void setImage(XFile? image) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(image: image)));
|
||||
validateImage();
|
||||
}
|
||||
|
||||
void setPlanState(String planState) {
|
||||
@@ -74,9 +85,319 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
|
||||
|
||||
void setPlanType(List<bool> type) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(type: type)));
|
||||
if (type.any((elem) => elem)) {
|
||||
validateType();
|
||||
}
|
||||
}
|
||||
|
||||
void setSelectedOptionIndex(int? index) {
|
||||
emit(state.copyWith(selectedOptionIndex: index));
|
||||
}
|
||||
|
||||
bool validateName() {
|
||||
final name = state.planData.name;
|
||||
if (name == null || name.length < 3) {
|
||||
emit(state.copyWith.validationState(titleHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(titleHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateDetails() {
|
||||
final details = state.planData.details;
|
||||
if (details == null || details.length < 3) {
|
||||
emit(state.copyWith.validationState(descriptionHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(descriptionHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateDuration() {
|
||||
if (state.planData.duration == null) {
|
||||
emit(state.copyWith.validationState(durationHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(durationHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateType() {
|
||||
final type = state.planData.type;
|
||||
if (type.isEmpty || type.every((elem) => elem)) {
|
||||
emit(state.copyWith.validationState(typeHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(typeHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateImage() {
|
||||
if (state.planData.image == null) {
|
||||
emit(state.copyWith.validationState(imageHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(imageHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionPrice() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
if (option.price < 1200) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validatePriceOfAllFilledOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
if (options[0].price < 1200) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[1].price < 1200) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[2].price < 1200) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionItems() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
if (option.items.isEmpty) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(isItemsEmpty: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
final itemsErrors = Map<int, bool>.from(optionsValidationsStates[state.selectedOptionIndex ?? 0].itemsErrors);
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(isItemsEmpty: false);
|
||||
for (final (index, item) in option.items.indexed) {
|
||||
if (item.length < 3) {
|
||||
itemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
itemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(itemsErrors: itemsErrors);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateItemsOfAllFilledOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
|
||||
if (options[0].items.isEmpty) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(isItemsEmpty: true);
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(isItemsEmpty: false);
|
||||
}
|
||||
|
||||
final option1ItemsErrors = Map<int, bool>.from(optionsValidationsStates[0].itemsErrors);
|
||||
for (final (index, item) in options[0].items.indexed) {
|
||||
if (item.length < 3) {
|
||||
option1ItemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
option1ItemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(itemsErrors: option1ItemsErrors);
|
||||
if (state.selectedOptionIndex == null) {
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return result;
|
||||
}
|
||||
if (options[1].items.isEmpty) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(isItemsEmpty: true);
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(isItemsEmpty: false);
|
||||
}
|
||||
|
||||
final option2ItemsErrors = Map<int, bool>.from(optionsValidationsStates[1].itemsErrors);
|
||||
for (final (index, item) in options[1].items.indexed) {
|
||||
if (item.length < 3) {
|
||||
option2ItemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
option2ItemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(itemsErrors: option2ItemsErrors);
|
||||
|
||||
if (options[2].items.isEmpty) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(isItemsEmpty: true);
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(isItemsEmpty: false);
|
||||
}
|
||||
|
||||
final option3ItemsErrors = Map<int, bool>.from(optionsValidationsStates[2].itemsErrors);
|
||||
for (final (index, item) in options[2].items.indexed) {
|
||||
if (item.length < 3) {
|
||||
option3ItemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
option3ItemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(itemsErrors: option3ItemsErrors);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionTitle() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
if (option.short.length < 3) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateTitlesOfAllFilledOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
if (options[0].short.length < 3) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
if (state.selectedOptionIndex == null) return result;
|
||||
|
||||
if (options[1].short.length < 3) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[2].short.length < 3) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionDescription() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
if (option.description.length < 3) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateDescriptionsOfAllOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
|
||||
if (options[0].description.length < 3) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
if (state.selectedOptionIndex == null) return result;
|
||||
if (options[1].description.length < 3) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[2].description.length < 3) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateAllFields() {
|
||||
final isNameValid = validateName();
|
||||
final isDetailsValid = validateDetails();
|
||||
final isImageValid = validateImage();
|
||||
final isDurationValid = validateDuration();
|
||||
final areTitlesValid = validateTitlesOfAllFilledOptions();
|
||||
final arePricesValid = validatePriceOfAllFilledOptions();
|
||||
final areDescriptionsValid = validateDescriptionsOfAllOptions();
|
||||
final areItemsValid = validateItemsOfAllFilledOptions();
|
||||
return isNameValid && isDetailsValid && isImageValid && isDurationValid && areTitlesValid && arePricesValid && areDescriptionsValid && areItemsValid;
|
||||
}
|
||||
|
||||
Future<void> publishPlan() async {
|
||||
if (validateAllFields()) {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final planModel = await state.planData.toCreationJson();
|
||||
final plan = await createPlanRepository.createPlan(model: planModel);
|
||||
if (state.planData.options[1].short.isNotEmpty) {
|
||||
final options = state.planData.toOptionsCreationJson();
|
||||
await createPlanRepository.createPacketOptions(slug: plan.slug, model: options);
|
||||
} else {
|
||||
final option = state.planData.toOptionCreationJson();
|
||||
await createPlanRepository.createPacketOption(slug: plan.slug, model: option);
|
||||
}
|
||||
emit(state.copyWith(isLoading: false, planCreated: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ class CreatePlanState with _$CreatePlanState {
|
||||
const factory CreatePlanState({
|
||||
required bool isLoading,
|
||||
required String? errorMessage,
|
||||
required CreatePlanModel planData,
|
||||
required CreatePlanModel planData,
|
||||
required PlanValidationState validationState,
|
||||
required bool planCreated,
|
||||
int? durationIndex,
|
||||
int? selectedOptionIndex,
|
||||
}) = _CreatePlanState;
|
||||
@@ -17,9 +19,55 @@ class CreatePlanState with _$CreatePlanState {
|
||||
factory CreatePlanState.initial() => CreatePlanState(
|
||||
isLoading: false,
|
||||
errorMessage: null,
|
||||
planCreated: false,
|
||||
validationState: PlanValidationState.empty(),
|
||||
planData: CreatePlanModel(
|
||||
type: [false, false, false, false],
|
||||
options: [PacketOptions.empty(), PacketOptions.empty(), PacketOptions.empty()],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class PlanValidationState with _$PlanValidationState {
|
||||
factory PlanValidationState({
|
||||
required bool titleHasError,
|
||||
required bool durationHasError,
|
||||
required bool typeHasError,
|
||||
required bool descriptionHasError,
|
||||
required bool imageHasError,
|
||||
required List<OptionValidationState> optionsValidationState,
|
||||
}) = _PlanValidationState;
|
||||
|
||||
factory PlanValidationState.empty() => PlanValidationState(
|
||||
titleHasError: false,
|
||||
durationHasError: false,
|
||||
typeHasError: false,
|
||||
descriptionHasError: false,
|
||||
imageHasError: false,
|
||||
optionsValidationState: [
|
||||
OptionValidationState.empty(),
|
||||
OptionValidationState.empty(),
|
||||
OptionValidationState.empty(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class OptionValidationState with _$OptionValidationState {
|
||||
factory OptionValidationState({
|
||||
required bool titleHasError,
|
||||
required bool priceHasError,
|
||||
required bool descriptionHasError,
|
||||
required Map<int, bool> itemsErrors,
|
||||
required bool isItemsEmpty,
|
||||
}) = _OptionValidationState;
|
||||
|
||||
factory OptionValidationState.empty() => OptionValidationState(
|
||||
descriptionHasError: false,
|
||||
titleHasError: false,
|
||||
priceHasError: false,
|
||||
isItemsEmpty: false,
|
||||
itemsErrors: {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -39,34 +41,53 @@ class _AddItemWidgetState extends State<AddItemWidget> {
|
||||
if (widget.items.length > widget.index && controller.text != widget.items[widget.index]) {
|
||||
controller.text = widget.items[widget.index];
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
controller: controller,
|
||||
hintText: widget.enableHint ? s.option_content_hint_text : null,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setOptionContent(value, widget.index);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.canBeDeleted) ...[
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().removeOptionContentItem(widget.index);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.lightGray0,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 24),
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.validationState != current.validationState,
|
||||
builder: (context, state) {
|
||||
final hasError = state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].isItemsEmpty ||
|
||||
(state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].itemsErrors[widget.index] ?? false);
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
controller: controller,
|
||||
hasError: hasError,
|
||||
hintText: widget.enableHint ? s.option_content_hint_text : null,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setOptionContent(value, widget.index);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.canBeDeleted) ...[
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().removeOptionContentItem(widget.index);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.lightGray0,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].isItemsEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.addAtLeastOneItem)
|
||||
] else if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].itemsErrors[widget.index] ?? false) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols)
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/widgets/add_item_widget.dart';
|
||||
@@ -32,35 +33,9 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
super.initState();
|
||||
customerPriceController.addListener(() {
|
||||
if (!customerPriceController.text.endsWith('₽')) {
|
||||
final textWithoutCurrency = customerPriceController.text.replaceAll('₽', '');
|
||||
customerPriceController.value = TextEditingValue(
|
||||
text: "$textWithoutCurrency₽",
|
||||
selection: TextSelection.collapsed(offset: textWithoutCurrency.length),
|
||||
);
|
||||
}
|
||||
final price = double.tryParse(customerPriceController.text.replaceAll('₽', ''));
|
||||
if (price != null && price < 1200) {
|
||||
customerPriceController.value = const TextEditingValue(
|
||||
text: "1200₽",
|
||||
selection: TextSelection.collapsed(offset: 4),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
creatorPriceController.addListener(() {
|
||||
if (!creatorPriceController.text.endsWith('₽')) {
|
||||
final textWithoutCurrency = creatorPriceController.text.replaceAll('₽', '');
|
||||
creatorPriceController.value = TextEditingValue(
|
||||
text: "$textWithoutCurrency₽",
|
||||
selection: TextSelection.collapsed(offset: textWithoutCurrency.length),
|
||||
);
|
||||
}
|
||||
final price = double.tryParse(creatorPriceController.text.replaceAll('₽', ''));
|
||||
if (price != null && price < 1000) {
|
||||
creatorPriceController.value = const TextEditingValue(
|
||||
text: "1000₽",
|
||||
selection: TextSelection.collapsed(offset: 4),
|
||||
);
|
||||
final text = customerPriceController.text.replaceAll('₽', '');
|
||||
customerPriceController.text = '$text₽';
|
||||
customerPriceController.selection = TextSelection.collapsed(offset: text.length);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -94,12 +69,17 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: optionNameHintText,
|
||||
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].titleHasError,
|
||||
controller: optionTitleController,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setOptionTitle(value);
|
||||
},
|
||||
maxLength: 150,
|
||||
),
|
||||
if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].titleHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
s.option_desc,
|
||||
@@ -114,6 +94,7 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
child: CustomTextFormField(
|
||||
controller: optionDescriptionController,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].descriptionHasError,
|
||||
maxLength: 1000,
|
||||
minLines: null,
|
||||
maxLines: null,
|
||||
@@ -124,6 +105,10 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
},
|
||||
),
|
||||
),
|
||||
if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].descriptionHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
@@ -141,14 +126,8 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: creatorPriceController,
|
||||
onChanged: (value) {
|
||||
double? price = double.tryParse(value.replaceAll('₽', ''));
|
||||
if (price != null) {
|
||||
price = price * 1.2;
|
||||
customerPriceController.text = price.toStringAsFixed(2);
|
||||
context.read<CreatePlanCubit>().setPrice(price);
|
||||
}
|
||||
},
|
||||
readOnly: true,
|
||||
textStyle: TextStyle(color: appTheme.gray),
|
||||
maxLength: 20,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -169,10 +148,11 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
CustomTextFormField(
|
||||
controller: customerPriceController,
|
||||
textInputType: const TextInputType.numberWithOptions(decimal: true),
|
||||
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].priceHasError,
|
||||
onChanged: (value) {
|
||||
final price = double.tryParse(value.replaceAll('₽', ''));
|
||||
creatorPriceController.text = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽';
|
||||
if (price != null) {
|
||||
creatorPriceController.text = (price * 0.8).toStringAsFixed(2);
|
||||
context.read<CreatePlanCubit>().setPrice(price);
|
||||
}
|
||||
},
|
||||
@@ -235,33 +215,33 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const CustomDivider(),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: isChecked,
|
||||
activeColor: appTheme.gray,
|
||||
checkColor: appTheme.gray,
|
||||
fillColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
side: WidgetStateBorderSide.resolveWith(
|
||||
(Set<WidgetState> states) {
|
||||
return BorderSide(width: 1, color: appTheme.gray);
|
||||
},
|
||||
),
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
isChecked = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
s.option_personal_diet_desc,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// const SizedBox(height: 16),
|
||||
// Row(
|
||||
// children: [
|
||||
// Checkbox(
|
||||
// value: isChecked,
|
||||
// activeColor: appTheme.gray,
|
||||
// checkColor: appTheme.gray,
|
||||
// fillColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
// side: WidgetStateBorderSide.resolveWith(
|
||||
// (Set<WidgetState> states) {
|
||||
// return BorderSide(width: 1, color: appTheme.gray);
|
||||
// },
|
||||
// ),
|
||||
// onChanged: (bool? value) {
|
||||
// setState(() {
|
||||
// isChecked = value ?? false;
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// s.option_personal_diet_desc,
|
||||
// style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -36,28 +37,37 @@ class _PlanDurationDropDownWidgetState extends State<PlanDurationDropDownWidget>
|
||||
final s = S.of(context);
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
return CustomDropDownButton(
|
||||
hint: Text(
|
||||
s.choose_plan_duration,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
context.read<CreatePlanCubit>().setDuration(durationDropDownTitles.indexOf(value));
|
||||
}
|
||||
},
|
||||
items: durationDropDownTitles
|
||||
.mapIndexed(
|
||||
(index, value) => DropdownMenuItem(
|
||||
value: durationDropDownTitles[index],
|
||||
child: Text(
|
||||
durationDropDownTitles[index],
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
|
||||
return Column(
|
||||
children: [
|
||||
CustomDropDownButton(
|
||||
hasError: state.validationState.durationHasError,
|
||||
hint: Text(
|
||||
s.choose_plan_duration,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
context.read<CreatePlanCubit>().setDuration(durationDropDownTitles.indexOf(value));
|
||||
}
|
||||
},
|
||||
items: durationDropDownTitles
|
||||
.mapIndexed(
|
||||
(index, value) => DropdownMenuItem(
|
||||
value: durationDropDownTitles[index],
|
||||
child: Text(
|
||||
durationDropDownTitles[index],
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
|
||||
),
|
||||
if (state.validationState.durationHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.choose_plan_duration),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:dotted_border/dotted_border.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/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -72,74 +73,79 @@ class _PlanImagePickerWidgetState extends State<PlanImagePickerWidget> {
|
||||
final s = S.of(context);
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showImagePickerOptions(s, context.read<CreatePlanCubit>());
|
||||
},
|
||||
child: Container(
|
||||
height: 240,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: state.planData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.planData.image?.path ?? ''))) : null,
|
||||
color: appTheme.gray50,
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_showImagePickerOptions(s, context.read<CreatePlanCubit>());
|
||||
},
|
||||
child: Container(
|
||||
height: 240,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: state.planData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.planData.image?.path ?? ''))) : null,
|
||||
color: appTheme.gray50,
|
||||
),
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
padding: state.planData.image != null ? null : const EdgeInsets.all(8),
|
||||
child: state.planData.image != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 6, right: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().setImage(null);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.dividerColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close_rounded,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
DottedBorder(
|
||||
stackFit: StackFit.expand,
|
||||
dashPattern: const [14, 14],
|
||||
color: state.validationState.imageHasError ? appTheme.red : appTheme.dottedBorder,
|
||||
borderType: BorderType.RRect,
|
||||
radius: const Radius.circular(12),
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/eatvio_logo.svg'),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
s.add_cover,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
padding: state.planData.image != null ? null : const EdgeInsets.all(8),
|
||||
child: state.planData.image != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 6, right: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().setImage(null);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.dividerColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close_rounded,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
DottedBorder(
|
||||
stackFit: StackFit.expand,
|
||||
dashPattern: const [14, 14],
|
||||
color: appTheme.dottedBorder,
|
||||
borderType: BorderType.RRect,
|
||||
radius: const Radius.circular(12),
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/eatvio_logo.svg'),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
s.add_cover,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.validationState.imageHasError) ...[const SizedBox(height: 8), ValidationErrorWidget(error: s.add_cover)],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -50,49 +51,55 @@ class _PlanTypeChoiceWidgetState extends State<PlanTypeChoiceWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
return Column(
|
||||
children: [
|
||||
...List.generate(4, (index) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final planTypes = [...state.planData.type];
|
||||
planTypes[index] = !planTypes[index];
|
||||
context.read<CreatePlanCubit>().setPlanType(planTypes);
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: index == 3 ? 0 : 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: state.planData.type[index] ? appTheme.cardBackground : appTheme.whiteA700,
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: state.planData.type[index] ? appTheme.blueA400 : appTheme.lightGray00,
|
||||
Row(
|
||||
children: [
|
||||
...List.generate(4, (index) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final planTypes = [...state.planData.type];
|
||||
planTypes[index] = !planTypes[index];
|
||||
context.read<CreatePlanCubit>().setPlanType(planTypes);
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: index == 3 ? 0 : 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: state.planData.type[index] ? appTheme.cardBackground : appTheme.whiteA700,
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: state.planData.type[index] ? appTheme.blueA400 : appTheme.lightGray00,
|
||||
),
|
||||
),
|
||||
height: 104,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Image.asset(
|
||||
choiceContent[index].$1,
|
||||
height: 30,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
choiceContent[index].$2,
|
||||
overflow: TextOverflow.clip,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textFS10FW500.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
height: 104,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Image.asset(
|
||||
choiceContent[index].$1,
|
||||
height: 30,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
choiceContent[index].$2,
|
||||
overflow: TextOverflow.clip,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textFS10FW500.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
if (state.validationState.typeHasError) ...[const SizedBox(height: 8), ValidationErrorWidget(error: s.chosePlanType)]
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user