diff --git a/.DS_Store b/.DS_Store
index 1ea62397..097b747a 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/assets/icons/star.svg b/assets/icons/star.svg
new file mode 100644
index 00000000..6a077d0e
--- /dev/null
+++ b/assets/icons/star.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart
index 930ab4d0..e596c98e 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.dart
@@ -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;
final chatId = data['chatId'] as String;
- return ChatScreen(chatId: chatId);
+ final sendInitialMessage = data['sendInitialMessage'] as bool;
+ return ChatScreen(chatId: chatId, sendInitialMessage: sendInitialMessage,);
},
),
GoRoute(
diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart
index 8794ce95..ebd8d539 100644
--- a/lib/core/locator_service.dart
+++ b/lib/core/locator_service.dart
@@ -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 init() async {
sl.registerFactory(() => BlogPostCreateCubit(blogPostRepository: sl()));
sl.registerFactory(() => SubCubit(profileRepository: sl()));
sl.registerFactory(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
- sl.registerFactory(() => CreatePlanCubit());
+ sl.registerFactory(() => CreatePlanCubit(createPlanRepository: sl()));
sl.registerFactory(() => FeedProfileCubit(profileRepository: sl()));
sl.registerFactory(() => AboutPlanCubit(planRepository: sl()));
sl.registerFactory(() => CreateRecipeCubit(recipeRepository: sl()));
@@ -158,6 +160,7 @@ Future init() async {
sl.registerLazySingleton(() => CardsRepository(cardsService: sl()));
sl.registerLazySingleton(() => ConsultationsRepository(consultationService: sl()));
sl.registerLazySingleton(() => CancelPlanRepository(cancelService: sl()));
+ sl.registerLazySingleton(() => CreatePlanRepository(createPlanService: sl()));
// Services
sl.registerLazySingleton(() => ChatService(sl()));
@@ -184,6 +187,7 @@ Future init() async {
sl.registerLazySingleton(() => CardsService(sl()));
sl.registerLazySingleton(() => ConsultationService(sl()));
sl.registerLazySingleton(() => CancelPlanService(sl()));
+ sl.registerLazySingleton(() => CreatePlanService(sl()));
// External
sl.registerLazySingleton(
diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart
index 9bed83fd..aa4e30a6 100644
--- a/lib/core/theme/theme_helper.dart
+++ b/lib/core/theme/theme_helper.dart
@@ -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);
diff --git a/lib/core/widgets/custom_text_form_field.dart b/lib/core/widgets/custom_text_form_field.dart
index aedfda0f..041bee70 100644
--- a/lib/core/widgets/custom_text_form_field.dart
+++ b/lib/core/widgets/custom_text_form_field.dart
@@ -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,
diff --git a/lib/core/widgets/success_published_diet_bottom_sheet.dart b/lib/core/widgets/success_published_diet_bottom_sheet.dart
index 0145d041..223ed220 100644
--- a/lib/core/widgets/success_published_diet_bottom_sheet.dart
+++ b/lib/core/widgets/success_published_diet_bottom_sheet.dart
@@ -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(
diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart
index bb8e6d9f..bd53c0ac 100644
--- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart
+++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart
@@ -72,7 +72,7 @@ class ChatRoomCubit extends Cubit {
}
}
- 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 {
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();
diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart
index 6cf7c9da..250c86fe 100644
--- a/lib/features/chat/presentation/pages/chat_screen.dart
+++ b/lib/features/chat/presentation/pages/chat_screen.dart
@@ -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 createState() => _ChatScreenState();
@@ -46,7 +48,7 @@ class _ChatScreenState extends State {
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 {
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 {
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 {
),
),
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(),
+ ),
+ ),
],
],
),
diff --git a/lib/features/chat/presentation/widgets/notification_banner.dart b/lib/features/chat/presentation/widgets/notification_banner.dart
index 0059f7cc..d4eaf577 100644
--- a/lib/features/chat/presentation/widgets/notification_banner.dart
+++ b/lib/features/chat/presentation/widgets/notification_banner.dart
@@ -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 createState() => _NotificationBannerState();
@@ -31,96 +35,145 @@ class _NotificationBannerState extends State {
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();
+ 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().state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
+ setState(() {
+ isLoading = true;
+ });
+ final paymentLink = await context.read().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();
- 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().state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
- setState(() {
- isLoading = true;
- });
- final paymentLink = await context.read().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 }
diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart
index a8d2e019..48d1d27e 100644
--- a/lib/features/create_diet/presentation/create_diet_screen.dart
+++ b/lib/features/create_diet/presentation/create_diet_screen.dart
@@ -72,7 +72,7 @@ class _CreateDietScreenContentState extends State {
showBottomSheetWithBlur(
userRootNavigator: true,
context: context,
- child: SuccessPublishedDietBottomSheet(
+ child: SuccessPublishedDietOrPlanBottomSheet(
title: state.dietData.name ?? 'Диета',
),
);
@@ -83,7 +83,7 @@ class _CreateDietScreenContentState extends State {
context.pop();
showBottomSheetWithBlur(
context: context,
- child: SuccessPublishedDietBottomSheet(
+ child: SuccessPublishedDietOrPlanBottomSheet(
title: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
),
);
@@ -193,8 +193,8 @@ class _CreateDietScreenContentState extends State {
context.read().setDietState(DietState.draft);
if (widget.diet != null) {
final result = await context.read().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}');
diff --git a/lib/features/create_plan/models/create_plan_model.dart b/lib/features/create_plan/models/create_plan_model.dart
index 75187284..4b85ffba 100644
--- a/lib/features/create_plan/models/create_plan_model.dart
+++ b/lib/features/create_plan/models/create_plan_model.dart
@@ -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 type,
XFile? image,
required List options,
@@ -25,9 +25,12 @@ class CreatePlanModel with _$CreatePlanModel {
Future