Merge pull request 'Plans creation has been completed' (#401) from misha/eatvio_app:main into main

Reviewed-on: eatvio/eatvio_app#401
This commit is contained in:
2025-04-29 13:56:59 +00:00
43 changed files with 2450 additions and 618 deletions

View File

@@ -174,7 +174,10 @@ class AppRouter {
GoRoute(
path: CardSettingsScreen.path,
name: CardSettingsScreen.name,
builder: (context, state) => const CardSettingsScreen(),
builder: (context, state) {
final chosenPlanId = state.extra as int?;
return CardSettingsScreen(chosenPlanId: chosenPlanId);
},
),
GoRoute(
path: '/about-plan-screen',
@@ -220,7 +223,8 @@ class AppRouter {
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final chatId = data['chatId'] as String;
return ChatScreen(chatId: chatId);
final sendInitialMessage = data['sendInitialMessage'] as bool;
return ChatScreen(chatId: chatId, sendInitialMessage: sendInitialMessage,);
},
),
GoRoute(

View File

@@ -19,6 +19,8 @@ import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_c
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/diets/services/diet_service.dart';
@@ -117,7 +119,7 @@ Future<void> init() async {
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
sl.registerFactory<CreateDietCubit>(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit());
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl()));
@@ -158,6 +160,7 @@ Future<void> init() async {
sl.registerLazySingleton<CardsRepository>(() => CardsRepository(cardsService: sl()));
sl.registerLazySingleton<ConsultationsRepository>(() => ConsultationsRepository(consultationService: sl()));
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
// Services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
@@ -184,6 +187,7 @@ Future<void> init() async {
sl.registerLazySingleton<CardsService>(() => CardsService(sl()));
sl.registerLazySingleton<ConsultationService>(() => ConsultationService(sl()));
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
// External
sl.registerLazySingleton(

View File

@@ -147,6 +147,8 @@ class PrimaryColors {
Color get greenA70001 => const Color(0XFF00BE13);
Color get green => const Color(0XFF0BB450);
// LightGreen
Color get lightGreen300 => const Color(0XFFB2DF8A);

View File

@@ -99,7 +99,7 @@ class CustomTextFormField extends StatelessWidget {
controller: controller,
focusNode: focusNode,
autofocus: autofocus,
style: const TextStyle(fontSize: 16, color: Colors.black, fontWeight: FontWeight.w500),
style: textStyle ?? const TextStyle(fontSize: 16, color: Colors.black, fontWeight: FontWeight.w500),
obscureText: obscureText!,
textInputAction: textInputAction,
maxLines: maxLines,
@@ -115,20 +115,17 @@ class CustomTextFormField extends StatelessWidget {
prefixIconConstraints: prefixConstraints,
suffixIcon: suffix,
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixStyle: textStyle ?? const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)),
suffixStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)),
suffixIconConstraints: suffixConstraints,
isDense: false,
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
fillColor: const Color(0xFFF6F6F6),
filled: true,
border:
borderDecoration ??
border: borderDecoration ??
OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none),
enabledBorder:
borderDecoration ??
enabledBorder: borderDecoration ??
OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none),
focusedBorder:
borderDecoration ??
focusedBorder: borderDecoration ??
OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none),
),
keyboardType: keyboardType,

View File

