From f590c49deff3760469e37c62dcc29ab9ece6bce9 Mon Sep 17 00:00:00 2001 From: Username1548 Date: Mon, 10 Mar 2025 22:25:30 +0700 Subject: [PATCH] pre merge commit --- assets/icons/reversed_exclamation_mark.svg | 4 + lib/core/app_router.dart | 6 +- lib/core/const.dart | 6 +- lib/core/locator_service.dart | 4 +- .../chat/presentation/pages/chat_screen.dart | 9 +- .../widgets/chat_entry_widget.dart | 1 - .../widgets/notification_banner.dart | 77 +++++ .../plans/models/chosen_plan_model.dart | 20 ++ .../models/chosen_plan_model.freezed.dart | 283 ++++++++++++++++++ .../plans/models/chosen_plan_model.g.dart | 27 ++ .../presentation/cubit/about_plan_cubit.dart | 25 ++ .../presentation/pages/about_plan_screen.dart | 24 +- .../plans/repositories/plan_repository.dart | 12 +- lib/features/plans/services/plan_service.dart | 14 +- .../plans/services/plan_service.g.dart | 31 ++ lib/generated/intl/messages_en.dart | 10 + lib/generated/l10n.dart | 72 +++++ lib/l10n/intl_en.arb | 11 +- 18 files changed, 604 insertions(+), 32 deletions(-) create mode 100644 assets/icons/reversed_exclamation_mark.svg create mode 100644 lib/features/chat/presentation/widgets/notification_banner.dart create mode 100644 lib/features/plans/models/chosen_plan_model.dart create mode 100644 lib/features/plans/models/chosen_plan_model.freezed.dart create mode 100644 lib/features/plans/models/chosen_plan_model.g.dart diff --git a/assets/icons/reversed_exclamation_mark.svg b/assets/icons/reversed_exclamation_mark.svg new file mode 100644 index 00000000..77b506bc --- /dev/null +++ b/assets/icons/reversed_exclamation_mark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 78d7d25f..bee9eaf8 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -241,12 +241,10 @@ class AppRouter { path: '/chat-screen', name: 'chatScreen', builder: (context, state) { - final data = state.extra as Map; - final chatsCubit = data['chatsCubit'] as ChatsCubit; + final data = state.extra as Map; final chatId = data['chatId'] as String; return ChatScreen( - chatId: chatId, - chatsCubit: chatsCubit, + chatId: chatId, ); }, ), diff --git a/lib/core/const.dart b/lib/core/const.dart index dfeae1fc..0b91d766 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]; @@ -8,4 +8,4 @@ const List planTypeValues = ['fit', 'slim', 'wellness', 'behavior']; const newMessagesChannelName = 'private-new-chat-messages'; const newNotificationsChannelName = 'private-message-events'; -const directChatChannelName = 'private-direct-chat'; +const directChatChannelName = 'private-direct-chat'; \ No newline at end of file diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 7e79faf4..cfca3868 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -87,6 +87,7 @@ Future init() async { sl.registerLazySingleton(() => NotificationsCubit(wsManager: sl())); sl.registerLazySingleton(() => FeedCubit(feedRepository: sl())); sl.registerLazySingleton(() => ProfileAvatarCubit(profileRepository: sl())); + sl.registerLazySingleton(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl())); // Cubit factories sl.registerFactory(() => RegistrationCubit(registrationRepository: sl())); @@ -96,8 +97,7 @@ Future init() async { sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl())); sl.registerFactory(() => profilePlans.PlansCubit(plansRepository: sl())); sl.registerFactory(() => DietsCubit(feedRepository: sl(), dietsRepository: sl())); - sl.registerFactory(() => CommentsCubit(feedRepository: sl())); - sl.registerFactory(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl())); + sl.registerFactory(() => CommentsCubit(feedRepository: sl())); sl.registerFactory(() => PlansCubit(feedRepository: sl(), planRepository: sl())); sl.registerFactory(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl())); sl.registerFactory(() => RegisterConsultantCubit(consultantRepository: sl())); diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 4144fbd5..4b2d8b94 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -11,6 +11,7 @@ import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget 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:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -19,10 +20,8 @@ import 'package:visibility_detector/visibility_detector.dart'; class ChatScreen extends StatefulWidget { final String chatId; - final ChatsCubit chatsCubit; const ChatScreen({ required this.chatId, - required this.chatsCubit, super.key, }); @@ -174,7 +173,9 @@ class _ChatScreenState extends State { () async { final result = await context.read().markMessagesAsRead(state.chatRoom.id, unreadMessagesIdsCopy); if (!result) return; - widget.chatsCubit.reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIdsCopy.length); + context + .read() + .reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIdsCopy.length); unreadMessagesIdsCopy.clear(); }, ); @@ -203,7 +204,7 @@ class _ChatScreenState extends State { () async { final result = await context.read().markMessagesAsRead(state.chatRoom.id, unreadMessagesIds); if (!result) return; - widget.chatsCubit.reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIds.length); + context.read().reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIds.length); unreadMessagesIds.clear(); }, ); diff --git a/lib/features/chat/presentation/widgets/chat_entry_widget.dart b/lib/features/chat/presentation/widgets/chat_entry_widget.dart index 61e6ea17..66e0d700 100644 --- a/lib/features/chat/presentation/widgets/chat_entry_widget.dart +++ b/lib/features/chat/presentation/widgets/chat_entry_widget.dart @@ -28,7 +28,6 @@ class _ChatEntryWidgetState extends State { 'chatScreen', extra: { 'chatId': widget.chatRoom.id.toString(), - 'chatsCubit': context.read(), }, ), child: Ink( diff --git a/lib/features/chat/presentation/widgets/notification_banner.dart b/lib/features/chat/presentation/widgets/notification_banner.dart new file mode 100644 index 00000000..5d3c8097 --- /dev/null +++ b/lib/features/chat/presentation/widgets/notification_banner.dart @@ -0,0 +1,77 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class NotificationBanner extends StatelessWidget { + const NotificationBanner({super.key, required this.bannerType}); + final BannerType bannerType; + @override + Widget build(BuildContext context) { + final size = MediaQuery.sizeOf(context); + final s = S.of(context); + return Container( + decoration: BoxDecoration( + color: appTheme.gray50, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 12, right: 8, left: 12), + child: SvgPicture.asset('assets/icons/reversed_exclamation_mark.svg', height: 68, width: 68), + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: switch (bannerType) { + BannerType.date => [ + SizedBox( + width: size.width * 0.6, + child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ), + SizedBox(height: 12), + GestureDetector( + onTap: () {}, + child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + ), + SizedBox(height: 16), + ], + BannerType.card => [ + SizedBox( + width: size.width * 0.6, + child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ), + SizedBox(height: 12), + GestureDetector( + onTap: () {}, + child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + ), + SizedBox(height: 16), + ], + BannerType.payment => [ + SizedBox( + width: size.width * 0.6, + child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ), + SizedBox(height: 12), + GestureDetector( + onTap: () {}, + child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + ), + SizedBox(height: 16), + ], + }, + ), + ), + ], + ), + ); + } +} + +enum BannerType { date, card, payment } diff --git a/lib/features/plans/models/chosen_plan_model.dart b/lib/features/plans/models/chosen_plan_model.dart new file mode 100644 index 00000000..525418b2 --- /dev/null +++ b/lib/features/plans/models/chosen_plan_model.dart @@ -0,0 +1,20 @@ +import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chosen_plan_model.freezed.dart'; +part 'chosen_plan_model.g.dart'; + +@freezed +class ChosenPlanModel with _$ChosenPlanModel { + + factory ChosenPlanModel({ + @JsonKey() required int id, + @JsonKey(name: 'option_type') required String optionType, + @JsonKey(name: 'start_date') required DateTime startDate, + @JsonKey(name: 'chat_id') required String chatId, + @JsonKey(name: 'plan') required PlanModel plan, + + }) = _ChosenPlanModel; + + factory ChosenPlanModel.fromJson(Map json) => _$ChosenPlanModelFromJson(json); +} \ No newline at end of file diff --git a/lib/features/plans/models/chosen_plan_model.freezed.dart b/lib/features/plans/models/chosen_plan_model.freezed.dart new file mode 100644 index 00000000..b26b04cd --- /dev/null +++ b/lib/features/plans/models/chosen_plan_model.freezed.dart @@ -0,0 +1,283 @@ +// 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 'chosen_plan_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'); + +ChosenPlanModel _$ChosenPlanModelFromJson(Map json) { + return _ChosenPlanModel.fromJson(json); +} + +/// @nodoc +mixin _$ChosenPlanModel { + @JsonKey() + int get id => throw _privateConstructorUsedError; + @JsonKey(name: 'option_type') + String get optionType => throw _privateConstructorUsedError; + @JsonKey(name: 'start_date') + DateTime get startDate => throw _privateConstructorUsedError; + @JsonKey(name: 'chat_id') + String get chatId => throw _privateConstructorUsedError; + @JsonKey(name: 'plan') + PlanModel get plan => throw _privateConstructorUsedError; + + /// Serializes this ChosenPlanModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ChosenPlanModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ChosenPlanModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChosenPlanModelCopyWith<$Res> { + factory $ChosenPlanModelCopyWith( + ChosenPlanModel value, $Res Function(ChosenPlanModel) then) = + _$ChosenPlanModelCopyWithImpl<$Res, ChosenPlanModel>; + @useResult + $Res call( + {@JsonKey() int id, + @JsonKey(name: 'option_type') String optionType, + @JsonKey(name: 'start_date') DateTime startDate, + @JsonKey(name: 'chat_id') String chatId, + @JsonKey(name: 'plan') PlanModel plan}); + + $PlanModelCopyWith<$Res> get plan; +} + +/// @nodoc +class _$ChosenPlanModelCopyWithImpl<$Res, $Val extends ChosenPlanModel> + implements $ChosenPlanModelCopyWith<$Res> { + _$ChosenPlanModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ChosenPlanModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? optionType = null, + Object? startDate = null, + Object? chatId = null, + Object? plan = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + optionType: null == optionType + ? _value.optionType + : optionType // ignore: cast_nullable_to_non_nullable + as String, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + chatId: null == chatId + ? _value.chatId + : chatId // ignore: cast_nullable_to_non_nullable + as String, + plan: null == plan + ? _value.plan + : plan // ignore: cast_nullable_to_non_nullable + as PlanModel, + ) as $Val); + } + + /// Create a copy of ChosenPlanModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $PlanModelCopyWith<$Res> get plan { + return $PlanModelCopyWith<$Res>(_value.plan, (value) { + return _then(_value.copyWith(plan: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ChosenPlanModelImplCopyWith<$Res> + implements $ChosenPlanModelCopyWith<$Res> { + factory _$$ChosenPlanModelImplCopyWith(_$ChosenPlanModelImpl value, + $Res Function(_$ChosenPlanModelImpl) then) = + __$$ChosenPlanModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey() int id, + @JsonKey(name: 'option_type') String optionType, + @JsonKey(name: 'start_date') DateTime startDate, + @JsonKey(name: 'chat_id') String chatId, + @JsonKey(name: 'plan') PlanModel plan}); + + @override + $PlanModelCopyWith<$Res> get plan; +} + +/// @nodoc +class __$$ChosenPlanModelImplCopyWithImpl<$Res> + extends _$ChosenPlanModelCopyWithImpl<$Res, _$ChosenPlanModelImpl> + implements _$$ChosenPlanModelImplCopyWith<$Res> { + __$$ChosenPlanModelImplCopyWithImpl( + _$ChosenPlanModelImpl _value, $Res Function(_$ChosenPlanModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ChosenPlanModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? optionType = null, + Object? startDate = null, + Object? chatId = null, + Object? plan = null, + }) { + return _then(_$ChosenPlanModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + optionType: null == optionType + ? _value.optionType + : optionType // ignore: cast_nullable_to_non_nullable + as String, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + chatId: null == chatId + ? _value.chatId + : chatId // ignore: cast_nullable_to_non_nullable + as String, + plan: null == plan + ? _value.plan + : plan // ignore: cast_nullable_to_non_nullable + as PlanModel, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ChosenPlanModelImpl implements _ChosenPlanModel { + _$ChosenPlanModelImpl( + {@JsonKey() required this.id, + @JsonKey(name: 'option_type') required this.optionType, + @JsonKey(name: 'start_date') required this.startDate, + @JsonKey(name: 'chat_id') required this.chatId, + @JsonKey(name: 'plan') required this.plan}); + + factory _$ChosenPlanModelImpl.fromJson(Map json) => + _$$ChosenPlanModelImplFromJson(json); + + @override + @JsonKey() + final int id; + @override + @JsonKey(name: 'option_type') + final String optionType; + @override + @JsonKey(name: 'start_date') + final DateTime startDate; + @override + @JsonKey(name: 'chat_id') + final String chatId; + @override + @JsonKey(name: 'plan') + final PlanModel plan; + + @override + String toString() { + return 'ChosenPlanModel(id: $id, optionType: $optionType, startDate: $startDate, chatId: $chatId, plan: $plan)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChosenPlanModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.optionType, optionType) || + other.optionType == optionType) && + (identical(other.startDate, startDate) || + other.startDate == startDate) && + (identical(other.chatId, chatId) || other.chatId == chatId) && + (identical(other.plan, plan) || other.plan == plan)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, id, optionType, startDate, chatId, plan); + + /// Create a copy of ChosenPlanModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ChosenPlanModelImplCopyWith<_$ChosenPlanModelImpl> get copyWith => + __$$ChosenPlanModelImplCopyWithImpl<_$ChosenPlanModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ChosenPlanModelImplToJson( + this, + ); + } +} + +abstract class _ChosenPlanModel implements ChosenPlanModel { + factory _ChosenPlanModel( + {@JsonKey() required final int id, + @JsonKey(name: 'option_type') required final String optionType, + @JsonKey(name: 'start_date') required final DateTime startDate, + @JsonKey(name: 'chat_id') required final String chatId, + @JsonKey(name: 'plan') required final PlanModel plan}) = + _$ChosenPlanModelImpl; + + factory _ChosenPlanModel.fromJson(Map json) = + _$ChosenPlanModelImpl.fromJson; + + @override + @JsonKey() + int get id; + @override + @JsonKey(name: 'option_type') + String get optionType; + @override + @JsonKey(name: 'start_date') + DateTime get startDate; + @override + @JsonKey(name: 'chat_id') + String get chatId; + @override + @JsonKey(name: 'plan') + PlanModel get plan; + + /// Create a copy of ChosenPlanModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ChosenPlanModelImplCopyWith<_$ChosenPlanModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/plans/models/chosen_plan_model.g.dart b/lib/features/plans/models/chosen_plan_model.g.dart new file mode 100644 index 00000000..52ecbdb7 --- /dev/null +++ b/lib/features/plans/models/chosen_plan_model.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chosen_plan_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ChosenPlanModelImpl _$$ChosenPlanModelImplFromJson( + Map json) => + _$ChosenPlanModelImpl( + id: (json['id'] as num).toInt(), + optionType: json['option_type'] as String, + startDate: DateTime.parse(json['start_date'] as String), + chatId: json['chat_id'] as String, + plan: PlanModel.fromJson(json['plan'] as Map), + ); + +Map _$$ChosenPlanModelImplToJson( + _$ChosenPlanModelImpl instance) => + { + 'id': instance.id, + 'option_type': instance.optionType, + 'start_date': instance.startDate.toIso8601String(), + 'chat_id': instance.chatId, + 'plan': instance.plan, + }; diff --git a/lib/features/plans/presentation/cubit/about_plan_cubit.dart b/lib/features/plans/presentation/cubit/about_plan_cubit.dart index 20ca860b..4cf93e12 100644 --- a/lib/features/plans/presentation/cubit/about_plan_cubit.dart +++ b/lib/features/plans/presentation/cubit/about_plan_cubit.dart @@ -1,8 +1,10 @@ import 'dart:developer'; +import 'package:dio/dio.dart'; import 'package:easy_debounce/easy_debounce.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/features/diets/repositories/diet_repository.dart'; +import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart'; import 'package:eatvio_app/features/plans/repositories/plan_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -125,4 +127,27 @@ class AboutPlanCubit extends Cubit { }, ); } + + Future 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); + } + } on DioException catch (e, st) { + log(e.toString(), stackTrace: st); + } catch (e, st) { + log(e.toString(), stackTrace: st); + } + return null; + } + + String _getOptionNameFromIndex(int index) => switch (index) { + 1 => 'options2', + 2 => 'options3', + _ => 'options1', + }; } diff --git a/lib/features/plans/presentation/pages/about_plan_screen.dart b/lib/features/plans/presentation/pages/about_plan_screen.dart index 0022a4f2..c34985a1 100644 --- a/lib/features/plans/presentation/pages/about_plan_screen.dart +++ b/lib/features/plans/presentation/pages/about_plan_screen.dart @@ -10,6 +10,7 @@ import 'package:eatvio_app/core/widgets/bookmark_button.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; @@ -30,6 +31,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; import '../widgets/option_widget.dart'; @@ -217,17 +219,21 @@ class _AboutPlanScreenState extends State { borderRadius: BorderRadius.circular(16), ), backgroundColor: appTheme.greenA700, - onPressed: () { - /// TODO: добавить обработку покупки плана - // showModalBottomSheet( - // context: context, - // builder: (context) => StartDietBottomSheet( - // aboutDietCubit: context.read(), - // ), - // ); + onPressed: () async { + final chosenPlan = await context.read().chosePlan(); + if (chosenPlan == null) { + showErrorDialog(context, title: 'Не удалось заказать план'); + return; + } + GoRouter.of(context).pushReplacementNamed( + 'chatScreen', + extra: { + 'chatId': chosenPlan.chatId, + }, + ); }, label: Text( - s.startDiet, + s.to_order, style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), ), ), diff --git a/lib/features/plans/repositories/plan_repository.dart b/lib/features/plans/repositories/plan_repository.dart index 313ad8b1..75ca7a08 100644 --- a/lib/features/plans/repositories/plan_repository.dart +++ b/lib/features/plans/repositories/plan_repository.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:eatvio_app/features/diets/repositories/diet_repository.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; +import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/features/plans/services/plan_service.dart'; - class PlanRepository { final PlanService planService; @@ -48,9 +48,15 @@ class PlanRepository { } } + Future chosePlan(String slug, Map option ) async { + try { + return await planService.chosePlan(slug: slug, option: option); + } catch (e) { + rethrow; + } + } + void emitLikesStateToFeed(LikesState state) => _planLikeStateStreamController.add(state); void emitFavoriteStateToFeed(FavoriteState state) => _planFavoriteStateStreamController.add(state); void emitCommentsStateToFeed(CommentsState state) => _planCommentsStateStreamController.add(state); } - - diff --git a/lib/features/plans/services/plan_service.dart b/lib/features/plans/services/plan_service.dart index d8a49a90..8a179760 100644 --- a/lib/features/plans/services/plan_service.dart +++ b/lib/features/plans/services/plan_service.dart @@ -1,20 +1,18 @@ - - import 'package:dio/dio.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; +import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:retrofit/retrofit.dart'; part 'plan_service.g.dart'; @RestApi() abstract class PlanService { - factory PlanService(Dio dio, {String baseUrl}) = _PlanService; @GET('/api/plans/show/{slug}') Future getPlan(@Path('slug') String slug); - @POST('/api/plans/{slug}/like') + @POST('/api/plans/{slug}/like') Future likePlan({ @Path() required String slug, }); @@ -28,4 +26,10 @@ abstract class PlanService { Future removeFromFavorite({ @Path() required String slug, }); -} \ No newline at end of file + + @POST('/api/plans/{slug}/choose') + Future chosePlan({ + @Path() required String slug, + @Body() required Map option, + }); +} diff --git a/lib/features/plans/services/plan_service.g.dart b/lib/features/plans/services/plan_service.g.dart index 09f90b41..189f1e31 100644 --- a/lib/features/plans/services/plan_service.g.dart +++ b/lib/features/plans/services/plan_service.g.dart @@ -101,6 +101,37 @@ class _PlanService implements PlanService { await _dio.fetch(_options); } + @override + Future chosePlan({ + required String slug, + required Map option, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(option); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/plans/${slug}/choose', + 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/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 9b5fef07..6d76f87e 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -32,6 +32,7 @@ class MessageLookup extends MessageLookupByLibrary { "Кол-во активных консультаций"), "active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"), "add": MessageLookupByLibrary.simpleMessage("Добавить"), + "add_card": MessageLookupByLibrary.simpleMessage("Привязать карту"), "add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"), "add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"), "adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"), @@ -58,6 +59,8 @@ class MessageLookup extends MessageLookupByLibrary { "calls_for_bullying": MessageLookupByLibrary.simpleMessage("Призывы к травле"), "calories": MessageLookupByLibrary.simpleMessage("Калории"), + "can_not_choose_start_date": MessageLookupByLibrary.simpleMessage( + "Дату старта плана невозможно выбрать, пока Вы не привяжите карту"), "can_not_load_data_please_try_again": MessageLookupByLibrary.simpleMessage( "Не удалось загрузить данные. Попробуйте обновить страницу"), @@ -72,10 +75,13 @@ class MessageLookup extends MessageLookupByLibrary { "chest": MessageLookupByLibrary.simpleMessage("Грудь"), "chest_circumference": MessageLookupByLibrary.simpleMessage("Обхват груди"), + "choose_date": MessageLookupByLibrary.simpleMessage("Выбрать дату"), "choose_diet_duration": MessageLookupByLibrary.simpleMessage("Выберите срок диеты"), "choose_plan_duration": MessageLookupByLibrary.simpleMessage("Выберите срок плана"), + "choose_plan_start_date": MessageLookupByLibrary.simpleMessage( + "Выберите дату старта плана питания"), "choose_start_diet_date": MessageLookupByLibrary.simpleMessage( "Выберите дату старты диеты. Она отобразиться у Вас в дневнике"), "circumference_empty": MessageLookupByLibrary.simpleMessage( @@ -130,6 +136,8 @@ class MessageLookup extends MessageLookupByLibrary { "create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"), "customer_price": MessageLookupByLibrary.simpleMessage("Цена для покупателя"), + "date_has_ben_set": MessageLookupByLibrary.simpleMessage( + "Дата старта начала консультации установлена"), "date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"), "day": MessageLookupByLibrary.simpleMessage("День"), "days": MessageLookupByLibrary.simpleMessage("дней"), @@ -327,6 +335,7 @@ class MessageLookup extends MessageLookupByLibrary { "Пароль обязателен для заполнения"), "password_short": MessageLookupByLibrary.simpleMessage( "В пароле должно быть минимум 6 символов"), + "pay": MessageLookupByLibrary.simpleMessage("Оплатить"), "pcs": MessageLookupByLibrary.simpleMessage("шт"), "personal": MessageLookupByLibrary.simpleMessage("Персональные"), "personal_diet_state": @@ -424,6 +433,7 @@ class MessageLookup extends MessageLookupByLibrary { "time": MessageLookupByLibrary.simpleMessage("Время прохождения"), "toOffer": MessageLookupByLibrary.simpleMessage("Предложить"), "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), + "to_order": MessageLookupByLibrary.simpleMessage("Заказать"), "try_again": MessageLookupByLibrary.simpleMessage("Попробовать снова"), "type_here_your_complaint": MessageLookupByLibrary.simpleMessage("Напишите здесь свою жалобу"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 1bca8570..3c20bf57 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -3179,6 +3179,78 @@ class S { args: [], ); } + + /// `Заказать` + String get to_order { + return Intl.message( + 'Заказать', + name: 'to_order', + desc: '', + args: [], + ); + } + + /// `Дату старта плана невозможно выбрать, пока Вы не привяжите карту` + String get can_not_choose_start_date { + return Intl.message( + 'Дату старта плана невозможно выбрать, пока Вы не привяжите карту', + name: 'can_not_choose_start_date', + desc: '', + args: [], + ); + } + + /// `Привязать карту` + String get add_card { + return Intl.message( + 'Привязать карту', + name: 'add_card', + desc: '', + args: [], + ); + } + + /// `Выберите дату старта плана питания` + String get choose_plan_start_date { + return Intl.message( + 'Выберите дату старта плана питания', + name: 'choose_plan_start_date', + desc: '', + args: [], + ); + } + + /// `Выбрать дату` + String get choose_date { + return Intl.message( + 'Выбрать дату', + name: 'choose_date', + desc: '', + args: [], + ); + } + + /// `Дата старта начала консультации установлена` + String get date_has_ben_set { + return Intl.message( + 'Дата старта начала консультации установлена', + name: 'date_has_ben_set', + desc: '', + args: [], + ); + } + + // skipped getter for the 'tap_on_link_to)pay' key + + /// `Оплатить` + String get pay { + return Intl.message( + 'Оплатить', + name: 'pay', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index c976ed0e..3594ada0 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -311,6 +311,15 @@ "could_not_update_diet": "Не удалось обновить диету", "could_not_publish_blog_post": "Не удалось опубликовать пост", "could_not_update_blog_post": "Не удалось обновить пост", - "could_not_delete_blog_post": "Не удалось удалить пост" + "could_not_delete_blog_post": "Не удалось удалить пост", + "to_order": "Заказать", + "can_not_choose_start_date": "Дату старта плана невозможно выбрать, пока Вы не привяжите карту", + "add_card": "Привязать карту", + "choose_plan_start_date": "Выберите дату старта плана питания", + "choose_date": "Выбрать дату", + "date_has_ben_set": "Дата старта начала консультации установлена", + "tap_on_link_to)pay": "Перейдите по ссылке для оплаты консультации", + "pay": "Оплатить" + }