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/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'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; class OptionWidget extends StatefulWidget { const OptionWidget({super.key}); @override State createState() => _OptionWidgetState(); } class _OptionWidgetState extends State { static const _optionsCount = 3; final List _customerPriceControllers = List.generate( _optionsCount, (_) => TextEditingController(text: '1200₽'), ); final List _titleControllers = List.generate( _optionsCount, (_) => TextEditingController(), ); final List _descriptionControllers = List.generate( _optionsCount, (_) => TextEditingController(), ); final creatorPriceController = TextEditingController(text: '1000₽'); int? _syncedOptionIndex; bool firstExplanationVisible = false; bool isChecked = false; bool secondExplanationVisible = false; @override void initState() { super.initState(); for (final controller in _customerPriceControllers) { controller.addListener(() => _ensureRubleSuffix(controller)); } } @override void dispose() { for (final controller in _customerPriceControllers) { controller.dispose(); } for (final controller in _titleControllers) { controller.dispose(); } for (final controller in _descriptionControllers) { controller.dispose(); } creatorPriceController.dispose(); super.dispose(); } void _ensureRubleSuffix(TextEditingController controller) { if (!controller.text.endsWith('₽')) { final text = controller.text.replaceAll('₽', ''); controller.text = '$text₽'; controller.selection = TextSelection.collapsed(offset: text.length); } } void _syncController(TextEditingController controller, String value) { if (controller.text == value) { return; } controller.value = TextEditingValue( text: value, selection: TextSelection.collapsed(offset: value.length), ); } String _formatCustomerPrice(dynamic price) { if (price is num) { return '${price.toStringAsFixed(0)}₽'; } final parsed = double.tryParse(price.toString().replaceAll('₽', '')); return '${(parsed ?? 0).toStringAsFixed(0)}₽'; } void _applyOptionToControllers(int index, PacketOptions option) { _syncController(_titleControllers[index], option.short); _syncController(_descriptionControllers[index], option.description ?? ''); _syncController( _customerPriceControllers[index], _formatCustomerPrice(option.price), ); } void _syncSelectedOption(CreatePlanState state) { final selectedOptionIndex = state.selectedOptionIndex ?? 0; final option = state.planData.options[selectedOptionIndex]; final price = double.tryParse( _customerPriceControllers[selectedOptionIndex].text.replaceAll('₽', ''), ); final creatorPrice = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽'; if (_syncedOptionIndex != selectedOptionIndex) { _syncedOptionIndex = selectedOptionIndex; WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) { return; } _applyOptionToControllers(selectedOptionIndex, option); creatorPriceController.text = creatorPrice; }); return; } if (creatorPriceController.text != creatorPrice) { creatorPriceController.text = creatorPrice; } } @override Widget build(BuildContext context) { final s = S.of(context); return BlocBuilder( builder: (context, state) { final selectedOptionIndex = state.selectedOptionIndex ?? 0; _syncSelectedOption(state); final optionNameHintText = switch (selectedOptionIndex) { 0 => s.economy, 2 => s.comfort, _ => s.standard, }; final optionTitleController = _titleControllers[selectedOptionIndex]; final optionDescriptionController = _descriptionControllers[selectedOptionIndex]; final customerPriceController = _customerPriceControllers[selectedOptionIndex]; final optionItems = state.planData.options[selectedOptionIndex].items; final optionValidation = state.validationState.optionsValidationState[selectedOptionIndex]; return MainBackgroundBouble( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( s.option_title, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.textSMedium.copyWith(color: appTheme.black), ), const SizedBox(height: 4), CustomTextFormField( hintText: optionNameHintText, hasError: optionValidation.titleHasError, controller: optionTitleController, onChanged: (value) { context.read().setOptionTitle(value); }, maxLength: 150, ), if (optionValidation.titleHasError) ...[ const SizedBox(height: 4), ValidationErrorWidget(error: s.atLeast3Symbols), ], const SizedBox(height: 12), Text( s.option_desc, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.textSMedium.copyWith(color: appTheme.black), ), const SizedBox(height: 4), SizedBox( height: 120, child: CustomTextFormField( controller: optionDescriptionController, textAlignVertical: TextAlignVertical.top, hasError: optionValidation.descriptionHasError, maxLength: 1000, minLines: null, maxLines: null, expands: true, hintText: s.option_desc_hint_text, onChanged: (value) { context.read().setOptionDescription(value); }, ), ), if (optionValidation.descriptionHasError) ...[ const SizedBox(height: 4), ValidationErrorWidget(error: s.atLeast3Symbols), ], const SizedBox(height: 12), Row( children: [ Text( s.you_will_get, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.textSMedium.copyWith( color: appTheme.black, ), ), const SizedBox(width: 4), const _PriceExplanationWidget(), ], ), const SizedBox(height: 4), CustomTextFormField( controller: creatorPriceController, readOnly: true, textStyle: TextStyle(color: appTheme.gray), maxLength: 20, ), const SizedBox(height: 12), Row( children: [ Text( s.customer_price, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.textSMedium.copyWith( color: appTheme.black, ), ), const SizedBox(width: 4), const _PriceExplanationWidget(), ], ), const SizedBox(height: 4), CustomTextFormField( controller: customerPriceController, textInputType: const TextInputType.numberWithOptions( decimal: true, ), hasError: optionValidation.priceHasError, onChanged: (value) { final price = double.tryParse(value.replaceAll('₽', '')); if (price != null) { context.read().setPrice(price); } }, maxLength: 20, ), const SizedBox(height: 10), Text( s.minimal_customer_price, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.textSMedium.copyWith(color: appTheme.black), ), const SizedBox(height: 16), const CustomDivider(), const SizedBox(height: 16), Text( s.option_content, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.textSMedium.copyWith(color: appTheme.black), ), const SizedBox(height: 4), ListView.separated( padding: EdgeInsets.zero, separatorBuilder: (context, index) => const SizedBox(height: 8), reverse: false, itemBuilder: (context, index) { if (optionItems.isEmpty) { return AddItemWidget( key: ValueKey( 'plan-option-$selectedOptionIndex-item-$index', ), index: index, canBeDeleted: false, enableHint: true, items: optionItems, ); } else { if (index < optionItems.length) { return AddItemWidget( key: ValueKey( 'plan-option-$selectedOptionIndex-item-$index', ), canBeDeleted: true, enableHint: false, index: index, items: optionItems, ); } else { return AddItemWidget( key: ValueKey( 'plan-option-$selectedOptionIndex-item-$index', ), canBeDeleted: false, enableHint: false, index: index, items: optionItems, ); } } }, itemCount: optionItems.length + 1, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), ), const SizedBox(height: 16), const CustomDivider(), const SizedBox(height: 16), Row( children: [ Checkbox( value: state .planData .options[selectedOptionIndex] .hasPersonalDiet, activeColor: appTheme.gray, checkColor: appTheme.gray, fillColor: const WidgetStatePropertyAll(Colors.transparent), side: WidgetStateBorderSide.resolveWith(( Set states, ) { return BorderSide(width: 1, color: appTheme.gray); }), onChanged: (bool? value) { context .read() .togglePersonalizedDietOptionState(); }, ), Expanded( child: Text( s.option_personal_diet_desc, style: TextStyles.textMMedium.copyWith( color: appTheme.black, ), ), ), ], ), ], ), ); }, ); } } class _PriceExplanationWidget extends StatefulWidget { const _PriceExplanationWidget(); @override State<_PriceExplanationWidget> createState() => _PriceExplanationWidgetState(); } class _PriceExplanationWidgetState extends State<_PriceExplanationWidget> { bool visible = false; @override Widget build(BuildContext context) { final s = S.of(context); return PortalTarget( closeDuration: const Duration(milliseconds: 300), portalFollower: AnimatedContainer( padding: const EdgeInsets.all(10), duration: const Duration(milliseconds: 300), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: visible ? appTheme.black : Colors.transparent, ), child: AnimatedDefaultTextStyle( style: TextStyles.textMMediumAuto.copyWith( color: visible ? appTheme.whiteA700 : Colors.transparent, ), duration: const Duration(milliseconds: 300), child: Text(s.price_explanation), ), ), visible: visible, anchor: const Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.bottomLeft, target: Alignment.topRight, ), child: GestureDetector( onTap: () { setState(() { visible = !visible; }); }, child: SvgPicture.asset( 'assets/icons/question_mark.svg', height: 12, width: 12, ), ), ); } }