feat(plans): plan creating has been completed

This commit is contained in:
Username1548
2025-04-29 14:44:33 +07:00
parent c8b828bfe1
commit 0fcb2c1a0d
43 changed files with 1602 additions and 635 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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