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/models/chat_room_model.freezed.dart b/lib/features/chat/models/chat_room_model.freezed.dart index 829dd69f..04415447 100644 --- a/lib/features/chat/models/chat_room_model.freezed.dart +++ b/lib/features/chat/models/chat_room_model.freezed.dart @@ -25,7 +25,8 @@ mixin _$ChatRoomModel { ChatRoomSettingsModel get chatRoomSettings => throw _privateConstructorUsedError; Users get users => throw _privateConstructorUsedError; - @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) + @JsonKey(name: 'chosen_plan_id') + int? get chosenPlanId => throw _privateConstructorUsedError; ChosenPlanModel? get chosenPlan => throw _privateConstructorUsedError; /// Serializes this ChatRoomModel to a JSON map. @@ -49,7 +50,7 @@ abstract class $ChatRoomModelCopyWith<$Res> { @JsonKey(name: 'chat_room_setting') ChatRoomSettingsModel chatRoomSettings, Users users, - @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) + @JsonKey(name: 'chosen_plan_id') int? chosenPlanId, ChosenPlanModel? chosenPlan}); $ChatRoomSettingsModelCopyWith<$Res> get chatRoomSettings; @@ -75,6 +76,7 @@ class _$ChatRoomModelCopyWithImpl<$Res, $Val extends ChatRoomModel> Object? id = null, Object? chatRoomSettings = null, Object? users = null, + Object? chosenPlanId = freezed, Object? chosenPlan = freezed, }) { return _then(_value.copyWith( @@ -90,6 +92,10 @@ class _$ChatRoomModelCopyWithImpl<$Res, $Val extends ChatRoomModel> ? _value.users : users // ignore: cast_nullable_to_non_nullable as Users, + chosenPlanId: freezed == chosenPlanId + ? _value.chosenPlanId + : chosenPlanId // ignore: cast_nullable_to_non_nullable + as int?, chosenPlan: freezed == chosenPlan ? _value.chosenPlan : chosenPlan // ignore: cast_nullable_to_non_nullable @@ -146,7 +152,7 @@ abstract class _$$ChatRoomModelImplCopyWith<$Res> @JsonKey(name: 'chat_room_setting') ChatRoomSettingsModel chatRoomSettings, Users users, - @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) + @JsonKey(name: 'chosen_plan_id') int? chosenPlanId, ChosenPlanModel? chosenPlan}); @override @@ -173,6 +179,7 @@ class __$$ChatRoomModelImplCopyWithImpl<$Res> Object? id = null, Object? chatRoomSettings = null, Object? users = null, + Object? chosenPlanId = freezed, Object? chosenPlan = freezed, }) { return _then(_$ChatRoomModelImpl( @@ -188,6 +195,10 @@ class __$$ChatRoomModelImplCopyWithImpl<$Res> ? _value.users : users // ignore: cast_nullable_to_non_nullable as Users, + chosenPlanId: freezed == chosenPlanId + ? _value.chosenPlanId + : chosenPlanId // ignore: cast_nullable_to_non_nullable + as int?, chosenPlan: freezed == chosenPlan ? _value.chosenPlan : chosenPlan // ignore: cast_nullable_to_non_nullable @@ -203,8 +214,8 @@ class _$ChatRoomModelImpl implements _ChatRoomModel { {required this.id, @JsonKey(name: 'chat_room_setting') required this.chatRoomSettings, required this.users, - @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) - required this.chosenPlan}); + @JsonKey(name: 'chosen_plan_id') required this.chosenPlanId, + this.chosenPlan}); factory _$ChatRoomModelImpl.fromJson(Map json) => _$$ChatRoomModelImplFromJson(json); @@ -217,12 +228,14 @@ class _$ChatRoomModelImpl implements _ChatRoomModel { @override final Users users; @override - @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) + @JsonKey(name: 'chosen_plan_id') + final int? chosenPlanId; + @override final ChosenPlanModel? chosenPlan; @override String toString() { - return 'ChatRoomModel(id: $id, chatRoomSettings: $chatRoomSettings, users: $users, chosenPlan: $chosenPlan)'; + return 'ChatRoomModel(id: $id, chatRoomSettings: $chatRoomSettings, users: $users, chosenPlanId: $chosenPlanId, chosenPlan: $chosenPlan)'; } @override @@ -234,14 +247,16 @@ class _$ChatRoomModelImpl implements _ChatRoomModel { (identical(other.chatRoomSettings, chatRoomSettings) || other.chatRoomSettings == chatRoomSettings) && (identical(other.users, users) || other.users == users) && + (identical(other.chosenPlanId, chosenPlanId) || + other.chosenPlanId == chosenPlanId) && (identical(other.chosenPlan, chosenPlan) || other.chosenPlan == chosenPlan)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, id, chatRoomSettings, users, chosenPlan); + int get hashCode => Object.hash( + runtimeType, id, chatRoomSettings, users, chosenPlanId, chosenPlan); /// Create a copy of ChatRoomModel /// with the given fields replaced by the non-null parameter values. @@ -265,8 +280,8 @@ abstract class _ChatRoomModel implements ChatRoomModel { @JsonKey(name: 'chat_room_setting') required final ChatRoomSettingsModel chatRoomSettings, required final Users users, - @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) - required final ChosenPlanModel? chosenPlan}) = _$ChatRoomModelImpl; + @JsonKey(name: 'chosen_plan_id') required final int? chosenPlanId, + final ChosenPlanModel? chosenPlan}) = _$ChatRoomModelImpl; factory _ChatRoomModel.fromJson(Map json) = _$ChatRoomModelImpl.fromJson; @@ -279,7 +294,9 @@ abstract class _ChatRoomModel implements ChatRoomModel { @override Users get users; @override - @JsonKey(name: 'chosen_plan', fromJson: parseChosenPlan) + @JsonKey(name: 'chosen_plan_id') + int? get chosenPlanId; + @override ChosenPlanModel? get chosenPlan; /// Create a copy of ChatRoomModel diff --git a/lib/features/chat/models/chat_room_model.g.dart b/lib/features/chat/models/chat_room_model.g.dart index fb9e6257..a5ce8e0d 100644 --- a/lib/features/chat/models/chat_room_model.g.dart +++ b/lib/features/chat/models/chat_room_model.g.dart @@ -12,7 +12,11 @@ _$ChatRoomModelImpl _$$ChatRoomModelImplFromJson(Map json) => chatRoomSettings: ChatRoomSettingsModel.fromJson( json['chat_room_setting'] as Map), users: Users.fromJson(json['users'] as Map), - chosenPlan: parseChosenPlan(json['chosen_plan']), + chosenPlanId: (json['chosen_plan_id'] as num?)?.toInt(), + chosenPlan: json['chosenPlan'] == null + ? null + : ChosenPlanModel.fromJson( + json['chosenPlan'] as Map), ); Map _$$ChatRoomModelImplToJson(_$ChatRoomModelImpl instance) => @@ -20,7 +24,8 @@ Map _$$ChatRoomModelImplToJson(_$ChatRoomModelImpl instance) => 'id': instance.id, 'chat_room_setting': instance.chatRoomSettings, 'users': instance.users, - 'chosen_plan': instance.chosenPlan, + 'chosen_plan_id': instance.chosenPlanId, + 'chosenPlan': instance.chosenPlan, }; _$UsersImpl _$$UsersImplFromJson(Map json) => _$UsersImpl( 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/chat/services/chat_service.g.dart b/lib/features/chat/services/chat_service.g.dart index d25bec4d..9280c2af 100644 --- a/lib/features/chat/services/chat_service.g.dart +++ b/lib/features/chat/services/chat_service.g.dart @@ -262,6 +262,33 @@ class _ChatService implements ChatService { return _value; } + @override + Future getChosenPlan({required int id}) 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/chosen_plans/show/${id}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch>(_options); + late ChosenPlanModel _value; + try { + _value = ChosenPlanModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || 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 d5b7bc18..c4dd36d7 100644 --- a/lib/features/consultations/presentation/pages/consultations_screen.dart +++ b/lib/features/consultations/presentation/pages/consultations_screen.dart @@ -1,33 +1,61 @@ -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: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: ListView.separated( - padding: const EdgeInsets.symmetric(vertical: 12), - itemBuilder: (context, index) => ConsultationCardWidget( - consultationsEntity: testData[index], - ), - separatorBuilder: (context, index) => const SizedBox( - height: 12, - ), - itemCount: testData.length, - ), + 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>(_options); + late ConsultationPaginationModel _value; + try { + _value = ConsultationPaginationModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + 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/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index a6732979..efaa4916 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -124,7 +124,7 @@ class _CreateDietScreenContentState extends State { const SizedBox( height: 4, ), - CustomErrorWidget(error: state.dietNameError ?? ''), + ValidationErrorWidget(error: state.dietNameError ?? ''), ], const SizedBox(height: 12), SizedBox( @@ -146,7 +146,7 @@ class _CreateDietScreenContentState extends State { const SizedBox( height: 4, ), - CustomErrorWidget(error: state.dietDescriptionError ?? ''), + ValidationErrorWidget(error: state.dietDescriptionError ?? ''), ], const SizedBox(height: 12), const DietDurationDropDownWidget(), diff --git a/lib/features/create_diet/presentation/widgets/day_widget.dart b/lib/features/create_diet/presentation/widgets/day_widget.dart index 2bb1f93c..68b4424b 100644 --- a/lib/features/create_diet/presentation/widgets/day_widget.dart +++ b/lib/features/create_diet/presentation/widgets/day_widget.dart @@ -218,7 +218,7 @@ class _DayWidgetState extends State { ), if (state.dailyScheduleErrors[widget.index].isNotEmpty) ...[ const SizedBox(height: 12), - CustomErrorWidget(error: state.dailyScheduleErrors[widget.index]), + ValidationErrorWidget(error: state.dailyScheduleErrors[widget.index]), ], const SizedBox(height: 20), ], diff --git a/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart b/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart index 8ab35278..0c05c61f 100644 --- a/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart @@ -68,7 +68,7 @@ class _DietDurationDropDownWidgetState extends State const SizedBox( height: 4, ), - CustomErrorWidget(error: state.dietDurationError ?? ''), + ValidationErrorWidget(error: state.dietDurationError ?? ''), ], ], ); diff --git a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart index b32a01b5..dd0e44aa 100644 --- a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart @@ -155,7 +155,7 @@ class _DietImagePickerWidgetState extends State { ), ), ), - if (state.dietImageError != null) ...[const SizedBox(height: 8), CustomErrorWidget(error: state.dietImageError ?? '')], + if (state.dietImageError != null) ...[const SizedBox(height: 8), ValidationErrorWidget(error: state.dietImageError ?? '')], ], ); }, diff --git a/lib/features/create_diet/presentation/widgets/error_widget.dart b/lib/features/create_diet/presentation/widgets/error_widget.dart index 84ba0577..cab9324e 100644 --- a/lib/features/create_diet/presentation/widgets/error_widget.dart +++ b/lib/features/create_diet/presentation/widgets/error_widget.dart @@ -3,8 +3,8 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -class CustomErrorWidget extends StatelessWidget { - const CustomErrorWidget({super.key, required this.error}); +class ValidationErrorWidget extends StatelessWidget { + const ValidationErrorWidget({super.key, required this.error}); final String error; @override Widget build(BuildContext context) { diff --git a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart index 456133e9..70af9220 100644 --- a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart +++ b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart @@ -76,7 +76,7 @@ class _StatusDropDownWidgetState extends State { const SizedBox( height: 4, ), - CustomErrorWidget(error: state.dietStateError ?? ''), + ValidationErrorWidget(error: state.dietStateError ?? ''), ], ], ); diff --git a/lib/features/create_recipe/presentation/widgets/info_about_recipe_widget.dart b/lib/features/create_recipe/presentation/widgets/info_about_recipe_widget.dart index a83ee2a3..bc32ca54 100644 --- a/lib/features/create_recipe/presentation/widgets/info_about_recipe_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/info_about_recipe_widget.dart @@ -60,7 +60,7 @@ class _InfoAboutRecipeWidgetState extends State { ), if (state.titleValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.titleValidationError!, ), ], @@ -111,7 +111,7 @@ class _InfoAboutRecipeWidgetState extends State { ), if (state.descriptionValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.descriptionValidationError!, ), ], @@ -135,7 +135,7 @@ class _InfoAboutRecipeWidgetState extends State { ), if (state.imageValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.imageValidationError!, ), ], diff --git a/lib/features/create_recipe/presentation/widgets/portion_widget.dart b/lib/features/create_recipe/presentation/widgets/portion_widget.dart index 2b2bc008..99372193 100644 --- a/lib/features/create_recipe/presentation/widgets/portion_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/portion_widget.dart @@ -55,7 +55,7 @@ class _PortionWidgetState extends State { ), if (state.servingsValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.servingsValidationError!, ), ], @@ -88,7 +88,7 @@ class _PortionWidgetState extends State { ), if (state.energyPerServingValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.energyPerServingValidationError!, ), ], diff --git a/lib/features/create_recipe/presentation/widgets/time_of_cook_widget.dart b/lib/features/create_recipe/presentation/widgets/time_of_cook_widget.dart index 64269dcc..dacd76cb 100644 --- a/lib/features/create_recipe/presentation/widgets/time_of_cook_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/time_of_cook_widget.dart @@ -82,7 +82,7 @@ class _TimeOfCookWidgetState extends State { ), if (state.cookingTimeValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.cookingTimeValidationError!, ), ], @@ -108,7 +108,7 @@ class _TimeOfCookWidgetState extends State { ), if (state.cookingTimeValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.cookingTimeValidationError!, ), ], @@ -169,7 +169,7 @@ class _TimeOfCookWidgetState extends State { ), if (state.activeTimeValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.activeTimeValidationError!, ), ], @@ -196,7 +196,7 @@ class _TimeOfCookWidgetState extends State { ), if (state.activeTimeValidationError != null) ...[ const SizedBox(height: 4), - CustomErrorWidget( + ValidationErrorWidget( error: state.activeTimeValidationError!, ), ], diff --git a/lib/features/diary/models/consultant_comment_model.dart b/lib/features/diary/models/consultant_comment_model.dart new file mode 100644 index 00000000..20cf7d39 --- /dev/null +++ b/lib/features/diary/models/consultant_comment_model.dart @@ -0,0 +1,36 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'consultant_comment_model.freezed.dart'; +part 'consultant_comment_model.g.dart'; + +@freezed +class ConsultantCommentModel with _$ConsultantCommentModel { + factory ConsultantCommentModel({ + String? comment, + @Default({}) @JsonKey(name: 'meals') Map mealsComments, + @Default([]) List portions, + }) = _ConsultantCommentModel; + + factory ConsultantCommentModel.fromJson(Map json) => _$ConsultantCommentModelFromJson(json); +} + +@freezed +class PortionCommentModel with _$PortionCommentModel { + factory PortionCommentModel({ + required String comment, + required int id, + }) = _PortionCommentModel; + + factory PortionCommentModel.fromJson(Map json) => _$PortionCommentModelFromJson(json); +} + +@freezed +class MealCommentModel with _$MealCommentModel { + + factory MealCommentModel({ + required String comment, + @JsonKey(name: 'type_no')required int typeNumber + }) = _MealCommentModel; + + factory MealCommentModel.fromJson(Map json) => _$MealCommentModelFromJson(json); +} \ No newline at end of file diff --git a/lib/features/diary/models/consultant_comment_model.freezed.dart b/lib/features/diary/models/consultant_comment_model.freezed.dart new file mode 100644 index 00000000..93b59f59 --- /dev/null +++ b/lib/features/diary/models/consultant_comment_model.freezed.dart @@ -0,0 +1,581 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'consultant_comment_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'); + +ConsultantCommentModel _$ConsultantCommentModelFromJson( + Map json) { + return _ConsultantCommentModel.fromJson(json); +} + +/// @nodoc +mixin _$ConsultantCommentModel { + String? get comment => throw _privateConstructorUsedError; + @JsonKey(name: 'meals') + Map get mealsComments => + throw _privateConstructorUsedError; + List get portions => throw _privateConstructorUsedError; + + /// Serializes this ConsultantCommentModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ConsultantCommentModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ConsultantCommentModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ConsultantCommentModelCopyWith<$Res> { + factory $ConsultantCommentModelCopyWith(ConsultantCommentModel value, + $Res Function(ConsultantCommentModel) then) = + _$ConsultantCommentModelCopyWithImpl<$Res, ConsultantCommentModel>; + @useResult + $Res call( + {String? comment, + @JsonKey(name: 'meals') Map mealsComments, + List portions}); +} + +/// @nodoc +class _$ConsultantCommentModelCopyWithImpl<$Res, + $Val extends ConsultantCommentModel> + implements $ConsultantCommentModelCopyWith<$Res> { + _$ConsultantCommentModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ConsultantCommentModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = freezed, + Object? mealsComments = null, + Object? portions = null, + }) { + return _then(_value.copyWith( + comment: freezed == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String?, + mealsComments: null == mealsComments + ? _value.mealsComments + : mealsComments // ignore: cast_nullable_to_non_nullable + as Map, + portions: null == portions + ? _value.portions + : portions // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ConsultantCommentModelImplCopyWith<$Res> + implements $ConsultantCommentModelCopyWith<$Res> { + factory _$$ConsultantCommentModelImplCopyWith( + _$ConsultantCommentModelImpl value, + $Res Function(_$ConsultantCommentModelImpl) then) = + __$$ConsultantCommentModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? comment, + @JsonKey(name: 'meals') Map mealsComments, + List portions}); +} + +/// @nodoc +class __$$ConsultantCommentModelImplCopyWithImpl<$Res> + extends _$ConsultantCommentModelCopyWithImpl<$Res, + _$ConsultantCommentModelImpl> + implements _$$ConsultantCommentModelImplCopyWith<$Res> { + __$$ConsultantCommentModelImplCopyWithImpl( + _$ConsultantCommentModelImpl _value, + $Res Function(_$ConsultantCommentModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ConsultantCommentModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = freezed, + Object? mealsComments = null, + Object? portions = null, + }) { + return _then(_$ConsultantCommentModelImpl( + comment: freezed == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String?, + mealsComments: null == mealsComments + ? _value._mealsComments + : mealsComments // ignore: cast_nullable_to_non_nullable + as Map, + portions: null == portions + ? _value._portions + : portions // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ConsultantCommentModelImpl implements _ConsultantCommentModel { + _$ConsultantCommentModelImpl( + {this.comment, + @JsonKey(name: 'meals') + final Map mealsComments = const {}, + final List portions = const []}) + : _mealsComments = mealsComments, + _portions = portions; + + factory _$ConsultantCommentModelImpl.fromJson(Map json) => + _$$ConsultantCommentModelImplFromJson(json); + + @override + final String? comment; + final Map _mealsComments; + @override + @JsonKey(name: 'meals') + Map get mealsComments { + if (_mealsComments is EqualUnmodifiableMapView) return _mealsComments; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_mealsComments); + } + + final List _portions; + @override + @JsonKey() + List get portions { + if (_portions is EqualUnmodifiableListView) return _portions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_portions); + } + + @override + String toString() { + return 'ConsultantCommentModel(comment: $comment, mealsComments: $mealsComments, portions: $portions)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ConsultantCommentModelImpl && + (identical(other.comment, comment) || other.comment == comment) && + const DeepCollectionEquality() + .equals(other._mealsComments, _mealsComments) && + const DeepCollectionEquality().equals(other._portions, _portions)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + comment, + const DeepCollectionEquality().hash(_mealsComments), + const DeepCollectionEquality().hash(_portions)); + + /// Create a copy of ConsultantCommentModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ConsultantCommentModelImplCopyWith<_$ConsultantCommentModelImpl> + get copyWith => __$$ConsultantCommentModelImplCopyWithImpl< + _$ConsultantCommentModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ConsultantCommentModelImplToJson( + this, + ); + } +} + +abstract class _ConsultantCommentModel implements ConsultantCommentModel { + factory _ConsultantCommentModel( + {final String? comment, + @JsonKey(name: 'meals') final Map mealsComments, + final List portions}) = _$ConsultantCommentModelImpl; + + factory _ConsultantCommentModel.fromJson(Map json) = + _$ConsultantCommentModelImpl.fromJson; + + @override + String? get comment; + @override + @JsonKey(name: 'meals') + Map get mealsComments; + @override + List get portions; + + /// Create a copy of ConsultantCommentModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ConsultantCommentModelImplCopyWith<_$ConsultantCommentModelImpl> + get copyWith => throw _privateConstructorUsedError; +} + +PortionCommentModel _$PortionCommentModelFromJson(Map json) { + return _PortionCommentModel.fromJson(json); +} + +/// @nodoc +mixin _$PortionCommentModel { + String get comment => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + + /// Serializes this PortionCommentModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of PortionCommentModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PortionCommentModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PortionCommentModelCopyWith<$Res> { + factory $PortionCommentModelCopyWith( + PortionCommentModel value, $Res Function(PortionCommentModel) then) = + _$PortionCommentModelCopyWithImpl<$Res, PortionCommentModel>; + @useResult + $Res call({String comment, int id}); +} + +/// @nodoc +class _$PortionCommentModelCopyWithImpl<$Res, $Val extends PortionCommentModel> + implements $PortionCommentModelCopyWith<$Res> { + _$PortionCommentModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PortionCommentModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = null, + Object? id = null, + }) { + return _then(_value.copyWith( + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PortionCommentModelImplCopyWith<$Res> + implements $PortionCommentModelCopyWith<$Res> { + factory _$$PortionCommentModelImplCopyWith(_$PortionCommentModelImpl value, + $Res Function(_$PortionCommentModelImpl) then) = + __$$PortionCommentModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String comment, int id}); +} + +/// @nodoc +class __$$PortionCommentModelImplCopyWithImpl<$Res> + extends _$PortionCommentModelCopyWithImpl<$Res, _$PortionCommentModelImpl> + implements _$$PortionCommentModelImplCopyWith<$Res> { + __$$PortionCommentModelImplCopyWithImpl(_$PortionCommentModelImpl _value, + $Res Function(_$PortionCommentModelImpl) _then) + : super(_value, _then); + + /// Create a copy of PortionCommentModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = null, + Object? id = null, + }) { + return _then(_$PortionCommentModelImpl( + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PortionCommentModelImpl implements _PortionCommentModel { + _$PortionCommentModelImpl({required this.comment, required this.id}); + + factory _$PortionCommentModelImpl.fromJson(Map json) => + _$$PortionCommentModelImplFromJson(json); + + @override + final String comment; + @override + final int id; + + @override + String toString() { + return 'PortionCommentModel(comment: $comment, id: $id)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PortionCommentModelImpl && + (identical(other.comment, comment) || other.comment == comment) && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, comment, id); + + /// Create a copy of PortionCommentModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PortionCommentModelImplCopyWith<_$PortionCommentModelImpl> get copyWith => + __$$PortionCommentModelImplCopyWithImpl<_$PortionCommentModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$PortionCommentModelImplToJson( + this, + ); + } +} + +abstract class _PortionCommentModel implements PortionCommentModel { + factory _PortionCommentModel( + {required final String comment, + required final int id}) = _$PortionCommentModelImpl; + + factory _PortionCommentModel.fromJson(Map json) = + _$PortionCommentModelImpl.fromJson; + + @override + String get comment; + @override + int get id; + + /// Create a copy of PortionCommentModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PortionCommentModelImplCopyWith<_$PortionCommentModelImpl> get copyWith => + throw _privateConstructorUsedError; +} + +MealCommentModel _$MealCommentModelFromJson(Map json) { + return _MealCommentModel.fromJson(json); +} + +/// @nodoc +mixin _$MealCommentModel { + String get comment => throw _privateConstructorUsedError; + @JsonKey(name: 'type_no') + int get typeNumber => throw _privateConstructorUsedError; + + /// Serializes this MealCommentModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of MealCommentModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $MealCommentModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MealCommentModelCopyWith<$Res> { + factory $MealCommentModelCopyWith( + MealCommentModel value, $Res Function(MealCommentModel) then) = + _$MealCommentModelCopyWithImpl<$Res, MealCommentModel>; + @useResult + $Res call({String comment, @JsonKey(name: 'type_no') int typeNumber}); +} + +/// @nodoc +class _$MealCommentModelCopyWithImpl<$Res, $Val extends MealCommentModel> + implements $MealCommentModelCopyWith<$Res> { + _$MealCommentModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of MealCommentModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = null, + Object? typeNumber = null, + }) { + return _then(_value.copyWith( + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + typeNumber: null == typeNumber + ? _value.typeNumber + : typeNumber // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MealCommentModelImplCopyWith<$Res> + implements $MealCommentModelCopyWith<$Res> { + factory _$$MealCommentModelImplCopyWith(_$MealCommentModelImpl value, + $Res Function(_$MealCommentModelImpl) then) = + __$$MealCommentModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String comment, @JsonKey(name: 'type_no') int typeNumber}); +} + +/// @nodoc +class __$$MealCommentModelImplCopyWithImpl<$Res> + extends _$MealCommentModelCopyWithImpl<$Res, _$MealCommentModelImpl> + implements _$$MealCommentModelImplCopyWith<$Res> { + __$$MealCommentModelImplCopyWithImpl(_$MealCommentModelImpl _value, + $Res Function(_$MealCommentModelImpl) _then) + : super(_value, _then); + + /// Create a copy of MealCommentModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = null, + Object? typeNumber = null, + }) { + return _then(_$MealCommentModelImpl( + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + typeNumber: null == typeNumber + ? _value.typeNumber + : typeNumber // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MealCommentModelImpl implements _MealCommentModel { + _$MealCommentModelImpl( + {required this.comment, + @JsonKey(name: 'type_no') required this.typeNumber}); + + factory _$MealCommentModelImpl.fromJson(Map json) => + _$$MealCommentModelImplFromJson(json); + + @override + final String comment; + @override + @JsonKey(name: 'type_no') + final int typeNumber; + + @override + String toString() { + return 'MealCommentModel(comment: $comment, typeNumber: $typeNumber)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MealCommentModelImpl && + (identical(other.comment, comment) || other.comment == comment) && + (identical(other.typeNumber, typeNumber) || + other.typeNumber == typeNumber)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, comment, typeNumber); + + /// Create a copy of MealCommentModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MealCommentModelImplCopyWith<_$MealCommentModelImpl> get copyWith => + __$$MealCommentModelImplCopyWithImpl<_$MealCommentModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$MealCommentModelImplToJson( + this, + ); + } +} + +abstract class _MealCommentModel implements MealCommentModel { + factory _MealCommentModel( + {required final String comment, + @JsonKey(name: 'type_no') required final int typeNumber}) = + _$MealCommentModelImpl; + + factory _MealCommentModel.fromJson(Map json) = + _$MealCommentModelImpl.fromJson; + + @override + String get comment; + @override + @JsonKey(name: 'type_no') + int get typeNumber; + + /// Create a copy of MealCommentModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MealCommentModelImplCopyWith<_$MealCommentModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/diary/models/consultant_comment_model.g.dart b/lib/features/diary/models/consultant_comment_model.g.dart new file mode 100644 index 00000000..4ea3943a --- /dev/null +++ b/lib/features/diary/models/consultant_comment_model.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'consultant_comment_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ConsultantCommentModelImpl _$$ConsultantCommentModelImplFromJson( + Map json) => + _$ConsultantCommentModelImpl( + comment: json['comment'] as String?, + mealsComments: (json['meals'] as Map?)?.map( + (k, e) => MapEntry( + k, MealCommentModel.fromJson(e as Map)), + ) ?? + const {}, + portions: (json['portions'] as List?) + ?.map((e) => + PortionCommentModel.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$$ConsultantCommentModelImplToJson( + _$ConsultantCommentModelImpl instance) => + { + 'comment': instance.comment, + 'meals': instance.mealsComments, + 'portions': instance.portions, + }; + +_$PortionCommentModelImpl _$$PortionCommentModelImplFromJson( + Map json) => + _$PortionCommentModelImpl( + comment: json['comment'] as String, + id: (json['id'] as num).toInt(), + ); + +Map _$$PortionCommentModelImplToJson( + _$PortionCommentModelImpl instance) => + { + 'comment': instance.comment, + 'id': instance.id, + }; + +_$MealCommentModelImpl _$$MealCommentModelImplFromJson( + Map json) => + _$MealCommentModelImpl( + comment: json['comment'] as String, + typeNumber: (json['type_no'] as num).toInt(), + ); + +Map _$$MealCommentModelImplToJson( + _$MealCommentModelImpl instance) => + { + 'comment': instance.comment, + 'type_no': instance.typeNumber, + }; diff --git a/lib/features/diary/models/consultant_review_model.dart b/lib/features/diary/models/consultant_review_model.dart new file mode 100644 index 00000000..95b12813 --- /dev/null +++ b/lib/features/diary/models/consultant_review_model.dart @@ -0,0 +1,53 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'consultant_review_model.freezed.dart'; +part 'consultant_review_model.g.dart'; + +@freezed +class ConsultantReviewModel with _$ConsultantReviewModel { + + factory ConsultantReviewModel({ + required String planName, + required int planDuration, + required int currentPlanDay, + required ConsultantComments consultantComments, + }) = _ConsultantReviewModel; + + factory ConsultantReviewModel.fromJson(Map json) => _$ConsultantReviewModelFromJson(json); +} + + +@freezed +class ConsultantComments with _$ConsultantComments { + + factory ConsultantComments({ + required String? dayComment, + required List mealsComments, + required List portionsComments, + }) = _ConsultantComments; + + factory ConsultantComments.fromJson(Map json) => _$ConsultantCommentsFromJson(json); +} + + + +@freezed +class MealComment with _$MealComment { + + factory MealComment({ + required String comment, + }) = _MealComment; + + factory MealComment.fromJson(Map json) => _$MealCommentFromJson(json); +} + +@freezed +class PortionComment with _$PortionComment { + + factory PortionComment({ + @JsonKey(name: 'commentable_id') required int portionId, + required String comment, + }) = _PortionComment; + + factory PortionComment.fromJson(Map json) => _$PortionCommentFromJson(json); +} \ No newline at end of file diff --git a/lib/features/diary/models/consultant_review_model.freezed.dart b/lib/features/diary/models/consultant_review_model.freezed.dart new file mode 100644 index 00000000..96310b65 --- /dev/null +++ b/lib/features/diary/models/consultant_review_model.freezed.dart @@ -0,0 +1,796 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'consultant_review_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ConsultantReviewModel _$ConsultantReviewModelFromJson( + Map json) { + return _ConsultantReviewModel.fromJson(json); +} + +/// @nodoc +mixin _$ConsultantReviewModel { + String get planName => throw _privateConstructorUsedError; + int get planDuration => throw _privateConstructorUsedError; + int get currentPlanDay => throw _privateConstructorUsedError; + ConsultantComments get consultantComments => + throw _privateConstructorUsedError; + + /// Serializes this ConsultantReviewModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ConsultantReviewModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ConsultantReviewModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ConsultantReviewModelCopyWith<$Res> { + factory $ConsultantReviewModelCopyWith(ConsultantReviewModel value, + $Res Function(ConsultantReviewModel) then) = + _$ConsultantReviewModelCopyWithImpl<$Res, ConsultantReviewModel>; + @useResult + $Res call( + {String planName, + int planDuration, + int currentPlanDay, + ConsultantComments consultantComments}); + + $ConsultantCommentsCopyWith<$Res> get consultantComments; +} + +/// @nodoc +class _$ConsultantReviewModelCopyWithImpl<$Res, + $Val extends ConsultantReviewModel> + implements $ConsultantReviewModelCopyWith<$Res> { + _$ConsultantReviewModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ConsultantReviewModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? planName = null, + Object? planDuration = null, + Object? currentPlanDay = null, + Object? consultantComments = null, + }) { + return _then(_value.copyWith( + planName: null == planName + ? _value.planName + : planName // ignore: cast_nullable_to_non_nullable + as String, + planDuration: null == planDuration + ? _value.planDuration + : planDuration // ignore: cast_nullable_to_non_nullable + as int, + currentPlanDay: null == currentPlanDay + ? _value.currentPlanDay + : currentPlanDay // ignore: cast_nullable_to_non_nullable + as int, + consultantComments: null == consultantComments + ? _value.consultantComments + : consultantComments // ignore: cast_nullable_to_non_nullable + as ConsultantComments, + ) as $Val); + } + + /// Create a copy of ConsultantReviewModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ConsultantCommentsCopyWith<$Res> get consultantComments { + return $ConsultantCommentsCopyWith<$Res>(_value.consultantComments, + (value) { + return _then(_value.copyWith(consultantComments: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ConsultantReviewModelImplCopyWith<$Res> + implements $ConsultantReviewModelCopyWith<$Res> { + factory _$$ConsultantReviewModelImplCopyWith( + _$ConsultantReviewModelImpl value, + $Res Function(_$ConsultantReviewModelImpl) then) = + __$$ConsultantReviewModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String planName, + int planDuration, + int currentPlanDay, + ConsultantComments consultantComments}); + + @override + $ConsultantCommentsCopyWith<$Res> get consultantComments; +} + +/// @nodoc +class __$$ConsultantReviewModelImplCopyWithImpl<$Res> + extends _$ConsultantReviewModelCopyWithImpl<$Res, + _$ConsultantReviewModelImpl> + implements _$$ConsultantReviewModelImplCopyWith<$Res> { + __$$ConsultantReviewModelImplCopyWithImpl(_$ConsultantReviewModelImpl _value, + $Res Function(_$ConsultantReviewModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ConsultantReviewModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? planName = null, + Object? planDuration = null, + Object? currentPlanDay = null, + Object? consultantComments = null, + }) { + return _then(_$ConsultantReviewModelImpl( + planName: null == planName + ? _value.planName + : planName // ignore: cast_nullable_to_non_nullable + as String, + planDuration: null == planDuration + ? _value.planDuration + : planDuration // ignore: cast_nullable_to_non_nullable + as int, + currentPlanDay: null == currentPlanDay + ? _value.currentPlanDay + : currentPlanDay // ignore: cast_nullable_to_non_nullable + as int, + consultantComments: null == consultantComments + ? _value.consultantComments + : consultantComments // ignore: cast_nullable_to_non_nullable + as ConsultantComments, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ConsultantReviewModelImpl implements _ConsultantReviewModel { + _$ConsultantReviewModelImpl( + {required this.planName, + required this.planDuration, + required this.currentPlanDay, + required this.consultantComments}); + + factory _$ConsultantReviewModelImpl.fromJson(Map json) => + _$$ConsultantReviewModelImplFromJson(json); + + @override + final String planName; + @override + final int planDuration; + @override + final int currentPlanDay; + @override + final ConsultantComments consultantComments; + + @override + String toString() { + return 'ConsultantReviewModel(planName: $planName, planDuration: $planDuration, currentPlanDay: $currentPlanDay, consultantComments: $consultantComments)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ConsultantReviewModelImpl && + (identical(other.planName, planName) || + other.planName == planName) && + (identical(other.planDuration, planDuration) || + other.planDuration == planDuration) && + (identical(other.currentPlanDay, currentPlanDay) || + other.currentPlanDay == currentPlanDay) && + (identical(other.consultantComments, consultantComments) || + other.consultantComments == consultantComments)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, planName, planDuration, currentPlanDay, consultantComments); + + /// Create a copy of ConsultantReviewModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ConsultantReviewModelImplCopyWith<_$ConsultantReviewModelImpl> + get copyWith => __$$ConsultantReviewModelImplCopyWithImpl< + _$ConsultantReviewModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ConsultantReviewModelImplToJson( + this, + ); + } +} + +abstract class _ConsultantReviewModel implements ConsultantReviewModel { + factory _ConsultantReviewModel( + {required final String planName, + required final int planDuration, + required final int currentPlanDay, + required final ConsultantComments consultantComments}) = + _$ConsultantReviewModelImpl; + + factory _ConsultantReviewModel.fromJson(Map json) = + _$ConsultantReviewModelImpl.fromJson; + + @override + String get planName; + @override + int get planDuration; + @override + int get currentPlanDay; + @override + ConsultantComments get consultantComments; + + /// Create a copy of ConsultantReviewModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ConsultantReviewModelImplCopyWith<_$ConsultantReviewModelImpl> + get copyWith => throw _privateConstructorUsedError; +} + +ConsultantComments _$ConsultantCommentsFromJson(Map json) { + return _ConsultantComments.fromJson(json); +} + +/// @nodoc +mixin _$ConsultantComments { + String? get dayComment => throw _privateConstructorUsedError; + List get mealsComments => throw _privateConstructorUsedError; + List get portionsComments => + throw _privateConstructorUsedError; + + /// Serializes this ConsultantComments to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ConsultantComments + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ConsultantCommentsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ConsultantCommentsCopyWith<$Res> { + factory $ConsultantCommentsCopyWith( + ConsultantComments value, $Res Function(ConsultantComments) then) = + _$ConsultantCommentsCopyWithImpl<$Res, ConsultantComments>; + @useResult + $Res call( + {String? dayComment, + List mealsComments, + List portionsComments}); +} + +/// @nodoc +class _$ConsultantCommentsCopyWithImpl<$Res, $Val extends ConsultantComments> + implements $ConsultantCommentsCopyWith<$Res> { + _$ConsultantCommentsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ConsultantComments + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayComment = freezed, + Object? mealsComments = null, + Object? portionsComments = null, + }) { + return _then(_value.copyWith( + dayComment: freezed == dayComment + ? _value.dayComment + : dayComment // ignore: cast_nullable_to_non_nullable + as String?, + mealsComments: null == mealsComments + ? _value.mealsComments + : mealsComments // ignore: cast_nullable_to_non_nullable + as List, + portionsComments: null == portionsComments + ? _value.portionsComments + : portionsComments // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ConsultantCommentsImplCopyWith<$Res> + implements $ConsultantCommentsCopyWith<$Res> { + factory _$$ConsultantCommentsImplCopyWith(_$ConsultantCommentsImpl value, + $Res Function(_$ConsultantCommentsImpl) then) = + __$$ConsultantCommentsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? dayComment, + List mealsComments, + List portionsComments}); +} + +/// @nodoc +class __$$ConsultantCommentsImplCopyWithImpl<$Res> + extends _$ConsultantCommentsCopyWithImpl<$Res, _$ConsultantCommentsImpl> + implements _$$ConsultantCommentsImplCopyWith<$Res> { + __$$ConsultantCommentsImplCopyWithImpl(_$ConsultantCommentsImpl _value, + $Res Function(_$ConsultantCommentsImpl) _then) + : super(_value, _then); + + /// Create a copy of ConsultantComments + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayComment = freezed, + Object? mealsComments = null, + Object? portionsComments = null, + }) { + return _then(_$ConsultantCommentsImpl( + dayComment: freezed == dayComment + ? _value.dayComment + : dayComment // ignore: cast_nullable_to_non_nullable + as String?, + mealsComments: null == mealsComments + ? _value._mealsComments + : mealsComments // ignore: cast_nullable_to_non_nullable + as List, + portionsComments: null == portionsComments + ? _value._portionsComments + : portionsComments // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ConsultantCommentsImpl implements _ConsultantComments { + _$ConsultantCommentsImpl( + {required this.dayComment, + required final List mealsComments, + required final List portionsComments}) + : _mealsComments = mealsComments, + _portionsComments = portionsComments; + + factory _$ConsultantCommentsImpl.fromJson(Map json) => + _$$ConsultantCommentsImplFromJson(json); + + @override + final String? dayComment; + final List _mealsComments; + @override + List get mealsComments { + if (_mealsComments is EqualUnmodifiableListView) return _mealsComments; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_mealsComments); + } + + final List _portionsComments; + @override + List get portionsComments { + if (_portionsComments is EqualUnmodifiableListView) + return _portionsComments; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_portionsComments); + } + + @override + String toString() { + return 'ConsultantComments(dayComment: $dayComment, mealsComments: $mealsComments, portionsComments: $portionsComments)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ConsultantCommentsImpl && + (identical(other.dayComment, dayComment) || + other.dayComment == dayComment) && + const DeepCollectionEquality() + .equals(other._mealsComments, _mealsComments) && + const DeepCollectionEquality() + .equals(other._portionsComments, _portionsComments)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + dayComment, + const DeepCollectionEquality().hash(_mealsComments), + const DeepCollectionEquality().hash(_portionsComments)); + + /// Create a copy of ConsultantComments + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ConsultantCommentsImplCopyWith<_$ConsultantCommentsImpl> get copyWith => + __$$ConsultantCommentsImplCopyWithImpl<_$ConsultantCommentsImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ConsultantCommentsImplToJson( + this, + ); + } +} + +abstract class _ConsultantComments implements ConsultantComments { + factory _ConsultantComments( + {required final String? dayComment, + required final List mealsComments, + required final List portionsComments}) = + _$ConsultantCommentsImpl; + + factory _ConsultantComments.fromJson(Map json) = + _$ConsultantCommentsImpl.fromJson; + + @override + String? get dayComment; + @override + List get mealsComments; + @override + List get portionsComments; + + /// Create a copy of ConsultantComments + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ConsultantCommentsImplCopyWith<_$ConsultantCommentsImpl> get copyWith => + throw _privateConstructorUsedError; +} + +MealComment _$MealCommentFromJson(Map json) { + return _MealComment.fromJson(json); +} + +/// @nodoc +mixin _$MealComment { + String get comment => throw _privateConstructorUsedError; + + /// Serializes this MealComment to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of MealComment + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $MealCommentCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MealCommentCopyWith<$Res> { + factory $MealCommentCopyWith( + MealComment value, $Res Function(MealComment) then) = + _$MealCommentCopyWithImpl<$Res, MealComment>; + @useResult + $Res call({String comment}); +} + +/// @nodoc +class _$MealCommentCopyWithImpl<$Res, $Val extends MealComment> + implements $MealCommentCopyWith<$Res> { + _$MealCommentCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of MealComment + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = null, + }) { + return _then(_value.copyWith( + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MealCommentImplCopyWith<$Res> + implements $MealCommentCopyWith<$Res> { + factory _$$MealCommentImplCopyWith( + _$MealCommentImpl value, $Res Function(_$MealCommentImpl) then) = + __$$MealCommentImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String comment}); +} + +/// @nodoc +class __$$MealCommentImplCopyWithImpl<$Res> + extends _$MealCommentCopyWithImpl<$Res, _$MealCommentImpl> + implements _$$MealCommentImplCopyWith<$Res> { + __$$MealCommentImplCopyWithImpl( + _$MealCommentImpl _value, $Res Function(_$MealCommentImpl) _then) + : super(_value, _then); + + /// Create a copy of MealComment + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? comment = null, + }) { + return _then(_$MealCommentImpl( + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MealCommentImpl implements _MealComment { + _$MealCommentImpl({required this.comment}); + + factory _$MealCommentImpl.fromJson(Map json) => + _$$MealCommentImplFromJson(json); + + @override + final String comment; + + @override + String toString() { + return 'MealComment(comment: $comment)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MealCommentImpl && + (identical(other.comment, comment) || other.comment == comment)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, comment); + + /// Create a copy of MealComment + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MealCommentImplCopyWith<_$MealCommentImpl> get copyWith => + __$$MealCommentImplCopyWithImpl<_$MealCommentImpl>(this, _$identity); + + @override + Map toJson() { + return _$$MealCommentImplToJson( + this, + ); + } +} + +abstract class _MealComment implements MealComment { + factory _MealComment({required final String comment}) = _$MealCommentImpl; + + factory _MealComment.fromJson(Map json) = + _$MealCommentImpl.fromJson; + + @override + String get comment; + + /// Create a copy of MealComment + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MealCommentImplCopyWith<_$MealCommentImpl> get copyWith => + throw _privateConstructorUsedError; +} + +PortionComment _$PortionCommentFromJson(Map json) { + return _PortionComment.fromJson(json); +} + +/// @nodoc +mixin _$PortionComment { + @JsonKey(name: 'commentable_id') + int get portionId => throw _privateConstructorUsedError; + String get comment => throw _privateConstructorUsedError; + + /// Serializes this PortionComment to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of PortionComment + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PortionCommentCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PortionCommentCopyWith<$Res> { + factory $PortionCommentCopyWith( + PortionComment value, $Res Function(PortionComment) then) = + _$PortionCommentCopyWithImpl<$Res, PortionComment>; + @useResult + $Res call({@JsonKey(name: 'commentable_id') int portionId, String comment}); +} + +/// @nodoc +class _$PortionCommentCopyWithImpl<$Res, $Val extends PortionComment> + implements $PortionCommentCopyWith<$Res> { + _$PortionCommentCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PortionComment + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? portionId = null, + Object? comment = null, + }) { + return _then(_value.copyWith( + portionId: null == portionId + ? _value.portionId + : portionId // ignore: cast_nullable_to_non_nullable + as int, + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PortionCommentImplCopyWith<$Res> + implements $PortionCommentCopyWith<$Res> { + factory _$$PortionCommentImplCopyWith(_$PortionCommentImpl value, + $Res Function(_$PortionCommentImpl) then) = + __$$PortionCommentImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({@JsonKey(name: 'commentable_id') int portionId, String comment}); +} + +/// @nodoc +class __$$PortionCommentImplCopyWithImpl<$Res> + extends _$PortionCommentCopyWithImpl<$Res, _$PortionCommentImpl> + implements _$$PortionCommentImplCopyWith<$Res> { + __$$PortionCommentImplCopyWithImpl( + _$PortionCommentImpl _value, $Res Function(_$PortionCommentImpl) _then) + : super(_value, _then); + + /// Create a copy of PortionComment + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? portionId = null, + Object? comment = null, + }) { + return _then(_$PortionCommentImpl( + portionId: null == portionId + ? _value.portionId + : portionId // ignore: cast_nullable_to_non_nullable + as int, + comment: null == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PortionCommentImpl implements _PortionComment { + _$PortionCommentImpl( + {@JsonKey(name: 'commentable_id') required this.portionId, + required this.comment}); + + factory _$PortionCommentImpl.fromJson(Map json) => + _$$PortionCommentImplFromJson(json); + + @override + @JsonKey(name: 'commentable_id') + final int portionId; + @override + final String comment; + + @override + String toString() { + return 'PortionComment(portionId: $portionId, comment: $comment)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PortionCommentImpl && + (identical(other.portionId, portionId) || + other.portionId == portionId) && + (identical(other.comment, comment) || other.comment == comment)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, portionId, comment); + + /// Create a copy of PortionComment + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PortionCommentImplCopyWith<_$PortionCommentImpl> get copyWith => + __$$PortionCommentImplCopyWithImpl<_$PortionCommentImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$PortionCommentImplToJson( + this, + ); + } +} + +abstract class _PortionComment implements PortionComment { + factory _PortionComment( + {@JsonKey(name: 'commentable_id') required final int portionId, + required final String comment}) = _$PortionCommentImpl; + + factory _PortionComment.fromJson(Map json) = + _$PortionCommentImpl.fromJson; + + @override + @JsonKey(name: 'commentable_id') + int get portionId; + @override + String get comment; + + /// Create a copy of PortionComment + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PortionCommentImplCopyWith<_$PortionCommentImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/diary/models/consultant_review_model.g.dart b/lib/features/diary/models/consultant_review_model.g.dart new file mode 100644 index 00000000..f08faeb1 --- /dev/null +++ b/lib/features/diary/models/consultant_review_model.g.dart @@ -0,0 +1,69 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'consultant_review_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ConsultantReviewModelImpl _$$ConsultantReviewModelImplFromJson( + Map json) => + _$ConsultantReviewModelImpl( + planName: json['planName'] as String, + planDuration: (json['planDuration'] as num).toInt(), + currentPlanDay: (json['currentPlanDay'] as num).toInt(), + consultantComments: ConsultantComments.fromJson( + json['consultantComments'] as Map), + ); + +Map _$$ConsultantReviewModelImplToJson( + _$ConsultantReviewModelImpl instance) => + { + 'planName': instance.planName, + 'planDuration': instance.planDuration, + 'currentPlanDay': instance.currentPlanDay, + 'consultantComments': instance.consultantComments, + }; + +_$ConsultantCommentsImpl _$$ConsultantCommentsImplFromJson( + Map json) => + _$ConsultantCommentsImpl( + dayComment: json['dayComment'] as String?, + mealsComments: (json['mealsComments'] as List) + .map((e) => MealComment.fromJson(e as Map)) + .toList(), + portionsComments: (json['portionsComments'] as List) + .map((e) => PortionComment.fromJson(e as Map)) + .toList(), + ); + +Map _$$ConsultantCommentsImplToJson( + _$ConsultantCommentsImpl instance) => + { + 'dayComment': instance.dayComment, + 'mealsComments': instance.mealsComments, + 'portionsComments': instance.portionsComments, + }; + +_$MealCommentImpl _$$MealCommentImplFromJson(Map json) => + _$MealCommentImpl( + comment: json['comment'] as String, + ); + +Map _$$MealCommentImplToJson(_$MealCommentImpl instance) => + { + 'comment': instance.comment, + }; + +_$PortionCommentImpl _$$PortionCommentImplFromJson(Map json) => + _$PortionCommentImpl( + portionId: (json['commentable_id'] as num).toInt(), + comment: json['comment'] as String, + ); + +Map _$$PortionCommentImplToJson( + _$PortionCommentImpl instance) => + { + 'commentable_id': instance.portionId, + 'comment': instance.comment, + }; diff --git a/lib/features/diary/models/diary_day_model.dart b/lib/features/diary/models/diary_day_model.dart index fe742176..dbed9b6f 100644 --- a/lib/features/diary/models/diary_day_model.dart +++ b/lib/features/diary/models/diary_day_model.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'diary_day_model.freezed.dart'; @@ -39,6 +40,7 @@ class DiaryDayModel with _$DiaryDayModel { @JsonKey(name: "comment") required String comment, @JsonKey(name: "state") required TypeOfDiaryModel typeOfDiary, @JsonKey(name: "meals") required List meals, + @JsonKey(name: "chosen_plan_id") required int? chosenPlanId, }) = _DiaryDayModel; factory DiaryDayModel.fromJson(Map json) => _$DiaryDayModelFromJson(json); @@ -91,6 +93,7 @@ class TypeOfDiaryModel with _$TypeOfDiaryModel { const factory TypeOfDiaryModel({ @JsonKey(name: "has_chosen_diet") required bool hasChosenDiet, @JsonKey(name: "has_chosen_plan") required bool hasChosenPlan, + @JsonKey(name: "last_state") required DiaryStateEnum diaryState, }) = _TypeOfDiaryModel; factory TypeOfDiaryModel.fromJson(Map json) => _$TypeOfDiaryModelFromJson(json); diff --git a/lib/features/diary/models/diary_day_model.freezed.dart b/lib/features/diary/models/diary_day_model.freezed.dart index 38983ff8..8b5ce178 100644 --- a/lib/features/diary/models/diary_day_model.freezed.dart +++ b/lib/features/diary/models/diary_day_model.freezed.dart @@ -209,6 +209,8 @@ mixin _$DiaryDayModel { TypeOfDiaryModel get typeOfDiary => throw _privateConstructorUsedError; @JsonKey(name: "meals") List get meals => throw _privateConstructorUsedError; + @JsonKey(name: "chosen_plan_id") + int? get chosenPlanId => throw _privateConstructorUsedError; /// Serializes this DiaryDayModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -234,7 +236,8 @@ abstract class $DiaryDayModelCopyWith<$Res> { @JsonKey(name: "closed") int closed, @JsonKey(name: "comment") String comment, @JsonKey(name: "state") TypeOfDiaryModel typeOfDiary, - @JsonKey(name: "meals") List meals}); + @JsonKey(name: "meals") List meals, + @JsonKey(name: "chosen_plan_id") int? chosenPlanId}); $TypeOfDiaryModelCopyWith<$Res> get typeOfDiary; } @@ -262,6 +265,7 @@ class _$DiaryDayModelCopyWithImpl<$Res, $Val extends DiaryDayModel> Object? comment = null, Object? typeOfDiary = null, Object? meals = null, + Object? chosenPlanId = freezed, }) { return _then(_value.copyWith( id: null == id @@ -296,6 +300,10 @@ class _$DiaryDayModelCopyWithImpl<$Res, $Val extends DiaryDayModel> ? _value.meals : meals // ignore: cast_nullable_to_non_nullable as List, + chosenPlanId: freezed == chosenPlanId + ? _value.chosenPlanId + : chosenPlanId // ignore: cast_nullable_to_non_nullable + as int?, ) as $Val); } @@ -326,7 +334,8 @@ abstract class _$$DiaryDayModelImplCopyWith<$Res> @JsonKey(name: "closed") int closed, @JsonKey(name: "comment") String comment, @JsonKey(name: "state") TypeOfDiaryModel typeOfDiary, - @JsonKey(name: "meals") List meals}); + @JsonKey(name: "meals") List meals, + @JsonKey(name: "chosen_plan_id") int? chosenPlanId}); @override $TypeOfDiaryModelCopyWith<$Res> get typeOfDiary; @@ -353,6 +362,7 @@ class __$$DiaryDayModelImplCopyWithImpl<$Res> Object? comment = null, Object? typeOfDiary = null, Object? meals = null, + Object? chosenPlanId = freezed, }) { return _then(_$DiaryDayModelImpl( id: null == id @@ -387,6 +397,10 @@ class __$$DiaryDayModelImplCopyWithImpl<$Res> ? _value._meals : meals // ignore: cast_nullable_to_non_nullable as List, + chosenPlanId: freezed == chosenPlanId + ? _value.chosenPlanId + : chosenPlanId // ignore: cast_nullable_to_non_nullable + as int?, )); } } @@ -402,7 +416,8 @@ class _$DiaryDayModelImpl implements _DiaryDayModel { @JsonKey(name: "closed") required this.closed, @JsonKey(name: "comment") required this.comment, @JsonKey(name: "state") required this.typeOfDiary, - @JsonKey(name: "meals") required final List meals}) + @JsonKey(name: "meals") required final List meals, + @JsonKey(name: "chosen_plan_id") required this.chosenPlanId}) : _meals = meals; factory _$DiaryDayModelImpl.fromJson(Map json) => @@ -438,9 +453,13 @@ class _$DiaryDayModelImpl implements _DiaryDayModel { return EqualUnmodifiableListView(_meals); } + @override + @JsonKey(name: "chosen_plan_id") + final int? chosenPlanId; + @override String toString() { - return 'DiaryDayModel(id: $id, date: $date, wakeup: $wakeup, retire: $retire, closed: $closed, comment: $comment, typeOfDiary: $typeOfDiary, meals: $meals)'; + return 'DiaryDayModel(id: $id, date: $date, wakeup: $wakeup, retire: $retire, closed: $closed, comment: $comment, typeOfDiary: $typeOfDiary, meals: $meals, chosenPlanId: $chosenPlanId)'; } @override @@ -456,13 +475,24 @@ class _$DiaryDayModelImpl implements _DiaryDayModel { (identical(other.comment, comment) || other.comment == comment) && (identical(other.typeOfDiary, typeOfDiary) || other.typeOfDiary == typeOfDiary) && - const DeepCollectionEquality().equals(other._meals, _meals)); + const DeepCollectionEquality().equals(other._meals, _meals) && + (identical(other.chosenPlanId, chosenPlanId) || + other.chosenPlanId == chosenPlanId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, id, date, wakeup, retire, closed, - comment, typeOfDiary, const DeepCollectionEquality().hash(_meals)); + int get hashCode => Object.hash( + runtimeType, + id, + date, + wakeup, + retire, + closed, + comment, + typeOfDiary, + const DeepCollectionEquality().hash(_meals), + chosenPlanId); /// Create a copy of DiaryDayModel /// with the given fields replaced by the non-null parameter values. @@ -489,7 +519,8 @@ abstract class _DiaryDayModel implements DiaryDayModel { @JsonKey(name: "closed") required final int closed, @JsonKey(name: "comment") required final String comment, @JsonKey(name: "state") required final TypeOfDiaryModel typeOfDiary, - @JsonKey(name: "meals") required final List meals}) = + @JsonKey(name: "meals") required final List meals, + @JsonKey(name: "chosen_plan_id") required final int? chosenPlanId}) = _$DiaryDayModelImpl; factory _DiaryDayModel.fromJson(Map json) = @@ -519,6 +550,9 @@ abstract class _DiaryDayModel implements DiaryDayModel { @override @JsonKey(name: "meals") List get meals; + @override + @JsonKey(name: "chosen_plan_id") + int? get chosenPlanId; /// Create a copy of DiaryDayModel /// with the given fields replaced by the non-null parameter values. @@ -1387,6 +1421,8 @@ mixin _$TypeOfDiaryModel { bool get hasChosenDiet => throw _privateConstructorUsedError; @JsonKey(name: "has_chosen_plan") bool get hasChosenPlan => throw _privateConstructorUsedError; + @JsonKey(name: "last_state") + DiaryStateEnum get diaryState => throw _privateConstructorUsedError; /// Serializes this TypeOfDiaryModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -1406,7 +1442,8 @@ abstract class $TypeOfDiaryModelCopyWith<$Res> { @useResult $Res call( {@JsonKey(name: "has_chosen_diet") bool hasChosenDiet, - @JsonKey(name: "has_chosen_plan") bool hasChosenPlan}); + @JsonKey(name: "has_chosen_plan") bool hasChosenPlan, + @JsonKey(name: "last_state") DiaryStateEnum diaryState}); } /// @nodoc @@ -1426,6 +1463,7 @@ class _$TypeOfDiaryModelCopyWithImpl<$Res, $Val extends TypeOfDiaryModel> $Res call({ Object? hasChosenDiet = null, Object? hasChosenPlan = null, + Object? diaryState = null, }) { return _then(_value.copyWith( hasChosenDiet: null == hasChosenDiet @@ -1436,6 +1474,10 @@ class _$TypeOfDiaryModelCopyWithImpl<$Res, $Val extends TypeOfDiaryModel> ? _value.hasChosenPlan : hasChosenPlan // ignore: cast_nullable_to_non_nullable as bool, + diaryState: null == diaryState + ? _value.diaryState + : diaryState // ignore: cast_nullable_to_non_nullable + as DiaryStateEnum, ) as $Val); } } @@ -1450,7 +1492,8 @@ abstract class _$$TypeOfDiaryModelImplCopyWith<$Res> @useResult $Res call( {@JsonKey(name: "has_chosen_diet") bool hasChosenDiet, - @JsonKey(name: "has_chosen_plan") bool hasChosenPlan}); + @JsonKey(name: "has_chosen_plan") bool hasChosenPlan, + @JsonKey(name: "last_state") DiaryStateEnum diaryState}); } /// @nodoc @@ -1468,6 +1511,7 @@ class __$$TypeOfDiaryModelImplCopyWithImpl<$Res> $Res call({ Object? hasChosenDiet = null, Object? hasChosenPlan = null, + Object? diaryState = null, }) { return _then(_$TypeOfDiaryModelImpl( hasChosenDiet: null == hasChosenDiet @@ -1478,6 +1522,10 @@ class __$$TypeOfDiaryModelImplCopyWithImpl<$Res> ? _value.hasChosenPlan : hasChosenPlan // ignore: cast_nullable_to_non_nullable as bool, + diaryState: null == diaryState + ? _value.diaryState + : diaryState // ignore: cast_nullable_to_non_nullable + as DiaryStateEnum, )); } } @@ -1487,7 +1535,8 @@ class __$$TypeOfDiaryModelImplCopyWithImpl<$Res> class _$TypeOfDiaryModelImpl implements _TypeOfDiaryModel { const _$TypeOfDiaryModelImpl( {@JsonKey(name: "has_chosen_diet") required this.hasChosenDiet, - @JsonKey(name: "has_chosen_plan") required this.hasChosenPlan}); + @JsonKey(name: "has_chosen_plan") required this.hasChosenPlan, + @JsonKey(name: "last_state") required this.diaryState}); factory _$TypeOfDiaryModelImpl.fromJson(Map json) => _$$TypeOfDiaryModelImplFromJson(json); @@ -1498,10 +1547,13 @@ class _$TypeOfDiaryModelImpl implements _TypeOfDiaryModel { @override @JsonKey(name: "has_chosen_plan") final bool hasChosenPlan; + @override + @JsonKey(name: "last_state") + final DiaryStateEnum diaryState; @override String toString() { - return 'TypeOfDiaryModel(hasChosenDiet: $hasChosenDiet, hasChosenPlan: $hasChosenPlan)'; + return 'TypeOfDiaryModel(hasChosenDiet: $hasChosenDiet, hasChosenPlan: $hasChosenPlan, diaryState: $diaryState)'; } @override @@ -1512,12 +1564,15 @@ class _$TypeOfDiaryModelImpl implements _TypeOfDiaryModel { (identical(other.hasChosenDiet, hasChosenDiet) || other.hasChosenDiet == hasChosenDiet) && (identical(other.hasChosenPlan, hasChosenPlan) || - other.hasChosenPlan == hasChosenPlan)); + other.hasChosenPlan == hasChosenPlan) && + (identical(other.diaryState, diaryState) || + other.diaryState == diaryState)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, hasChosenDiet, hasChosenPlan); + int get hashCode => + Object.hash(runtimeType, hasChosenDiet, hasChosenPlan, diaryState); /// Create a copy of TypeOfDiaryModel /// with the given fields replaced by the non-null parameter values. @@ -1539,8 +1594,9 @@ class _$TypeOfDiaryModelImpl implements _TypeOfDiaryModel { abstract class _TypeOfDiaryModel implements TypeOfDiaryModel { const factory _TypeOfDiaryModel( {@JsonKey(name: "has_chosen_diet") required final bool hasChosenDiet, - @JsonKey(name: "has_chosen_plan") - required final bool hasChosenPlan}) = _$TypeOfDiaryModelImpl; + @JsonKey(name: "has_chosen_plan") required final bool hasChosenPlan, + @JsonKey(name: "last_state") + required final DiaryStateEnum diaryState}) = _$TypeOfDiaryModelImpl; factory _TypeOfDiaryModel.fromJson(Map json) = _$TypeOfDiaryModelImpl.fromJson; @@ -1551,6 +1607,9 @@ abstract class _TypeOfDiaryModel implements TypeOfDiaryModel { @override @JsonKey(name: "has_chosen_plan") bool get hasChosenPlan; + @override + @JsonKey(name: "last_state") + DiaryStateEnum get diaryState; /// Create a copy of TypeOfDiaryModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/diary/models/diary_day_model.g.dart b/lib/features/diary/models/diary_day_model.g.dart index ca269007..9eb402a3 100644 --- a/lib/features/diary/models/diary_day_model.g.dart +++ b/lib/features/diary/models/diary_day_model.g.dart @@ -31,6 +31,7 @@ _$DiaryDayModelImpl _$$DiaryDayModelImplFromJson(Map json) => meals: (json['meals'] as List) .map((e) => MealModel.fromJson(e as Map)) .toList(), + chosenPlanId: (json['chosen_plan_id'] as num?)?.toInt(), ); Map _$$DiaryDayModelImplToJson(_$DiaryDayModelImpl instance) => @@ -43,6 +44,7 @@ Map _$$DiaryDayModelImplToJson(_$DiaryDayModelImpl instance) => 'comment': instance.comment, 'state': instance.typeOfDiary, 'meals': instance.meals, + 'chosen_plan_id': instance.chosenPlanId, }; _$MealModelImpl _$$MealModelImplFromJson(Map json) => @@ -127,6 +129,7 @@ _$TypeOfDiaryModelImpl _$$TypeOfDiaryModelImplFromJson( _$TypeOfDiaryModelImpl( hasChosenDiet: json['has_chosen_diet'] as bool, hasChosenPlan: json['has_chosen_plan'] as bool, + diaryState: $enumDecode(_$DiaryStateEnumEnumMap, json['last_state']), ); Map _$$TypeOfDiaryModelImplToJson( @@ -134,8 +137,20 @@ Map _$$TypeOfDiaryModelImplToJson( { 'has_chosen_diet': instance.hasChosenDiet, 'has_chosen_plan': instance.hasChosenPlan, + 'last_state': _$DiaryStateEnumEnumMap[instance.diaryState]!, }; +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', +}; + _$ProductInMealImpl _$$ProductInMealImplFromJson(Map json) => _$ProductInMealImpl( productName: json['product_name'] as String, diff --git a/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart new file mode 100644 index 00000000..5ef8dea1 --- /dev/null +++ b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart @@ -0,0 +1,80 @@ +import 'dart:developer'; +import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart'; +import 'package:eatvio_app/features/diary/repositories/diary_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ConsultantCommentsCubit extends Cubit { + final DiaryRepository diaryRepository; + ConsultantCommentsCubit({required this.diaryRepository}) + : super( + ConsultantCommentsState( + commentsModel: ConsultantCommentModel( + comment: '', + ), + ), + ); + + void setComment(String comment) { + emit( + state.copyWith( + commentsModel: state.commentsModel.copyWith(comment: comment), + dayCommentNeedsToBeFilled: comment.length < 3, + ), + ); + } + + void setMealComment({required int mealNumber, required String comment}) { + if (comment.isEmpty) { + final mealsComments = Map.from(state.commentsModel.mealsComments); + mealsComments.remove(mealNumber.toString()); + emit(state.copyWith.commentsModel(mealsComments: mealsComments)); + return; + } + final mealsComments = Map.from(state.commentsModel.mealsComments); + mealsComments[mealNumber.toString()] = MealCommentModel(comment: comment, typeNumber: mealNumber); + emit(state.copyWith.commentsModel(mealsComments: mealsComments)); + } + + void addPortionComment(PortionCommentModel portionComment) { + final portions = [...state.commentsModel.portions]; + portions.add(portionComment); + emit(state.copyWith.commentsModel(portions: portions)); + } + + void updatePortionComment(PortionCommentModel portionCommentModel) { + if (portionCommentModel.comment.isEmpty) { + final portions = [...state.commentsModel.portions]; + final updatedPortions = portions.where((portion) => portion.id != portionCommentModel.id).toList(); + emit(state.copyWith.commentsModel(portions: updatedPortions)); + return; + } + final portions = [...state.commentsModel.portions]; + final updatedPortions = portions.map((portion) { + if (portion.id == portionCommentModel.id) { + return portionCommentModel; + } + return portion; + }).toList(); + + emit(state.copyWith.commentsModel(portions: updatedPortions)); + } + + Future submitComments(int diaryId) async { + try { + if (state.commentsModel.comment == null || state.commentsModel.comment!.isEmpty || state.commentsModel.comment!.length < 3) { + emit(state.copyWith(dayCommentNeedsToBeFilled: true)); + return; + } + if (state.commentsModel.mealsComments.values.any((element) => element.comment.length < 3)) { + return; + } + if (state.commentsModel.portions.any((element) => element.comment.length < 3)) { + return; + } + await diaryRepository.commentUserDay(diaryId: diaryId, commentModel: state.commentsModel); + } catch (e, st) { + log(e.toString(), stackTrace: st); + } + } +} diff --git a/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart new file mode 100644 index 00000000..39166a7a --- /dev/null +++ b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart @@ -0,0 +1,15 @@ + +import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'consultant_comments_state.freezed.dart'; + +@freezed +class ConsultantCommentsState with _$ConsultantCommentsState { + + factory ConsultantCommentsState({ + required ConsultantCommentModel commentsModel, + @Default(false) bool dayCommentNeedsToBeFilled + }) = _ConsultantCommentsState; + +} diff --git a/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.freezed.dart b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.freezed.dart new file mode 100644 index 00000000..2f527110 --- /dev/null +++ b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.freezed.dart @@ -0,0 +1,190 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'consultant_comments_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 _$ConsultantCommentsState { + ConsultantCommentModel get commentsModel => + throw _privateConstructorUsedError; + bool get dayCommentNeedsToBeFilled => throw _privateConstructorUsedError; + + /// Create a copy of ConsultantCommentsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ConsultantCommentsStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ConsultantCommentsStateCopyWith<$Res> { + factory $ConsultantCommentsStateCopyWith(ConsultantCommentsState value, + $Res Function(ConsultantCommentsState) then) = + _$ConsultantCommentsStateCopyWithImpl<$Res, ConsultantCommentsState>; + @useResult + $Res call( + {ConsultantCommentModel commentsModel, bool dayCommentNeedsToBeFilled}); + + $ConsultantCommentModelCopyWith<$Res> get commentsModel; +} + +/// @nodoc +class _$ConsultantCommentsStateCopyWithImpl<$Res, + $Val extends ConsultantCommentsState> + implements $ConsultantCommentsStateCopyWith<$Res> { + _$ConsultantCommentsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ConsultantCommentsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? commentsModel = null, + Object? dayCommentNeedsToBeFilled = null, + }) { + return _then(_value.copyWith( + commentsModel: null == commentsModel + ? _value.commentsModel + : commentsModel // ignore: cast_nullable_to_non_nullable + as ConsultantCommentModel, + dayCommentNeedsToBeFilled: null == dayCommentNeedsToBeFilled + ? _value.dayCommentNeedsToBeFilled + : dayCommentNeedsToBeFilled // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } + + /// Create a copy of ConsultantCommentsState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ConsultantCommentModelCopyWith<$Res> get commentsModel { + return $ConsultantCommentModelCopyWith<$Res>(_value.commentsModel, (value) { + return _then(_value.copyWith(commentsModel: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ConsultantCommentsStateImplCopyWith<$Res> + implements $ConsultantCommentsStateCopyWith<$Res> { + factory _$$ConsultantCommentsStateImplCopyWith( + _$ConsultantCommentsStateImpl value, + $Res Function(_$ConsultantCommentsStateImpl) then) = + __$$ConsultantCommentsStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {ConsultantCommentModel commentsModel, bool dayCommentNeedsToBeFilled}); + + @override + $ConsultantCommentModelCopyWith<$Res> get commentsModel; +} + +/// @nodoc +class __$$ConsultantCommentsStateImplCopyWithImpl<$Res> + extends _$ConsultantCommentsStateCopyWithImpl<$Res, + _$ConsultantCommentsStateImpl> + implements _$$ConsultantCommentsStateImplCopyWith<$Res> { + __$$ConsultantCommentsStateImplCopyWithImpl( + _$ConsultantCommentsStateImpl _value, + $Res Function(_$ConsultantCommentsStateImpl) _then) + : super(_value, _then); + + /// Create a copy of ConsultantCommentsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? commentsModel = null, + Object? dayCommentNeedsToBeFilled = null, + }) { + return _then(_$ConsultantCommentsStateImpl( + commentsModel: null == commentsModel + ? _value.commentsModel + : commentsModel // ignore: cast_nullable_to_non_nullable + as ConsultantCommentModel, + dayCommentNeedsToBeFilled: null == dayCommentNeedsToBeFilled + ? _value.dayCommentNeedsToBeFilled + : dayCommentNeedsToBeFilled // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$ConsultantCommentsStateImpl implements _ConsultantCommentsState { + _$ConsultantCommentsStateImpl( + {required this.commentsModel, this.dayCommentNeedsToBeFilled = false}); + + @override + final ConsultantCommentModel commentsModel; + @override + @JsonKey() + final bool dayCommentNeedsToBeFilled; + + @override + String toString() { + return 'ConsultantCommentsState(commentsModel: $commentsModel, dayCommentNeedsToBeFilled: $dayCommentNeedsToBeFilled)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ConsultantCommentsStateImpl && + (identical(other.commentsModel, commentsModel) || + other.commentsModel == commentsModel) && + (identical(other.dayCommentNeedsToBeFilled, + dayCommentNeedsToBeFilled) || + other.dayCommentNeedsToBeFilled == dayCommentNeedsToBeFilled)); + } + + @override + int get hashCode => + Object.hash(runtimeType, commentsModel, dayCommentNeedsToBeFilled); + + /// Create a copy of ConsultantCommentsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ConsultantCommentsStateImplCopyWith<_$ConsultantCommentsStateImpl> + get copyWith => __$$ConsultantCommentsStateImplCopyWithImpl< + _$ConsultantCommentsStateImpl>(this, _$identity); +} + +abstract class _ConsultantCommentsState implements ConsultantCommentsState { + factory _ConsultantCommentsState( + {required final ConsultantCommentModel commentsModel, + final bool dayCommentNeedsToBeFilled}) = _$ConsultantCommentsStateImpl; + + @override + ConsultantCommentModel get commentsModel; + @override + bool get dayCommentNeedsToBeFilled; + + /// Create a copy of ConsultantCommentsState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ConsultantCommentsStateImplCopyWith<_$ConsultantCommentsStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 60e6a903..3cfe66c7 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -2,6 +2,9 @@ import 'dart:developer'; import 'dart:io'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; +import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; @@ -16,12 +19,17 @@ class DiaryCubit extends Cubit { late int selectedMeal; final DiaryRepository diaryRepository; final GlossaryRepository glossaryRepository; - + int? diaryId; DiaryCubit({required this.diaryRepository, required this.glossaryRepository}) : super(const DiaryState.loading()) { today = DateTime.now(); selectedDay = today; + diaryRepository.forceFetchStream.listen((_) { + fetchDiary(true, diaryId); + }); } + String comment = ''; + setSelectedDay(DateTime day) async { selectedDay = day; await fetchDiary(true); @@ -32,21 +40,41 @@ class DiaryCubit extends Cubit { emit((state as DiaryStateLoaded).copyWith(selectedMeal: selectedMeal)); } - fetchDiary([bool needLoading = false]) async { + fetchDiary([bool needLoading = false, int? diaryId]) async { + this.diaryId ??= diaryId; if (needLoading) { emit(const DiaryState.loading()); selectedMeal = 1; } try { - final diaryDay = await diaryRepository.getDiaryByDate('$selectedDay'); - + final DiaryDayModel diaryDay; + if (diaryId != null) { + diaryDay = await diaryRepository.getDiaryDay(diaryId); + } else { + final stringDate = selectedDay.toUtc().format('yyyy-MM-dd'); + diaryDay = await diaryRepository.getDiaryByDate(stringDate); + } + ConsultantReviewModel? consultantReview; + if (diaryDay.chosenPlanId != null) { + consultantReview = await diaryRepository.getDiaryComments(planId: diaryDay.chosenPlanId!, diaryId: diaryDay.id); + } final nutrition = await getNutrition(diaryDay.id); + + final isDiaryFinished = diaryDay.typeOfDiary.diaryState == DiaryStateEnum.finished || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayFinished; + + final isDiaryCommented = diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented || + diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented || + diaryDay.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser; emit( DiaryState.loaded( diary: diaryDay, nutrition: nutrition, selectedDay: selectedDay, selectedMeal: selectedMeal, + isConsultantView: this.diaryId != null, + consultantReview: consultantReview, + isDiaryFinished: isDiaryFinished, + isDiaryCommented: isDiaryCommented, ), ); } catch (e, st) { @@ -55,7 +83,11 @@ class DiaryCubit extends Cubit { } } - finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async { + finishDiaryDay({ + required int diaryId, + required String wakeUp, + required String retire, + }) async { await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment); await fetchDiary(); } @@ -192,6 +224,6 @@ class DiaryCubit extends Cubit { } fillComment(String comment) { - emit((state as DiaryStateLoaded).copyWith(diary: (state as DiaryStateLoaded).diary.copyWith(comment: comment))); + this.comment = comment; } } diff --git a/lib/features/diary/presentation/bloc/diary_state.dart b/lib/features/diary/presentation/bloc/diary_state.dart index 0fa800cf..f08b970f 100644 --- a/lib/features/diary/presentation/bloc/diary_state.dart +++ b/lib/features/diary/presentation/bloc/diary_state.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -19,6 +20,9 @@ class DiaryState with _$DiaryState { required NutritionDataModel nutrition, required DateTime selectedDay, required int selectedMeal, - + @Default(false) bool isConsultantView, + ConsultantReviewModel? consultantReview, + @Default(false) bool isDiaryFinished, + @Default(false) bool isDiaryCommented, }) = DiaryStateLoaded; } diff --git a/lib/features/diary/presentation/bloc/diary_state.freezed.dart b/lib/features/diary/presentation/bloc/diary_state.freezed.dart index 176be0c9..b0621309 100644 --- a/lib/features/diary/presentation/bloc/diary_state.freezed.dart +++ b/lib/features/diary/presentation/bloc/diary_state.freezed.dart @@ -21,8 +21,15 @@ mixin _$DiaryState { TResult Function() $default, { required TResult Function() loading, required TResult Function(String errorMessage) error, - required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal) + required TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented) loaded, }) => throw _privateConstructorUsedError; @@ -31,8 +38,15 @@ mixin _$DiaryState { TResult? Function()? $default, { TResult? Function()? loading, TResult? Function(String errorMessage)? error, - TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult? Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, }) => throw _privateConstructorUsedError; @@ -41,8 +55,15 @@ mixin _$DiaryState { TResult Function()? $default, { TResult Function()? loading, TResult Function(String errorMessage)? error, - TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, required TResult orElse(), }) => @@ -139,8 +160,15 @@ class _$DiaryStateImpl implements _DiaryState { TResult Function() $default, { required TResult Function() loading, required TResult Function(String errorMessage) error, - required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal) + required TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented) loaded, }) { return $default(); @@ -152,8 +180,15 @@ class _$DiaryStateImpl implements _DiaryState { TResult? Function()? $default, { TResult? Function()? loading, TResult? Function(String errorMessage)? error, - TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult? Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, }) { return $default?.call(); @@ -165,8 +200,15 @@ class _$DiaryStateImpl implements _DiaryState { TResult Function()? $default, { TResult Function()? loading, TResult Function(String errorMessage)? error, - TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, required TResult orElse(), }) { @@ -262,8 +304,15 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading { TResult Function() $default, { required TResult Function() loading, required TResult Function(String errorMessage) error, - required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal) + required TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented) loaded, }) { return loading(); @@ -275,8 +324,15 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading { TResult? Function()? $default, { TResult? Function()? loading, TResult? Function(String errorMessage)? error, - TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult? Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, }) { return loading?.call(); @@ -288,8 +344,15 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading { TResult Function()? $default, { TResult Function()? loading, TResult Function(String errorMessage)? error, - TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, required TResult orElse(), }) { @@ -414,8 +477,15 @@ class _$DiaryStateErrorImpl implements DiaryStateError { TResult Function() $default, { required TResult Function() loading, required TResult Function(String errorMessage) error, - required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal) + required TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented) loaded, }) { return error(errorMessage); @@ -427,8 +497,15 @@ class _$DiaryStateErrorImpl implements DiaryStateError { TResult? Function()? $default, { TResult? Function()? loading, TResult? Function(String errorMessage)? error, - TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult? Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, }) { return error?.call(errorMessage); @@ -440,8 +517,15 @@ class _$DiaryStateErrorImpl implements DiaryStateError { TResult Function()? $default, { TResult Function()? loading, TResult Function(String errorMessage)? error, - TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, required TResult orElse(), }) { @@ -512,10 +596,15 @@ abstract class _$$DiaryStateLoadedImplCopyWith<$Res> { {DiaryDayModel diary, NutritionDataModel nutrition, DateTime selectedDay, - int selectedMeal}); + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented}); $DiaryDayModelCopyWith<$Res> get diary; $NutritionDataModelCopyWith<$Res> get nutrition; + $ConsultantReviewModelCopyWith<$Res>? get consultantReview; } /// @nodoc @@ -535,6 +624,10 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res> Object? nutrition = null, Object? selectedDay = null, Object? selectedMeal = null, + Object? isConsultantView = null, + Object? consultantReview = freezed, + Object? isDiaryFinished = null, + Object? isDiaryCommented = null, }) { return _then(_$DiaryStateLoadedImpl( diary: null == diary @@ -553,6 +646,22 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res> ? _value.selectedMeal : selectedMeal // ignore: cast_nullable_to_non_nullable as int, + isConsultantView: null == isConsultantView + ? _value.isConsultantView + : isConsultantView // ignore: cast_nullable_to_non_nullable + as bool, + consultantReview: freezed == consultantReview + ? _value.consultantReview + : consultantReview // ignore: cast_nullable_to_non_nullable + as ConsultantReviewModel?, + isDiaryFinished: null == isDiaryFinished + ? _value.isDiaryFinished + : isDiaryFinished // ignore: cast_nullable_to_non_nullable + as bool, + isDiaryCommented: null == isDiaryCommented + ? _value.isDiaryCommented + : isDiaryCommented // ignore: cast_nullable_to_non_nullable + as bool, )); } @@ -575,6 +684,21 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res> return _then(_value.copyWith(nutrition: value)); }); } + + /// Create a copy of DiaryState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ConsultantReviewModelCopyWith<$Res>? get consultantReview { + if (_value.consultantReview == null) { + return null; + } + + return $ConsultantReviewModelCopyWith<$Res>(_value.consultantReview!, + (value) { + return _then(_value.copyWith(consultantReview: value)); + }); + } } /// @nodoc @@ -584,7 +708,11 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { {required this.diary, required this.nutrition, required this.selectedDay, - required this.selectedMeal}); + required this.selectedMeal, + this.isConsultantView = false, + this.consultantReview, + this.isDiaryFinished = false, + this.isDiaryCommented = false}); @override final DiaryDayModel diary; @@ -594,10 +722,21 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { final DateTime selectedDay; @override final int selectedMeal; + @override + @JsonKey() + final bool isConsultantView; + @override + final ConsultantReviewModel? consultantReview; + @override + @JsonKey() + final bool isDiaryFinished; + @override + @JsonKey() + final bool isDiaryCommented; @override String toString() { - return 'DiaryState.loaded(diary: $diary, nutrition: $nutrition, selectedDay: $selectedDay, selectedMeal: $selectedMeal)'; + return 'DiaryState.loaded(diary: $diary, nutrition: $nutrition, selectedDay: $selectedDay, selectedMeal: $selectedMeal, isConsultantView: $isConsultantView, consultantReview: $consultantReview, isDiaryFinished: $isDiaryFinished, isDiaryCommented: $isDiaryCommented)'; } @override @@ -611,12 +750,28 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { (identical(other.selectedDay, selectedDay) || other.selectedDay == selectedDay) && (identical(other.selectedMeal, selectedMeal) || - other.selectedMeal == selectedMeal)); + other.selectedMeal == selectedMeal) && + (identical(other.isConsultantView, isConsultantView) || + other.isConsultantView == isConsultantView) && + (identical(other.consultantReview, consultantReview) || + other.consultantReview == consultantReview) && + (identical(other.isDiaryFinished, isDiaryFinished) || + other.isDiaryFinished == isDiaryFinished) && + (identical(other.isDiaryCommented, isDiaryCommented) || + other.isDiaryCommented == isDiaryCommented)); } @override - int get hashCode => - Object.hash(runtimeType, diary, nutrition, selectedDay, selectedMeal); + int get hashCode => Object.hash( + runtimeType, + diary, + nutrition, + selectedDay, + selectedMeal, + isConsultantView, + consultantReview, + isDiaryFinished, + isDiaryCommented); /// Create a copy of DiaryState /// with the given fields replaced by the non-null parameter values. @@ -633,11 +788,19 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { TResult Function() $default, { required TResult Function() loading, required TResult Function(String errorMessage) error, - required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal) + required TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented) loaded, }) { - return loaded(diary, nutrition, selectedDay, selectedMeal); + return loaded(diary, nutrition, selectedDay, selectedMeal, isConsultantView, + consultantReview, isDiaryFinished, isDiaryCommented); } @override @@ -646,11 +809,19 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { TResult? Function()? $default, { TResult? Function()? loading, TResult? Function(String errorMessage)? error, - TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult? Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, }) { - return loaded?.call(diary, nutrition, selectedDay, selectedMeal); + return loaded?.call(diary, nutrition, selectedDay, selectedMeal, + isConsultantView, consultantReview, isDiaryFinished, isDiaryCommented); } @override @@ -659,13 +830,28 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { TResult Function()? $default, { TResult Function()? loading, TResult Function(String errorMessage)? error, - TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - DateTime selectedDay, int selectedMeal)? + TResult Function( + DiaryDayModel diary, + NutritionDataModel nutrition, + DateTime selectedDay, + int selectedMeal, + bool isConsultantView, + ConsultantReviewModel? consultantReview, + bool isDiaryFinished, + bool isDiaryCommented)? loaded, required TResult orElse(), }) { if (loaded != null) { - return loaded(diary, nutrition, selectedDay, selectedMeal); + return loaded( + diary, + nutrition, + selectedDay, + selectedMeal, + isConsultantView, + consultantReview, + isDiaryFinished, + isDiaryCommented); } return orElse(); } @@ -713,12 +899,20 @@ abstract class DiaryStateLoaded implements DiaryState { {required final DiaryDayModel diary, required final NutritionDataModel nutrition, required final DateTime selectedDay, - required final int selectedMeal}) = _$DiaryStateLoadedImpl; + required final int selectedMeal, + final bool isConsultantView, + final ConsultantReviewModel? consultantReview, + final bool isDiaryFinished, + final bool isDiaryCommented}) = _$DiaryStateLoadedImpl; DiaryDayModel get diary; NutritionDataModel get nutrition; DateTime get selectedDay; int get selectedMeal; + bool get isConsultantView; + ConsultantReviewModel? get consultantReview; + bool get isDiaryFinished; + bool get isDiaryCommented; /// Create a copy of DiaryState /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index ee821f8a..1e05e832 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -1,31 +1,62 @@ import 'dart:developer'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/calendar_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/calories_chart_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/comment_container.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/comment_for_consultant_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/day_comment_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/finish_day_button_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/meal_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/wakeup_and_retire_widget.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class DiaryScreen extends StatefulWidget { +class DiaryScreen extends StatelessWidget { const DiaryScreen({super.key}); @override - State createState() => _DiaryScreenState(); + Widget build(BuildContext context) { + return const DiaryScreenContent(); + } } -class _DiaryScreenState extends State { +class UserDiaryScreen extends StatelessWidget { + static const path = '/user-diary-screen'; + static const name = 'userDiaryScreen'; + const UserDiaryScreen({super.key, required this.diaryId}); + final int diaryId; @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => sl()..fetchDiary(true), + return DiaryScreenContent(diaryId: diaryId); + } +} + +class DiaryScreenContent extends StatelessWidget { + const DiaryScreenContent({super.key, this.diaryId}); + final int? diaryId; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (BuildContext context) => sl()..fetchDiary(true, diaryId), + ), + BlocProvider( + create: (context) => sl(), + ), + ], child: BlocBuilder( builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { @@ -33,9 +64,10 @@ class _DiaryScreenState extends State { } if (state is DiaryStateError) { return Scaffold( - body: DataErrorWidget(onReload: () => context.read().fetchDiary()), + body: DataErrorWidget(onReload: () => context.read().fetchDiary(false, diaryId)), ); } + return Scaffold( backgroundColor: Colors.grey.shade200, appBar: AppBar( @@ -46,24 +78,79 @@ class _DiaryScreenState extends State { physics: const BouncingScrollPhysics(), child: Column( children: [ - const CalendarWidget(), + if (diaryId == null) const CalendarWidget(), if (state is DiaryStateLoading) const Loader(), - if (state is DiaryStateLoaded) WakeupAndRetireWidget(showRetire: state.diary.typeOfDiary.hasChosenPlan), + if (state is DiaryStateLoaded) + WakeupAndRetireWidget( + showRetire: state.diary.typeOfDiary.hasChosenPlan, + disable: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented, + ), const SizedBox(height: 12), if (state is DiaryStateLoaded) MealWidget( showMood: state.diary.typeOfDiary.hasChosenPlan, + showAddButton: !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented, ), if (state is DiaryStateLoaded) const CaloriesChartWidget(), const SizedBox(height: 12), - if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) const CommentForConsultantWidget(), - if (state is DiaryStateLoaded && state.diary.closed == 0) + Builder(builder: (context) { + if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) { + if (state.diary.comment.isNotEmpty) { + return CommentForConsultantWidget( + comment: state.diary.comment, + ); + } + if (state.isDiaryCommented || state.isDiaryFinished) { + return const SizedBox.shrink(); + } + return const CommentForConsultantWidget(); + } + return const SizedBox.shrink(); + }), + if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) Padding( padding: const EdgeInsets.only(top: 12), child: FinishDayButtonWidget( diaryDay: state.diary, ), ), + if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[ + const SizedBox(height: 16), + const MainBackgroundBouble( + padding: EdgeInsets.all(16), + child: DayCommentWidget(), + ) + ], + if (state is DiaryStateLoaded && state.consultantReview?.consultantComments.dayComment != null) ...[ + const SizedBox(height: 12), + MainBackgroundBouble( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Text( + s.dayComment, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + const SizedBox(height: 12), + CommentContainer(comment: state.consultantReview?.consultantComments.dayComment ?? ''), + ], + ), + ), + const SizedBox(height: 28), + ], + if (state is DiaryStateLoaded && state.isConsultantView && state.isDiaryFinished && !state.isDiaryCommented) ...[ + const SizedBox(height: 16), + CustomOutlinedButton( + text: s.send, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: () async { + await context.read().submitComments(state.diary.id); + }, + ), + const SizedBox(height: 16), + ], ], ), ), diff --git a/lib/features/diary/presentation/widgets/comment_container.dart b/lib/features/diary/presentation/widgets/comment_container.dart new file mode 100644 index 00000000..24f7ff38 --- /dev/null +++ b/lib/features/diary/presentation/widgets/comment_container.dart @@ -0,0 +1,23 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:flutter/material.dart'; + +class CommentContainer extends StatelessWidget { + const CommentContainer({super.key, required this.comment}); + final String comment; + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + width: double.infinity, + decoration: BoxDecoration( + color: appTheme.commentBg, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + comment, + style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black), + ), + ); + } +} diff --git a/lib/features/diary/presentation/widgets/comment_for_consultant_widget.dart b/lib/features/diary/presentation/widgets/comment_for_consultant_widget.dart index 5e05b859..3e311b59 100644 --- a/lib/features/diary/presentation/widgets/comment_for_consultant_widget.dart +++ b/lib/features/diary/presentation/widgets/comment_for_consultant_widget.dart @@ -1,17 +1,26 @@ import 'package:easy_debounce/easy_debounce.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_text_form_field.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class CommentForConsultantWidget extends StatefulWidget { - const CommentForConsultantWidget({super.key}); - + const CommentForConsultantWidget({super.key, this.comment}); + final String? comment; @override State createState() => _CommentForConsultantWidgetState(); } class _CommentForConsultantWidgetState extends State { + final controller = TextEditingController(); + @override + void initState() { + super.initState(); + if (widget.comment != null) controller.text = widget.comment ?? ''; + } + @override Widget build(BuildContext context) { return Material( @@ -22,19 +31,22 @@ class _CommentForConsultantWidgetState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( 'Дополнительная информация для консультанта', textAlign: TextAlign.center, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF323F4B), height: 1), + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), const SizedBox(height: 12), CustomTextFormField( + controller: controller, onChanged: (value) { - EasyDebounce.debounce('client-typing', const Duration(seconds: 3), () => context.read().fillComment(value)); + EasyDebounce.debounce('client-typing', const Duration(milliseconds: 500), () => context.read().fillComment(value)); }, hintText: 'Укажите дополнительную информацию', minLines: 4, maxLines: 4, + readOnly: widget.comment != null, ), ], ), diff --git a/lib/features/diary/presentation/widgets/day_comment_widget.dart b/lib/features/diary/presentation/widgets/day_comment_widget.dart new file mode 100644 index 00000000..fa6d3ffa --- /dev/null +++ b/lib/features/diary/presentation/widgets/day_comment_widget.dart @@ -0,0 +1,45 @@ +import 'package:easy_debounce/easy_debounce.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_text_form_field.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DayCommentWidget extends StatelessWidget { + const DayCommentWidget({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + s.dayCommentTitle, + textAlign: TextAlign.center, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + const SizedBox(height: 12), + CustomTextFormField( + onChanged: (value) { + EasyDebounce.debounce('client-typing', const Duration(milliseconds: 500), () => context.read().setComment(value)); + }, + hasError: state.dayCommentNeedsToBeFilled, + hintText: s.typeDayComment, + minLines: 4, + maxLines: 4, + readOnly: false, + ), + if (state.dayCommentNeedsToBeFilled) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Минимум 3 символа')], + ], + ); + }, + ); + } +} diff --git a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart index e5d4e30c..042c418c 100644 --- a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart +++ b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart @@ -17,12 +17,10 @@ class _FinishDayButtonWidgetState extends State { @override Widget build(BuildContext context) { return InkWell( - onTap: - () => context.read().finishDiaryDay( + onTap: () => context.read().finishDiaryDay( diaryId: widget.diaryDay.id, wakeUp: widget.diaryDay.wakeup.toString(), retire: widget.diaryDay.retire.toString(), - comment: widget.diaryDay.comment.isEmpty ? null : widget.diaryDay.comment, ), borderRadius: BorderRadius.circular(20), child: Ink( diff --git a/lib/features/diary/presentation/widgets/meal_comment_widget.dart b/lib/features/diary/presentation/widgets/meal_comment_widget.dart new file mode 100644 index 00000000..da5f3b11 --- /dev/null +++ b/lib/features/diary/presentation/widgets/meal_comment_widget.dart @@ -0,0 +1,74 @@ +import 'package:easy_debounce/easy_debounce.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_text_form_field.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MealCommentWidget extends StatefulWidget { + const MealCommentWidget({super.key, required this.selectedMeal}); + final int selectedMeal; + + @override + State createState() => _MealCommentWidgetState(); +} + +class _MealCommentWidgetState extends State { + final controller = TextEditingController(); + + @override + void didUpdateWidget(MealCommentWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedMeal != oldWidget.selectedMeal) { + final state = context.read().state; + controller.text = state.commentsModel.mealsComments[widget.selectedMeal.toString()]?.comment ?? ''; + } + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + final bool hasError; + if (state.commentsModel.mealsComments[widget.selectedMeal.toString()] != null && + state.commentsModel.mealsComments[widget.selectedMeal.toString()]!.comment.length < 3) { + hasError = true; + } else { + hasError = false; + } + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '${s.commentForMeal} ${widget.selectedMeal}', + textAlign: TextAlign.center, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + const SizedBox(height: 12), + CustomTextFormField( + controller: controller, + hasError: hasError, + onChanged: (value) { + EasyDebounce.debounce( + 'client-typing', + const Duration(milliseconds: 500), + () => context.read().setMealComment(mealNumber: widget.selectedMeal, comment: value), + ); + }, + hintText: s.typeMealComment, + minLines: 4, + maxLines: 4, + readOnly: false, + ), + if (hasError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Минимум 3 символа')], + ], + ); + }, + ); + } +} diff --git a/lib/features/diary/presentation/widgets/meal_widget.dart b/lib/features/diary/presentation/widgets/meal_widget.dart index dee82d41..591d1a65 100644 --- a/lib/features/diary/presentation/widgets/meal_widget.dart +++ b/lib/features/diary/presentation/widgets/meal_widget.dart @@ -1,15 +1,24 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/add_product_button_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/comment_container.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/meal_comment_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/mood_rating_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/photo_gallery_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/products_list_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_picker_widget.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class MealWidget extends StatefulWidget { - const MealWidget({super.key, required this.showMood}); + const MealWidget({super.key, required this.showMood, required this.showAddButton}); final bool showMood; + final bool showAddButton; @override State createState() => _MealWidgetState(); } @@ -17,6 +26,7 @@ class MealWidget extends StatefulWidget { class _MealWidgetState extends State { @override Widget build(BuildContext context) { + final s = S.of(context); return Material( borderRadius: BorderRadius.circular(20), color: Colors.white, @@ -48,8 +58,10 @@ class _MealWidgetState extends State { const TimeOfMealPickerWidget(), const SizedBox(height: 12), const ProductsListWidget(), - const SizedBox(height: 12), - const AddProductButtonWidget(), + if (widget.showAddButton) ...[ + const SizedBox(height: 12), + const AddProductButtonWidget(), + ], const SizedBox(height: 12), const PhotoGalleryWidget(), const SizedBox(height: 12), @@ -57,7 +69,35 @@ class _MealWidgetState extends State { if (widget.showMood) ...[ const SizedBox(height: 12), const MoodRatingWidget(), - ] + ], + BlocBuilder( + builder: (context, state) { + if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) { + return MealCommentWidget(selectedMeal: state.selectedMeal); + } + return const SizedBox.shrink(); + }, + ), + BlocBuilder( + builder: (context, state) { + if (state is DiaryStateLoaded) { + final mealsComments = state.consultantReview?.consultantComments.mealsComments; + if (mealsComments != null && (state.selectedMeal - 1 < mealsComments.length)) { + return Column( + children: [ + Text( + '${s.mealComment} ${state.selectedMeal}', + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + const SizedBox(height: 12), + CommentContainer(comment: mealsComments[state.selectedMeal - 1].comment), + ], + ); + } + } + return const SizedBox.shrink(); + }, + ) ], ), ), diff --git a/lib/features/diary/presentation/widgets/mood_rating_widget.dart b/lib/features/diary/presentation/widgets/mood_rating_widget.dart index 6e72e3a4..41640b78 100644 --- a/lib/features/diary/presentation/widgets/mood_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/mood_rating_widget.dart @@ -32,9 +32,10 @@ class _MoodRatingWidgetState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( - 'Ваше настроение после приёма пищи', - style: TextStyle( + Text( + state.isConsultantView ? 'Настроение пользователя после приема пищи' : 'Ваше настроение после приёма пищи', + textAlign: TextAlign.center, + style: const TextStyle( fontSize: 16, ), ), @@ -47,6 +48,7 @@ class _MoodRatingWidgetState extends State { width: 32, ), RatingBar.builder( + ignoreGestures: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented, initialRating: state.diary.meals[selectedIndex].mood.toDouble() + 1, itemCount: 6, itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), diff --git a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart index 320db629..80c7e58a 100644 --- a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart +++ b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart @@ -90,7 +90,7 @@ class _PhotoGalleryWidgetState extends State { builder: (context, state) { if (state is DiaryStateLoaded) { int mealIndex = state.selectedMeal - 1; - if (state.diary.meals[mealIndex].photos.isEmpty) { + if (state.diary.meals[mealIndex].photos.isEmpty && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) { return GestureDetector( onTap: () => showImagePicker(state.diary), child: SvgPicture.asset( @@ -99,6 +99,9 @@ class _PhotoGalleryWidgetState extends State { ), ); } else { + if (state.diary.meals[mealIndex].photos.isEmpty) { + return const SizedBox.shrink(); + } return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -112,30 +115,31 @@ class _PhotoGalleryWidgetState extends State { imageUrl: '$baseUrl/storage/${state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path}', ), ), - Padding( - padding: const EdgeInsets.all(12), - child: IconButton.filled( - style: IconButton.styleFrom( - backgroundColor: Colors.white, - elevation: 0, - ), - onPressed: () async { - await diaryCubit.deletePhoto( - photo: state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex], - diaryId: state.diary.id, - ); - setState(() { - if (currentSelectedPhotoIndex == state.diary.meals[mealIndex].photos.length - 1 && currentSelectedPhotoIndex != 0) { - currentSelectedPhotoIndex = state.diary.meals[mealIndex].photos.length - 2; - } - }); - }, - icon: const Icon( - Icons.close_rounded, - color: Colors.grey, + if (!state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) + Padding( + padding: const EdgeInsets.all(12), + child: IconButton.filled( + style: IconButton.styleFrom( + backgroundColor: Colors.white, + elevation: 0, + ), + onPressed: () async { + await diaryCubit.deletePhoto( + photo: state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex], + diaryId: state.diary.id, + ); + setState(() { + if (currentSelectedPhotoIndex == state.diary.meals[mealIndex].photos.length - 1 && currentSelectedPhotoIndex != 0) { + currentSelectedPhotoIndex = state.diary.meals[mealIndex].photos.length - 2; + } + }); + }, + icon: const Icon( + Icons.close_rounded, + color: Colors.grey, + ), ), ), - ), ], ), const SizedBox(height: 8), @@ -147,7 +151,7 @@ class _PhotoGalleryWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ for (int i = 0; i < state.diary.meals[mealIndex].photos.length + 1; i++) ...[ - if (i == state.diary.meals[mealIndex].photos.length) + if (i == state.diary.meals[mealIndex].photos.length && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) Padding( padding: const EdgeInsets.only(left: 8), child: InkWell( diff --git a/lib/features/diary/presentation/widgets/portion_comment_widget.dart b/lib/features/diary/presentation/widgets/portion_comment_widget.dart new file mode 100644 index 00000000..f1acbdc1 --- /dev/null +++ b/lib/features/diary/presentation/widgets/portion_comment_widget.dart @@ -0,0 +1,86 @@ +import 'package:collection/collection.dart'; +import 'package:easy_debounce/easy_debounce.dart'; +import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart'; +import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/comment_container.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PortionCommentWidget extends StatefulWidget { + const PortionCommentWidget({super.key, required this.enabled, required this.portionId, this.comment}); + final bool enabled; + final int portionId; + final String? comment; + @override + State createState() => _PortionCommentWidgetState(); +} + +class _PortionCommentWidgetState extends State { + final controller = TextEditingController(); + @override + void didUpdateWidget(PortionCommentWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.portionId != oldWidget.portionId) { + final state = context.read().state; + controller.text = state.commentsModel.portions.firstWhereOrNull((portion) => portion.id == widget.portionId)?.comment ?? ''; + } + } + + @override + void initState() { + super.initState(); + final state = context.read().state; + controller.text = state.commentsModel.portions.firstWhereOrNull((portion) => portion.id == widget.portionId)?.comment ?? ''; + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + if (widget.enabled) { + return BlocBuilder( + builder: (context, state) { + final bool hasError; + if (state.commentsModel.portions.firstWhereOrNull((elem) => elem.id == widget.portionId) != null && + state.commentsModel.portions.firstWhereOrNull((elem) => elem.id == widget.portionId)!.comment.length < 3) { + hasError = true; + } else { + hasError = false; + } + return Column( + children: [ + CustomTextFormField( + controller: controller, + hintText: s.typePortionComment, + hasError: hasError, + onChanged: (value) { + EasyDebounce.debounce( + 'client-typing', + const Duration(milliseconds: 500), + () { + final hasPortionComment = state.commentsModel.portions.any((portion) => portion.id == widget.portionId); + if (hasPortionComment) { + final portion = state.commentsModel.portions.firstWhere((portion) => portion.id == widget.portionId); + context.read().updatePortionComment(portion.copyWith(comment: value)); + return; + } + context.read().addPortionComment(PortionCommentModel(id: widget.portionId, comment: value)); + }, + ); + }, + ), + if (hasError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Минимум 3 символа')], + ], + ); + }, + ); + } + if (widget.comment != null) { + return CommentContainer(comment: widget.comment ?? ''); + } + return const SizedBox.shrink(); + } +} diff --git a/lib/features/diary/presentation/widgets/product_widget.dart b/lib/features/diary/presentation/widgets/product_widget.dart index 71ac4c5a..bc9020cb 100644 --- a/lib/features/diary/presentation/widgets/product_widget.dart +++ b/lib/features/diary/presentation/widgets/product_widget.dart @@ -1,9 +1,13 @@ +import 'package:collection/collection.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/add_product_modal.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/portion_comment_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -24,7 +28,7 @@ class _ProductWidgetState extends State { late final DiaryCubit diaryCubit; late String portionName; late bool isEaten; - + bool showCommentWidget = false; @override void initState() { super.initState(); @@ -55,6 +59,17 @@ class _ProductWidgetState extends State { return BlocBuilder( builder: (context, state) { if (state is DiaryStateLoaded) { + final isDiaryCommented = state.diary.typeOfDiary.diaryState == DiaryStateEnum.commented || + state.diary.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented || + state.diary.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser; + final isDiaryFinished = + state.diary.typeOfDiary.diaryState == DiaryStateEnum.finished || state.diary.typeOfDiary.diaryState == DiaryStateEnum.lastDayFinished; + final canEditPortion = !state.isConsultantView && !isDiaryCommented && !isDiaryFinished; + final consultantComments = state.consultantReview?.consultantComments; + PortionComment? portionComment; + if (consultantComments != null) { + portionComment = consultantComments.portionsComments.firstWhereOrNull((elem) => elem.portionId == widget.portion.id); + } if (widget.portion.portionState == PortionState.eatenDeleted) { return DottedBorder( dashPattern: const [6, 6], @@ -84,50 +99,74 @@ class _ProductWidgetState extends State { ), ); } else { - return ListTile( - onTap: () => updateProductInMeal(state, widget.portion), - trailing: const Icon(Icons.more_vert_rounded), - leading: widget.portion.fromDiet - ? Transform.scale( - scale: 1.2, - child: Checkbox( - value: isEaten, - onChanged: (value) { - setState(() { - isEaten = value!; - }); - if (isEaten) { - diaryCubit.markEaten( - diaryId: state.diary.id, - mealId: state.selectedMeal, - portionId: widget.portion.id, - ); - } else { - diaryCubit.unmarkEaten( - diaryId: state.diary.id, - mealId: state.selectedMeal, - portionId: widget.portion.id, - ); - } - }, - activeColor: appTheme.blueA400, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - side: const BorderSide( - color: Color(0xFF809CB9), - ), - ), - ) - : null, - horizontalTitleGap: 0, - contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 4) : null, - title: Text(widget.portion.productName), - subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - tileColor: const Color(0xFFF6FAFF), + Widget? trailing; + if (state.isConsultantView && !isDiaryCommented) { + trailing = GestureDetector( + onTap: () { + setState(() { + showCommentWidget = !showCommentWidget; + }); + }, + child: SvgPicture.asset('assets/icons/edit.svg')); + } else if (!isDiaryCommented) { + trailing = const Icon(Icons.more_vert_rounded); + } + return Column( + children: [ + ListTile( + onTap: canEditPortion ? () => updateProductInMeal(state, widget.portion) : null, + trailing: trailing, + leading: widget.portion.fromDiet + ? Transform.scale( + scale: 1.2, + child: Checkbox( + value: isEaten, + onChanged: (value) { + setState(() { + isEaten = value!; + }); + if (isEaten) { + diaryCubit.markEaten( + diaryId: state.diary.id, + mealId: state.selectedMeal, + portionId: widget.portion.id, + ); + } else { + diaryCubit.unmarkEaten( + diaryId: state.diary.id, + mealId: state.selectedMeal, + portionId: widget.portion.id, + ); + } + }, + activeColor: appTheme.blueA400, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + side: const BorderSide( + color: Color(0xFF809CB9), + ), + ), + ) + : null, + horizontalTitleGap: 0, + contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 4) : null, + title: Text(widget.portion.productName), + subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + tileColor: const Color(0xFFF6FAFF), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: PortionCommentWidget( + enabled: showCommentWidget, + portionId: widget.portion.id, + comment: portionComment?.comment, + ), + ) + ], ); } } diff --git a/lib/features/diary/presentation/widgets/products_list_widget.dart b/lib/features/diary/presentation/widgets/products_list_widget.dart index b1775f53..5f1fbd26 100644 --- a/lib/features/diary/presentation/widgets/products_list_widget.dart +++ b/lib/features/diary/presentation/widgets/products_list_widget.dart @@ -15,7 +15,7 @@ class _ProductsListWidgetState extends State { @override Widget build(BuildContext context) { return BlocBuilder( - builder: (BuildContext context, DiaryState state) { + builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { int mealIndex = state.selectedMeal - 1; return Column( diff --git a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart index 1f3b332c..d473f691 100644 --- a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart @@ -32,9 +32,10 @@ class _SatietyRatingWidgetState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( - 'Ваша сытость после приёма пищи', - style: TextStyle( + Text( + state.isConsultantView ? 'Сытость пользователя после приема пищи' : 'Ваша сытость после приёма пищи', + textAlign: TextAlign.center, + style: const TextStyle( fontSize: 16, ), ), @@ -47,6 +48,7 @@ class _SatietyRatingWidgetState extends State { width: 32, ), RatingBar.builder( + ignoreGestures: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented, initialRating: state.diary.meals[selectedIndex].satiety.toDouble() + 1, itemCount: 6, itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), @@ -172,6 +174,7 @@ class _SatietyRatingWidgetState extends State { } }, onRatingUpdate: (value) { + if (state.isConsultantView) return; setState(() { currentSelectedRating[selectedIndex] = value; }); diff --git a/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart b/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart index 6de52279..b73bb6d5 100644 --- a/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart +++ b/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart @@ -69,6 +69,7 @@ class _TimeOfMealPickerWidgetState extends State { ), ), ), + const SizedBox(height: 30) ], ), ), @@ -97,7 +98,7 @@ class _TimeOfMealPickerWidgetState extends State { ), ), InkWell( - onTap: () => showTimePicker(state.diary.id), + onTap: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented ? null : () => showTimePicker(state.diary.id), borderRadius: BorderRadius.circular(100), child: Ink( height: 32, diff --git a/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart b/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart index b4aee1c5..8c9f6028 100644 --- a/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart +++ b/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart @@ -8,8 +8,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../../../../core/theme/theme_helper.dart'; class WakeupAndRetireWidget extends StatefulWidget { - const WakeupAndRetireWidget({super.key, required this.showRetire}); + const WakeupAndRetireWidget({super.key, required this.showRetire, this.disable = false}); final bool showRetire; + final bool disable; @override State createState() => _WakeupAndRetireWidgetState(); } @@ -82,6 +83,7 @@ class _WakeupAndRetireWidgetState extends State { ), ), ), + const SizedBox(height: 30) ], ), ), @@ -125,7 +127,7 @@ class _WakeupAndRetireWidgetState extends State { ), const SizedBox(height: 8), InkWell( - onTap: () => showTimePicker(state.diary.id, true), + onTap: widget.disable ? null : () => showTimePicker(state.diary.id, true), borderRadius: BorderRadius.circular(100), child: Ink( height: 32, @@ -185,7 +187,7 @@ class _WakeupAndRetireWidgetState extends State { ), const SizedBox(height: 8), InkWell( - onTap: () => showTimePicker(state.diary.id, false), + onTap: widget.disable ? null : () => showTimePicker(state.diary.id, false), borderRadius: BorderRadius.circular(100), child: Ink( height: 32, diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index ad7a0448..5a9df1da 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -1,4 +1,7 @@ +import 'dart:async'; import 'dart:io'; +import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; +import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; @@ -8,6 +11,9 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; class DiaryRepository { final DiaryService diaryService; + final StreamController _diaryForceFetchController = StreamController(); + + Stream get forceFetchStream => _diaryForceFetchController.stream; DiaryRepository({ required this.diaryService, @@ -25,6 +31,10 @@ class DiaryRepository { return (await diaryService.getDiaryIndexByDate(date: date)); } + Future getDiaryDay(int diaryId) async { + return (await diaryService.getDiaryDay(diary: diaryId)).diary; + } + Future getNutritionData(int index) async { return await diaryService.getNutrition(diary: index); } @@ -162,11 +172,7 @@ class DiaryRepository { }) async { return await diaryService.finishDiaryDay( diary: diaryId, - finishDiaryDayModel: FinishDiaryDayModel( - wakeUp: wakeUp, - retire: retire, - comment: comment - ), + finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment), ); } @@ -263,4 +269,24 @@ class DiaryRepository { rethrow; } } + + Future commentUserDay({required int diaryId, required ConsultantCommentModel commentModel}) async { + try { + await diaryService.commentUserDay(id: diaryId, body: commentModel); + _diaryForceFetchController.add('force'); + } catch (e) { + rethrow; + } + } + + Future getDiaryComments({ + required int planId, + required int diaryId, + }) async { + try { + return await diaryService.getDiaryComments(planId: planId, diaryId: diaryId); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index 8506e466..bbaa5563 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -1,5 +1,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; +import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; @@ -131,4 +133,16 @@ abstract class DiaryService { Future sendProductToModeration({ @Body() required Map data, }); + + @POST('/api/diary/{id}/consultant/comment') + Future commentUserDay({ + @Path('id') required int id, + @Body() required ConsultantCommentModel body, + }); + + @GET('/api/chosen_plans/{planId}/{diaryId}/comments/show') + Future getDiaryComments({ + @Path() required int planId, + @Path() required int diaryId, + }); } diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 1262bf09..4a8dde15 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -478,6 +478,58 @@ class _DiaryService implements DiaryService { return _value; } + @override + Future commentUserDay({ + required int id, + required ConsultantCommentModel body, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = body; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${id}/consultant/comment', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + + @override + Future getDiaryComments({ + required int planId, + required int diaryId, + }) 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/chosen_plans/${planId}/${diaryId}/comments/show', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch>(_options); + late ConsultantReviewModel _value; + try { + _value = ConsultantReviewModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/features/notifications/models/notification_model.dart b/lib/features/notifications/models/notification_model.dart new file mode 100644 index 00000000..2b757de1 --- /dev/null +++ b/lib/features/notifications/models/notification_model.dart @@ -0,0 +1,25 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'notification_model.freezed.dart'; +part 'notification_model.g.dart'; + +@freezed +class NotificationModel with _$NotificationModel { + + factory NotificationModel({ + required NotificationType type + }) = _NotificationModel; + + factory NotificationModel.fromJson(Map json) => _$NotificationModelFromJson(json); +} + + +enum NotificationType { + @JsonValue('App\\Notifications\\UserSubscribedNotification') + userSubscribedNotification, + @JsonValue('App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor') + planSubscriptionStartedToAuthor, + @JsonValue('App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor') + planSubscriptionCanceledToAuthor, + +} \ No newline at end of file diff --git a/lib/features/notifications/models/notification_model.freezed.dart b/lib/features/notifications/models/notification_model.freezed.dart new file mode 100644 index 00000000..2ea9b158 --- /dev/null +++ b/lib/features/notifications/models/notification_model.freezed.dart @@ -0,0 +1,166 @@ +// 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 'notification_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'); + +NotificationModel _$NotificationModelFromJson(Map json) { + return _NotificationModel.fromJson(json); +} + +/// @nodoc +mixin _$NotificationModel { + NotificationType get type => throw _privateConstructorUsedError; + + /// Serializes this NotificationModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of NotificationModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $NotificationModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NotificationModelCopyWith<$Res> { + factory $NotificationModelCopyWith( + NotificationModel value, $Res Function(NotificationModel) then) = + _$NotificationModelCopyWithImpl<$Res, NotificationModel>; + @useResult + $Res call({NotificationType type}); +} + +/// @nodoc +class _$NotificationModelCopyWithImpl<$Res, $Val extends NotificationModel> + implements $NotificationModelCopyWith<$Res> { + _$NotificationModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of NotificationModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + }) { + return _then(_value.copyWith( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as NotificationType, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NotificationModelImplCopyWith<$Res> + implements $NotificationModelCopyWith<$Res> { + factory _$$NotificationModelImplCopyWith(_$NotificationModelImpl value, + $Res Function(_$NotificationModelImpl) then) = + __$$NotificationModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({NotificationType type}); +} + +/// @nodoc +class __$$NotificationModelImplCopyWithImpl<$Res> + extends _$NotificationModelCopyWithImpl<$Res, _$NotificationModelImpl> + implements _$$NotificationModelImplCopyWith<$Res> { + __$$NotificationModelImplCopyWithImpl(_$NotificationModelImpl _value, + $Res Function(_$NotificationModelImpl) _then) + : super(_value, _then); + + /// Create a copy of NotificationModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + }) { + return _then(_$NotificationModelImpl( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as NotificationType, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$NotificationModelImpl implements _NotificationModel { + _$NotificationModelImpl({required this.type}); + + factory _$NotificationModelImpl.fromJson(Map json) => + _$$NotificationModelImplFromJson(json); + + @override + final NotificationType type; + + @override + String toString() { + return 'NotificationModel(type: $type)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NotificationModelImpl && + (identical(other.type, type) || other.type == type)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, type); + + /// Create a copy of NotificationModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$NotificationModelImplCopyWith<_$NotificationModelImpl> get copyWith => + __$$NotificationModelImplCopyWithImpl<_$NotificationModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$NotificationModelImplToJson( + this, + ); + } +} + +abstract class _NotificationModel implements NotificationModel { + factory _NotificationModel({required final NotificationType type}) = + _$NotificationModelImpl; + + factory _NotificationModel.fromJson(Map json) = + _$NotificationModelImpl.fromJson; + + @override + NotificationType get type; + + /// Create a copy of NotificationModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$NotificationModelImplCopyWith<_$NotificationModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/notifications/models/notification_model.g.dart b/lib/features/notifications/models/notification_model.g.dart new file mode 100644 index 00000000..9d68bb8d --- /dev/null +++ b/lib/features/notifications/models/notification_model.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notification_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$NotificationModelImpl _$$NotificationModelImplFromJson( + Map json) => + _$NotificationModelImpl( + type: $enumDecode(_$NotificationTypeEnumMap, json['type']), + ); + +Map _$$NotificationModelImplToJson( + _$NotificationModelImpl instance) => + { + 'type': _$NotificationTypeEnumMap[instance.type]!, + }; + +const _$NotificationTypeEnumMap = { + NotificationType.userSubscribedNotification: + 'App\\Notifications\\UserSubscribedNotification', + NotificationType.planSubscriptionStartedToAuthor: + 'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor', + NotificationType.planSubscriptionCanceledToAuthor: + 'App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor', +}; diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart new file mode 100644 index 00000000..506865b6 --- /dev/null +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart @@ -0,0 +1,27 @@ +import 'package:eatvio_app/core/websockets/ws_manager.dart'; +import 'package:eatvio_app/features/notifications/models/notification_model.dart'; +import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class NotificationsCubit extends Cubit { + final List _notifications = []; + final WsManager wsManager; + + NotificationsCubit({required this.wsManager}) : super(const NotificationsState.empty()) { + wsManager.newNotificationsStream?.listen((event) { + _handleNotification(event.data); + }); + } + + void _handleNotification(String notificationData) { + // final data = jsonDecode(notificationData) as Map; + // final notification = NotificationModel.fromJson(data); + // _notifications.add(notification); + emit( + NotificationsState.loaded( + notifications: _notifications, + notificationsCount: _notifications.length, + ), + ); + } +} diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_state.dart similarity index 79% rename from lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart rename to lib/features/notifications/presentation/notifications_cubit/notifications_state.dart index d44c7b2b..b47dc762 100644 --- a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_state.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/notifications/models/notification_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'notifications_state.freezed.dart'; @@ -9,7 +10,7 @@ abstract class NotificationsState with _$NotificationsState { const factory NotificationsState.empty() = NotificationsStateEmpty; const factory NotificationsState.loaded({ - required List notifications, + required List notifications, required int notificationsCount, }) = NotificationsStateLoaded; diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.freezed.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_state.freezed.dart similarity index 92% rename from lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.freezed.dart rename to lib/features/notifications/presentation/notifications_cubit/notifications_state.freezed.dart index d634b318..af80737f 100644 --- a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.freezed.dart +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_state.freezed.dart @@ -21,7 +21,7 @@ mixin _$NotificationsState { TResult Function() $default, { required TResult Function() empty, required TResult Function( - List notifications, int notificationsCount) + List notifications, int notificationsCount) loaded, required TResult Function(String errorMessage) error, }) => @@ -30,7 +30,8 @@ mixin _$NotificationsState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? empty, - TResult? Function(List notifications, int notificationsCount)? + TResult? Function( + List notifications, int notificationsCount)? loaded, TResult? Function(String errorMessage)? error, }) => @@ -39,7 +40,8 @@ mixin _$NotificationsState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? empty, - TResult Function(List notifications, int notificationsCount)? + TResult Function( + List notifications, int notificationsCount)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), @@ -137,7 +139,7 @@ class _$NotificationsStateImpl implements _NotificationsState { TResult Function() $default, { required TResult Function() empty, required TResult Function( - List notifications, int notificationsCount) + List notifications, int notificationsCount) loaded, required TResult Function(String errorMessage) error, }) { @@ -149,7 +151,8 @@ class _$NotificationsStateImpl implements _NotificationsState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? empty, - TResult? Function(List notifications, int notificationsCount)? + TResult? Function( + List notifications, int notificationsCount)? loaded, TResult? Function(String errorMessage)? error, }) { @@ -161,7 +164,8 @@ class _$NotificationsStateImpl implements _NotificationsState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? empty, - TResult Function(List notifications, int notificationsCount)? + TResult Function( + List notifications, int notificationsCount)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), @@ -262,7 +266,7 @@ class _$NotificationsStateEmptyImpl implements NotificationsStateEmpty { TResult Function() $default, { required TResult Function() empty, required TResult Function( - List notifications, int notificationsCount) + List notifications, int notificationsCount) loaded, required TResult Function(String errorMessage) error, }) { @@ -274,7 +278,8 @@ class _$NotificationsStateEmptyImpl implements NotificationsStateEmpty { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? empty, - TResult? Function(List notifications, int notificationsCount)? + TResult? Function( + List notifications, int notificationsCount)? loaded, TResult? Function(String errorMessage)? error, }) { @@ -286,7 +291,8 @@ class _$NotificationsStateEmptyImpl implements NotificationsStateEmpty { TResult maybeWhen( TResult Function()? $default, { TResult Function()? empty, - TResult Function(List notifications, int notificationsCount)? + TResult Function( + List notifications, int notificationsCount)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), @@ -346,7 +352,7 @@ abstract class _$$NotificationsStateLoadedImplCopyWith<$Res> { $Res Function(_$NotificationsStateLoadedImpl) then) = __$$NotificationsStateLoadedImplCopyWithImpl<$Res>; @useResult - $Res call({List notifications, int notificationsCount}); + $Res call({List notifications, int notificationsCount}); } /// @nodoc @@ -371,7 +377,7 @@ class __$$NotificationsStateLoadedImplCopyWithImpl<$Res> notifications: null == notifications ? _value._notifications : notifications // ignore: cast_nullable_to_non_nullable - as List, + as List, notificationsCount: null == notificationsCount ? _value.notificationsCount : notificationsCount // ignore: cast_nullable_to_non_nullable @@ -384,13 +390,13 @@ class __$$NotificationsStateLoadedImplCopyWithImpl<$Res> class _$NotificationsStateLoadedImpl implements NotificationsStateLoaded { const _$NotificationsStateLoadedImpl( - {required final List notifications, + {required final List notifications, required this.notificationsCount}) : _notifications = notifications; - final List _notifications; + final List _notifications; @override - List get notifications { + List get notifications { if (_notifications is EqualUnmodifiableListView) return _notifications; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_notifications); @@ -434,7 +440,7 @@ class _$NotificationsStateLoadedImpl implements NotificationsStateLoaded { TResult Function() $default, { required TResult Function() empty, required TResult Function( - List notifications, int notificationsCount) + List notifications, int notificationsCount) loaded, required TResult Function(String errorMessage) error, }) { @@ -446,7 +452,8 @@ class _$NotificationsStateLoadedImpl implements NotificationsStateLoaded { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? empty, - TResult? Function(List notifications, int notificationsCount)? + TResult? Function( + List notifications, int notificationsCount)? loaded, TResult? Function(String errorMessage)? error, }) { @@ -458,7 +465,8 @@ class _$NotificationsStateLoadedImpl implements NotificationsStateLoaded { TResult maybeWhen( TResult Function()? $default, { TResult Function()? empty, - TResult Function(List notifications, int notificationsCount)? + TResult Function( + List notifications, int notificationsCount)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), @@ -509,10 +517,10 @@ class _$NotificationsStateLoadedImpl implements NotificationsStateLoaded { abstract class NotificationsStateLoaded implements NotificationsState { const factory NotificationsStateLoaded( - {required final List notifications, + {required final List notifications, required final int notificationsCount}) = _$NotificationsStateLoadedImpl; - List get notifications; + List get notifications; int get notificationsCount; /// Create a copy of NotificationsState @@ -598,7 +606,7 @@ class _$NotificationsStateErrorImpl implements NotificationsStateError { TResult Function() $default, { required TResult Function() empty, required TResult Function( - List notifications, int notificationsCount) + List notifications, int notificationsCount) loaded, required TResult Function(String errorMessage) error, }) { @@ -610,7 +618,8 @@ class _$NotificationsStateErrorImpl implements NotificationsStateError { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? empty, - TResult? Function(List notifications, int notificationsCount)? + TResult? Function( + List notifications, int notificationsCount)? loaded, TResult? Function(String errorMessage)? error, }) { @@ -622,7 +631,8 @@ class _$NotificationsStateErrorImpl implements NotificationsStateError { TResult maybeWhen( TResult Function()? $default, { TResult Function()? empty, - TResult Function(List notifications, int notificationsCount)? + TResult Function( + List notifications, int notificationsCount)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), diff --git a/lib/features/notifications/presentation/notifications_screen.dart b/lib/features/notifications/presentation/notifications_screen.dart new file mode 100644 index 00000000..73c244f1 --- /dev/null +++ b/lib/features/notifications/presentation/notifications_screen.dart @@ -0,0 +1,34 @@ +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/notifications/presentation/widgets/notification_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class NotificationsScreen extends StatelessWidget { + const NotificationsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, NotificationsState state) { + return Scaffold( + appBar: AppBar( + title: const Text('Уведомления'), + ), + body: state is NotificationsStateLoaded + ? CustomScrollView( + slivers: [ + SliverList.builder( + itemBuilder: (context, index) => NotificationWidget( + notification: state.notifications[index], + ), + itemCount: state.notifications.length, + ) + ], + ) + : const Placeholder(), + ); + }, + ); + } +} diff --git a/lib/features/notifications/presentation/pages/notifications_screen.dart b/lib/features/notifications/presentation/pages/notifications_screen.dart deleted file mode 100644 index c569dd37..00000000 --- a/lib/features/notifications/presentation/pages/notifications_screen.dart +++ /dev/null @@ -1,27 +0,0 @@ -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:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class NotificationsScreen extends StatelessWidget { - const NotificationsScreen({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (BuildContext context, NotificationsState state) { - return Scaffold( - appBar: AppBar( - title: const Text('Уведомления'), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Text( - state is NotificationsStateLoaded ? state.notifications.toString() : 'Уведомлений нет', - ), - ), - ); - }, - ); - } -} diff --git a/lib/features/notifications/presentation/widgets/notification_widget.dart b/lib/features/notifications/presentation/widgets/notification_widget.dart new file mode 100644 index 00000000..cc0a3a2a --- /dev/null +++ b/lib/features/notifications/presentation/widgets/notification_widget.dart @@ -0,0 +1,15 @@ +import 'package:eatvio_app/features/notifications/models/notification_model.dart'; +import 'package:flutter/material.dart'; + +class NotificationWidget extends StatelessWidget { + const NotificationWidget({super.key, required this.notification}); + final NotificationModel notification; + @override + Widget build(BuildContext context) { + // return (notification.type) { + // NotificationType.userSubscribedNotification => const _UserSubscribedNotification(), + // }; + return Container(); + } +} + diff --git a/lib/features/order_info/cubit/order_info_cubit.dart b/lib/features/order_info/cubit/order_info_cubit.dart index 4cb4d576..cc98f13d 100644 --- a/lib/features/order_info/cubit/order_info_cubit.dart +++ b/lib/features/order_info/cubit/order_info_cubit.dart @@ -20,10 +20,11 @@ class OrderInfoCubit extends Cubit { } } - Future getChosenPlan(int id) async{ + Future getChosenPlan(int id) async { try { emit(state.copyWith(isLoading: true)); - emit(state.copyWith(isLoading: false)); + final chosenPlan = await chatRepository.getChosenPlan(id: id); + emit(state.copyWith(isLoading: false, chosenPlan: chosenPlan)); } catch (e) { log(e.toString()); emit(state.copyWith(isLoading: false)); diff --git a/lib/features/order_info/cubit/order_info_state.dart b/lib/features/order_info/cubit/order_info_state.dart index 717f0058..c98f13cc 100644 --- a/lib/features/order_info/cubit/order_info_state.dart +++ b/lib/features/order_info/cubit/order_info_state.dart @@ -7,7 +7,7 @@ part 'order_info_state.freezed.dart'; class OrderInfoState with _$OrderInfoState { factory OrderInfoState({ - @Default(false) bool isLoading, + @Default(true) bool isLoading, @Default(null) ChosenPlanModel? chosenPlan, }) = _OrderInfoState; diff --git a/lib/features/order_info/cubit/order_info_state.freezed.dart b/lib/features/order_info/cubit/order_info_state.freezed.dart index 4d217ef1..2b658235 100644 --- a/lib/features/order_info/cubit/order_info_state.freezed.dart +++ b/lib/features/order_info/cubit/order_info_state.freezed.dart @@ -128,7 +128,7 @@ class __$$OrderInfoStateImplCopyWithImpl<$Res> /// @nodoc class _$OrderInfoStateImpl implements _OrderInfoState { - _$OrderInfoStateImpl({this.isLoading = false, this.chosenPlan = null}); + _$OrderInfoStateImpl({this.isLoading = true, this.chosenPlan = null}); @override @JsonKey() diff --git a/lib/features/order_info/presentation/info_by_order_screen.dart b/lib/features/order_info/presentation/info_by_order_screen.dart index bbddd69d..2198b589 100644 --- a/lib/features/order_info/presentation/info_by_order_screen.dart +++ b/lib/features/order_info/presentation/info_by_order_screen.dart @@ -1,6 +1,8 @@ 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/cancel_plan_bottom_sheet/cancel_plan_bottom_sheet.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; @@ -117,55 +119,72 @@ class _InfoByOrderScreenContentState extends State { planStatus = PlanStatusValues.pendingPayment; } return Scaffold( - backgroundColor: appTheme.gray100, - appBar: AppBar( - title: Text( - s.infoAboutOrder, - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - GoRouter.of(context).pop(); - }), + backgroundColor: appTheme.gray100, + appBar: AppBar( + title: Text( + s.infoAboutOrder, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), - body: SingleChildScrollView( - child: Column( - children: [ - if (widget.successfulPayment != null) ...[ - const SizedBox(height: 8), - PaymentBanner( - successfulPayment: widget.successfulPayment ?? false, - ), - ], + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + GoRouter.of(context).pop(); + }), + ), + body: SingleChildScrollView( + child: Column( + children: [ + if (widget.successfulPayment != null) ...[ const SizedBox(height: 8), - AuthorCard( - author: plan.plan.author, - title: s.plan_seller, - titleStyle: TextStyles.textSSemibold.copyWith(color: appTheme.gray), - backgroundColor: appTheme.whiteA700, + PaymentBanner( + successfulPayment: widget.successfulPayment ?? false, ), - const SizedBox(height: 8), - PlanProps(planDuration: plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: planStatus), - const SizedBox(height: 8), - PlanInfo( - title: plan.plan.name, - description: plan.plan.details, - imagePath: plan.plan.image, - ), - const SizedBox(height: 8), - OptionWidget( - option: chosenOption, - disableDuration: true, - ), - const SizedBox(height: 8), - PlanState( - state: plan.chosenPlanState, - ), - const SizedBox(height: 30), ], - ), - )); + const SizedBox(height: 8), + AuthorCard( + author: plan.plan.author, + title: s.plan_seller, + titleStyle: TextStyles.textSSemibold.copyWith(color: appTheme.gray), + backgroundColor: appTheme.whiteA700, + ), + const SizedBox(height: 8), + PlanProps(planDuration: plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: planStatus), + const SizedBox(height: 8), + PlanInfo( + title: plan.plan.name, + description: plan.plan.details, + imagePath: plan.plan.image, + ), + const SizedBox(height: 8), + OptionWidget( + option: chosenOption, + disableDuration: true, + ), + const SizedBox(height: 8), + PlanState( + state: plan.chosenPlanState, + ), + const SizedBox(height: 20), + if (widget.successfulPayment == null) + CustomOutlinedButton( + text: s.cancelConsultation, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.strongRed), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.lightRed)), + onPressed: () async { + showModalBottomSheet( + context: context, + builder: (context) => CancelPlanBottomSheet( + enableForm: plan.chosenPlanState.isStateAfterPaidState(), + planId: plan.id, + ), + ); + }, + ) + ], + ), + ), + ); }, ); } diff --git a/lib/features/order_info/presentation/widgets/payment_banner.dart b/lib/features/order_info/presentation/widgets/payment_banner.dart index 2fcee13c..316a318d 100644 --- a/lib/features/order_info/presentation/widgets/payment_banner.dart +++ b/lib/features/order_info/presentation/widgets/payment_banner.dart @@ -142,20 +142,6 @@ class _PaymentBannerState extends State { ], ), ) - ]) - // ? Column(children: [ - // SizedBox( - // width: size.width * 0.6, - // child: Text( - // s.smth_went_wrong_payment_failed, - // style: TextStyles.textLSemibold.copyWith(color: appTheme.black), - // ), - // ), - // ]) - // : Padding( - // padding: const EdgeInsets.only(right: 16), - // child: , - // ), - ); + ])); } } diff --git a/lib/features/order_info/presentation/widgets/plan_status_box.dart b/lib/features/order_info/presentation/widgets/plan_status_box.dart index 609f2b04..70801b41 100644 --- a/lib/features/order_info/presentation/widgets/plan_status_box.dart +++ b/lib/features/order_info/presentation/widgets/plan_status_box.dart @@ -17,7 +17,7 @@ class PlanStatusBox extends StatelessWidget { final s = S.of(context); final title = switch (status) { PlanStatusValues.paid => s.order_paid, - PlanStatusValues.pendingPayment => s.payment_failed, + PlanStatusValues.pendingPayment => s.pending_payment, PlanStatusValues.paymentFailed => s.payment_failed, }; return Container( diff --git a/lib/features/plans/enums/chosen_plan_state_enum.dart b/lib/features/plans/enums/chosen_plan_state_enum.dart index 03016712..5bb92efd 100644 --- a/lib/features/plans/enums/chosen_plan_state_enum.dart +++ b/lib/features/plans/enums/chosen_plan_state_enum.dart @@ -39,6 +39,19 @@ enum ChosenPlanStateEnum { ChosenPlanStateEnum.stateLastDayStarted, ChosenPlanStateEnum.stateLastDayFinished, ChosenPlanStateEnum.stateDone, + ChosenPlanStateEnum.stateInProgress, ]; } + + bool isStateAfterPaidState() { + switch (this) { + case ChosenPlanStateEnum.stateInited: + case ChosenPlanStateEnum.stateInCoordination: + case ChosenPlanStateEnum.stateStartDateHasBeenSet: + case ChosenPlanStateEnum.statePendingPayment: + return false; + default: + return true; + } + } } diff --git a/lib/features/plans/presentation/cubit/about_plan_cubit.dart b/lib/features/plans/presentation/cubit/about_plan_cubit.dart index 87aed754..a45b3bc1 100644 --- a/lib/features/plans/presentation/cubit/about_plan_cubit.dart +++ b/lib/features/plans/presentation/cubit/about_plan_cubit.dart @@ -128,21 +128,24 @@ class AboutPlanCubit extends Cubit { ); } - Future chosePlan() async { + Future<(ChosenPlanModel?, String?)> chosePlan() async { try { final slug = state.planModel?.slug; final optionIndex = state.chosenOptionIndex; if (slug != null) { final optionName = _getOptionNameFromIndex(optionIndex); final body = {'options_type': optionName}; - return await planRepository.chosePlan(slug, body); + return (await planRepository.chosePlan(slug, body), null); } } on DioException catch (e, st) { - log(e.toString(), stackTrace: st); + if (e.response?.statusCode == 400) { + return (null, 'hasPlan'); + } + log(e.toString(), stackTrace: st); } catch (e, st) { log(e.toString(), stackTrace: st); } - return null; + return (null, ''); } String _getOptionNameFromIndex(int index) => switch (index) { diff --git a/lib/features/plans/presentation/pages/about_plan_screen.dart b/lib/features/plans/presentation/pages/about_plan_screen.dart index 0590e1be..fa292a5c 100644 --- a/lib/features/plans/presentation/pages/about_plan_screen.dart +++ b/lib/features/plans/presentation/pages/about_plan_screen.dart @@ -220,7 +220,11 @@ class _AboutPlanScreenState extends State { ), backgroundColor: appTheme.greenA700, onPressed: () async { - final chosenPlan = await context.read().chosePlan(); + final (chosenPlan, error) = await context.read().chosePlan(); + if (error == 'hasPlan') { + showErrorDialog(context, title: s.end_previous_plan, description: s.you_can_not_start_plan_until_end_of_prev_one); + return; + } if (chosenPlan == null) { if (!context.mounted) return; showErrorDialog(context, title: 'Не удалось заказать план'); diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 846b1ed4..7225e5c7 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -75,6 +75,10 @@ class MessageLookup extends MessageLookupByLibrary { "can_not_see_next_day": MessageLookupByLibrary.simpleMessage( "Просмотр последующего дня возможен только при прохождении диеты", ), + "cancel": MessageLookupByLibrary.simpleMessage("Отменить"), + "cancelConsultation": MessageLookupByLibrary.simpleMessage( + "Отменить консультацию", + ), "cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"), "cancel_order": MessageLookupByLibrary.simpleMessage("Отменить заказ"), "carb_type": MessageLookupByLibrary.simpleMessage("Мучное"), @@ -117,6 +121,9 @@ class MessageLookup extends MessageLookupByLibrary { "city_of_living": MessageLookupByLibrary.simpleMessage("Город проживания"), "comfort": MessageLookupByLibrary.simpleMessage("Комфорт"), "comment": MessageLookupByLibrary.simpleMessage("комментарий"), + "commentForMeal": MessageLookupByLibrary.simpleMessage( + "Прокомментировать прием пищи", + ), "comments": MessageLookupByLibrary.simpleMessage("Комментарии"), "comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"), "complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"), @@ -141,6 +148,9 @@ class MessageLookup extends MessageLookupByLibrary { "copy_referral_link": MessageLookupByLibrary.simpleMessage( "Скопировать пригласительную ссылку", ), + "couldNotCancelOrder": MessageLookupByLibrary.simpleMessage( + "Не удалось отменить заказ", + ), "could_not_cancel_diet": MessageLookupByLibrary.simpleMessage( "Не удалось отменить диету", ), @@ -181,6 +191,12 @@ class MessageLookup extends MessageLookupByLibrary { ), "date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"), "day": MessageLookupByLibrary.simpleMessage("День"), + "dayComment": MessageLookupByLibrary.simpleMessage( + "Комментарий ко всему дню", + ), + "dayCommentTitle": MessageLookupByLibrary.simpleMessage( + "Прокомментировать день", + ), "days": MessageLookupByLibrary.simpleMessage("дней"), "deception": MessageLookupByLibrary.simpleMessage("Обман"), "deleted": MessageLookupByLibrary.simpleMessage("Удалена"), @@ -228,6 +244,9 @@ class MessageLookup extends MessageLookupByLibrary { "email_invalid": MessageLookupByLibrary.simpleMessage( "Некорректный адрес электронной почты", ), + "end_previous_plan": MessageLookupByLibrary.simpleMessage( + "Закончите прохождение предыдущего плана", + ), "enter_additional_information": MessageLookupByLibrary.simpleMessage( "Введите любую дополнительную информацию на своё усмотрение:", ), @@ -242,6 +261,9 @@ class MessageLookup extends MessageLookupByLibrary { ), "exit": MessageLookupByLibrary.simpleMessage("Выйти"), "expand": MessageLookupByLibrary.simpleMessage("Показать полностью"), + "explainResonOfCancelation": MessageLookupByLibrary.simpleMessage( + "Опишите причину отказа", + ), "explicitImage": MessageLookupByLibrary.simpleMessage( "Откровенное изображение", ), @@ -300,6 +322,9 @@ class MessageLookup extends MessageLookupByLibrary { "Склонность к гипертонии", ), "has_hypo": MessageLookupByLibrary.simpleMessage("Склонность к гипотонии"), + "has_not_filled_it_yet": MessageLookupByLibrary.simpleMessage( + "ещё не закончил(a) этот день", + ), "hatred": MessageLookupByLibrary.simpleMessage("Оскорбление"), "hatred_description": MessageLookupByLibrary.simpleMessage( "Враждебные высказывания", @@ -348,6 +373,9 @@ class MessageLookup extends MessageLookupByLibrary { "lower": MessageLookupByLibrary.simpleMessage("Нижнее"), "male": MessageLookupByLibrary.simpleMessage("Мужской"), "meal": MessageLookupByLibrary.simpleMessage("Приём пищи"), + "mealComment": MessageLookupByLibrary.simpleMessage( + "Комментарий к приему пищи", + ), "meat": MessageLookupByLibrary.simpleMessage("Мясо"), "meat_type": MessageLookupByLibrary.simpleMessage("С мясом"), "meat_type_desc": MessageLookupByLibrary.simpleMessage( @@ -414,10 +442,12 @@ class MessageLookup extends MessageLookupByLibrary { ), "option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"), "order": MessageLookupByLibrary.simpleMessage("Порядок"), + "orderCanceled": MessageLookupByLibrary.simpleMessage("Заказ отменен"), "order_created": MessageLookupByLibrary.simpleMessage("Заказ создан"), "order_paid": MessageLookupByLibrary.simpleMessage("Заказ оплачен"), "order_price": MessageLookupByLibrary.simpleMessage("Сумма заказа"), "order_status": MessageLookupByLibrary.simpleMessage("Статус заказа"), + "orederCancelation": MessageLookupByLibrary.simpleMessage("Отмена заказа"), "other": MessageLookupByLibrary.simpleMessage("Другое"), "other_info": MessageLookupByLibrary.simpleMessage("Прочая информация"), "password": MessageLookupByLibrary.simpleMessage("Пароль"), @@ -436,6 +466,7 @@ class MessageLookup extends MessageLookupByLibrary { "pay": MessageLookupByLibrary.simpleMessage("Оплатить"), "payment_failed": MessageLookupByLibrary.simpleMessage("Оплата не прошла"), "pcs": MessageLookupByLibrary.simpleMessage("шт"), + "pending_payment": MessageLookupByLibrary.simpleMessage("Ожидается оплата"), "personal": MessageLookupByLibrary.simpleMessage("Персональные"), "personal_diet_state": MessageLookupByLibrary.simpleMessage("Персональная"), "photo": MessageLookupByLibrary.simpleMessage("Фото"), @@ -494,6 +525,7 @@ class MessageLookup extends MessageLookupByLibrary { "rating_history": MessageLookupByLibrary.simpleMessage( "Начисление рейтинга", ), + "reasonExample": MessageLookupByLibrary.simpleMessage("Заказал по ошибке"), "recipe_author": MessageLookupByLibrary.simpleMessage("Автор рецепта"), "recipe_publishing": MessageLookupByLibrary.simpleMessage( "Публикация рецепта", @@ -505,6 +537,7 @@ class MessageLookup extends MessageLookupByLibrary { "Снять с публикации", ), "save": MessageLookupByLibrary.simpleMessage("Сохранить"), + "send": MessageLookupByLibrary.simpleMessage("Отправить"), "send_complain": MessageLookupByLibrary.simpleMessage("Отправить жалобу"), "services": MessageLookupByLibrary.simpleMessage("Оказание услуг"), "settings": MessageLookupByLibrary.simpleMessage("Настройки"), @@ -572,6 +605,18 @@ class MessageLookup extends MessageLookupByLibrary { "to_order": MessageLookupByLibrary.simpleMessage("Заказать"), "try_again": MessageLookupByLibrary.simpleMessage("Попробовать снова"), "try_again_2": MessageLookupByLibrary.simpleMessage("Попробуйте ещё раз"), + "typeAmoutOfReturn": MessageLookupByLibrary.simpleMessage( + "Укажите сумму которую вы хотите вернуть", + ), + "typeDayComment": MessageLookupByLibrary.simpleMessage( + "Как можно подробнее прокомментируйте дневник пользователя", + ), + "typeMealComment": MessageLookupByLibrary.simpleMessage( + "Как можно подробнее прокомментируйте прием пищи", + ), + "typePortionComment": MessageLookupByLibrary.simpleMessage( + "Прокомментируйте блюдо", + ), "type_here_your_complaint": MessageLookupByLibrary.simpleMessage( "Напишите здесь свою жалобу", ), @@ -628,6 +673,16 @@ class MessageLookup extends MessageLookupByLibrary { "wrong_credentials": MessageLookupByLibrary.simpleMessage( "Неверные данные пользователя", ), + "youShureYouWantToCancelOrder": MessageLookupByLibrary.simpleMessage( + "Вы уверены, что хотите отменить заказ?", + ), + "you_can_not_open_this_day": MessageLookupByLibrary.simpleMessage( + "Вы не можете перейти в этот день", + ), + "you_can_not_start_plan_until_end_of_prev_one": + MessageLookupByLibrary.simpleMessage( + "Вы не можете начать план, пока не закончите прохождение предыдущего", + ), "you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"), "your_blood_pressure": MessageLookupByLibrary.simpleMessage( "Ваше артериальное давление?", diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 2d55c794..adbb32ad 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2640,6 +2640,16 @@ class S { ); } + /// `Ожидается оплата` + String get pending_payment { + return Intl.message( + 'Ожидается оплата', + name: 'pending_payment', + desc: '', + args: [], + ); + } + /// `Мои карты` String get my_cards { return Intl.message('Мои карты', name: 'my_cards', desc: '', args: []); @@ -2844,6 +2854,206 @@ class S { args: [], ); } + + /// `Вы не можете начать план, пока не закончите прохождение предыдущего` + String get you_can_not_start_plan_until_end_of_prev_one { + return Intl.message( + 'Вы не можете начать план, пока не закончите прохождение предыдущего', + name: 'you_can_not_start_plan_until_end_of_prev_one', + desc: '', + args: [], + ); + } + + /// `Закончите прохождение предыдущего плана` + String get end_previous_plan { + return Intl.message( + 'Закончите прохождение предыдущего плана', + name: 'end_previous_plan', + desc: '', + args: [], + ); + } + + /// `Вы не можете перейти в этот день` + String get you_can_not_open_this_day { + return Intl.message( + 'Вы не можете перейти в этот день', + name: 'you_can_not_open_this_day', + desc: '', + args: [], + ); + } + + /// `ещё не закончил(a) этот день` + String get has_not_filled_it_yet { + return Intl.message( + 'ещё не закончил(a) этот день', + name: 'has_not_filled_it_yet', + desc: '', + args: [], + ); + } + + /// `Как можно подробнее прокомментируйте прием пищи` + String get typeMealComment { + return Intl.message( + 'Как можно подробнее прокомментируйте прием пищи', + name: 'typeMealComment', + desc: '', + args: [], + ); + } + + /// `Прокомментируйте блюдо` + String get typePortionComment { + return Intl.message( + 'Прокомментируйте блюдо', + name: 'typePortionComment', + desc: '', + args: [], + ); + } + + /// `Прокомментировать прием пищи` + String get commentForMeal { + return Intl.message( + 'Прокомментировать прием пищи', + name: 'commentForMeal', + desc: '', + args: [], + ); + } + + /// `Отправить` + String get send { + return Intl.message('Отправить', name: 'send', desc: '', args: []); + } + + /// `Прокомментировать день` + String get dayCommentTitle { + return Intl.message( + 'Прокомментировать день', + name: 'dayCommentTitle', + desc: '', + args: [], + ); + } + + /// `Как можно подробнее прокомментируйте дневник пользователя` + String get typeDayComment { + return Intl.message( + 'Как можно подробнее прокомментируйте дневник пользователя', + name: 'typeDayComment', + desc: '', + args: [], + ); + } + + /// `Отменить консультацию` + String get cancelConsultation { + return Intl.message( + 'Отменить консультацию', + name: 'cancelConsultation', + desc: '', + args: [], + ); + } + + /// `Отмена заказа` + String get orederCancelation { + return Intl.message( + 'Отмена заказа', + name: 'orederCancelation', + desc: '', + args: [], + ); + } + + /// `Вы уверены, что хотите отменить заказ?` + String get youShureYouWantToCancelOrder { + return Intl.message( + 'Вы уверены, что хотите отменить заказ?', + name: 'youShureYouWantToCancelOrder', + desc: '', + args: [], + ); + } + + /// `Опишите причину отказа` + String get explainResonOfCancelation { + return Intl.message( + 'Опишите причину отказа', + name: 'explainResonOfCancelation', + desc: '', + args: [], + ); + } + + /// `Заказал по ошибке` + String get reasonExample { + return Intl.message( + 'Заказал по ошибке', + name: 'reasonExample', + desc: '', + args: [], + ); + } + + /// `Укажите сумму которую вы хотите вернуть` + String get typeAmoutOfReturn { + return Intl.message( + 'Укажите сумму которую вы хотите вернуть', + name: 'typeAmoutOfReturn', + desc: '', + args: [], + ); + } + + /// `Отменить` + String get cancel { + return Intl.message('Отменить', name: 'cancel', desc: '', args: []); + } + + /// `Заказ отменен` + String get orderCanceled { + return Intl.message( + 'Заказ отменен', + name: 'orderCanceled', + desc: '', + args: [], + ); + } + + /// `Не удалось отменить заказ` + String get couldNotCancelOrder { + return Intl.message( + 'Не удалось отменить заказ', + name: 'couldNotCancelOrder', + desc: '', + args: [], + ); + } + + /// `Комментарий ко всему дню` + String get dayComment { + return Intl.message( + 'Комментарий ко всему дню', + name: 'dayComment', + desc: '', + args: [], + ); + } + + /// `Комментарий к приему пищи` + String get mealComment { + return Intl.message( + 'Комментарий к приему пищи', + name: 'mealComment', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 3eb3575c..0c4e46d0 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -325,6 +325,7 @@ "cancel_order": "Отменить заказ", "refuse_order":"Отказаться от заказа", "user_info": "Анкета пользователя", + "pending_payment": "Ожидается оплата", "my_cards": "Мои карты", "new_card": "Добавить карту", "no_default_cards": "У вас нет привязанных карт. Что бы начать работу, привяжите карту", @@ -346,7 +347,27 @@ "try_again_2": "Попробуйте ещё раз", "sorry_we_did_not_find_the_plan": "Извините, план где-то потерялся, мы уже ищем его", "can_not_see_next_day": "Просмотр последующего дня возможен только при прохождении диеты", - "want_to_see_tomorrow": "Хотите заглянуть в завтра?" - + "want_to_see_tomorrow": "Хотите заглянуть в завтра?", + "you_can_not_start_plan_until_end_of_prev_one": "Вы не можете начать план, пока не закончите прохождение предыдущего", + "end_previous_plan": "Закончите прохождение предыдущего плана", + "you_can_not_open_this_day": "Вы не можете перейти в этот день", + "has_not_filled_it_yet": "ещё не закончил(a) этот день", + "typeMealComment": "Как можно подробнее прокомментируйте прием пищи", + "typePortionComment": "Прокомментируйте блюдо", + "commentForMeal": "Прокомментировать прием пищи", + "send": "Отправить", + "dayCommentTitle": "Прокомментировать день", + "typeDayComment": "Как можно подробнее прокомментируйте дневник пользователя", + "cancelConsultation": "Отменить консультацию", + "orederCancelation": "Отмена заказа", + "youShureYouWantToCancelOrder": "Вы уверены, что хотите отменить заказ?", + "explainResonOfCancelation": "Опишите причину отказа", + "reasonExample": "Заказал по ошибке", + "typeAmoutOfReturn": "Укажите сумму которую вы хотите вернуть", + "cancel": "Отменить", + "orderCanceled": "Заказ отменен", + "couldNotCancelOrder": "Не удалось отменить заказ", + "dayComment": "Комментарий ко всему дню", + "mealComment": "Комментарий к приему пищи" } diff --git a/lib/main.dart b/lib/main.dart index 82fed7ea..29f7bdf8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,7 @@ import 'package:eatvio_app/core/locator_service.dart' as di; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.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/chat/presentation/bloc/chats_cubit/chats_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';