added plan pagination and updated plans creation screen

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

View File

@@ -0,0 +1,72 @@
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_plan/presentation/cubit/create_plan_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
class AddItemWidget extends StatefulWidget {
AddItemWidget({
super.key,
required this.canBeDeleted,
required this.enableHint,
required this.index,
required this.items,
});
final int index;
final bool canBeDeleted;
final bool enableHint;
final List<String> items;
@override
State<AddItemWidget> createState() => _AddItemWidgetState();
}
class _AddItemWidgetState extends State<AddItemWidget> {
final controller = TextEditingController();
@override
void didUpdateWidget(oldWidget) {
super.didUpdateWidget(oldWidget);
if (!widget.canBeDeleted && widget.items.length >= widget.index) {
controller.clear();
}
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
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) ...[
SizedBox(width: 8),
GestureDetector(
onTap: () {
context.read<CreatePlanCubit>().removeOptionContentItem(widget.index);
},
child: Container(
padding: EdgeInsets.all(14),
decoration: BoxDecoration(
color: appTheme.lightGray0,
borderRadius: BorderRadius.circular(20),
),
child: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 24),
),
),
],
],
);
}
}

View File

@@ -1,10 +1,320 @@
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_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/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 StatelessWidget {
const OptionWidget({super.key});
class OptionWidget extends StatefulWidget {
OptionWidget({super.key});
@override
State<OptionWidget> createState() => _OptionWidgetState();
}
class _OptionWidgetState extends State<OptionWidget> {
final customerPriceController = TextEditingController()..text = '1200₽';
final creatorPriceController = TextEditingController()..text = '1000₽';
final optionTitleController = TextEditingController();
final optionDescriptionController = TextEditingController();
bool firstExplanationVisible = false;
bool isChecked = false;
bool secondExplanationVisible = false;
@override
void initState() {
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 = 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 = TextEditingValue(
text: "1000₽",
selection: TextSelection.collapsed(offset: 4),
);
}
});
}
@override
Widget build(BuildContext context) {
return Placeholder();
final s = S.of(context);
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
builder: (context, state) {
final optionNameHintText = switch (state.selectedOptionIndex ?? 1) {
0 => s.economy,
2 => s.comfort,
_ => s.standard,
};
final selectedOptionIndex = state.selectedOptionIndex ?? 0;
optionTitleController.text = state.planData.options[selectedOptionIndex].short;
optionDescriptionController.text = state.planData.options[selectedOptionIndex].description;
return MainBackgroundBouble(
padding: 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),
),
SizedBox(height: 4),
CustomTextFormField(
hintText: optionNameHintText,
controller: optionTitleController,
onChanged: (value) {
context.read<CreatePlanCubit>().setOptionTitle(value);
},
maxLength: 150,
),
SizedBox(height: 12),
Text(
s.option_desc,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(height: 4),
SizedBox(
height: 120,
child: CustomTextFormField(
controller: optionDescriptionController,
textAlignVertical: TextAlignVertical.top,
maxLength: 1000,
minLines: null,
maxLines: null,
expands: true,
hintText: s.option_desc_hint_text,
onChanged: (value) {
context.read<CreatePlanCubit>().setOptionDescription(value);
},
),
),
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),
),
SizedBox(width: 4),
_PriceExplanationWidget(),
],
),
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);
}
},
maxLength: 20,
),
SizedBox(height: 12),
Row(
children: [
Text(
s.customer_price,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(width: 4),
_PriceExplanationWidget(),
],
),
SizedBox(height: 4),
CustomTextFormField(
controller: customerPriceController,
textInputType: TextInputType.numberWithOptions(decimal: true),
onChanged: (value) {
final price = double.tryParse(value.replaceAll('₽', ''));
if (price != null) {
creatorPriceController.text = (price * 0.8).toStringAsFixed(2);
context.read<CreatePlanCubit>().setPrice(price);
}
},
maxLength: 20,
),
SizedBox(height: 10),
Text(
s.minimal_customer_price,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(height: 16),
CustomDivider(),
SizedBox(height: 16),
Text(
s.option_content,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(height: 4),
ListView.separated(
padding: EdgeInsets.zero,
separatorBuilder: (context, index) => SizedBox(height: 8),
reverse: false,
itemBuilder: (context, index) {
final items = state.planData.options[selectedOptionIndex].items;
if (items.isEmpty) {
return AddItemWidget(
index: index,
canBeDeleted: false,
enableHint: true,
items: items,
);
} else {
if (index < items.length) {
return AddItemWidget(
canBeDeleted: true,
enableHint: false,
index: index,
items: items,
);
} else {
return AddItemWidget(
canBeDeleted: false,
enableHint: false,
index: index,
items: items,
);
}
}
},
itemCount: state.planData.options[selectedOptionIndex].items.length + 1,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
),
SizedBox(height: 16),
CustomDivider(),
SizedBox(height: 16),
Row(
children: [
Checkbox(
value: isChecked,
activeColor: appTheme.gray,
checkColor: appTheme.gray,
fillColor: 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),
),
),
],
),
],
),
);
},
);
}
}
}
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: Duration(milliseconds: 300),
portalFollower: AnimatedContainer(
padding: 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: 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),
),
);
}
}

View File

@@ -61,7 +61,7 @@ class _AnimatedOptionButton extends StatelessWidget {
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
duration: const Duration(milliseconds: 300),
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
@@ -69,7 +69,7 @@ class _AnimatedOptionButton extends StatelessWidget {
),
child: Center(
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 200),
duration: const Duration(milliseconds: 300),
style: TextStyles.textMSemibold.copyWith(
color: isSelected ? appTheme.whiteA700 : appTheme.black,
),

View File

@@ -1,25 +1,47 @@
import 'package:eatvio_app/core/theme/theme_helper.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SelectExactOptionWidget extends StatelessWidget {
class SelectExactOptionWidget extends StatefulWidget {
const SelectExactOptionWidget({super.key});
@override
State<SelectExactOptionWidget> createState() => _SelectExactOptionWidgetState();
}
class _SelectExactOptionWidgetState extends State<SelectExactOptionWidget> {
@override
Widget build(BuildContext context) {
return Container(
width: 140,
height: 48,
decoration: BoxDecoration(
color: appTheme.black,
borderRadius: BorderRadius.circular(100),
),
child: Row(
children: [
_OptionButton(isSelected: true, text: '1'),
_OptionButton(isSelected: true, text: '2'),
_OptionButton(isSelected: true, text: '3'),
],
),
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
builder: (context, state) {
final selectedIndex = state.selectedOptionIndex;
return Container(
padding: EdgeInsets.all(4),
width: 140,
height: 48,
decoration: BoxDecoration(
color: appTheme.black,
borderRadius: BorderRadius.circular(100),
),
child: Row(
children: [
...List.generate(
3,
(index) => Expanded(
child: GestureDetector(
onTap: () {
context.read<CreatePlanCubit>().setSelectedOptionIndex(index);
},
child: _OptionButton(isSelected: selectedIndex == index, text: '${index + 1}'),
),
),
),
],
),
);
},
);
}
}
@@ -30,13 +52,21 @@ class _OptionButton extends StatelessWidget {
final String text;
@override
Widget build(BuildContext context) {
return Container(
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
decoration: BoxDecoration(
color: appTheme.whiteA700,
color: isSelected ? appTheme.whiteA700 : Colors.transparent,
shape: BoxShape.circle,
),
child: Text(text),
child: Center(
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 300),
style: TextStyle(color: isSelected ? appTheme.black : appTheme.whiteA700),
child: Text(
text,
),
),
),
);
}
}