@@ -7,9 +7,10 @@ import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class SuccessPublishedDietBottomSheet extends StatelessWidget {
const SuccessPublishedDietBottomSheet({super.key, required this.title});
class SuccessPublishedDietOrPlanBottomSheet extends StatelessWidget {
const SuccessPublishedDietOrPlanBottomSheet({super.key, required this.title, this.isPlan = false});
final String title;
final bool isPlan;
@override
Widget build(BuildContext context) {
final s = S.of(context);
@@ -27,7 +28,7 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
Text(
'опубликованна',
isPlan ? 'опубликован' : 'опубликованна',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(

View File

@@ -72,7 +72,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
}
}
initChat(String chatId) async {
initChat(String chatId, {bool sendInitialMessage = false}) async {
try {
final chatRoom = await chatRepository.getChatRoom(chatId);
ChosenPlanModel? chosenPlan;
@@ -92,10 +92,14 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
hasCard: false,
),
);
if (chatRoom.chosenPlan?.consultantId == chatRoom.users.receiver.id) {
await checkCards();
}
if (sendInitialMessage) {
await chatRepository.sendMessage(chatId: chatId, data: 'Здравствуйте, хочу заказать у вас: ${chosenPlan?.plan.name ?? 'план'}');
}
} catch (e) {
log(e.toString());
typingSubscription?.cancel();

View File

@@ -15,6 +15,7 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
@@ -28,7 +29,8 @@ import 'package:visibility_detector/visibility_detector.dart';
class ChatScreen extends StatefulWidget {
final String chatId;
const ChatScreen({required this.chatId, super.key});
final bool sendInitialMessage;
const ChatScreen({required this.chatId, super.key, required this.sendInitialMessage});
@override
State<ChatScreen> createState() => _ChatScreenState();
@@ -46,7 +48,7 @@ class _ChatScreenState extends State<ChatScreen> {
void initState() {
super.initState();
scrollController.addListener(_onScroll);
chatRoomCubit.initChat(widget.chatId);
chatRoomCubit.initChat(widget.chatId, sendInitialMessage: widget.sendInitialMessage);
}
_onScroll() {
@@ -96,13 +98,24 @@ class _ChatScreenState extends State<ChatScreen> {
BannerType? bannerType;
if (chosenPlan == null) {
bannerType = null;
} else if (amIConsultant && state.hasCard) {
} else if (!amIConsultant && !state.hasCard) {
bannerType = BannerType.card;
} else if (amIConsultant && chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateInCoordination) {
bannerType = BannerType.date;
} else if (!amIConsultant &&
(chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet || chosenPlan.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) {
bannerType = BannerType.payment;
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateDone) {
bannerType = BannerType.collectCharge;
}
PacketOptions? chosenOption;
if (chosenPlan?.optionType == 'options1') {
chosenOption = chosenPlan?.plan.packet1Options;
} else if (chosenPlan?.optionType == 'options2') {
chosenOption = chosenPlan?.plan.packet2Options;
} else if (chosenPlan?.optionType == 'options3') {
chosenOption = chosenPlan?.plan.packet3Options;
}
return Scaffold(
resizeToAvoidBottomInset: true,
@@ -263,7 +276,7 @@ class _ChatScreenState extends State<ChatScreen> {
itemCount: state.chatMessages.length + (state.isLoading ? 1 : 0),
),
const SizedBox(height: 16),
if (showUserDetailsButton) ...[
if (!showUserDetailsButton) ...[
// OrderCard(
// plan: chosenPlan.plan,
// isCustomerView: !amIConsultant,
@@ -289,7 +302,15 @@ class _ChatScreenState extends State<ChatScreen> {
),
),
if (bannerType != null)
Padding(padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: NotificationBanner(bannerType: bannerType)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: NotificationBanner(
bannerType: bannerType,
chosenPlanId: chosenPlan?.id,
planName: chosenPlan?.plan.name,
planPrice: chosenOption?.price.toDouble(),
),
),
],
],
),

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/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
@@ -13,8 +14,11 @@ import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
class NotificationBanner extends StatefulWidget {
const NotificationBanner({super.key, required this.bannerType});
const NotificationBanner({super.key, required this.bannerType, this.planPrice, this.planName, this.chosenPlanId});
final BannerType bannerType;
final double? planPrice;
final String? planName;
final int? chosenPlanId;
@override
State<NotificationBanner> createState() => _NotificationBannerState();
@@ -31,96 +35,145 @@ class _NotificationBannerState extends State<NotificationBanner> {
color: appTheme.gray50,
borderRadius: BorderRadius.circular(20),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 12, right: 8, left: 12),
child: SvgPicture.asset('assets/icons/reversed_exclamation_mark.svg', height: 68, width: 68),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.bannerType != BannerType.collectCharge)
Padding(
padding: const EdgeInsets.only(top: 12, right: 8, left: 12),
child: SvgPicture.asset('assets/icons/reversed_exclamation_mark.svg', height: 68, width: 68),
)
else
const Padding(
padding: EdgeInsets.only(top: 12, right: 8, left: 12),
child: _StarIcon(),
),
if (isLoading)
const Padding(
padding: EdgeInsets.only(left: 40, top: 24),
child: Loader(),
)
else
Padding(
padding: const EdgeInsets.only(top: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: switch (widget.bannerType) {
BannerType.date => [
SizedBox(
width: size.width * 0.6,
child: Text(s.choose_plan_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
final cubit = context.read<ChatRoomCubit>();
showModalBottomSheet(
context: context,
builder: (context) => ChosePlanDateWidget(
chatCubit: cubit,
),
);
},
child: Text(s.choose_date, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
],
BannerType.card => [
SizedBox(
width: size.width * 0.6,
child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
GoRouter.of(context).pushReplacement(
CardSettingsScreen.path,
);
},
child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
],
BannerType.payment => [
SizedBox(
width: size.width * 0.6,
child: Text(s.tap_on_link_to_pay, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () async {
final planId = (context.read<ChatRoomCubit>().state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
setState(() {
isLoading = true;
});
final paymentLink = await context.read<ChatRoomCubit>().getPaymentLink();
setState(() {
isLoading = false;
});
if (paymentLink != null && planId != null) {
if (!context.mounted) return;
GoRouter.of(context).pushReplacement(
PaymentScreen.path,
extra: {'url': paymentLink, 'planId': planId},
);
}
},
child: Text(s.pay, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
],
BannerType.collectCharge => [
SizedBox(
width: size.width * 0.6,
child:
Text('${s.congratsYouHaveFinishedPlan} «${widget.planName}»', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)),
),
const SizedBox(height: 8),
SizedBox(
width: size.width * 0.6,
child: Text('${s.userConfirmedPlanCompletion} ${widget.planPrice}₽', style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
),
const SizedBox(height: 8),
],
},
),
),
],
),
if (isLoading)
const Padding(
padding: EdgeInsets.only(left: 40, top: 24),
child: Loader(),
)
else
if (widget.bannerType == BannerType.collectCharge)
Padding(
padding: const EdgeInsets.only(top: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: switch (widget.bannerType) {
BannerType.date => [
SizedBox(
width: size.width * 0.6,
child: Text(s.choose_plan_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
final cubit = context.read<ChatRoomCubit>();
showModalBottomSheet(
context: context,
builder: (context) => ChosePlanDateWidget(
chatCubit: cubit,
),
);
},
child: Text(s.choose_date, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
],
BannerType.card => [
SizedBox(
width: size.width * 0.6,
child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
GoRouter.of(context).pushReplacement(
CardSettingsScreen.path,
);
},
child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
],
BannerType.payment => [
SizedBox(
width: size.width * 0.6,
child: Text(s.tap_on_link_to_pay, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () async {
final planId = (context.read<ChatRoomCubit>().state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
setState(() {
isLoading = true;
});
final paymentLink = await context.read<ChatRoomCubit>().getPaymentLink();
setState(() {
isLoading = false;
});
if (paymentLink != null && planId != null) {
if (!context.mounted) return;
GoRouter.of(context).pushReplacement(
PaymentScreen.path,
extra: {'url': paymentLink, 'planId': planId},
);
}
},
child: Text(s.pay, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
],
padding: const EdgeInsets.symmetric(horizontal: 12),
child: CustomOutlinedButton(
text: s.collectCharge,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
onPressed: () async {
GoRouter.of(context).push(CardSettingsScreen.path, extra: widget.chosenPlanId);
},
),
),
)
],
),
);
}
}
enum BannerType { date, card, payment }
class _StarIcon extends StatelessWidget {
const _StarIcon();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: appTheme.whiteA700, shape: BoxShape.circle),
child: SvgPicture.asset('assets/icons/star.svg'),
);
}
}
enum BannerType { date, card, payment, collectCharge }

View File

@@ -72,7 +72,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
showBottomSheetWithBlur(
userRootNavigator: true,
context: context,
child: SuccessPublishedDietBottomSheet(
child: SuccessPublishedDietOrPlanBottomSheet(
title: state.dietData.name ?? 'Диета',
),
);
@@ -83,7 +83,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
context.pop();
showBottomSheetWithBlur(
context: context,
child: SuccessPublishedDietBottomSheet(
child: SuccessPublishedDietOrPlanBottomSheet(
title: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
),
);
@@ -193,8 +193,8 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
context.read<CreateDietCubit>().setDietState(DietState.draft);
if (widget.diet != null) {
final result = await context.read<CreateDietCubit>().updateDiet(
widget.diet!.id,
);
widget.diet!.id,
);
if (result == 'error') {
if (!context.mounted) return;
showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}');

View File

@@ -15,8 +15,8 @@ class CreatePlanModel with _$CreatePlanModel {
const factory CreatePlanModel({
String? name,
String? details,
String? state,
int? duration,
@Default('active') String? state,
int? duration,
required List<bool> type,
XFile? image,
required List<PacketOptions> options,
@@ -25,9 +25,12 @@ class CreatePlanModel with _$CreatePlanModel {
Future<Map<String, dynamic>> toCreationJson() async => {
'name': name,
'duration': duration,
'state': state,
'type': type,
'state': 'active',
'description_md': details,
'type[slim]': type[0] ? '1' : '0',
'type[fit]': type[1] ? '1' : '0',
'type[wellness]': type[2] ? '1' : '0',
'type[behavior]': type[3] ? '1' : '0',
'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image!.path))).path, filename: image!.name) : null,
};

View File

@@ -0,0 +1,11 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'plan_slug_model.freezed.dart';
part 'plan_slug_model.g.dart';
@freezed
class PlanSlugModel with _$PlanSlugModel {
factory PlanSlugModel({@JsonKey(name: 'slug') required String slug}) = _PlanSlugModel;
factory PlanSlugModel.fromJson(Map<String, dynamic> json) => _$PlanSlugModelFromJson(json);
}

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

View File

@@ -1,3 +1,5 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/create_plan/models/plan_slug_model.dart';
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
class CreatePlanRepository {
@@ -5,10 +7,11 @@ class CreatePlanRepository {
CreatePlanRepository({required this.createPlanService});
Future<void> createPlan({
Future<PlanSlugModel> createPlan({
required Map<String, dynamic> model,
}) async {
createPlanService.createPlan(model: model);
final formData = FormData.fromMap(model);
return await createPlanService.createPlan(formData: formData);
}
Future<void> createPacketOption({

View File

@@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/create_plan/models/plan_slug_model.dart';
import 'package:retrofit/retrofit.dart';
part 'create_plan_service.g.dart';
@@ -9,8 +10,8 @@ abstract class CreatePlanService {
@POST('/api/plans/create')
@MultiPart()
Future<void> createPlan({
@Body() required Map<String, dynamic> model,
Future<PlanSlugModel> createPlan({
@Body() required FormData formData,
});
@POST('/api/plans/create/{slug}/packet_option')
@@ -19,7 +20,7 @@ abstract class CreatePlanService {
@Body() required Map<String, dynamic> model,
});
@POST('/api/plans/create/{slug}/packet_options')
@POST('/api/plans/create/{slug}/packet_options')
Future<void> createPacketOptions({
@Path() required String slug,
@Body() required Map<String, dynamic> model,

View File

@@ -36,6 +36,7 @@ class MealComment with _$MealComment {
factory MealComment({
required String comment,
required int typeNo,
}) = _MealComment;
factory MealComment.fromJson(Map<String, dynamic> json) => _$MealCommentFromJson(json);
@@ -45,7 +46,7 @@ class MealComment with _$MealComment {
class PortionComment with _$PortionComment {
factory PortionComment({
@JsonKey(name: 'commentable_id') required int portionId,
@JsonKey(name: 'id') required int portionId,
required String comment,
}) = _PortionComment;

View File

@@ -0,0 +1,813 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'consultant_review_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
ConsultantReviewModel _$ConsultantReviewModelFromJson(
Map<String, dynamic> json) {
return _ConsultantReviewModel.fromJson(json);
}
/// @nodoc
mixin _$ConsultantReviewModel {
String get planName => throw _privateConstructorUsedError;
int get planDuration => throw _privateConstructorUsedError;
int get currentPlanDay => throw _privateConstructorUsedError;
ConsultantComments get consultantComments =>
throw _privateConstructorUsedError;
/// Serializes this ConsultantReviewModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of ConsultantReviewModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ConsultantReviewModelCopyWith<ConsultantReviewModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ConsultantReviewModelCopyWith<$Res> {
factory $ConsultantReviewModelCopyWith(ConsultantReviewModel value,
$Res Function(ConsultantReviewModel) then) =
_$ConsultantReviewModelCopyWithImpl<$Res, ConsultantReviewModel>;
@useResult
$Res call(
{String planName,
int planDuration,
int currentPlanDay,
ConsultantComments consultantComments});
$ConsultantCommentsCopyWith<$Res> get consultantComments;
}
/// @nodoc
class _$ConsultantReviewModelCopyWithImpl<$Res,
$Val extends ConsultantReviewModel>
implements $ConsultantReviewModelCopyWith<$Res> {
_$ConsultantReviewModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ConsultantReviewModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? planName = null,
Object? planDuration = null,
Object? currentPlanDay = null,
Object? consultantComments = null,
}) {
return _then(_value.copyWith(
planName: null == planName
? _value.planName
: planName // ignore: cast_nullable_to_non_nullable
as String,
planDuration: null == planDuration
? _value.planDuration
: planDuration // ignore: cast_nullable_to_non_nullable
as int,
currentPlanDay: null == currentPlanDay
? _value.currentPlanDay
: currentPlanDay // ignore: cast_nullable_to_non_nullable
as int,
consultantComments: null == consultantComments
? _value.consultantComments
: consultantComments // ignore: cast_nullable_to_non_nullable
as ConsultantComments,
) as $Val);
}
/// Create a copy of ConsultantReviewModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ConsultantCommentsCopyWith<$Res> get consultantComments {
return $ConsultantCommentsCopyWith<$Res>(_value.consultantComments,
(value) {
return _then(_value.copyWith(consultantComments: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$ConsultantReviewModelImplCopyWith<$Res>
implements $ConsultantReviewModelCopyWith<$Res> {
factory _$$ConsultantReviewModelImplCopyWith(
_$ConsultantReviewModelImpl value,
$Res Function(_$ConsultantReviewModelImpl) then) =
__$$ConsultantReviewModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String planName,
int planDuration,
int currentPlanDay,
ConsultantComments consultantComments});
@override
$ConsultantCommentsCopyWith<$Res> get consultantComments;
}
/// @nodoc
class __$$ConsultantReviewModelImplCopyWithImpl<$Res>
extends _$ConsultantReviewModelCopyWithImpl<$Res,
_$ConsultantReviewModelImpl>
implements _$$ConsultantReviewModelImplCopyWith<$Res> {
__$$ConsultantReviewModelImplCopyWithImpl(_$ConsultantReviewModelImpl _value,
$Res Function(_$ConsultantReviewModelImpl) _then)
: super(_value, _then);
/// Create a copy of ConsultantReviewModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? planName = null,
Object? planDuration = null,
Object? currentPlanDay = null,
Object? consultantComments = null,
}) {
return _then(_$ConsultantReviewModelImpl(
planName: null == planName
? _value.planName
: planName // ignore: cast_nullable_to_non_nullable
as String,
planDuration: null == planDuration
? _value.planDuration
: planDuration // ignore: cast_nullable_to_non_nullable
as int,
currentPlanDay: null == currentPlanDay
? _value.currentPlanDay
: currentPlanDay // ignore: cast_nullable_to_non_nullable
as int,
consultantComments: null == consultantComments
? _value.consultantComments
: consultantComments // ignore: cast_nullable_to_non_nullable
as ConsultantComments,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ConsultantReviewModelImpl implements _ConsultantReviewModel {
_$ConsultantReviewModelImpl(
{required this.planName,
required this.planDuration,
required this.currentPlanDay,
required this.consultantComments});
factory _$ConsultantReviewModelImpl.fromJson(Map<String, dynamic> json) =>
_$$ConsultantReviewModelImplFromJson(json);
@override
final String planName;
@override
final int planDuration;
@override
final int currentPlanDay;
@override
final ConsultantComments consultantComments;
@override
String toString() {
return 'ConsultantReviewModel(planName: $planName, planDuration: $planDuration, currentPlanDay: $currentPlanDay, consultantComments: $consultantComments)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ConsultantReviewModelImpl &&
(identical(other.planName, planName) ||
other.planName == planName) &&
(identical(other.planDuration, planDuration) ||
other.planDuration == planDuration) &&
(identical(other.currentPlanDay, currentPlanDay) ||
other.currentPlanDay == currentPlanDay) &&
(identical(other.consultantComments, consultantComments) ||
other.consultantComments == consultantComments));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, planName, planDuration, currentPlanDay, consultantComments);
/// Create a copy of ConsultantReviewModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ConsultantReviewModelImplCopyWith<_$ConsultantReviewModelImpl>
get copyWith => __$$ConsultantReviewModelImplCopyWithImpl<
_$ConsultantReviewModelImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ConsultantReviewModelImplToJson(
this,
);
}
}
abstract class _ConsultantReviewModel implements ConsultantReviewModel {
factory _ConsultantReviewModel(
{required final String planName,
required final int planDuration,
required final int currentPlanDay,
required final ConsultantComments consultantComments}) =
_$ConsultantReviewModelImpl;
factory _ConsultantReviewModel.fromJson(Map<String, dynamic> json) =
_$ConsultantReviewModelImpl.fromJson;
@override
String get planName;
@override
int get planDuration;
@override
int get currentPlanDay;
@override
ConsultantComments get consultantComments;
/// Create a copy of ConsultantReviewModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ConsultantReviewModelImplCopyWith<_$ConsultantReviewModelImpl>
get copyWith => throw _privateConstructorUsedError;
}
ConsultantComments _$ConsultantCommentsFromJson(Map<String, dynamic> json) {
return _ConsultantComments.fromJson(json);
}
/// @nodoc
mixin _$ConsultantComments {
String? get dayComment => throw _privateConstructorUsedError;
List<MealComment> get mealsComments => throw _privateConstructorUsedError;
List<PortionComment> get portionsComments =>
throw _privateConstructorUsedError;
/// Serializes this ConsultantComments to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of ConsultantComments
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ConsultantCommentsCopyWith<ConsultantComments> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ConsultantCommentsCopyWith<$Res> {
factory $ConsultantCommentsCopyWith(
ConsultantComments value, $Res Function(ConsultantComments) then) =
_$ConsultantCommentsCopyWithImpl<$Res, ConsultantComments>;
@useResult
$Res call(
{String? dayComment,
List<MealComment> mealsComments,
List<PortionComment> portionsComments});
}
/// @nodoc
class _$ConsultantCommentsCopyWithImpl<$Res, $Val extends ConsultantComments>
implements $ConsultantCommentsCopyWith<$Res> {
_$ConsultantCommentsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ConsultantComments
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? dayComment = freezed,
Object? mealsComments = null,
Object? portionsComments = null,
}) {
return _then(_value.copyWith(
dayComment: freezed == dayComment
? _value.dayComment
: dayComment // ignore: cast_nullable_to_non_nullable
as String?,
mealsComments: null == mealsComments
? _value.mealsComments
: mealsComments // ignore: cast_nullable_to_non_nullable
as List<MealComment>,
portionsComments: null == portionsComments
? _value.portionsComments
: portionsComments // ignore: cast_nullable_to_non_nullable
as List<PortionComment>,
) as $Val);
}
}
/// @nodoc
abstract class _$$ConsultantCommentsImplCopyWith<$Res>
implements $ConsultantCommentsCopyWith<$Res> {
factory _$$ConsultantCommentsImplCopyWith(_$ConsultantCommentsImpl value,
$Res Function(_$ConsultantCommentsImpl) then) =
__$$ConsultantCommentsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String? dayComment,
List<MealComment> mealsComments,
List<PortionComment> portionsComments});
}
/// @nodoc
class __$$ConsultantCommentsImplCopyWithImpl<$Res>
extends _$ConsultantCommentsCopyWithImpl<$Res, _$ConsultantCommentsImpl>
implements _$$ConsultantCommentsImplCopyWith<$Res> {
__$$ConsultantCommentsImplCopyWithImpl(_$ConsultantCommentsImpl _value,
$Res Function(_$ConsultantCommentsImpl) _then)
: super(_value, _then);
/// Create a copy of ConsultantComments
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? dayComment = freezed,
Object? mealsComments = null,
Object? portionsComments = null,
}) {
return _then(_$ConsultantCommentsImpl(
dayComment: freezed == dayComment
? _value.dayComment
: dayComment // ignore: cast_nullable_to_non_nullable
as String?,
mealsComments: null == mealsComments
? _value._mealsComments
: mealsComments // ignore: cast_nullable_to_non_nullable
as List<MealComment>,
portionsComments: null == portionsComments
? _value._portionsComments
: portionsComments // ignore: cast_nullable_to_non_nullable
as List<PortionComment>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ConsultantCommentsImpl implements _ConsultantComments {
_$ConsultantCommentsImpl(
{required this.dayComment,
required final List<MealComment> mealsComments,
required final List<PortionComment> portionsComments})
: _mealsComments = mealsComments,
_portionsComments = portionsComments;
factory _$ConsultantCommentsImpl.fromJson(Map<String, dynamic> json) =>
_$$ConsultantCommentsImplFromJson(json);
@override
final String? dayComment;
final List<MealComment> _mealsComments;
@override
List<MealComment> get mealsComments {
if (_mealsComments is EqualUnmodifiableListView) return _mealsComments;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_mealsComments);
}
final List<PortionComment> _portionsComments;
@override
List<PortionComment> get portionsComments {
if (_portionsComments is EqualUnmodifiableListView)
return _portionsComments;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_portionsComments);
}
@override
String toString() {
return 'ConsultantComments(dayComment: $dayComment, mealsComments: $mealsComments, portionsComments: $portionsComments)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ConsultantCommentsImpl &&
(identical(other.dayComment, dayComment) ||
other.dayComment == dayComment) &&
const DeepCollectionEquality()
.equals(other._mealsComments, _mealsComments) &&
const DeepCollectionEquality()
.equals(other._portionsComments, _portionsComments));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
dayComment,
const DeepCollectionEquality().hash(_mealsComments),
const DeepCollectionEquality().hash(_portionsComments));
/// Create a copy of ConsultantComments
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ConsultantCommentsImplCopyWith<_$ConsultantCommentsImpl> get copyWith =>
__$$ConsultantCommentsImplCopyWithImpl<_$ConsultantCommentsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ConsultantCommentsImplToJson(
this,
);
}
}
abstract class _ConsultantComments implements ConsultantComments {
factory _ConsultantComments(
{required final String? dayComment,
required final List<MealComment> mealsComments,
required final List<PortionComment> portionsComments}) =
_$ConsultantCommentsImpl;
factory _ConsultantComments.fromJson(Map<String, dynamic> json) =
_$ConsultantCommentsImpl.fromJson;
@override
String? get dayComment;
@override
List<MealComment> get mealsComments;
@override
List<PortionComment> get portionsComments;
/// Create a copy of ConsultantComments
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ConsultantCommentsImplCopyWith<_$ConsultantCommentsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
MealComment _$MealCommentFromJson(Map<String, dynamic> json) {
return _MealComment.fromJson(json);
}
/// @nodoc
mixin _$MealComment {
String get comment => throw _privateConstructorUsedError;
int get typeNo => throw _privateConstructorUsedError;
/// Serializes this MealComment to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of MealComment
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MealCommentCopyWith<MealComment> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $MealCommentCopyWith<$Res> {
factory $MealCommentCopyWith(
MealComment value, $Res Function(MealComment) then) =
_$MealCommentCopyWithImpl<$Res, MealComment>;
@useResult
$Res call({String comment, int typeNo});
}
/// @nodoc
class _$MealCommentCopyWithImpl<$Res, $Val extends MealComment>
implements $MealCommentCopyWith<$Res> {
_$MealCommentCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of MealComment
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? comment = null,
Object? typeNo = null,
}) {
return _then(_value.copyWith(
comment: null == comment
? _value.comment
: comment // ignore: cast_nullable_to_non_nullable
as String,
typeNo: null == typeNo
? _value.typeNo
: typeNo // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$MealCommentImplCopyWith<$Res>
implements $MealCommentCopyWith<$Res> {
factory _$$MealCommentImplCopyWith(
_$MealCommentImpl value, $Res Function(_$MealCommentImpl) then) =
__$$MealCommentImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String comment, int typeNo});
}
/// @nodoc
class __$$MealCommentImplCopyWithImpl<$Res>
extends _$MealCommentCopyWithImpl<$Res, _$MealCommentImpl>
implements _$$MealCommentImplCopyWith<$Res> {
__$$MealCommentImplCopyWithImpl(
_$MealCommentImpl _value, $Res Function(_$MealCommentImpl) _then)
: super(_value, _then);
/// Create a copy of MealComment
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? comment = null,
Object? typeNo = null,
}) {
return _then(_$MealCommentImpl(
comment: null == comment
? _value.comment
: comment // ignore: cast_nullable_to_non_nullable
as String,
typeNo: null == typeNo
? _value.typeNo
: typeNo // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$MealCommentImpl implements _MealComment {
_$MealCommentImpl({required this.comment, required this.typeNo});
factory _$MealCommentImpl.fromJson(Map<String, dynamic> json) =>
_$$MealCommentImplFromJson(json);
@override
final String comment;
@override
final int typeNo;
@override
String toString() {
return 'MealComment(comment: $comment, typeNo: $typeNo)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$MealCommentImpl &&
(identical(other.comment, comment) || other.comment == comment) &&
(identical(other.typeNo, typeNo) || other.typeNo == typeNo));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, comment, typeNo);
/// Create a copy of MealComment
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$MealCommentImplCopyWith<_$MealCommentImpl> get copyWith =>
__$$MealCommentImplCopyWithImpl<_$MealCommentImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$MealCommentImplToJson(
this,
);
}
}
abstract class _MealComment implements MealComment {
factory _MealComment(
{required final String comment,
required final int typeNo}) = _$MealCommentImpl;
factory _MealComment.fromJson(Map<String, dynamic> json) =
_$MealCommentImpl.fromJson;
@override
String get comment;
@override
int get typeNo;
/// Create a copy of MealComment
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$MealCommentImplCopyWith<_$MealCommentImpl> get copyWith =>
throw _privateConstructorUsedError;
}
PortionComment _$PortionCommentFromJson(Map<String, dynamic> json) {
return _PortionComment.fromJson(json);
}
/// @nodoc
mixin _$PortionComment {
@JsonKey(name: 'id')
int get portionId => throw _privateConstructorUsedError;
String get comment => throw _privateConstructorUsedError;
/// Serializes this PortionComment to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of PortionComment
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PortionCommentCopyWith<PortionComment> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PortionCommentCopyWith<$Res> {
factory $PortionCommentCopyWith(
PortionComment value, $Res Function(PortionComment) then) =
_$PortionCommentCopyWithImpl<$Res, PortionComment>;
@useResult
$Res call({@JsonKey(name: 'id') int portionId, String comment});
}
/// @nodoc
class _$PortionCommentCopyWithImpl<$Res, $Val extends PortionComment>
implements $PortionCommentCopyWith<$Res> {
_$PortionCommentCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PortionComment
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? portionId = null,
Object? comment = null,
}) {
return _then(_value.copyWith(
portionId: null == portionId
? _value.portionId
: portionId // ignore: cast_nullable_to_non_nullable
as int,
comment: null == comment
? _value.comment
: comment // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$PortionCommentImplCopyWith<$Res>
implements $PortionCommentCopyWith<$Res> {
factory _$$PortionCommentImplCopyWith(_$PortionCommentImpl value,
$Res Function(_$PortionCommentImpl) then) =
__$$PortionCommentImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({@JsonKey(name: 'id') int portionId, String comment});
}
/// @nodoc
class __$$PortionCommentImplCopyWithImpl<$Res>
extends _$PortionCommentCopyWithImpl<$Res, _$PortionCommentImpl>
implements _$$PortionCommentImplCopyWith<$Res> {
__$$PortionCommentImplCopyWithImpl(
_$PortionCommentImpl _value, $Res Function(_$PortionCommentImpl) _then)
: super(_value, _then);
/// Create a copy of PortionComment
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? portionId = null,
Object? comment = null,
}) {
return _then(_$PortionCommentImpl(
portionId: null == portionId
? _value.portionId
: portionId // ignore: cast_nullable_to_non_nullable
as int,
comment: null == comment
? _value.comment
: comment // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PortionCommentImpl implements _PortionComment {
_$PortionCommentImpl(
{@JsonKey(name: 'id') required this.portionId, required this.comment});
factory _$PortionCommentImpl.fromJson(Map<String, dynamic> json) =>
_$$PortionCommentImplFromJson(json);
@override
@JsonKey(name: 'id')
final int portionId;
@override
final String comment;
@override
String toString() {
return 'PortionComment(portionId: $portionId, comment: $comment)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PortionCommentImpl &&
(identical(other.portionId, portionId) ||
other.portionId == portionId) &&
(identical(other.comment, comment) || other.comment == comment));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, portionId, comment);
/// Create a copy of PortionComment
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PortionCommentImplCopyWith<_$PortionCommentImpl> get copyWith =>
__$$PortionCommentImplCopyWithImpl<_$PortionCommentImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$PortionCommentImplToJson(
this,
);
}
}
abstract class _PortionComment implements PortionComment {
factory _PortionComment(
{@JsonKey(name: 'id') required final int portionId,
required final String comment}) = _$PortionCommentImpl;
factory _PortionComment.fromJson(Map<String, dynamic> json) =
_$PortionCommentImpl.fromJson;
@override
@JsonKey(name: 'id')
int get portionId;
@override
String get comment;
/// Create a copy of PortionComment
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PortionCommentImplCopyWith<_$PortionCommentImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,71 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'consultant_review_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ConsultantReviewModelImpl _$$ConsultantReviewModelImplFromJson(
Map<String, dynamic> json) =>
_$ConsultantReviewModelImpl(
planName: json['planName'] as String,
planDuration: (json['planDuration'] as num).toInt(),
currentPlanDay: (json['currentPlanDay'] as num).toInt(),
consultantComments: ConsultantComments.fromJson(
json['consultantComments'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$ConsultantReviewModelImplToJson(
_$ConsultantReviewModelImpl instance) =>
<String, dynamic>{
'planName': instance.planName,
'planDuration': instance.planDuration,
'currentPlanDay': instance.currentPlanDay,
'consultantComments': instance.consultantComments,
};
_$ConsultantCommentsImpl _$$ConsultantCommentsImplFromJson(
Map<String, dynamic> json) =>
_$ConsultantCommentsImpl(
dayComment: json['dayComment'] as String?,
mealsComments: (json['mealsComments'] as List<dynamic>)
.map((e) => MealComment.fromJson(e as Map<String, dynamic>))
.toList(),
portionsComments: (json['portionsComments'] as List<dynamic>)
.map((e) => PortionComment.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$ConsultantCommentsImplToJson(
_$ConsultantCommentsImpl instance) =>
<String, dynamic>{
'dayComment': instance.dayComment,
'mealsComments': instance.mealsComments,
'portionsComments': instance.portionsComments,
};
_$MealCommentImpl _$$MealCommentImplFromJson(Map<String, dynamic> json) =>
_$MealCommentImpl(
comment: json['comment'] as String,
typeNo: (json['typeNo'] as num).toInt(),
);
Map<String, dynamic> _$$MealCommentImplToJson(_$MealCommentImpl instance) =>
<String, dynamic>{
'comment': instance.comment,
'typeNo': instance.typeNo,
};
_$PortionCommentImpl _$$PortionCommentImplFromJson(Map<String, dynamic> json) =>
_$PortionCommentImpl(
portionId: (json['id'] as num).toInt(),
comment: json['comment'] as String,
);
Map<String, dynamic> _$$PortionCommentImplToJson(
_$PortionCommentImpl instance) =>
<String, dynamic>{
'id': instance.portionId,
'comment': instance.comment,
};

View File

@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
@@ -6,7 +7,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class MealButtonWidget extends StatefulWidget {
final int mealNumber;
const MealButtonWidget({
super.key,
required this.mealNumber,
@@ -22,6 +22,18 @@ class _MealButtonWidgetState extends State<MealButtonWidget> {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
final consultantComment = state.consultantReview?.consultantComments.mealsComments.firstWhereOrNull(
(element) => element.typeNo == widget.mealNumber,
);
final Color bgColor;
if (state.selectedMeal == widget.mealNumber) {
bgColor = appTheme.blueA400;
} else if (consultantComment != null) {
bgColor = appTheme.commentBg;
} else {
bgColor = const Color(0xFFF1F2F6);
}
return InkWell(
onTap: () {
context.read<DiaryCubit>().setSelectedMeal(widget.mealNumber);
@@ -32,7 +44,7 @@ class _MealButtonWidgetState extends State<MealButtonWidget> {
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: state.selectedMeal == widget.mealNumber ? appTheme.blueA400 : const Color(0xFFF1F2F6),
color: bgColor,
),
child: Center(
child: Text(

View File

@@ -1,3 +1,4 @@
import 'package:collection/collection.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/diary/presentation/bloc/diary_cubit.dart';
@@ -81,8 +82,10 @@ class _MealWidgetState extends State<MealWidget> {
BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
final mealsComments = state.consultantReview?.consultantComments.mealsComments;
if (mealsComments != null && (state.selectedMeal - 1 < mealsComments.length)) {
final consultantCommentOfMeal = state.consultantReview?.consultantComments.mealsComments.firstWhereOrNull(
(element) => element.typeNo == state.selectedMeal,
);
if (consultantCommentOfMeal != null) {
return Column(
children: [
Text(
@@ -90,7 +93,7 @@ class _MealWidgetState extends State<MealWidget> {
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 12),
CommentContainer(comment: mealsComments[state.selectedMeal - 1].comment),
CommentContainer(comment: consultantCommentOfMeal.comment),
],
);
}

View File

@@ -11,7 +11,7 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
class DiaryRepository {
final DiaryService diaryService;
final StreamController<String> _diaryForceFetchController = StreamController<String>();
final StreamController<String> _diaryForceFetchController = StreamController<String>.broadcast();
Stream<String> get forceFetchStream => _diaryForceFetchController.stream;

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'plan_pagination_model.freezed.dart';
@@ -70,10 +71,10 @@ class PacketOptions with _$PacketOptions {
class PlanType with _$PlanType {
const PlanType._();
const factory PlanType({
@JsonKey(name: "fit") required bool? fit,
@JsonKey(name: "slim") required bool? slim,
@JsonKey(name: "behavior") required bool? behavior,
@JsonKey(name: "wellness") required bool? wellness,
@JsonKey(name: "fit", fromJson: parseBool) required bool? fit,
@JsonKey(name: "slim", fromJson: parseBool) required bool? slim,
@JsonKey(name: "behavior", fromJson: parseBool) required bool? behavior,
@JsonKey(name: "wellness", fromJson: parseBool) required bool? wellness,
}) = _PlanType;
factory PlanType.fromJson(Map<String, dynamic> json) => _$PlanTypeFromJson(json);

View File

@@ -118,7 +118,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
if (!context.mounted) return;
showBottomSheetWithBlur(
context: context,
child: SuccessPublishedDietBottomSheet(title: widget.diet.name),
child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name),
);
await context.read<MyDietsCubit>().loadAllDiets();
} else {

View File

@@ -5,7 +5,6 @@ part 'card_model.g.dart';
@freezed
class CardModel with _$CardModel {
factory CardModel({
required int id,
@JsonKey(name: 'bank_pan') required String cardNumber,
@@ -16,7 +15,7 @@ class CardModel with _$CardModel {
}
bool parseBool(json) {
if (json == 1 || json == true) {
if (json == 1 || json == true || json == '1') {
return true;
}
return false;

View File

@@ -28,20 +28,19 @@ class UserCardsCubit extends Cubit<UserCardsState> {
emit(state.copyWith(cards: cards, state: BaseState.success));
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(state.copyWith(state: BaseState.error, error: e.toString()));
emit(state.copyWith(state: BaseState.error, error: e.toString()));
}
}
Future<void> markAsDefault({required int id}) async {
final oldCards = [...state.cards];
if (isClosed) return;
final cards =
oldCards.map((card) {
if (card.id == id) {
return card.copyWith(isDefault: true);
}
return card.copyWith(isDefault: false);
}).toList();
final cards = oldCards.map((card) {
if (card.id == id) {
return card.copyWith(isDefault: true);
}
return card.copyWith(isDefault: false);
}).toList();
emit(state.copyWith(cards: cards));
EasyDebounce.debounce('markAsDefault', Duration(milliseconds: id), () async {
@@ -66,4 +65,14 @@ class UserCardsCubit extends Cubit<UserCardsState> {
log(e.toString(), stackTrace: st);
}
}
Future<bool> collectCharge({required int chosenPlaId}) async {
try {
await cardsRepository.collectCharge(chosenPlaId: chosenPlaId);
return true;
} catch (e, st) {
log(e.toString(), stackTrace: st);
}
return false;
}
}

View File

@@ -1,7 +1,11 @@
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/enums/base_state.dart';
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_handler_bar.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/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
@@ -11,104 +15,221 @@ import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart';
class CardSettingsScreen extends StatelessWidget {
static const path = '/card-settings-screen';
static const name = 'cardSettingsScreen';
const CardSettingsScreen({super.key});
const CardSettingsScreen({super.key, this.chosenPlanId});
final int? chosenPlanId;
@override
Widget build(BuildContext context) {
return BlocProvider(create: (context) => sl<UserCardsCubit>()..getCards(), child: const CardsSettingsContent());
return BlocProvider(
create: (context) => sl<UserCardsCubit>()..getCards(),
child: CardsSettingsContent(
chosenPlanId: chosenPlanId,
));
}
}
class CardsSettingsContent extends StatelessWidget {
const CardsSettingsContent({super.key});
const CardsSettingsContent({super.key, this.chosenPlanId});
final int? chosenPlanId;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
appBar: AppBar(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
title: Text(s.my_cards),
centerTitle: true,
),
body: Column(
children: [
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text('Привязанные карты', style: TextStyle(color: Color(0xFF323F4B), fontSize: 18, fontWeight: FontWeight.w600, height: 1)),
return BlocBuilder<UserCardsCubit, UserCardsState>(
builder: (context, state) {
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
appBar: AppBar(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
title: Text(s.my_cards),
centerTitle: true,
),
body: Column(
children: [
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text('Привязанные карты', style: TextStyle(color: Color(0xFF323F4B), fontSize: 18, fontWeight: FontWeight.w600, height: 1)),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Builder(
builder: (context) {
final defaultCardNumber = state.cards.firstWhereOrNull((element) => element.isDefault)?.cardNumber;
if (defaultCardNumber != null) {
return Text(
'Выплату будут производиться\nна карту ${defaultCardNumber.substring(defaultCardNumber.length - 7)}',
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500, height: 1),
);
}
return Text(s.no_default_cards, style: TextStyles.textMMedium.copyWith(color: appTheme.black));
},
),
),
const SizedBox(height: 16),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Builder(
builder: (context) {
switch (state.state) {
case BaseState.error:
return SizedBox(
width: MediaQuery.sizeOf(context).width,
child: Center(
child: DataErrorWidget(
onReload: () async {
context.read<UserCardsCubit>().getCards();
},
),
),
);
case BaseState.success:
return Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
spacing: 12,
children: List.generate(state.cards.length + 1, (index) {
if (index < state.cards.length) {
final cardsNumber = state.cards[index].cardNumber.substring(state.cards[index].cardNumber.length - 7);
return CardWidget(isSelected: state.cards[index].isDefault, cardId: state.cards[index].id, cardNumber: cardsNumber);
}
return const CardWidget(enabledAddCard: true);
}),
),
);
default:
return SizedBox(width: MediaQuery.sizeOf(context).width, height: 200, child: const Center(child: Loader()));
}
},
),
),
const SizedBox(height: 16),
],
),
const SizedBox(height: 8),
),
if (chosenPlanId != null && state.cards.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: BlocBuilder<UserCardsCubit, UserCardsState>(
builder: (context, state) {
child: CustomOutlinedButton(
text: s.collectCharge,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
onPressed: () async {
final defaultCardNumber = state.cards.firstWhereOrNull((element) => element.isDefault)?.cardNumber;
if (defaultCardNumber != null) {
return Text(
'Выплату будут производиться\nна карту ${defaultCardNumber.substring(defaultCardNumber.length - 7)}',
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500, height: 1),
final result = await context.read<UserCardsCubit>().collectCharge(chosenPlaId: chosenPlanId!);
if (context.mounted) {
if (!result) {
showBottomSheetWithBlur(
context: context,
child: Column(
children: [
const SizedBox(
height: 8,
),
const BottomSheetHandlerBar(),
const SizedBox(
height: 20,
),
Text(
s.chargeCollectionFailed,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(
height: 8,
),
Text(
s.contactSupport,
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
),
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: CustomOutlinedButton(
onPressed: () {
AppRouter.navigatorKey.currentContext?.pop();
},
text: s.ok,
height: 52,
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
),
],
),
);
return;
}
showBottomSheetWithBlur(
context: context,
child: Column(
children: [
const SizedBox(
height: 8,
),
const BottomSheetHandlerBar(),
const SizedBox(
height: 20,
),
Text(
s.chargeApproved,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(
height: 8,
),
Text(
'${s.moneySentToCard} ${defaultCardNumber?.substring(defaultCardNumber.length - 7)}',
textAlign: TextAlign.center,
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
),
const SizedBox(
height: 8,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: CustomOutlinedButton(
onPressed: () {
AppRouter.navigatorKey.currentContext?.pop();
},
text: s.ok,
height: 52,
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
),
],
),
);
}
return Text(s.no_default_cards, style: TextStyles.textMMedium.copyWith(color: appTheme.black));
},
),
),
const SizedBox(height: 16),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: BlocBuilder<UserCardsCubit, UserCardsState>(
builder: (context, state) {
switch (state.state) {
case BaseState.error:
return SizedBox(
width: MediaQuery.sizeOf(context).width,
child: Center(
child: DataErrorWidget(
onReload: () async {
context.read<UserCardsCubit>().getCards();
},
),
),
);
case BaseState.success:
return Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
spacing: 12,
children: List.generate(state.cards.length + 1, (index) {
if (index < state.cards.length) {
final cardsNumber = state.cards[index].cardNumber.substring(state.cards[index].cardNumber.length - 7);
return CardWidget(isSelected: state.cards[index].isDefault, cardId: state.cards[index].id, cardNumber: cardsNumber);
}
return const CardWidget(enabledAddCard: true);
}),
),
);
default:
return SizedBox(width: MediaQuery.sizeOf(context).width, height: 200, child: const Center(child: Loader()));
}
},
),
),
const SizedBox(height: 16),
],
),
)
]
],
),
],
),
);
},
);
}
}

View File

@@ -36,4 +36,12 @@ class CardsRepository {
rethrow;
}
}
Future<void> collectCharge({required int chosenPlaId}) async {
try {
await cardsService.collectCharge(chosenPlanId: chosenPlaId);
} catch (e) {
rethrow;
}
}
}

View File

@@ -24,5 +24,10 @@ abstract class CardsService {
Future<void> deleteCard({
@Path('id') required int id,
});
@POST('/api/chosen_plans/{id}/take_payin')
Future<void> collectCharge({
@Path('id') required int chosenPlanId,
});
}

View File

@@ -8,6 +8,7 @@ import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart';
import 'package:eatvio_app/features/order_info/presentation/widgets/collect_charge_banner.dart';
import 'package:eatvio_app/features/order_info/presentation/widgets/payment_banner.dart';
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_info.dart';
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_props.dart';
@@ -61,7 +62,7 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
builder: (context, state) {
final plan = state.chosenPlan;
if (state.isLoading) {
Scaffold(
return Scaffold(
appBar: AppBar(
title: Text(
s.infoAboutOrder,
@@ -113,7 +114,7 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
planStatus = PlanStatusValues.paid;
} else if (widget.successfulPayment == false) {
planStatus = PlanStatusValues.paymentFailed;
} else if (plan.chosenPlanState == ChosenPlanStateEnum.statePaid) {
} else if (plan.chosenPlanState.isStateAfterPaidState()) {
planStatus = PlanStatusValues.paid;
} else {
planStatus = PlanStatusValues.pendingPayment;
@@ -134,6 +135,12 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
body: SingleChildScrollView(
child: Column(
children: [
if (plan.chosenPlanState == ChosenPlanStateEnum.stateDone) ...[
CollectChargeBanner(
chosenPlan: plan,
price: chosenOption?.price ?? 0,
)
],
if (widget.successfulPayment != null) ...[
const SizedBox(height: 8),
PaymentBanner(
@@ -148,7 +155,10 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
backgroundColor: appTheme.whiteA700,
),
const SizedBox(height: 8),
PlanProps(planDuration: plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: planStatus),
PlanProps(
planDuration: plan.plan.duration,
planPrice: chosenOption?.price ?? 0,
planStatus: planStatus),
const SizedBox(height: 8),
PlanInfo(
title: plan.plan.name,

View File

@@ -0,0 +1,72 @@
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_outlined_button.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class CollectChargeBanner extends StatelessWidget {
const CollectChargeBanner({super.key, required this.chosenPlan, required this.price});
final ChosenPlanModel chosenPlan;
final double price;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return MainBackgroundBouble(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Stack(
children: [
Positioned(
left: 0,
top: 0,
bottom: 0,
child: Container(
width: 4,
decoration: BoxDecoration(
color: appTheme.blueA400, borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20))),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(right: 80),
child: Text(
'${s.congratsYouHaveFinishedPlan} «${chosenPlan.plan.name}»',
style: TextStyles.textMSemibold.copyWith(
color: appTheme.black,
),
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.only(right: 80),
child: Text(
'${s.userConfirmedPlanCompletion} $price₽',
style: TextStyles.textSMedium.copyWith(
color: appTheme.black,
),
),
),
const SizedBox(height: 12),
CustomOutlinedButton(
text: s.collectCharge,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
onPressed: () async {
GoRouter.of(context).push(CardSettingsScreen.path, extra: chosenPlan.id);
},
)
],
),
)
],
),
);
}
}

View File

@@ -14,6 +14,7 @@ class PaymentBanner extends StatefulWidget {
const PaymentBanner({
super.key,
required this.successfulPayment,
});
final bool successfulPayment;

View File

@@ -237,6 +237,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
'chatScreen',
extra: {
'chatId': chosenPlan.chatId,
'sendInitialMessage': true,
},
);
},

View File

@@ -33,6 +33,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
"addAtLeastOneItem": MessageLookupByLibrary.simpleMessage(
"Добавьте хотя бы одину опцию",
),
"add_card": MessageLookupByLibrary.simpleMessage("Привязать карту"),
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
@@ -55,6 +58,9 @@ class MessageLookup extends MessageLookupByLibrary {
"another_form_of_stage": MessageLookupByLibrary.simpleMessage("этапа"),
"answer": MessageLookupByLibrary.simpleMessage("Ответить"),
"archived": MessageLookupByLibrary.simpleMessage("В архиве"),
"atLeast3Symbols": MessageLookupByLibrary.simpleMessage(
"Минимум 3 символа",
),
"authentificate": MessageLookupByLibrary.simpleMessage("Войти"),
"average_pressure": MessageLookupByLibrary.simpleMessage(
"Средние показатели давления:",
@@ -86,6 +92,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
),
"carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"),
"chargeApproved": MessageLookupByLibrary.simpleMessage("Выплата одобрена"),
"chargeCollectionFailed": MessageLookupByLibrary.simpleMessage(
"Не удалось оформить выплату",
),
"chat": MessageLookupByLibrary.simpleMessage("Чат"),
"check_out_video_recipe": MessageLookupByLibrary.simpleMessage(
"Посмотреть видео рецепт",
@@ -105,6 +115,7 @@ class MessageLookup extends MessageLookupByLibrary {
"choose_start_diet_date": MessageLookupByLibrary.simpleMessage(
"Выберите дату старты диеты. Она отобразиться у Вас в дневнике",
),
"chosePlanType": MessageLookupByLibrary.simpleMessage("Выберите тип плана"),
"chose_plan_start_date_2": MessageLookupByLibrary.simpleMessage(
"Выберите дату старты плана. Она отобразиться в дневнике пользователя",
),
@@ -119,6 +130,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"city": MessageLookupByLibrary.simpleMessage("Город"),
"city_of_living": MessageLookupByLibrary.simpleMessage("Город проживания"),
"collectCharge": MessageLookupByLibrary.simpleMessage("Получить выплату"),
"comfort": MessageLookupByLibrary.simpleMessage("Комфорт"),
"comment": MessageLookupByLibrary.simpleMessage("комментарий"),
"commentForMeal": MessageLookupByLibrary.simpleMessage(
@@ -138,9 +150,15 @@ class MessageLookup extends MessageLookupByLibrary {
"Ваше обращение принято! Мы рассмотрим его в ближайшее время и примем необходимые меры. Спасибо за вашу бдительность и помощь в улучшении нашего сервиса.",
),
"confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"),
"congratsYouHaveFinishedPlan": MessageLookupByLibrary.simpleMessage(
"Поздравляем, Вы закончили план",
),
"consultations_count": MessageLookupByLibrary.simpleMessage(
"Кол-во проведенных консулдьтаций",
),
"contactSupport": MessageLookupByLibrary.simpleMessage(
"Обратитесь в поддержку",
),
"continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"),
"continue_": MessageLookupByLibrary.simpleMessage("Продолжить"),
"copied": MessageLookupByLibrary.simpleMessage("cкопирован"),
@@ -385,6 +403,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму.",
),
"milliliter": MessageLookupByLibrary.simpleMessage("мл"),
"minPlanPrice": MessageLookupByLibrary.simpleMessage(
"Минимальная сумма 1200₽",
),
"minimal_customer_price": MessageLookupByLibrary.simpleMessage(
"Минимальная цена для покупателя: 1200₽",
),
@@ -396,6 +417,9 @@ class MessageLookup extends MessageLookupByLibrary {
"misrepresentation_description": MessageLookupByLibrary.simpleMessage(
"Новости, которые могут вызвать широкий резонанс или несут угрозу, распространяемые с целью введения людей в заблуждение.",
),
"moneySentToCard": MessageLookupByLibrary.simpleMessage(
"Средства отправлены на карту",
),
"more_about_order": MessageLookupByLibrary.simpleMessage(
"Подробнее о заказе",
),
@@ -631,6 +655,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Верхние показатели давления:",
),
"upper": MessageLookupByLibrary.simpleMessage("Верхнее"),
"userConfirmedPlanCompletion": MessageLookupByLibrary.simpleMessage(
"Пользователь подтвердил прохождения плана. В ближайшее время Вы получите",
),
"user_authorization": MessageLookupByLibrary.simpleMessage(
"Аутентификация",
),

View File

@@ -3074,6 +3074,116 @@ class S {
args: [],
);
}
/// `Поздравляем, Вы закончили план`
String get congratsYouHaveFinishedPlan {
return Intl.message(
'Поздравляем, Вы закончили план',
name: 'congratsYouHaveFinishedPlan',
desc: '',
args: [],
);
}
/// `Пользователь подтвердил прохождения плана. В ближайшее время Вы получите`
String get userConfirmedPlanCompletion {
return Intl.message(
'Пользователь подтвердил прохождения плана. В ближайшее время Вы получите',
name: 'userConfirmedPlanCompletion',
desc: '',
args: [],
);
}
/// `Получить выплату`
String get collectCharge {
return Intl.message(
'Получить выплату',
name: 'collectCharge',
desc: '',
args: [],
);
}
/// `Выплата одобрена`
String get chargeApproved {
return Intl.message(
'Выплата одобрена',
name: 'chargeApproved',
desc: '',
args: [],
);
}
/// `Средства отправлены на карту`
String get moneySentToCard {
return Intl.message(
'Средства отправлены на карту',
name: 'moneySentToCard',
desc: '',
args: [],
);
}
/// `Не удалось оформить выплату`
String get chargeCollectionFailed {
return Intl.message(
'Не удалось оформить выплату',
name: 'chargeCollectionFailed',
desc: '',
args: [],
);
}
/// `Обратитесь в поддержку`
String get contactSupport {
return Intl.message(
'Обратитесь в поддержку',
name: 'contactSupport',
desc: '',
args: [],
);
}
/// `Минимум 3 символа`
String get atLeast3Symbols {
return Intl.message(
'Минимум 3 символа',
name: 'atLeast3Symbols',
desc: '',
args: [],
);
}
/// `Выберите тип плана`
String get chosePlanType {
return Intl.message(
'Выберите тип плана',
name: 'chosePlanType',
desc: '',
args: [],
);
}
/// `Минимальная сумма 1200₽`
String get minPlanPrice {
return Intl.message(
'Минимальная сумма 1200₽',
name: 'minPlanPrice',
desc: '',
args: [],
);
}
/// `Добавьте хотя бы одину опцию`
String get addAtLeastOneItem {
return Intl.message(
'Добавьте хотя бы одину опцию',
name: 'addAtLeastOneItem',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@@ -370,6 +370,17 @@
"orderCanceled": "Заказ отменен",
"couldNotCancelOrder": "Не удалось отменить заказ",
"dayComment": "Комментарий ко всему дню",
"mealComment": "Комментарий к приему пищи"
"mealComment": "Комментарий к приему пищи",
"congratsYouHaveFinishedPlan": "Поздравляем, Вы закончили план",
"userConfirmedPlanCompletion": "Пользователь подтвердил прохождения плана. В ближайшее время Вы получите",
"collectCharge": "Получить выплату",
"chargeApproved": "Выплата одобрена",
"moneySentToCard": "Средства отправлены на карту",
"chargeCollectionFailed": "Не удалось оформить выплату",
"contactSupport": "Обратитесь в поддержку",
"atLeast3Symbols": "Минимум 3 символа",
"chosePlanType": "Выберите тип плана",
"minPlanPrice": "Минимальная сумма 1200₽",
"addAtLeastOneItem": "Добавьте хотя бы одину опцию"
}