diff --git a/assets/icons/edit.svg b/assets/icons/edit.svg
new file mode 100644
index 00000000..2f5d6efd
--- /dev/null
+++ b/assets/icons/edit.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart
index 26776d2b..58ccde6e 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.dart
@@ -33,7 +33,7 @@ import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.
import 'package:eatvio_app/features/my_finance/presentation/pages/consultant_finance_screen.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
-import 'package:eatvio_app/features/notifications/presentation/pages/notifications_screen.dart';
+import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
@@ -99,8 +99,8 @@ class AppRouter {
GoRoute(path: '/feed-screen', name: 'feedScreen', pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen())),
GoRoute(path: '/diary-screen', name: 'diaryScreen', pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen())),
GoRoute(
- path: '/consultations-screen',
- name: 'consultationsScreen',
+ path: ConsultationsScreen.path,
+ name: ConsultationsScreen.name,
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen())),
GoRoute(path: '/chat-select-screen', name: 'chatSelectScreen', pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen())),
GoRoute(
@@ -256,29 +256,41 @@ class AppRouter {
GoRoute(path: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
GoRoute(
- path: InfoByOrderScreen.path,
- name: InfoByOrderScreen.name,
- builder: (context, state) {
- final data = state.extra as Map;
- final planId = data['planId'] as int;
- final successfulPayment = data['successfulPayment'] as bool?;
- return InfoByOrderScreen(
- planId: planId,
- successfulPayment: successfulPayment,
- );
- }),
+ path: InfoByOrderScreen.path,
+ name: InfoByOrderScreen.name,
+ builder: (context, state) {
+ final data = state.extra as Map;
+ final planId = data['planId'] as int;
+ final successfulPayment = data['successfulPayment'] as bool?;
+ return InfoByOrderScreen(
+ planId: planId,
+ successfulPayment: successfulPayment,
+ );
+ },
+ ),
GoRoute(
- path: PaymentScreen.path,
- name: PaymentScreen.name,
- builder: (context, state) {
- final data = state.extra as Map;
- final planId = data['planId'] as int;
- final url = data['url'] as String;
- return PaymentScreen(
- url: url,
- planId: planId,
- );
- }),
+ path: PaymentScreen.path,
+ name: PaymentScreen.name,
+ builder: (context, state) {
+ final data = state.extra as Map;
+ final planId = data['planId'] as int;
+ final url = data['url'] as String;
+ return PaymentScreen(
+ url: url,
+ planId: planId,
+ );
+ },
+ ),
+ GoRoute(
+ path: UserDiaryScreen.path,
+ name: UserDiaryScreen.name,
+ builder: (context, state) {
+ final diaryId = state.extra as int;
+ return UserDiaryScreen(
+ diaryId: diaryId,
+ );
+ },
+ ),
],
);
}
diff --git a/lib/core/const.dart b/lib/core/const.dart
index 31c47e9c..4f38a54e 100644
--- a/lib/core/const.dart
+++ b/lib/core/const.dart
@@ -1,5 +1,5 @@
-String baseUrl = 'https://eatvio.ru';
-// String baseUrl = 'https://dev2.eatvio.com';
+// String baseUrl = 'https://eatvio.ru';
+String baseUrl = 'https://dev2.eatvio.com';
const List durationValues = [7, 14, 21];
diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart
index b00a6d29..8794ce95 100644
--- a/lib/core/locator_service.dart
+++ b/lib/core/locator_service.dart
@@ -6,20 +6,27 @@ import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
+import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
+import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
+import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
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/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';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
-import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
+import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
@@ -123,6 +130,9 @@ Future init() async {
sl.registerFactory(() => ComplainingCubit(repository: sl()));
sl.registerFactory(() => UserCardsCubit(cardsRepository: sl()));
sl.registerFactory(() => OrderInfoCubit(chatRepository: sl()));
+ sl.registerFactory(() => ConsultationsCubit(consultationsRepository: sl()));
+ sl.registerFactory(() => ConsultantCommentsCubit(diaryRepository: sl()));
+ sl.registerFactory(() => CancelPlanCubit(cancelPlanRepository: sl()));
// Repository
sl.registerLazySingleton(() => ChatRepository(chatService: sl()));
@@ -146,6 +156,8 @@ Future init() async {
sl.registerLazySingleton(() => ComplainingRepository(service: sl()));
sl.registerLazySingleton(() => AuthRepository(authService: sl()));
sl.registerLazySingleton(() => CardsRepository(cardsService: sl()));
+ sl.registerLazySingleton(() => ConsultationsRepository(consultationService: sl()));
+ sl.registerLazySingleton(() => CancelPlanRepository(cancelService: sl()));
// Services
sl.registerLazySingleton(() => ChatService(sl()));
@@ -170,6 +182,8 @@ Future init() async {
sl.registerLazySingleton(() => ComplainingService(sl()));
sl.registerLazySingleton(() => AuthService(sl()));
sl.registerLazySingleton(() => CardsService(sl()));
+ sl.registerLazySingleton(() => ConsultationService(sl()));
+ sl.registerLazySingleton(() => CancelPlanService(sl()));
// External
sl.registerLazySingleton(
diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart
index a919944d..9bed83fd 100644
--- a/lib/core/theme/theme_helper.dart
+++ b/lib/core/theme/theme_helper.dart
@@ -185,6 +185,7 @@ class PrimaryColors {
Color get red => const Color(0xFFCD4141);
Color get strongRed => const Color(0xFFD01717);
+ Color get lightRed => const Color(0XFFD01717).withValues(alpha: 0.1);
Color get transparentRed => const Color(0xFFCD4141).withValues(alpha: 0.75);
@@ -196,6 +197,10 @@ class PrimaryColors {
Color get blueGray => const Color(0xFF809CB9);
Color get dialogShadow => const Color.fromARGB(125, 0, 0, 0);
+
+ Color get darkGray => const Color(0XFFBBBCC0);
+
+ Color get commentBg => const Color(0XFFFFFDD8);
}
PrimaryColors get appTheme => ThemeHelper().themeColor();
diff --git a/lib/core/websockets/ws_manager.dart b/lib/core/websockets/ws_manager.dart
index b22a2048..3a64ff14 100644
--- a/lib/core/websockets/ws_manager.dart
+++ b/lib/core/websockets/ws_manager.dart
@@ -20,7 +20,7 @@ class WsManager {
Stream? get newMessagesStream => _privateMessagesChannel?.bind('App\\Events\\ChatMessageUpdated');
// TODO: change name of event to a right one
- Stream? get newNotificationsStream => _privateNotificationChannel?.bind('App\\Events\\ChatMessageUpdated');
+ Stream? get newNotificationsStream => _privateNotificationChannel?.bind('Illuminate\\Notifications\\Events\\BroadcastNotificationCreated');
Stream? get directChatStream => _privateDirectChatChannel?.bind('App\\Events\\ChatMessageUpdated');
diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart
deleted file mode 100644
index e193a23c..00000000
--- a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart
+++ /dev/null
@@ -1,25 +0,0 @@
-import 'package:eatvio_app/core/websockets/ws_manager.dart';
-import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class NotificationsCubit extends Cubit {
- int _notificationsCount = 0;
- List notifications = [];
- final WsManager wsManager;
- NotificationsCubit({required this.wsManager}) : super(const NotificationsState.empty()) {
- wsManager.newNotificationsStream?.listen((event) {
- incrementCounter(event.data);
- });
- }
-
- incrementCounter(String data) {
- _notificationsCount++;
- notifications.add(data);
- emit(
- NotificationsState.loaded(
- notifications: notifications,
- notificationsCount: _notificationsCount,
- ),
- );
- }
-}
diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
index 57a20923..5fdf10c0 100644
--- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
+++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
@@ -1,7 +1,7 @@
import 'package:badges/badges.dart' as badges;
import 'package:eatvio_app/core/utils/image_constant.dart';
-import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
-import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
+import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
+import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
diff --git a/lib/features/cancel_plan_bottom_sheet/cancel_plan_bottom_sheet.dart b/lib/features/cancel_plan_bottom_sheet/cancel_plan_bottom_sheet.dart
new file mode 100644
index 00000000..ceec1294
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/cancel_plan_bottom_sheet.dart
@@ -0,0 +1,289 @@
+import 'package:eatvio_app/core/app_router.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/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/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
+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 CancelPlanBottomSheet extends StatefulWidget {
+ const CancelPlanBottomSheet({super.key, this.enableForm = false, required this.planId});
+ final bool enableForm;
+ final int planId;
+ @override
+ State createState() => _CancelPlanBottomSheetState();
+}
+
+class _CancelPlanBottomSheetState extends State {
+ final amountController = TextEditingController();
+ final reasonController = TextEditingController();
+ @override
+ void initState() {
+ super.initState();
+ amountController.addListener(() {
+ if (!amountController.text.endsWith('₽')) {
+ final text = amountController.text.replaceAll('₽', '');
+ amountController.text = '$text₽';
+ amountController.selection = TextSelection.collapsed(offset: text.length);
+ }
+ });
+ }
+
+ bool isPriceValid = true;
+ bool isReasonValid = true;
+ bool? cancelationResult;
+
+ bool isLoading = false;
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ if (cancelationResult != null) {
+ return _ComplaintWasSendWidget(isSuccess: cancelationResult!);
+ }
+ return BlocProvider(
+ create: (context) => sl(),
+ child: Builder(builder: (context) {
+ if (!widget.enableForm) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const SizedBox(height: 8),
+ const BottomSheetHandlerBar(),
+ const SizedBox(height: 20),
+ Text(
+ s.orederCancelation,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ ),
+ const SizedBox(height: 8),
+ Text(s.youShureYouWantToCancelOrder, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)),
+ const SizedBox(height: 20),
+ isLoading
+ ? const Center(child: Loader())
+ : CustomOutlinedButton(
+ text: s.cancel,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle:
+ ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.strongRed)),
+ onPressed: () async {
+ setState(() {
+ isLoading = true;
+ });
+ final result = await context.read().cancelChosenPlan(id: widget.planId);
+ setState(() {
+ cancelationResult = result;
+ isLoading = false;
+ });
+ },
+ ),
+ const SizedBox(height: 30),
+ ],
+ ),
+ );
+ }
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(height: 8),
+ const Center(child: BottomSheetHandlerBar()),
+ const SizedBox(height: 20),
+ Center(
+ child: Text(
+ s.orederCancelation,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ ),
+ ),
+ const SizedBox(height: 16),
+ Text(s.explainResonOfCancelation, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)),
+ const SizedBox(height: 4),
+ CustomTextFormField(
+ maxLines: 6,
+ minLines: 4,
+ hintText: s.reasonExample,
+ controller: reasonController,
+ hasError: !isReasonValid,
+ ),
+ if (!isReasonValid) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Минимум 3 символа')],
+ const SizedBox(height: 12),
+ Text(s.typeAmoutOfReturn, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)),
+ const SizedBox(height: 4),
+ CustomTextFormField(
+ hintText: '0 ₽',
+ controller: amountController,
+ keyboardType: TextInputType.number,
+ hasError: !isPriceValid,
+ ),
+ if (!isPriceValid) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Минимальная сумма 500₽')],
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ InkWell(
+ onTap: () {
+ amountController.text = '1000₽';
+ },
+ borderRadius: BorderRadius.circular(200),
+ child: Ink(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
+ decoration: BoxDecoration(
+ color: appTheme.lightGray0,
+ borderRadius: BorderRadius.circular(200),
+ ),
+ child: Text('1000 ₽', style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
+ ),
+ ),
+ const SizedBox(width: 8),
+ InkWell(
+ onTap: () {
+ amountController.text = '2000₽';
+ },
+ borderRadius: BorderRadius.circular(200),
+ child: Ink(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
+ decoration: BoxDecoration(
+ color: appTheme.lightGray0,
+ borderRadius: BorderRadius.circular(200),
+ ),
+ child: Text('2000 ₽', style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
+ ),
+ )
+ ],
+ ),
+ const SizedBox(height: 20),
+ isLoading
+ ? const Center(child: Loader())
+ : CustomOutlinedButton(
+ text: s.cancel,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(
+ side: const WidgetStatePropertyAll(BorderSide.none),
+ backgroundColor: WidgetStatePropertyAll(
+ appTheme.strongRed,
+ ),
+ ),
+ onPressed: () async {
+ bool isValid = true;
+ if (reasonController.text.length < 3) {
+ setState(() {
+ isReasonValid = false;
+ });
+ isValid = false;
+ } else {
+ setState(() {
+ isReasonValid = true;
+ });
+ }
+ final price = int.tryParse(amountController.text.replaceAll('₽', '').trim());
+ if (price == null || price < 500) {
+ setState(() {
+ isPriceValid = false;
+ });
+ isValid = false;
+ } else {
+ setState(() {
+ isPriceValid = true;
+ });
+ }
+ if (!isValid) {
+ return;
+ }
+ setState(() {
+ isLoading = true;
+ });
+ final result = await context.read().cancelChosenPlan(
+ id: widget.planId,
+ reason: reasonController.text,
+ price: price,
+ );
+ setState(() {
+ cancelationResult = result;
+ isLoading = false;
+ });
+ },
+ ),
+ const SizedBox(height: 30),
+ ],
+ ),
+ );
+ }),
+ );
+ }
+}
+
+class _ComplaintWasSendWidget extends StatelessWidget {
+ const _ComplaintWasSendWidget({required this.isSuccess});
+ final bool isSuccess;
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ if (isSuccess) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const SizedBox(height: 8),
+ const BottomSheetHandlerBar(),
+ const SizedBox(height: 20),
+ Text(
+ s.orderCanceled,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ ),
+ const SizedBox(height: 20),
+ CustomOutlinedButton(
+ text: s.ok,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.strongRed)),
+ onPressed: () async {
+ AppRouter.navigatorKey.currentContext?.pop();
+ },
+ ),
+ const SizedBox(height: 30),
+ ],
+ ),
+ );
+ }
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const SizedBox(height: 8),
+ const BottomSheetHandlerBar(),
+ const SizedBox(height: 20),
+ Text(
+ s.couldNotCancelOrder,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ ),
+ const SizedBox(height: 20),
+ CustomOutlinedButton(
+ text: s.ok,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.strongRed)),
+ onPressed: () async {
+ AppRouter.navigatorKey.currentContext?.pop();
+ },
+ ),
+ const SizedBox(height: 30),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart
new file mode 100644
index 00000000..aa6689a1
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart
@@ -0,0 +1,25 @@
+import 'dart:developer';
+
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.dart';
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class CancelPlanCubit extends Cubit {
+ final CancelPlanRepository cancelPlanRepository;
+ CancelPlanCubit({required this.cancelPlanRepository}) : super(CancelPlanState());
+
+ Future cancelChosenPlan({required int id, String? reason, int? price}) async{
+ try {
+ CancelationModel? body;
+ if (reason != null && price != null) {
+ body = CancelationModel(description: reason, price: price);
+ }
+ await cancelPlanRepository.cancelChosenPlan(id: id, body: body);
+ return true;
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
+ }
+ return false;
+ }
+}
diff --git a/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.dart b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.dart
new file mode 100644
index 00000000..3c5b52d7
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.dart
@@ -0,0 +1,9 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'cancel_plan_state.freezed.dart';
+
+@freezed
+class CancelPlanState with _$CancelPlanState {
+
+ factory CancelPlanState() = _CancelPlanState;
+}
\ No newline at end of file
diff --git a/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.freezed.dart b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.freezed.dart
new file mode 100644
index 00000000..90f3350c
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_state.freezed.dart
@@ -0,0 +1,82 @@
+// 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 'cancel_plan_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(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');
+
+/// @nodoc
+mixin _$CancelPlanState {}
+
+/// @nodoc
+abstract class $CancelPlanStateCopyWith<$Res> {
+ factory $CancelPlanStateCopyWith(
+ CancelPlanState value, $Res Function(CancelPlanState) then) =
+ _$CancelPlanStateCopyWithImpl<$Res, CancelPlanState>;
+}
+
+/// @nodoc
+class _$CancelPlanStateCopyWithImpl<$Res, $Val extends CancelPlanState>
+ implements $CancelPlanStateCopyWith<$Res> {
+ _$CancelPlanStateCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of CancelPlanState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+abstract class _$$CancelPlanStateImplCopyWith<$Res> {
+ factory _$$CancelPlanStateImplCopyWith(_$CancelPlanStateImpl value,
+ $Res Function(_$CancelPlanStateImpl) then) =
+ __$$CancelPlanStateImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$CancelPlanStateImplCopyWithImpl<$Res>
+ extends _$CancelPlanStateCopyWithImpl<$Res, _$CancelPlanStateImpl>
+ implements _$$CancelPlanStateImplCopyWith<$Res> {
+ __$$CancelPlanStateImplCopyWithImpl(
+ _$CancelPlanStateImpl _value, $Res Function(_$CancelPlanStateImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of CancelPlanState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$CancelPlanStateImpl implements _CancelPlanState {
+ _$CancelPlanStateImpl();
+
+ @override
+ String toString() {
+ return 'CancelPlanState()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$CancelPlanStateImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+}
+
+abstract class _CancelPlanState implements CancelPlanState {
+ factory _CancelPlanState() = _$CancelPlanStateImpl;
+}
diff --git a/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.dart b/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.dart
new file mode 100644
index 00000000..0ddfa748
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.dart
@@ -0,0 +1,14 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'cancelation_model.freezed.dart';
+part 'cancelation_model.g.dart';
+
+@freezed
+class CancelationModel with _$CancelationModel {
+ factory CancelationModel({
+ required String description,
+ required int price,
+ }) = _CancelationModel;
+
+ factory CancelationModel.fromJson(Map json) => _$CancelationModelFromJson(json);
+}
diff --git a/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.freezed.dart b/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.freezed.dart
new file mode 100644
index 00000000..d68a42ba
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.freezed.dart
@@ -0,0 +1,184 @@
+// 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 'cancelation_model.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(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');
+
+CancelationModel _$CancelationModelFromJson(Map json) {
+ return _CancelationModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$CancelationModel {
+ String get description => throw _privateConstructorUsedError;
+ int get price => throw _privateConstructorUsedError;
+
+ /// Serializes this CancelationModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of CancelationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $CancelationModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $CancelationModelCopyWith<$Res> {
+ factory $CancelationModelCopyWith(
+ CancelationModel value, $Res Function(CancelationModel) then) =
+ _$CancelationModelCopyWithImpl<$Res, CancelationModel>;
+ @useResult
+ $Res call({String description, int price});
+}
+
+/// @nodoc
+class _$CancelationModelCopyWithImpl<$Res, $Val extends CancelationModel>
+ implements $CancelationModelCopyWith<$Res> {
+ _$CancelationModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of CancelationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? description = null,
+ Object? price = null,
+ }) {
+ return _then(_value.copyWith(
+ description: null == description
+ ? _value.description
+ : description // ignore: cast_nullable_to_non_nullable
+ as String,
+ price: null == price
+ ? _value.price
+ : price // ignore: cast_nullable_to_non_nullable
+ as int,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$CancelationModelImplCopyWith<$Res>
+ implements $CancelationModelCopyWith<$Res> {
+ factory _$$CancelationModelImplCopyWith(_$CancelationModelImpl value,
+ $Res Function(_$CancelationModelImpl) then) =
+ __$$CancelationModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({String description, int price});
+}
+
+/// @nodoc
+class __$$CancelationModelImplCopyWithImpl<$Res>
+ extends _$CancelationModelCopyWithImpl<$Res, _$CancelationModelImpl>
+ implements _$$CancelationModelImplCopyWith<$Res> {
+ __$$CancelationModelImplCopyWithImpl(_$CancelationModelImpl _value,
+ $Res Function(_$CancelationModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of CancelationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? description = null,
+ Object? price = null,
+ }) {
+ return _then(_$CancelationModelImpl(
+ description: null == description
+ ? _value.description
+ : description // ignore: cast_nullable_to_non_nullable
+ as String,
+ price: null == price
+ ? _value.price
+ : price // ignore: cast_nullable_to_non_nullable
+ as int,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$CancelationModelImpl implements _CancelationModel {
+ _$CancelationModelImpl({required this.description, required this.price});
+
+ factory _$CancelationModelImpl.fromJson(Map json) =>
+ _$$CancelationModelImplFromJson(json);
+
+ @override
+ final String description;
+ @override
+ final int price;
+
+ @override
+ String toString() {
+ return 'CancelationModel(description: $description, price: $price)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$CancelationModelImpl &&
+ (identical(other.description, description) ||
+ other.description == description) &&
+ (identical(other.price, price) || other.price == price));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, description, price);
+
+ /// Create a copy of CancelationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$CancelationModelImplCopyWith<_$CancelationModelImpl> get copyWith =>
+ __$$CancelationModelImplCopyWithImpl<_$CancelationModelImpl>(
+ this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$CancelationModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _CancelationModel implements CancelationModel {
+ factory _CancelationModel(
+ {required final String description,
+ required final int price}) = _$CancelationModelImpl;
+
+ factory _CancelationModel.fromJson(Map json) =
+ _$CancelationModelImpl.fromJson;
+
+ @override
+ String get description;
+ @override
+ int get price;
+
+ /// Create a copy of CancelationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$CancelationModelImplCopyWith<_$CancelationModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.g.dart b/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.g.dart
new file mode 100644
index 00000000..d7bd21e9
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/models/cancelation_model.g.dart
@@ -0,0 +1,21 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'cancelation_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$CancelationModelImpl _$$CancelationModelImplFromJson(
+ Map json) =>
+ _$CancelationModelImpl(
+ description: json['description'] as String,
+ price: (json['price'] as num).toInt(),
+ );
+
+Map _$$CancelationModelImplToJson(
+ _$CancelationModelImpl instance) =>
+ {
+ 'description': instance.description,
+ 'price': instance.price,
+ };
diff --git a/lib/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart b/lib/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart
new file mode 100644
index 00000000..2e55549a
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart
@@ -0,0 +1,17 @@
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
+
+class CancelPlanRepository {
+ final CancelPlanService cancelService;
+ CancelPlanRepository({required this.cancelService});
+ Future cancelChosenPlan({
+ required int id,
+ CancelationModel? body
+ }) async {
+ try {
+ await cancelService.cancelChosenPlan(id: id, body: body);
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/lib/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart b/lib/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart
new file mode 100644
index 00000000..ae677280
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart
@@ -0,0 +1,16 @@
+import 'package:dio/dio.dart';
+import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
+import 'package:retrofit/retrofit.dart';
+
+part 'cancel_plan_service.g.dart';
+
+@RestApi()
+abstract class CancelPlanService {
+ factory CancelPlanService(Dio dio, {String baseUrl}) = _CancelPlanService;
+
+ @POST('/api/chosen_plans/{id}/cancel')
+ Future cancelChosenPlan({
+ @Path() required int id,
+ @Body() CancelationModel? body
+ });
+}
diff --git a/lib/features/cancel_plan_bottom_sheet/service/cancel_plan_service.g.dart b/lib/features/cancel_plan_bottom_sheet/service/cancel_plan_service.g.dart
new file mode 100644
index 00000000..943e9d25
--- /dev/null
+++ b/lib/features/cancel_plan_bottom_sheet/service/cancel_plan_service.g.dart
@@ -0,0 +1,69 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'cancel_plan_service.dart';
+
+// **************************************************************************
+// RetrofitGenerator
+// **************************************************************************
+
+// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
+
+class _CancelPlanService implements CancelPlanService {
+ _CancelPlanService(this._dio, {this.baseUrl, this.errorLogger});
+
+ final Dio _dio;
+
+ String? baseUrl;
+
+ final ParseErrorLogger? errorLogger;
+
+ @override
+ Future cancelChosenPlan({
+ required int id,
+ CancelationModel? body,
+ }) async {
+ final _extra = {};
+ final queryParameters = {};
+ queryParameters.removeWhere((k, v) => v == null);
+ final _headers = {};
+ final _data = body;
+ final _options = _setStreamType(
+ Options(method: 'POST', headers: _headers, extra: _extra)
+ .compose(
+ _dio.options,
+ '/api/chosen_plans/${id}/cancel',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+ );
+ await _dio.fetch(_options);
+ }
+
+ RequestOptions _setStreamType(RequestOptions requestOptions) {
+ if (T != dynamic &&
+ !(requestOptions.responseType == ResponseType.bytes ||
+ requestOptions.responseType == ResponseType.stream)) {
+ if (T == String) {
+ requestOptions.responseType = ResponseType.plain;
+ } else {
+ requestOptions.responseType = ResponseType.json;
+ }
+ }
+ return requestOptions;
+ }
+
+ String _combineBaseUrls(String dioBaseUrl, String? baseUrl) {
+ if (baseUrl == null || baseUrl.trim().isEmpty) {
+ return dioBaseUrl;
+ }
+
+ final url = Uri.parse(baseUrl);
+
+ if (url.isAbsolute) {
+ return url.toString();
+ }
+
+ return Uri.parse(dioBaseUrl).resolveUri(url).toString();
+ }
+}
diff --git a/lib/features/chat/models/chat_room_model.dart b/lib/features/chat/models/chat_room_model.dart
index 9d77fdf0..73daf1be 100644
--- a/lib/features/chat/models/chat_room_model.dart
+++ b/lib/features/chat/models/chat_room_model.dart
@@ -12,7 +12,8 @@ class ChatRoomModel with _$ChatRoomModel {
required String id,
@JsonKey(name: 'chat_room_setting') required ChatRoomSettingsModel chatRoomSettings,
required Users users,
- @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) required ChosenPlanModel? chosenPlan,
+ @JsonKey(name: 'chosen_plan_id') required int? chosenPlanId,
+ ChosenPlanModel? chosenPlan,
}) = _ChatRoomModel;
factory ChatRoomModel.fromJson(Map json) => _$ChatRoomModelFromJson(json);
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 e37fd767..bb8e6d9f 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
@@ -14,7 +14,7 @@ import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
-import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
+import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ChatRoomCubit extends Cubit {
@@ -57,13 +57,13 @@ class ChatRoomCubit extends Cubit {
Future checkCards() async {
try {
final cards = await cardsRepository.getCards();
- setCard(cards: cards);
- } catch (e) {
- emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
+ _setCard(cards: cards);
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
}
}
- void setCard({required List cards}) {
+ void _setCard({required List cards}) {
final currentState = state;
if (currentState is ChatRoomStateLoaded) {
if (cards.where((card) => card.isDefault).isNotEmpty) {
@@ -75,15 +75,27 @@ class ChatRoomCubit extends Cubit {
initChat(String chatId) async {
try {
final chatRoom = await chatRepository.getChatRoom(chatId);
-
+ ChosenPlanModel? chosenPlan;
+ if (chatRoom.chosenPlanId != null) {
+ chosenPlan = await chatRepository.getChosenPlan(id: chatRoom.chosenPlanId!);
+ }
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage);
lastPage = chatMessages.meta.lastPage;
await _subscribeToChatChannel(chatId: chatId);
+ emit(
+ ChatRoomState.loaded(
+ chatRoom: chatRoom.copyWith(chosenPlan: chosenPlan),
+ chatMessages: chatMessages.messagesList,
+ isTyping: false,
+ isLoading: isLoading,
+ hasCard: false,
+ ),
+ );
- emit(ChatRoomState.loaded(chatRoom: chatRoom, chatMessages: chatMessages.messagesList, isTyping: false, isLoading: isLoading, hasCard: false));
- // final cards = await cardsRepository.getCards();
- // setCard(cards: cards);
+ if (chatRoom.chosenPlan?.consultantId == chatRoom.users.receiver.id) {
+ await checkCards();
+ }
} catch (e) {
log(e.toString());
typingSubscription?.cancel();
@@ -93,14 +105,26 @@ class ChatRoomCubit extends Cubit {
}
}
+ Future updateChosenPlanState() async {
+ final currentState = state;
+ if (currentState is ChatRoomStateLoaded) {
+ final chosenPlanId = currentState.chatRoom.chosenPlanId;
+ if (chosenPlanId != null) {
+ final chosenPlan = await chatRepository.getChosenPlan(id: chosenPlanId);
+ emit(currentState.copyWith.chatRoom(chosenPlan: chosenPlan));
+ }
+ }
+ }
+
Future choseStartPlanDate(DateTime date) async {
try {
if (state is! ChatRoomStateLoaded) return false;
- final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
+ final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlanId;
if (planId == null) return false;
final stringDate = date.format('yyyy-MM-dd');
await chatRepository.choseStartPlanDate(id: planId, date: {'start_date': stringDate});
- emit((state as ChatRoomStateLoaded).copyWith.chatRoom.chosenPlan?.call(chosenPlanState: ChosenPlanStateEnum.stateStartDateHasBeenSet) ?? state);
+ final chosenPlan = await chatRepository.getChosenPlan(id: planId);
+ emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: chosenPlan));
return true;
} catch (e, st) {
log(e.toString(), stackTrace: st);
@@ -111,8 +135,7 @@ class ChatRoomCubit extends Cubit {
Future cancelChosenPlan({required String chatId, required int planId}) async {
try {
await chatRepository.cancelChosenPlan(id: planId);
- final chatRoom = await chatRepository.getChatRoom(chatId);
- emit((state as ChatRoomStateLoaded).copyWith(chatRoom: chatRoom));
+ emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: null));
} catch (e) {
emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
}
@@ -133,7 +156,8 @@ class ChatRoomCubit extends Cubit {
typingSubscription = wsManager.clientTypingStream?.listen((event) {
emit((state as ChatRoomStateLoaded).copyWith(isTyping: true, isLoading: isLoading));
- EasyDebounce.debounce('client-typing', const Duration(seconds: 3), () => emit((state as ChatRoomStateLoaded).copyWith(isTyping: false, isLoading: isLoading)));
+ EasyDebounce.debounce(
+ 'client-typing', const Duration(seconds: 3), () => emit((state as ChatRoomStateLoaded).copyWith(isTyping: false, isLoading: isLoading)));
});
}
diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart
index fe5ef6df..5f14e0bf 100644
--- a/lib/features/chat/presentation/pages/chat_screen.dart
+++ b/lib/features/chat/presentation/pages/chat_screen.dart
@@ -14,7 +14,6 @@ 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/features/chat/presentation/widgets/notification_banner.dart';
-import 'package:eatvio_app/features/chat/presentation/widgets/order_card.dart';
import 'package:eatvio_app/features/feed/models/diets_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';
@@ -85,15 +84,24 @@ class _ChatScreenState extends State {
_buildChat(BuildContext context, ChatRoomStateLoaded state) {
final chosenPlan = state.chatRoom.chosenPlan;
final amIConsultant = chosenPlan?.consultantId == state.chatRoom.users.sender.id;
- final showAboutOrderButton = showableChatBadgeStates.contains(chosenPlan?.chosenPlanState);
+ final showUserDetailsButton = amIConsultant &&
+ [
+ ChosenPlanStateEnum.stateStartDateHasBeenSet,
+ ChosenPlanStateEnum.statePendingPayment,
+ ChosenPlanStateEnum.stateInCoordination,
+ ].contains(chosenPlan?.chosenPlanState);
+
+ final showAboutOrderButton =
+ state.chatRoom.chosenPlan != null && state.chatRoom.chosenPlan!.chosenPlanState != ChosenPlanStateEnum.stateCanceledByTheInitiator;
BannerType? bannerType;
- if (amIConsultant && state.hasCard) {
+ if (chosenPlan == null) {
+ bannerType = null;
+ } else if (amIConsultant && state.hasCard) {
bannerType = BannerType.card;
- } else if (amIConsultant && chosenPlan?.chosenPlanState == ChosenPlanStateEnum.stateInCoordination) {
+ } else if (amIConsultant && chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateInCoordination) {
bannerType = BannerType.date;
} else if (!amIConsultant &&
- (chosenPlan?.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet ||
- chosenPlan?.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) {
+ (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet || chosenPlan.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) {
bannerType = BannerType.payment;
}
return Scaffold(
@@ -254,15 +262,15 @@ class _ChatScreenState extends State {
itemCount: state.chatMessages.length + (state.isLoading ? 1 : 0),
),
const SizedBox(height: 16),
- if (chosenPlan != null) ...[
- OrderCard(
- plan: chosenPlan.plan,
- isCustomerView: !amIConsultant,
- optionType: chosenPlan.optionType,
- onCancel: () async {
- await context.read().cancelChosenPlan(chatId: state.chatRoom.id, planId: chosenPlan.id);
- },
- ),
+ if (showUserDetailsButton) ...[
+ // OrderCard(
+ // plan: chosenPlan.plan,
+ // isCustomerView: !amIConsultant,
+ // optionType: chosenPlan.optionType,
+ // onCancel: () async {
+ // await context.read().cancelChosenPlan(chatId: state.chatRoom.id, planId: chosenPlan.id);
+ // },
+ // ),
if (amIConsultant)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
@@ -394,7 +402,7 @@ class _ChatScreenState extends State {
right: 15,
left: 16,
child: AboutOrderButton(
- onTap: () {
+ onTap: () async {
GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id});
},
)),
diff --git a/lib/features/chat/presentation/widgets/notification_banner.dart b/lib/features/chat/presentation/widgets/notification_banner.dart
index cfe81a39..0059f7cc 100644
--- a/lib/features/chat/presentation/widgets/notification_banner.dart
+++ b/lib/features/chat/presentation/widgets/notification_banner.dart
@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/chose_plan_date_widget.dart';
+import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -76,7 +77,11 @@ class _NotificationBannerState extends State {
),
const SizedBox(height: 12),
GestureDetector(
- onTap: () {},
+ onTap: () {
+ GoRouter.of(context).pushReplacement(
+ CardSettingsScreen.path,
+ );
+ },
child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart
index 5027aeed..30c34586 100644
--- a/lib/features/chat/repositories/chat_repository.dart
+++ b/lib/features/chat/repositories/chat_repository.dart
@@ -7,6 +7,7 @@ import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart';
+import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:http_parser/http_parser.dart';
class ChatRepository {
@@ -121,4 +122,14 @@ class ChatRepository {
rethrow;
}
}
+
+ Future getChosenPlan({
+ required int id,
+ }) async {
+ try {
+ return await chatService.getChosenPlan(id: id);
+ } catch (e) {
+ rethrow;
+ }
+ }
}
diff --git a/lib/features/chat/services/chat_service.dart b/lib/features/chat/services/chat_service.dart
index e8e47e2b..2e2a127e 100644
--- a/lib/features/chat/services/chat_service.dart
+++ b/lib/features/chat/services/chat_service.dart
@@ -3,6 +3,7 @@ import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/features/chat/models/payment_model.dart';
+import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:retrofit/retrofit.dart';
part 'chat_service.g.dart';
@@ -62,4 +63,9 @@ abstract class ChatService {
Future getPaymentLink({
@Path() required int id,
});
+
+ @GET('/api/chosen_plans/show/{id}')
+ Future getChosenPlan({
+ @Path() required int id,
+ });
}
diff --git a/lib/features/consultations/models/consultation_model.dart b/lib/features/consultations/models/consultation_model.dart
new file mode 100644
index 00000000..5e96cb68
--- /dev/null
+++ b/lib/features/consultations/models/consultation_model.dart
@@ -0,0 +1,64 @@
+import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
+import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'consultation_model.freezed.dart';
+part 'consultation_model.g.dart';
+
+@freezed
+class ConsultationModel with _$ConsultationModel {
+ factory ConsultationModel({
+ required int id,
+ @JsonKey(name: 'last_state') required ChosenPlanStateEnum chosenPlanState,
+ required String planTitle,
+ required String userName,
+ required String userSlug,
+ required double consultantPayment,
+ required int duration,
+ required int currentDiaryDay,
+ @JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
+ @JsonKey(name: 'end_date', fromJson: parseDate) required DateTime endDate,
+ @JsonKey(name: 'consultant_id') required int consultantId,
+ required List diaries,
+ }) = _ConsultationModel;
+
+ factory ConsultationModel.fromJson(Map json) => _$ConsultationModelFromJson(json);
+}
+
+@freezed
+class DiaryModel with _$DiaryModel {
+ factory DiaryModel({
+ required int id,
+ required DiaryStateEnum state,
+ @JsonKey(fromJson: parseUnformattedDate) required DateTime date,
+ }) = _DiaryModel;
+
+ factory DiaryModel.fromJson(Map json) => _$DiaryModelFromJson(json);
+}
+
+DateTime parseUnformattedDate(String date) {
+ final dateChunks = date.split('/').map(int.parse).toList();
+ return DateTime(dateChunks.last, dateChunks.first, dateChunks[1]);
+}
+
+enum DiaryStateEnum {
+ init,
+ started,
+ finished,
+ commented,
+ @JsonValue('reviewed_by_user')
+ reviewedByUser,
+ @JsonValue('last_day_started')
+ lastDayStarted,
+ @JsonValue('last_day_finished')
+ lastDayFinished,
+ @JsonValue('last_day_commented')
+ lastDayCommented,
+}
+
+@freezed
+class ConsultationPaginationModel with _$ConsultationPaginationModel {
+ factory ConsultationPaginationModel({required List data}) = _ConsultationPaginationModel;
+
+ factory ConsultationPaginationModel.fromJson(Map json) => _$ConsultationPaginationModelFromJson(json);
+}
diff --git a/lib/features/consultations/models/consultation_model.freezed.dart b/lib/features/consultations/models/consultation_model.freezed.dart
new file mode 100644
index 00000000..0fba8726
--- /dev/null
+++ b/lib/features/consultations/models/consultation_model.freezed.dart
@@ -0,0 +1,799 @@
+// 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 'consultation_model.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(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');
+
+ConsultationModel _$ConsultationModelFromJson(Map json) {
+ return _ConsultationModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$ConsultationModel {
+ int get id => throw _privateConstructorUsedError;
+ @JsonKey(name: 'last_state')
+ ChosenPlanStateEnum get chosenPlanState => throw _privateConstructorUsedError;
+ String get planTitle => throw _privateConstructorUsedError;
+ String get userName => throw _privateConstructorUsedError;
+ String get userSlug => throw _privateConstructorUsedError;
+ double get consultantPayment => throw _privateConstructorUsedError;
+ int get duration => throw _privateConstructorUsedError;
+ int get currentDiaryDay => throw _privateConstructorUsedError;
+ @JsonKey(name: 'start_date', fromJson: parseDate)
+ DateTime get startDate => throw _privateConstructorUsedError;
+ @JsonKey(name: 'end_date', fromJson: parseDate)
+ DateTime get endDate => throw _privateConstructorUsedError;
+ @JsonKey(name: 'consultant_id')
+ int get consultantId => throw _privateConstructorUsedError;
+ List get diaries => throw _privateConstructorUsedError;
+
+ /// Serializes this ConsultationModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of ConsultationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $ConsultationModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $ConsultationModelCopyWith<$Res> {
+ factory $ConsultationModelCopyWith(
+ ConsultationModel value, $Res Function(ConsultationModel) then) =
+ _$ConsultationModelCopyWithImpl<$Res, ConsultationModel>;
+ @useResult
+ $Res call(
+ {int id,
+ @JsonKey(name: 'last_state') ChosenPlanStateEnum chosenPlanState,
+ String planTitle,
+ String userName,
+ String userSlug,
+ double consultantPayment,
+ int duration,
+ int currentDiaryDay,
+ @JsonKey(name: 'start_date', fromJson: parseDate) DateTime startDate,
+ @JsonKey(name: 'end_date', fromJson: parseDate) DateTime endDate,
+ @JsonKey(name: 'consultant_id') int consultantId,
+ List diaries});
+}
+
+/// @nodoc
+class _$ConsultationModelCopyWithImpl<$Res, $Val extends ConsultationModel>
+ implements $ConsultationModelCopyWith<$Res> {
+ _$ConsultationModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of ConsultationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? chosenPlanState = null,
+ Object? planTitle = null,
+ Object? userName = null,
+ Object? userSlug = null,
+ Object? consultantPayment = null,
+ Object? duration = null,
+ Object? currentDiaryDay = null,
+ Object? startDate = null,
+ Object? endDate = null,
+ Object? consultantId = null,
+ Object? diaries = null,
+ }) {
+ return _then(_value.copyWith(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as int,
+ chosenPlanState: null == chosenPlanState
+ ? _value.chosenPlanState
+ : chosenPlanState // ignore: cast_nullable_to_non_nullable
+ as ChosenPlanStateEnum,
+ planTitle: null == planTitle
+ ? _value.planTitle
+ : planTitle // ignore: cast_nullable_to_non_nullable
+ as String,
+ userName: null == userName
+ ? _value.userName
+ : userName // ignore: cast_nullable_to_non_nullable
+ as String,
+ userSlug: null == userSlug
+ ? _value.userSlug
+ : userSlug // ignore: cast_nullable_to_non_nullable
+ as String,
+ consultantPayment: null == consultantPayment
+ ? _value.consultantPayment
+ : consultantPayment // ignore: cast_nullable_to_non_nullable
+ as double,
+ duration: null == duration
+ ? _value.duration
+ : duration // ignore: cast_nullable_to_non_nullable
+ as int,
+ currentDiaryDay: null == currentDiaryDay
+ ? _value.currentDiaryDay
+ : currentDiaryDay // ignore: cast_nullable_to_non_nullable
+ as int,
+ startDate: null == startDate
+ ? _value.startDate
+ : startDate // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ endDate: null == endDate
+ ? _value.endDate
+ : endDate // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ consultantId: null == consultantId
+ ? _value.consultantId
+ : consultantId // ignore: cast_nullable_to_non_nullable
+ as int,
+ diaries: null == diaries
+ ? _value.diaries
+ : diaries // ignore: cast_nullable_to_non_nullable
+ as List,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$ConsultationModelImplCopyWith<$Res>
+ implements $ConsultationModelCopyWith<$Res> {
+ factory _$$ConsultationModelImplCopyWith(_$ConsultationModelImpl value,
+ $Res Function(_$ConsultationModelImpl) then) =
+ __$$ConsultationModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {int id,
+ @JsonKey(name: 'last_state') ChosenPlanStateEnum chosenPlanState,
+ String planTitle,
+ String userName,
+ String userSlug,
+ double consultantPayment,
+ int duration,
+ int currentDiaryDay,
+ @JsonKey(name: 'start_date', fromJson: parseDate) DateTime startDate,
+ @JsonKey(name: 'end_date', fromJson: parseDate) DateTime endDate,
+ @JsonKey(name: 'consultant_id') int consultantId,
+ List diaries});
+}
+
+/// @nodoc
+class __$$ConsultationModelImplCopyWithImpl<$Res>
+ extends _$ConsultationModelCopyWithImpl<$Res, _$ConsultationModelImpl>
+ implements _$$ConsultationModelImplCopyWith<$Res> {
+ __$$ConsultationModelImplCopyWithImpl(_$ConsultationModelImpl _value,
+ $Res Function(_$ConsultationModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of ConsultationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? chosenPlanState = null,
+ Object? planTitle = null,
+ Object? userName = null,
+ Object? userSlug = null,
+ Object? consultantPayment = null,
+ Object? duration = null,
+ Object? currentDiaryDay = null,
+ Object? startDate = null,
+ Object? endDate = null,
+ Object? consultantId = null,
+ Object? diaries = null,
+ }) {
+ return _then(_$ConsultationModelImpl(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as int,
+ chosenPlanState: null == chosenPlanState
+ ? _value.chosenPlanState
+ : chosenPlanState // ignore: cast_nullable_to_non_nullable
+ as ChosenPlanStateEnum,
+ planTitle: null == planTitle
+ ? _value.planTitle
+ : planTitle // ignore: cast_nullable_to_non_nullable
+ as String,
+ userName: null == userName
+ ? _value.userName
+ : userName // ignore: cast_nullable_to_non_nullable
+ as String,
+ userSlug: null == userSlug
+ ? _value.userSlug
+ : userSlug // ignore: cast_nullable_to_non_nullable
+ as String,
+ consultantPayment: null == consultantPayment
+ ? _value.consultantPayment
+ : consultantPayment // ignore: cast_nullable_to_non_nullable
+ as double,
+ duration: null == duration
+ ? _value.duration
+ : duration // ignore: cast_nullable_to_non_nullable
+ as int,
+ currentDiaryDay: null == currentDiaryDay
+ ? _value.currentDiaryDay
+ : currentDiaryDay // ignore: cast_nullable_to_non_nullable
+ as int,
+ startDate: null == startDate
+ ? _value.startDate
+ : startDate // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ endDate: null == endDate
+ ? _value.endDate
+ : endDate // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ consultantId: null == consultantId
+ ? _value.consultantId
+ : consultantId // ignore: cast_nullable_to_non_nullable
+ as int,
+ diaries: null == diaries
+ ? _value._diaries
+ : diaries // ignore: cast_nullable_to_non_nullable
+ as List,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$ConsultationModelImpl implements _ConsultationModel {
+ _$ConsultationModelImpl(
+ {required this.id,
+ @JsonKey(name: 'last_state') required this.chosenPlanState,
+ required this.planTitle,
+ required this.userName,
+ required this.userSlug,
+ required this.consultantPayment,
+ required this.duration,
+ required this.currentDiaryDay,
+ @JsonKey(name: 'start_date', fromJson: parseDate) required this.startDate,
+ @JsonKey(name: 'end_date', fromJson: parseDate) required this.endDate,
+ @JsonKey(name: 'consultant_id') required this.consultantId,
+ required final List diaries})
+ : _diaries = diaries;
+
+ factory _$ConsultationModelImpl.fromJson(Map json) =>
+ _$$ConsultationModelImplFromJson(json);
+
+ @override
+ final int id;
+ @override
+ @JsonKey(name: 'last_state')
+ final ChosenPlanStateEnum chosenPlanState;
+ @override
+ final String planTitle;
+ @override
+ final String userName;
+ @override
+ final String userSlug;
+ @override
+ final double consultantPayment;
+ @override
+ final int duration;
+ @override
+ final int currentDiaryDay;
+ @override
+ @JsonKey(name: 'start_date', fromJson: parseDate)
+ final DateTime startDate;
+ @override
+ @JsonKey(name: 'end_date', fromJson: parseDate)
+ final DateTime endDate;
+ @override
+ @JsonKey(name: 'consultant_id')
+ final int consultantId;
+ final List _diaries;
+ @override
+ List get diaries {
+ if (_diaries is EqualUnmodifiableListView) return _diaries;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_diaries);
+ }
+
+ @override
+ String toString() {
+ return 'ConsultationModel(id: $id, chosenPlanState: $chosenPlanState, planTitle: $planTitle, userName: $userName, userSlug: $userSlug, consultantPayment: $consultantPayment, duration: $duration, currentDiaryDay: $currentDiaryDay, startDate: $startDate, endDate: $endDate, consultantId: $consultantId, diaries: $diaries)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$ConsultationModelImpl &&
+ (identical(other.id, id) || other.id == id) &&
+ (identical(other.chosenPlanState, chosenPlanState) ||
+ other.chosenPlanState == chosenPlanState) &&
+ (identical(other.planTitle, planTitle) ||
+ other.planTitle == planTitle) &&
+ (identical(other.userName, userName) ||
+ other.userName == userName) &&
+ (identical(other.userSlug, userSlug) ||
+ other.userSlug == userSlug) &&
+ (identical(other.consultantPayment, consultantPayment) ||
+ other.consultantPayment == consultantPayment) &&
+ (identical(other.duration, duration) ||
+ other.duration == duration) &&
+ (identical(other.currentDiaryDay, currentDiaryDay) ||
+ other.currentDiaryDay == currentDiaryDay) &&
+ (identical(other.startDate, startDate) ||
+ other.startDate == startDate) &&
+ (identical(other.endDate, endDate) || other.endDate == endDate) &&
+ (identical(other.consultantId, consultantId) ||
+ other.consultantId == consultantId) &&
+ const DeepCollectionEquality().equals(other._diaries, _diaries));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ id,
+ chosenPlanState,
+ planTitle,
+ userName,
+ userSlug,
+ consultantPayment,
+ duration,
+ currentDiaryDay,
+ startDate,
+ endDate,
+ consultantId,
+ const DeepCollectionEquality().hash(_diaries));
+
+ /// Create a copy of ConsultationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$ConsultationModelImplCopyWith<_$ConsultationModelImpl> get copyWith =>
+ __$$ConsultationModelImplCopyWithImpl<_$ConsultationModelImpl>(
+ this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$ConsultationModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _ConsultationModel implements ConsultationModel {
+ factory _ConsultationModel(
+ {required final int id,
+ @JsonKey(name: 'last_state')
+ required final ChosenPlanStateEnum chosenPlanState,
+ required final String planTitle,
+ required final String userName,
+ required final String userSlug,
+ required final double consultantPayment,
+ required final int duration,
+ required final int currentDiaryDay,
+ @JsonKey(name: 'start_date', fromJson: parseDate)
+ required final DateTime startDate,
+ @JsonKey(name: 'end_date', fromJson: parseDate)
+ required final DateTime endDate,
+ @JsonKey(name: 'consultant_id') required final int consultantId,
+ required final List diaries}) = _$ConsultationModelImpl;
+
+ factory _ConsultationModel.fromJson(Map json) =
+ _$ConsultationModelImpl.fromJson;
+
+ @override
+ int get id;
+ @override
+ @JsonKey(name: 'last_state')
+ ChosenPlanStateEnum get chosenPlanState;
+ @override
+ String get planTitle;
+ @override
+ String get userName;
+ @override
+ String get userSlug;
+ @override
+ double get consultantPayment;
+ @override
+ int get duration;
+ @override
+ int get currentDiaryDay;
+ @override
+ @JsonKey(name: 'start_date', fromJson: parseDate)
+ DateTime get startDate;
+ @override
+ @JsonKey(name: 'end_date', fromJson: parseDate)
+ DateTime get endDate;
+ @override
+ @JsonKey(name: 'consultant_id')
+ int get consultantId;
+ @override
+ List get diaries;
+
+ /// Create a copy of ConsultationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$ConsultationModelImplCopyWith<_$ConsultationModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+DiaryModel _$DiaryModelFromJson(Map json) {
+ return _DiaryModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$DiaryModel {
+ int get id => throw _privateConstructorUsedError;
+ DiaryStateEnum get state => throw _privateConstructorUsedError;
+ @JsonKey(fromJson: parseUnformattedDate)
+ DateTime get date => throw _privateConstructorUsedError;
+
+ /// Serializes this DiaryModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of DiaryModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $DiaryModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $DiaryModelCopyWith<$Res> {
+ factory $DiaryModelCopyWith(
+ DiaryModel value, $Res Function(DiaryModel) then) =
+ _$DiaryModelCopyWithImpl<$Res, DiaryModel>;
+ @useResult
+ $Res call(
+ {int id,
+ DiaryStateEnum state,
+ @JsonKey(fromJson: parseUnformattedDate) DateTime date});
+}
+
+/// @nodoc
+class _$DiaryModelCopyWithImpl<$Res, $Val extends DiaryModel>
+ implements $DiaryModelCopyWith<$Res> {
+ _$DiaryModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of DiaryModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? state = null,
+ Object? date = null,
+ }) {
+ return _then(_value.copyWith(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as int,
+ state: null == state
+ ? _value.state
+ : state // ignore: cast_nullable_to_non_nullable
+ as DiaryStateEnum,
+ date: null == date
+ ? _value.date
+ : date // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$DiaryModelImplCopyWith<$Res>
+ implements $DiaryModelCopyWith<$Res> {
+ factory _$$DiaryModelImplCopyWith(
+ _$DiaryModelImpl value, $Res Function(_$DiaryModelImpl) then) =
+ __$$DiaryModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {int id,
+ DiaryStateEnum state,
+ @JsonKey(fromJson: parseUnformattedDate) DateTime date});
+}
+
+/// @nodoc
+class __$$DiaryModelImplCopyWithImpl<$Res>
+ extends _$DiaryModelCopyWithImpl<$Res, _$DiaryModelImpl>
+ implements _$$DiaryModelImplCopyWith<$Res> {
+ __$$DiaryModelImplCopyWithImpl(
+ _$DiaryModelImpl _value, $Res Function(_$DiaryModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of DiaryModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? state = null,
+ Object? date = null,
+ }) {
+ return _then(_$DiaryModelImpl(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as int,
+ state: null == state
+ ? _value.state
+ : state // ignore: cast_nullable_to_non_nullable
+ as DiaryStateEnum,
+ date: null == date
+ ? _value.date
+ : date // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$DiaryModelImpl implements _DiaryModel {
+ _$DiaryModelImpl(
+ {required this.id,
+ required this.state,
+ @JsonKey(fromJson: parseUnformattedDate) required this.date});
+
+ factory _$DiaryModelImpl.fromJson(Map json) =>
+ _$$DiaryModelImplFromJson(json);
+
+ @override
+ final int id;
+ @override
+ final DiaryStateEnum state;
+ @override
+ @JsonKey(fromJson: parseUnformattedDate)
+ final DateTime date;
+
+ @override
+ String toString() {
+ return 'DiaryModel(id: $id, state: $state, date: $date)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$DiaryModelImpl &&
+ (identical(other.id, id) || other.id == id) &&
+ (identical(other.state, state) || other.state == state) &&
+ (identical(other.date, date) || other.date == date));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, id, state, date);
+
+ /// Create a copy of DiaryModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$DiaryModelImplCopyWith<_$DiaryModelImpl> get copyWith =>
+ __$$DiaryModelImplCopyWithImpl<_$DiaryModelImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$DiaryModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _DiaryModel implements DiaryModel {
+ factory _DiaryModel(
+ {required final int id,
+ required final DiaryStateEnum state,
+ @JsonKey(fromJson: parseUnformattedDate)
+ required final DateTime date}) = _$DiaryModelImpl;
+
+ factory _DiaryModel.fromJson(Map json) =
+ _$DiaryModelImpl.fromJson;
+
+ @override
+ int get id;
+ @override
+ DiaryStateEnum get state;
+ @override
+ @JsonKey(fromJson: parseUnformattedDate)
+ DateTime get date;
+
+ /// Create a copy of DiaryModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$DiaryModelImplCopyWith<_$DiaryModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+ConsultationPaginationModel _$ConsultationPaginationModelFromJson(
+ Map json) {
+ return _ConsultationPaginationModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$ConsultationPaginationModel {
+ List get data => throw _privateConstructorUsedError;
+
+ /// Serializes this ConsultationPaginationModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of ConsultationPaginationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $ConsultationPaginationModelCopyWith
+ get copyWith => throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $ConsultationPaginationModelCopyWith<$Res> {
+ factory $ConsultationPaginationModelCopyWith(
+ ConsultationPaginationModel value,
+ $Res Function(ConsultationPaginationModel) then) =
+ _$ConsultationPaginationModelCopyWithImpl<$Res,
+ ConsultationPaginationModel>;
+ @useResult
+ $Res call({List data});
+}
+
+/// @nodoc
+class _$ConsultationPaginationModelCopyWithImpl<$Res,
+ $Val extends ConsultationPaginationModel>
+ implements $ConsultationPaginationModelCopyWith<$Res> {
+ _$ConsultationPaginationModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of ConsultationPaginationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? data = null,
+ }) {
+ return _then(_value.copyWith(
+ data: null == data
+ ? _value.data
+ : data // ignore: cast_nullable_to_non_nullable
+ as List,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$ConsultationPaginationModelImplCopyWith<$Res>
+ implements $ConsultationPaginationModelCopyWith<$Res> {
+ factory _$$ConsultationPaginationModelImplCopyWith(
+ _$ConsultationPaginationModelImpl value,
+ $Res Function(_$ConsultationPaginationModelImpl) then) =
+ __$$ConsultationPaginationModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({List data});
+}
+
+/// @nodoc
+class __$$ConsultationPaginationModelImplCopyWithImpl<$Res>
+ extends _$ConsultationPaginationModelCopyWithImpl<$Res,
+ _$ConsultationPaginationModelImpl>
+ implements _$$ConsultationPaginationModelImplCopyWith<$Res> {
+ __$$ConsultationPaginationModelImplCopyWithImpl(
+ _$ConsultationPaginationModelImpl _value,
+ $Res Function(_$ConsultationPaginationModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of ConsultationPaginationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? data = null,
+ }) {
+ return _then(_$ConsultationPaginationModelImpl(
+ data: null == data
+ ? _value._data
+ : data // ignore: cast_nullable_to_non_nullable
+ as List,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$ConsultationPaginationModelImpl
+ implements _ConsultationPaginationModel {
+ _$ConsultationPaginationModelImpl(
+ {required final List data})
+ : _data = data;
+
+ factory _$ConsultationPaginationModelImpl.fromJson(
+ Map json) =>
+ _$$ConsultationPaginationModelImplFromJson(json);
+
+ final List _data;
+ @override
+ List get data {
+ if (_data is EqualUnmodifiableListView) return _data;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_data);
+ }
+
+ @override
+ String toString() {
+ return 'ConsultationPaginationModel(data: $data)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$ConsultationPaginationModelImpl &&
+ const DeepCollectionEquality().equals(other._data, _data));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode =>
+ Object.hash(runtimeType, const DeepCollectionEquality().hash(_data));
+
+ /// Create a copy of ConsultationPaginationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$ConsultationPaginationModelImplCopyWith<_$ConsultationPaginationModelImpl>
+ get copyWith => __$$ConsultationPaginationModelImplCopyWithImpl<
+ _$ConsultationPaginationModelImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$ConsultationPaginationModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _ConsultationPaginationModel
+ implements ConsultationPaginationModel {
+ factory _ConsultationPaginationModel(
+ {required final List data}) =
+ _$ConsultationPaginationModelImpl;
+
+ factory _ConsultationPaginationModel.fromJson(Map json) =
+ _$ConsultationPaginationModelImpl.fromJson;
+
+ @override
+ List get data;
+
+ /// Create a copy of ConsultationPaginationModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$ConsultationPaginationModelImplCopyWith<_$ConsultationPaginationModelImpl>
+ get copyWith => throw _privateConstructorUsedError;
+}
diff --git a/lib/features/consultations/models/consultation_model.g.dart b/lib/features/consultations/models/consultation_model.g.dart
new file mode 100644
index 00000000..ff93e365
--- /dev/null
+++ b/lib/features/consultations/models/consultation_model.g.dart
@@ -0,0 +1,100 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'consultation_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$ConsultationModelImpl _$$ConsultationModelImplFromJson(
+ Map json) =>
+ _$ConsultationModelImpl(
+ id: (json['id'] as num).toInt(),
+ chosenPlanState:
+ $enumDecode(_$ChosenPlanStateEnumEnumMap, json['last_state']),
+ planTitle: json['planTitle'] as String,
+ userName: json['userName'] as String,
+ userSlug: json['userSlug'] as String,
+ consultantPayment: (json['consultantPayment'] as num).toDouble(),
+ duration: (json['duration'] as num).toInt(),
+ currentDiaryDay: (json['currentDiaryDay'] as num).toInt(),
+ startDate: parseDate(json['start_date'] as String),
+ endDate: parseDate(json['end_date'] as String),
+ consultantId: (json['consultant_id'] as num).toInt(),
+ diaries: (json['diaries'] as List)
+ .map((e) => DiaryModel.fromJson(e as Map))
+ .toList(),
+ );
+
+Map _$$ConsultationModelImplToJson(
+ _$ConsultationModelImpl instance) =>
+ {
+ 'id': instance.id,
+ 'last_state': _$ChosenPlanStateEnumEnumMap[instance.chosenPlanState]!,
+ 'planTitle': instance.planTitle,
+ 'userName': instance.userName,
+ 'userSlug': instance.userSlug,
+ 'consultantPayment': instance.consultantPayment,
+ 'duration': instance.duration,
+ 'currentDiaryDay': instance.currentDiaryDay,
+ 'start_date': instance.startDate.toIso8601String(),
+ 'end_date': instance.endDate.toIso8601String(),
+ 'consultant_id': instance.consultantId,
+ 'diaries': instance.diaries,
+ };
+
+const _$ChosenPlanStateEnumEnumMap = {
+ ChosenPlanStateEnum.stateInited: 'initied',
+ ChosenPlanStateEnum.stateStartDateHasBeenSet: 'start_date_has_been_set',
+ ChosenPlanStateEnum.stateInCoordination: 'in_coordination',
+ ChosenPlanStateEnum.stateStarted: 'started',
+ ChosenPlanStateEnum.stateInProgress: 'in_progress',
+ ChosenPlanStateEnum.statePendingPayment: 'pending_payment',
+ ChosenPlanStateEnum.stateConfirmedByConsultant: 'confirmed_by_consultant',
+ ChosenPlanStateEnum.statePaid: 'paid',
+ ChosenPlanStateEnum.stateCanceledByTheInitiator: 'canceled_by_the_initiator',
+ ChosenPlanStateEnum.stateLastDayStarted: 'last_day_started',
+ ChosenPlanStateEnum.stateLastDayFinished: 'last_day_finished',
+ ChosenPlanStateEnum.stateComplained: 'complained',
+ ChosenPlanStateEnum.stateDone: 'done',
+ ChosenPlanStateEnum.stateChargeWasCollected: 'charge_was_collected',
+};
+
+_$DiaryModelImpl _$$DiaryModelImplFromJson(Map json) =>
+ _$DiaryModelImpl(
+ id: (json['id'] as num).toInt(),
+ state: $enumDecode(_$DiaryStateEnumEnumMap, json['state']),
+ date: parseUnformattedDate(json['date'] as String),
+ );
+
+Map _$$DiaryModelImplToJson(_$DiaryModelImpl instance) =>
+ {
+ 'id': instance.id,
+ 'state': _$DiaryStateEnumEnumMap[instance.state]!,
+ 'date': instance.date.toIso8601String(),
+ };
+
+const _$DiaryStateEnumEnumMap = {
+ DiaryStateEnum.init: 'init',
+ DiaryStateEnum.started: 'started',
+ DiaryStateEnum.finished: 'finished',
+ DiaryStateEnum.commented: 'commented',
+ DiaryStateEnum.reviewedByUser: 'reviewed_by_user',
+ DiaryStateEnum.lastDayStarted: 'last_day_started',
+ DiaryStateEnum.lastDayFinished: 'last_day_finished',
+ DiaryStateEnum.lastDayCommented: 'last_day_commented',
+};
+
+_$ConsultationPaginationModelImpl _$$ConsultationPaginationModelImplFromJson(
+ Map json) =>
+ _$ConsultationPaginationModelImpl(
+ data: (json['data'] as List)
+ .map((e) => ConsultationModel.fromJson(e as Map))
+ .toList(),
+ );
+
+Map _$$ConsultationPaginationModelImplToJson(
+ _$ConsultationPaginationModelImpl instance) =>
+ {
+ 'data': instance.data,
+ };
diff --git a/lib/features/consultations/presentation/cubit/consultations_cubit.dart b/lib/features/consultations/presentation/cubit/consultations_cubit.dart
new file mode 100644
index 00000000..5972e8d9
--- /dev/null
+++ b/lib/features/consultations/presentation/cubit/consultations_cubit.dart
@@ -0,0 +1,21 @@
+import 'dart:developer';
+
+import 'package:eatvio_app/core/enums/base_state.dart';
+import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
+import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class ConsultationsCubit extends Cubit {
+ final ConsultationsRepository consultationsRepository;
+ ConsultationsCubit({required this.consultationsRepository}) : super(ConsultationsState());
+
+ Future loadConsultations() async {
+ try {
+ final consultations = await consultationsRepository.getConsultations();
+ emit(state.copyWith(consultations: consultations, state: BaseState.success));
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
+ emit(state.copyWith(state: BaseState.error));
+ }
+ }
+}
diff --git a/lib/features/consultations/presentation/cubit/consultations_state.dart b/lib/features/consultations/presentation/cubit/consultations_state.dart
new file mode 100644
index 00000000..e4988f03
--- /dev/null
+++ b/lib/features/consultations/presentation/cubit/consultations_state.dart
@@ -0,0 +1,15 @@
+import 'package:eatvio_app/core/enums/base_state.dart';
+import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'consultations_state.freezed.dart';
+
+@freezed
+class ConsultationsState with _$ConsultationsState {
+
+ factory ConsultationsState({
+ @Default(BaseState.initial) BaseState state,
+ @Default([]) List consultations
+ }) = _ConsultationsState;
+
+}
\ No newline at end of file
diff --git a/lib/features/consultations/presentation/cubit/consultations_state.freezed.dart b/lib/features/consultations/presentation/cubit/consultations_state.freezed.dart
new file mode 100644
index 00000000..7d4203e5
--- /dev/null
+++ b/lib/features/consultations/presentation/cubit/consultations_state.freezed.dart
@@ -0,0 +1,175 @@
+// 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 'consultations_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(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');
+
+/// @nodoc
+mixin _$ConsultationsState {
+ BaseState get state => throw _privateConstructorUsedError;
+ List get consultations =>
+ throw _privateConstructorUsedError;
+
+ /// Create a copy of ConsultationsState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $ConsultationsStateCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $ConsultationsStateCopyWith<$Res> {
+ factory $ConsultationsStateCopyWith(
+ ConsultationsState value, $Res Function(ConsultationsState) then) =
+ _$ConsultationsStateCopyWithImpl<$Res, ConsultationsState>;
+ @useResult
+ $Res call({BaseState state, List consultations});
+}
+
+/// @nodoc
+class _$ConsultationsStateCopyWithImpl<$Res, $Val extends ConsultationsState>
+ implements $ConsultationsStateCopyWith<$Res> {
+ _$ConsultationsStateCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of ConsultationsState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? state = null,
+ Object? consultations = null,
+ }) {
+ return _then(_value.copyWith(
+ state: null == state
+ ? _value.state
+ : state // ignore: cast_nullable_to_non_nullable
+ as BaseState,
+ consultations: null == consultations
+ ? _value.consultations
+ : consultations // ignore: cast_nullable_to_non_nullable
+ as List,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$ConsultationsStateImplCopyWith<$Res>
+ implements $ConsultationsStateCopyWith<$Res> {
+ factory _$$ConsultationsStateImplCopyWith(_$ConsultationsStateImpl value,
+ $Res Function(_$ConsultationsStateImpl) then) =
+ __$$ConsultationsStateImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({BaseState state, List consultations});
+}
+
+/// @nodoc
+class __$$ConsultationsStateImplCopyWithImpl<$Res>
+ extends _$ConsultationsStateCopyWithImpl<$Res, _$ConsultationsStateImpl>
+ implements _$$ConsultationsStateImplCopyWith<$Res> {
+ __$$ConsultationsStateImplCopyWithImpl(_$ConsultationsStateImpl _value,
+ $Res Function(_$ConsultationsStateImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of ConsultationsState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? state = null,
+ Object? consultations = null,
+ }) {
+ return _then(_$ConsultationsStateImpl(
+ state: null == state
+ ? _value.state
+ : state // ignore: cast_nullable_to_non_nullable
+ as BaseState,
+ consultations: null == consultations
+ ? _value._consultations
+ : consultations // ignore: cast_nullable_to_non_nullable
+ as List,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$ConsultationsStateImpl implements _ConsultationsState {
+ _$ConsultationsStateImpl(
+ {this.state = BaseState.initial,
+ final List consultations = const []})
+ : _consultations = consultations;
+
+ @override
+ @JsonKey()
+ final BaseState state;
+ final List _consultations;
+ @override
+ @JsonKey()
+ List get consultations {
+ if (_consultations is EqualUnmodifiableListView) return _consultations;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_consultations);
+ }
+
+ @override
+ String toString() {
+ return 'ConsultationsState(state: $state, consultations: $consultations)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$ConsultationsStateImpl &&
+ (identical(other.state, state) || other.state == state) &&
+ const DeepCollectionEquality()
+ .equals(other._consultations, _consultations));
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ runtimeType, state, const DeepCollectionEquality().hash(_consultations));
+
+ /// Create a copy of ConsultationsState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$ConsultationsStateImplCopyWith<_$ConsultationsStateImpl> get copyWith =>
+ __$$ConsultationsStateImplCopyWithImpl<_$ConsultationsStateImpl>(
+ this, _$identity);
+}
+
+abstract class _ConsultationsState implements ConsultationsState {
+ factory _ConsultationsState(
+ {final BaseState state,
+ final List consultations}) = _$ConsultationsStateImpl;
+
+ @override
+ BaseState get state;
+ @override
+ List get consultations;
+
+ /// Create a copy of ConsultationsState
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$ConsultationsStateImplCopyWith<_$ConsultationsStateImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/consultations/presentation/pages/consultations_screen.dart b/lib/features/consultations/presentation/pages/consultations_screen.dart
index 7fd9c773..c4dd36d7 100644
--- a/lib/features/consultations/presentation/pages/consultations_screen.dart
+++ b/lib/features/consultations/presentation/pages/consultations_screen.dart
@@ -1,69 +1,61 @@
-import 'package:eatvio_app/core/theme/theme_helper.dart';
-import 'package:eatvio_app/features/consultations/entities/consultations_entity.dart';
+import 'package:eatvio_app/core/enums/base_state.dart';
+import 'package:eatvio_app/core/locator_service.dart';
+import 'package:eatvio_app/core/widgets/data_error_widget.dart';
+import 'package:eatvio_app/core/widgets/loader.dart';
+import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
+import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart';
import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
class ConsultationsScreen extends StatelessWidget {
+ static const path = '/consultations-screen';
+ static const name = 'consultationsScreen';
const ConsultationsScreen({super.key});
@override
Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: const Color(0xFFF0F2F5),
- appBar: AppBar(
- title: const Text('Консультации'),
- centerTitle: true,
- shape: const RoundedRectangleBorder(
- borderRadius: BorderRadiusDirectional.vertical(
- bottom: Radius.circular(12),
- ),
- ),
- ),
- body: SingleChildScrollView(
- child: Column(
- children: [
- ListView.separated(
- physics: const NeverScrollableScrollPhysics(),
- shrinkWrap: true,
- padding: const EdgeInsets.symmetric(vertical: 12),
- itemBuilder: (context, index) => ConsultationCardWidget(
- consultationsEntity: testData[index],
- ),
- separatorBuilder: (context, index) => const SizedBox(
- height: 12,
- ),
- itemCount: testData.length,
- ),
- const SizedBox(height: 20),
- InkWell(
- borderRadius: BorderRadius.circular(20),
- onTap: () {
- GoRouter.of(context).pushNamed('consultantFinanceScreen');
- },
- child: Ink(
- width: MediaQuery.of(context).size.width - 32,
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20),
- color: appTheme.blueA400,
- ),
- child: const Center(
- child: Text(
- 'Архив консультаций',
- style: TextStyle(
- color: Colors.white,
- fontSize: 16,
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- ),
- ),
- const SizedBox(height: 20),
- ],
- ),
- ),
+ return BlocProvider(
+ create: (context) => sl()..loadConsultations(),
+ child: const ConsultationsContent(),
+ );
+ }
+}
+
+class ConsultationsContent extends StatelessWidget {
+ const ConsultationsContent({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder(
+ builder: (context, state) {
+ return Scaffold(
+ backgroundColor: const Color(0xFFF0F2F5),
+ appBar: AppBar(
+ title: const Text('Консультации'),
+ centerTitle: true,
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadiusDirectional.vertical(
+ bottom: Radius.circular(12),
+ ),
+ ),
+ ),
+ body: switch (state.state) {
+ BaseState.loading => const Loader(),
+ BaseState.error => DataErrorWidget(onReload: () async {}),
+ BaseState.initial => const Loader(),
+ BaseState.success => ListView.separated(
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ itemBuilder: (context, index) => ConsultationCardWidget(
+ consultationsModel: state.consultations[index],
+ ),
+ separatorBuilder: (context, index) => const SizedBox(
+ height: 12,
+ ),
+ itemCount: state.consultations.length,
+ ),
+ });
+ },
);
}
}
diff --git a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart
index 9239e225..42d4f762 100644
--- a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart
+++ b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart
@@ -1,37 +1,66 @@
+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/core/widgets/custom_divider.dart';
-import 'package:eatvio_app/features/consultations/entities/consultations_entity.dart';
+import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
+import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
+import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
+import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'package:table_calendar/table_calendar.dart';
-class ConsultationCardWidget extends StatelessWidget {
- final List titles = const [
- 'Дневник не заполнялся и никаких движений в тот день не было.',
- 'Показывает границы плана питания или публичной диеты в будущем.',
- 'Начат дневник питания, но не закончен.',
- 'Дневник закончен и прокомментирован консультантом но не просмотрен пользователем.',
- 'Дневник заполнен, но не прокомментирован консультантом.',
- 'Дневник заполнен, прокомментирован консультантом и после этого клиент просмотрел комментарии.',
- ];
- final List legendColors = const [
- Colors.white,
- Color(0xFFCAC9C9),
- Color(0xFFF0C497),
- Color(0xFF2F80ED),
- Color(0xFFFF9500),
- Color(0xFF4CD964),
- ];
- final ConsultationsEntity consultationsEntity;
+class ConsultationCardWidget extends StatefulWidget {
+ final ConsultationModel consultationsModel;
const ConsultationCardWidget({
super.key,
- required this.consultationsEntity,
+ required this.consultationsModel,
});
+ @override
+ State createState() => _ConsultationCardWidgetState();
+}
+
+class _ConsultationCardWidgetState extends State {
+ final List titles = const [
+ 'Дневник не заполнялся и никаких движений в тот день не было.',
+ 'Начат дневник питания, но не закончен.',
+ 'Дневник заполнен, но не прокомментирован консультантом.',
+ 'Дневник закончен и прокомментирован консультантом но не просмотрен пользователем.',
+ 'Дневник заполнен, прокомментирован консультантом и после этого клиент просмотрел комментарии.',
+ ];
+
+ final List legendColors = const [
+ Colors.white,
+ Color(0xFFF0C497),
+ Color(0xFFFF9500),
+ Color(0xFF2F80ED),
+ Color(0xFF4CD964),
+ ];
+
+ late final List<(int, DiaryModel)> givenDaysWithDayNumber;
+ final List> legendStates = [
+ [DiaryStateEnum.init],
+ [DiaryStateEnum.started, DiaryStateEnum.lastDayStarted],
+ [DiaryStateEnum.finished, DiaryStateEnum.lastDayFinished],
+ [DiaryStateEnum.commented, DiaryStateEnum.lastDayCommented],
+ [DiaryStateEnum.reviewedByUser]
+ ];
+ @override
+ void initState() {
+ super.initState();
+ final startPlanDate = widget.consultationsModel.startDate;
+ givenDaysWithDayNumber = List.generate(widget.consultationsModel.diaries.length, (index) {
+ final dayNumber = widget.consultationsModel.diaries[index].date.difference(startPlanDate).inDays;
+ return (dayNumber, widget.consultationsModel.diaries[index]);
+ });
+ widget.consultationsModel.diaries;
+ }
+
@override
Widget build(BuildContext context) {
+ final s = S.of(context);
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
@@ -50,7 +79,7 @@ class ConsultationCardWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- consultationsEntity.name,
+ widget.consultationsModel.userName,
style: const TextStyle(
height: 1,
color: Color(0xFF323F4B),
@@ -60,7 +89,7 @@ class ConsultationCardWidget extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
- consultationsEntity.planName,
+ widget.consultationsModel.planTitle,
style: const TextStyle(
height: 1,
color: Color(0xFF88949F),
@@ -75,7 +104,7 @@ class ConsultationCardWidget extends StatelessWidget {
const SizedBox(height: 12),
GestureDetector(
onTap: () {
- GoRouter.of(context).pushNamed(UserDetailsScreen.name, extra: "id-754786b9-4ae6-4677-b847-1d05db44dfb9");
+ GoRouter.of(context).pushNamed(UserDetailsScreen.name, extra: widget.consultationsModel.userSlug);
},
child: Text(
'Анкета пользователя',
@@ -95,7 +124,7 @@ class ConsultationCardWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
- 'День ${consultationsEntity.currentDayOfPlan} из ${consultationsEntity.totalDaysOfPlan}',
+ 'День ${widget.consultationsModel.currentDiaryDay} из ${widget.consultationsModel.duration}',
style: const TextStyle(
height: 1,
color: Color(0xFF323F4B),
@@ -148,45 +177,102 @@ class ConsultationCardWidget extends StatelessWidget {
],
),
const SizedBox(height: 16),
- TableCalendar(
- focusedDay: DateTime.now(),
- firstDay: DateTime.utc(2020, 10, 16),
- lastDay: DateTime.utc(2030, 3, 14),
- locale: 'ru_RU',
- startingDayOfWeek: StartingDayOfWeek.monday,
- headerVisible: false,
- calendarFormat: CalendarFormat.week,
- calendarStyle: CalendarStyle(
- weekendDecoration: const BoxDecoration(
- color: Colors.white,
- shape: BoxShape.circle,
- ),
- defaultDecoration: const BoxDecoration(
- color: Colors.white,
- shape: BoxShape.circle,
- ),
- selectedDecoration: BoxDecoration(
- color: appTheme.blueA400,
- shape: BoxShape.circle,
- ),
- todayTextStyle: const TextStyle(
- color: Colors.black,
- ),
- todayDecoration: BoxDecoration(
- border: Border.all(
- color: appTheme.blueA400,
- width: 2,
- ),
- color: Colors.white,
- shape: BoxShape.circle,
- ),
+ SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ child: Row(
+ children: List.generate(widget.consultationsModel.duration, (index) {
+ final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index);
+ final hasActivity = day != null && day.$2.state != DiaryStateEnum.init;
+ return Padding(
+ padding: const EdgeInsets.only(right: 12),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ InkWell(
+ onTap: () {
+ if (!hasActivity) {
+ showErrorDialog(context,
+ title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
+ return;
+ }
+ GoRouter.of(context).push(UserDiaryScreen.path, extra: day.$2.id);
+ },
+ borderRadius: BorderRadius.circular(100),
+ child: Ink(
+ decoration: BoxDecoration(
+ color: hasActivity ? appTheme.dividerColor : appTheme.dividerColor.withValues(alpha: 0.4),
+ borderRadius: BorderRadius.circular(100)),
+ width: 58,
+ height: 32,
+ child: Center(
+ child: Text(
+ (index + 1).toString(),
+ style: TextStyles.textMSemibold.copyWith(
+ color: hasActivity ? appTheme.gray : appTheme.gray.withValues(alpha: 0.4),
+ ),
+ ),
+ ),
+ ),
+ ),
+ if (hasActivity) ...[
+ const SizedBox(
+ height: 4,
+ ),
+ switch (day.$2.state) {
+ DiaryStateEnum.init => const SizedBox.shrink(),
+ DiaryStateEnum.started => Container(
+ width: 8,
+ height: 8,
+ decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]),
+ ),
+ DiaryStateEnum.lastDayStarted => Container(
+ width: 8,
+ height: 8,
+ decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]),
+ ),
+ DiaryStateEnum.finished => Container(
+ width: 8,
+ height: 8,
+ decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]),
+ ),
+ DiaryStateEnum.lastDayFinished => Container(
+ width: 8,
+ height: 8,
+ decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]),
+ ),
+ DiaryStateEnum.commented => Container(
+ width: 8,
+ height: 8,
+ decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]),
+ ),
+ DiaryStateEnum.lastDayCommented => Container(
+ width: 8,
+ height: 8,
+ decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]),
+ ),
+ DiaryStateEnum.reviewedByUser => Container(
+ width: 8,
+ height: 8,
+ decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[4]),
+ ),
+ },
+ const SizedBox(
+ height: 12,
+ ),
+ ] else
+ const SizedBox(
+ height: 24,
+ ),
+ ],
+ ),
+ );
+ }),
),
),
- const SizedBox(height: 16),
const CustomDivider(),
const SizedBox(height: 12),
Text(
- '${consultationsEntity.paymentAmount} ₽',
+ '${widget.consultationsModel.consultantPayment.toStringAsFixed(0)} ₽',
style: const TextStyle(
color: Color(0xFF019A3E),
fontSize: 20,
@@ -198,7 +284,19 @@ class ConsultationCardWidget extends StatelessWidget {
children: [
InkWell(
borderRadius: BorderRadius.circular(20),
- onTap: () {},
+ onTap: () {
+ final lastActiveDay = givenDaysWithDayNumber
+ .where(
+ (e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished,
+ )
+ .lastOrNull;
+ if (lastActiveDay != null) {
+ GoRouter.of(context).push(UserDiaryScreen.path, extra: lastActiveDay.$2.id);
+ return;
+ }
+ showErrorDialog(context,
+ title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
+ },
child: Ink(
width: MediaQuery.of(context).size.width - 32 - 52 - 8,
padding: const EdgeInsets.all(16),
diff --git a/lib/features/consultations/repositories/consultations_repository.dart b/lib/features/consultations/repositories/consultations_repository.dart
new file mode 100644
index 00000000..93d84f67
--- /dev/null
+++ b/lib/features/consultations/repositories/consultations_repository.dart
@@ -0,0 +1,16 @@
+import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
+import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
+
+class ConsultationsRepository {
+ final ConsultationService consultationService;
+
+ ConsultationsRepository({required this.consultationService});
+ Future> getConsultations() async {
+ try {
+ final consultations = await consultationService.getConsultations();
+ return consultations.data;
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/lib/features/consultations/services/consultation_service.dart b/lib/features/consultations/services/consultation_service.dart
new file mode 100644
index 00000000..25719f8a
--- /dev/null
+++ b/lib/features/consultations/services/consultation_service.dart
@@ -0,0 +1,14 @@
+import 'package:dio/dio.dart';
+import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
+import 'package:retrofit/retrofit.dart';
+
+part 'consultation_service.g.dart';
+
+@RestApi()
+abstract class ConsultationService {
+ factory ConsultationService(Dio dio, {String baseUrl}) = _ConsultationService;
+
+ @GET('/api/consultations')
+ Future getConsultations();
+
+}
diff --git a/lib/features/consultations/services/consultation_service.g.dart b/lib/features/consultations/services/consultation_service.g.dart
new file mode 100644
index 00000000..ff0edd2b
--- /dev/null
+++ b/lib/features/consultations/services/consultation_service.g.dart
@@ -0,0 +1,73 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'consultation_service.dart';
+
+// **************************************************************************
+// RetrofitGenerator
+// **************************************************************************
+
+// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
+
+class _ConsultationService implements ConsultationService {
+ _ConsultationService(this._dio, {this.baseUrl, this.errorLogger});
+
+ final Dio _dio;
+
+ String? baseUrl;
+
+ final ParseErrorLogger? errorLogger;
+
+ @override
+ Future getConsultations() async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(
+ Options(method: 'GET', headers: _headers, extra: _extra)
+ .compose(
+ _dio.options,
+ '/api/consultations',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+ );
+ final _result = await _dio.fetch