diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index d12f15f2..26776d2b 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -35,7 +35,6 @@ import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_s 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/feed/models/plan_pagination_model.dart'; -import 'package:eatvio_app/features/plans/models/chosen_plan_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'; import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart'; @@ -259,15 +258,27 @@ class AppRouter { GoRoute( path: InfoByOrderScreen.path, name: InfoByOrderScreen.name, - builder: (context, state) => InfoByOrderScreen( - plan: state.extra as ChosenPlanModel, - )), + 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) => PaymentScreen( - url: state.extra as String, - )), + 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, + ); + }), ], ); } 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 87d05fba..b00a6d29 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -55,6 +55,7 @@ import 'package:eatvio_app/features/my_finance/services/cards_service.dart'; import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart'; import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart'; import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart'; +import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart'; import 'package:eatvio_app/features/plans/repositories/plans_repository.dart'; import 'package:eatvio_app/features/plans/services/plans_service.dart'; import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart'; @@ -121,6 +122,7 @@ Future init() async { sl.registerFactory(() => AuthCubit(authRepository: sl())); sl.registerFactory(() => ComplainingCubit(repository: sl())); sl.registerFactory(() => UserCardsCubit(cardsRepository: sl())); + sl.registerFactory(() => OrderInfoCubit(chatRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 2243829c..fe5ef6df 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -350,7 +350,8 @@ class _ChatScreenState extends State { hintText: 'Сообщение', hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)), filled: true, - border: OutlineInputBorder(borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), @@ -394,7 +395,7 @@ class _ChatScreenState extends State { left: 16, child: AboutOrderButton( onTap: () { - GoRouter.of(context).push(InfoByOrderScreen.path, extra: state.chatRoom.chosenPlan); + GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id}); }, )), ], diff --git a/lib/features/chat/presentation/pages/payment_screen.dart b/lib/features/chat/presentation/pages/payment_screen.dart index bdd496ea..236827ad 100644 --- a/lib/features/chat/presentation/pages/payment_screen.dart +++ b/lib/features/chat/presentation/pages/payment_screen.dart @@ -1,13 +1,14 @@ -import 'dart:developer'; - +import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:webview_flutter/webview_flutter.dart'; class PaymentScreen extends StatefulWidget { static const name = 'paymentScreen'; static const path = '/payment-screen'; final String url; - const PaymentScreen({super.key, required this.url}); + final int planId; + const PaymentScreen({super.key, required this.url, required this.planId}); @override State createState() => _PaymentScreenState(); @@ -23,13 +24,14 @@ class _PaymentScreenState extends State { ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate( - onProgress: (int progress) {}, - onPageStarted: (String url) {}, - onPageFinished: (String url) {}, - onHttpError: (HttpResponseError error) {}, - onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { - log(request.url); + if (request.url.contains('tbank/checkout-success')) { + if (request.url.contains('Success=true')) { + GoRouter.of(context).pushReplacement(InfoByOrderScreen.path, extra: {'planId': widget.planId, 'successfulPayment': true}); + } else { + GoRouter.of(context).pushReplacement(InfoByOrderScreen.path, extra: {'planId': widget.planId, 'successfulPayment': false}); + } + } return NavigationDecision.navigate; }, ), diff --git a/lib/features/chat/presentation/widgets/notification_banner.dart b/lib/features/chat/presentation/widgets/notification_banner.dart index 5b7bfefa..cfe81a39 100644 --- a/lib/features/chat/presentation/widgets/notification_banner.dart +++ b/lib/features/chat/presentation/widgets/notification_banner.dart @@ -1,6 +1,8 @@ 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/loader.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart'; +import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart'; 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/generated/l10n.dart'; @@ -9,9 +11,16 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; -class NotificationBanner extends StatelessWidget { +class NotificationBanner extends StatefulWidget { const NotificationBanner({super.key, required this.bannerType}); final BannerType bannerType; + + @override + State createState() => _NotificationBannerState(); +} + +class _NotificationBannerState extends State { + bool isLoading = false; @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); @@ -28,65 +37,81 @@ class NotificationBanner extends StatelessWidget { 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.choose_plan_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), - ), - const SizedBox(height: 12), - GestureDetector( - onTap: () { - final cubit = context.read(); - showModalBottomSheet( - context: context, - builder: (context) => ChosePlanDateWidget( - chatCubit: cubit, - ), - ); - }, - child: Text(s.choose_date, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), - ), - const SizedBox(height: 16), - ], - BannerType.card => [ - SizedBox( - width: size.width * 0.6, - child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), - ), - const SizedBox(height: 12), - GestureDetector( - onTap: () {}, - child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), - ), - const SizedBox(height: 16), - ], - BannerType.payment => [ - SizedBox( - width: size.width * 0.6, - child: Text(s.tap_on_link_to_pay, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), - ), - const SizedBox(height: 12), - GestureDetector( - onTap: () async { - final paymentLink = await context.read().getPaymentLink(); - if (paymentLink != null) { - if (!context.mounted) return; - GoRouter.of(context).push(PaymentScreen.path, extra: paymentLink); - } - }, - child: Text(s.pay, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), - ), - const SizedBox(height: 16), - ], - }, + if (isLoading) + const Padding( + padding: EdgeInsets.only(left: 40, top: 24), + child: Loader(), + ) + else + Padding( + padding: const EdgeInsets.only(top: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: switch (widget.bannerType) { + BannerType.date => [ + SizedBox( + width: size.width * 0.6, + child: Text(s.choose_plan_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: () { + final cubit = context.read(); + showModalBottomSheet( + context: context, + builder: (context) => ChosePlanDateWidget( + chatCubit: cubit, + ), + ); + }, + child: Text(s.choose_date, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + ), + const SizedBox(height: 16), + ], + BannerType.card => [ + SizedBox( + width: size.width * 0.6, + child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: () {}, + child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + ), + const SizedBox(height: 16), + ], + BannerType.payment => [ + SizedBox( + width: size.width * 0.6, + child: Text(s.tap_on_link_to_pay, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: () async { + final planId = (context.read().state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id; + setState(() { + isLoading = true; + }); + final paymentLink = await context.read().getPaymentLink(); + setState(() { + isLoading = false; + }); + if (paymentLink != null && planId != null) { + if (!context.mounted) return; + GoRouter.of(context).pushReplacement( + PaymentScreen.path, + extra: {'url': paymentLink, 'planId': planId}, + ); + } + }, + child: Text(s.pay, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + ), + const SizedBox(height: 16), + ], + }, + ), ), - ), ], ), ); diff --git a/lib/features/diary/models/diary_day_model.dart b/lib/features/diary/models/diary_day_model.dart index 9493ffb1..fe742176 100644 --- a/lib/features/diary/models/diary_day_model.dart +++ b/lib/features/diary/models/diary_day_model.dart @@ -5,14 +5,18 @@ part 'diary_day_model.freezed.dart'; part 'diary_day_model.g.dart'; enum PortionState { - active, + @JsonValue('filled_diet') filledDiet, + @JsonValue('eaten_from_diet') eatenFromDiet, eaten, + @JsonValue('eaten_deleted') eatenDeleted, + @JsonValue('eaten_edited') eatenEdited, commented, - revieweByUser, + @JsonValue('reviewed_by_user') + reviewedByUser, } @freezed diff --git a/lib/features/diary/models/diary_day_model.g.dart b/lib/features/diary/models/diary_day_model.g.dart index c48e76bf..ca269007 100644 --- a/lib/features/diary/models/diary_day_model.g.dart +++ b/lib/features/diary/models/diary_day_model.g.dart @@ -113,14 +113,13 @@ Map _$$PortionModelImplToJson(_$PortionModelImpl instance) => }; const _$PortionStateEnumMap = { - PortionState.active: 'active', - PortionState.filledDiet: 'filledDiet', - PortionState.eatenFromDiet: 'eatenFromDiet', + PortionState.filledDiet: 'filled_diet', + PortionState.eatenFromDiet: 'eaten_from_diet', PortionState.eaten: 'eaten', - PortionState.eatenDeleted: 'eatenDeleted', - PortionState.eatenEdited: 'eatenEdited', + PortionState.eatenDeleted: 'eaten_deleted', + PortionState.eatenEdited: 'eaten_edited', PortionState.commented: 'commented', - PortionState.revieweByUser: 'revieweByUser', + PortionState.reviewedByUser: 'reviewed_by_user', }; _$TypeOfDiaryModelImpl _$$TypeOfDiaryModelImplFromJson( diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index ce54bc87..60e6a903 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -39,17 +39,19 @@ class DiaryCubit extends Cubit { } try { final diaryDay = await diaryRepository.getDiaryByDate('$selectedDay'); + + final nutrition = await getNutrition(diaryDay.id); emit( DiaryState.loaded( diary: diaryDay, - nutrition: await getNutrition((await diaryRepository.getDiaryByDate('$selectedDay')).id), + nutrition: nutrition, selectedDay: selectedDay, selectedMeal: selectedMeal, ), ); - } catch (e) { + } catch (e, st) { + log(e.toString(), stackTrace: st); emit(DiaryState.error(errorMessage: e.toString())); - rethrow; } } @@ -181,10 +183,12 @@ class DiaryCubit extends Cubit { fillSatiety({required int diaryId, required int satiety}) async { await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety); + await fetchDiary(); } fillMood({required int diaryId, required int mood}) async { await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood); + await fetchDiary(); } fillComment(String comment) { diff --git a/lib/features/diary/presentation/bloc/diary_state.dart b/lib/features/diary/presentation/bloc/diary_state.dart index 02138c7a..0fa800cf 100644 --- a/lib/features/diary/presentation/bloc/diary_state.dart +++ b/lib/features/diary/presentation/bloc/diary_state.dart @@ -19,5 +19,6 @@ class DiaryState with _$DiaryState { required NutritionDataModel nutrition, required DateTime selectedDay, required int selectedMeal, + }) = DiaryStateLoaded; } diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index fe8298c6..ee821f8a 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -33,7 +33,7 @@ class _DiaryScreenState extends State { } if (state is DiaryStateError) { return Scaffold( - body: DataErrorWidget(onReload: context.read().fetchDiary()), + body: DataErrorWidget(onReload: () => context.read().fetchDiary()), ); } return Scaffold( @@ -48,12 +48,15 @@ class _DiaryScreenState extends State { children: [ const CalendarWidget(), if (state is DiaryStateLoading) const Loader(), - if (state is DiaryStateLoaded) const WakeupAndRetireWidget(), + if (state is DiaryStateLoaded) WakeupAndRetireWidget(showRetire: state.diary.typeOfDiary.hasChosenPlan), const SizedBox(height: 12), - if (state is DiaryStateLoaded) const MealWidget(), + if (state is DiaryStateLoaded) + MealWidget( + showMood: state.diary.typeOfDiary.hasChosenPlan, + ), if (state is DiaryStateLoaded) const CaloriesChartWidget(), const SizedBox(height: 12), - if (state is DiaryStateLoaded) const CommentForConsultantWidget(), + if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) const CommentForConsultantWidget(), if (state is DiaryStateLoaded && state.diary.closed == 0) Padding( padding: const EdgeInsets.only(top: 12), diff --git a/lib/features/diary/presentation/widgets/calendar_widget.dart b/lib/features/diary/presentation/widgets/calendar_widget.dart index 009d12ad..23c727d3 100644 --- a/lib/features/diary/presentation/widgets/calendar_widget.dart +++ b/lib/features/diary/presentation/widgets/calendar_widget.dart @@ -1,6 +1,8 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/show_error_dialog.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/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -19,6 +21,7 @@ class _CalendarWidgetState extends State { @override Widget build(BuildContext context) { + final s = S.of(context); return BlocConsumer( listener: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { @@ -48,6 +51,11 @@ class _CalendarWidgetState extends State { ), onDaySelected: (selectedDay, focusedDay) { if (!isSameDay(_selectedDay, selectedDay)) { + final today = DateTime.now(); + if (selectedDay.isAfter(today)) { + showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day); + return; + } setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; diff --git a/lib/features/diary/presentation/widgets/meal_widget.dart b/lib/features/diary/presentation/widgets/meal_widget.dart index 16d9cfac..dee82d41 100644 --- a/lib/features/diary/presentation/widgets/meal_widget.dart +++ b/lib/features/diary/presentation/widgets/meal_widget.dart @@ -8,8 +8,8 @@ import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_pick import 'package:flutter/material.dart'; class MealWidget extends StatefulWidget { - const MealWidget({super.key}); - + const MealWidget({super.key, required this.showMood}); + final bool showMood; @override State createState() => _MealWidgetState(); } @@ -20,12 +20,12 @@ class _MealWidgetState extends State { return Material( borderRadius: BorderRadius.circular(20), color: Colors.white, - child: const Padding( - padding: EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + const Text( 'Прием пищи', style: TextStyle( color: Color(0xFF323F4B), @@ -33,8 +33,8 @@ class _MealWidgetState extends State { fontWeight: FontWeight.w500, ), ), - SizedBox(height: 16), - Row( + const SizedBox(height: 16), + const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ MealButtonWidget(mealNumber: 1), @@ -44,18 +44,20 @@ class _MealWidgetState extends State { MealButtonWidget(mealNumber: 5), ], ), - SizedBox(height: 12), - TimeOfMealPickerWidget(), - SizedBox(height: 12), - ProductsListWidget(), - SizedBox(height: 12), - AddProductButtonWidget(), - SizedBox(height: 12), - PhotoGalleryWidget(), - SizedBox(height: 12), - SatietyRatingWidget(), - SizedBox(height: 12), - MoodRatingWidget(), + const SizedBox(height: 12), + const TimeOfMealPickerWidget(), + const SizedBox(height: 12), + const ProductsListWidget(), + const SizedBox(height: 12), + const AddProductButtonWidget(), + const SizedBox(height: 12), + const PhotoGalleryWidget(), + const SizedBox(height: 12), + const SatietyRatingWidget(), + if (widget.showMood) ...[ + const SizedBox(height: 12), + const MoodRatingWidget(), + ] ], ), ), diff --git a/lib/features/diary/presentation/widgets/mood_rating_widget.dart b/lib/features/diary/presentation/widgets/mood_rating_widget.dart index cb673098..6e72e3a4 100644 --- a/lib/features/diary/presentation/widgets/mood_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/mood_rating_widget.dart @@ -14,17 +14,18 @@ class MoodRatingWidget extends StatefulWidget { } class _MoodRatingWidgetState extends State { - late double currentSelectedRating; - bool isFirstFetch = true; + List currentSelectedRating = [0, 0, 0, 0, 0]; + List isFirstFetch = [true, true, true, true, true]; @override Widget build(BuildContext context) { return BlocBuilder( builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { - if (isFirstFetch) { - currentSelectedRating = state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1; - isFirstFetch = false; + final selectedIndex = state.selectedMeal - 1; + if (isFirstFetch[selectedIndex]) { + currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].mood.toDouble() + 1; + isFirstFetch[selectedIndex] = false; } return Padding( padding: const EdgeInsets.only(top: 24), @@ -46,7 +47,7 @@ class _MoodRatingWidgetState extends State { width: 32, ), RatingBar.builder( - initialRating: state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1, + initialRating: state.diary.meals[selectedIndex].mood.toDouble() + 1, itemCount: 6, itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), updateOnDrag: true, @@ -56,129 +57,129 @@ class _MoodRatingWidgetState extends State { return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFC82222), - border: currentSelectedRating == 1 + border: currentSelectedRating[selectedIndex] == 1 ? Border.all( - width: 2, - color: const Color(0xFFAA3333), - strokeAlign: BorderSide.strokeAlignOutside, - ) + width: 2, + color: const Color(0xFFAA3333), + strokeAlign: BorderSide.strokeAlignOutside, + ) : null, - borderRadius: currentSelectedRating == 1 + borderRadius: currentSelectedRating[selectedIndex] == 1 ? const BorderRadius.horizontal( - left: Radius.circular(8), - right: Radius.circular(2), - ) + left: Radius.circular(8), + right: Radius.circular(2), + ) : const BorderRadius.horizontal( - left: Radius.circular(8), - ), + left: Radius.circular(8), + ), ), ); case 1: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFFF3E3E), - border: currentSelectedRating == 2 + border: currentSelectedRating[selectedIndex] == 2 ? Border.all( - width: 2, - color: const Color(0xFF550000), - strokeAlign: BorderSide.strokeAlignOutside, - ) + width: 2, + color: const Color(0xFF550000), + strokeAlign: BorderSide.strokeAlignOutside, + ) : null, - borderRadius: currentSelectedRating == 2 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null, ), ); case 2: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFF8C926), - border: currentSelectedRating == 3 + border: currentSelectedRating[selectedIndex] == 3 ? Border.all( - width: 2, - color: const Color(0xFF8C5D00), - strokeAlign: BorderSide.strokeAlignOutside, - ) + width: 2, + color: const Color(0xFF8C5D00), + strokeAlign: BorderSide.strokeAlignOutside, + ) : null, - borderRadius: currentSelectedRating == 3 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null, ), ); case 3: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFFFF3BF), - border: currentSelectedRating == 4 + border: currentSelectedRating[selectedIndex] == 4 ? Border.all( - width: 2, - color: const Color(0xFFB5911B), - strokeAlign: BorderSide.strokeAlignOutside, - ) + width: 2, + color: const Color(0xFFB5911B), + strokeAlign: BorderSide.strokeAlignOutside, + ) : null, - borderRadius: currentSelectedRating == 4 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null, ), ); case 4: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFF62E833), - border: currentSelectedRating == 5 + border: currentSelectedRating[selectedIndex] == 5 ? Border.all( - width: 2, - color: const Color(0xFF276918), - strokeAlign: BorderSide.strokeAlignOutside, - ) + width: 2, + color: const Color(0xFF276918), + strokeAlign: BorderSide.strokeAlignOutside, + ) : null, - borderRadius: currentSelectedRating == 5 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null, ), ); default: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFF4DCE20), - border: currentSelectedRating == 6 + border: currentSelectedRating[selectedIndex] == 6 ? Border.all( - width: 2, - color: const Color(0xFF184D12), - strokeAlign: BorderSide.strokeAlignOutside, - ) + width: 2, + color: const Color(0xFF184D12), + strokeAlign: BorderSide.strokeAlignOutside, + ) : null, - borderRadius: currentSelectedRating == 6 + borderRadius: currentSelectedRating[selectedIndex] == 6 ? const BorderRadius.horizontal( - left: Radius.circular(2), - right: Radius.circular(8), - ) + left: Radius.circular(2), + right: Radius.circular(8), + ) : const BorderRadius.horizontal( - right: Radius.circular(8), - ), + right: Radius.circular(8), + ), ), ); } }, onRatingUpdate: (value) { setState(() { - currentSelectedRating = value; + currentSelectedRating[selectedIndex] = value; }); EasyDebounce.debounce( 'change-satiety', const Duration(milliseconds: 250), - () { - context.read().fillMood(diaryId: state.diary.id, mood: currentSelectedRating.toInt() - 1); + () { + context.read().fillMood(diaryId: state.diary.id, mood: currentSelectedRating[selectedIndex].toInt() - 1); }, ); }, diff --git a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart index 04bf3e64..1f3b332c 100644 --- a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart @@ -14,17 +14,18 @@ class SatietyRatingWidget extends StatefulWidget { } class _SatietyRatingWidgetState extends State { - late double currentSelectedRating; - bool isFirstFetch = true; + List currentSelectedRating = [0, 0, 0, 0, 0]; + List isFirstFetch = [true, true, true, true, true]; @override Widget build(BuildContext context) { return BlocBuilder( builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { - if (isFirstFetch) { - currentSelectedRating = state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1; - isFirstFetch = false; + final selectedIndex = state.selectedMeal - 1; + if (isFirstFetch[selectedIndex]) { + currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].satiety.toDouble() + 1; + isFirstFetch[selectedIndex] = false; } return Padding( padding: const EdgeInsets.only(top: 24), @@ -46,7 +47,7 @@ class _SatietyRatingWidgetState extends State { width: 32, ), RatingBar.builder( - initialRating: state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1, + initialRating: state.diary.meals[selectedIndex].satiety.toDouble() + 1, itemCount: 6, itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), updateOnDrag: true, @@ -56,17 +57,17 @@ class _SatietyRatingWidgetState extends State { return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFC82222), - border: currentSelectedRating == 1 + border: currentSelectedRating[selectedIndex] == 1 ? Border.all( width: 2, color: const Color(0xFFAA3333), strokeAlign: BorderSide.strokeAlignOutside, ) : null, - borderRadius: currentSelectedRating == 1 + borderRadius: currentSelectedRating[selectedIndex] == 1 ? const BorderRadius.horizontal( left: Radius.circular(8), right: Radius.circular(2), @@ -80,85 +81,85 @@ class _SatietyRatingWidgetState extends State { return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFFF3E3E), - border: currentSelectedRating == 2 + border: currentSelectedRating[selectedIndex] == 2 ? Border.all( width: 2, color: const Color(0xFF550000), strokeAlign: BorderSide.strokeAlignOutside, ) : null, - borderRadius: currentSelectedRating == 2 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null, ), ); case 2: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFF8C926), - border: currentSelectedRating == 3 + border: currentSelectedRating[selectedIndex] == 3 ? Border.all( width: 2, color: const Color(0xFF8C5D00), strokeAlign: BorderSide.strokeAlignOutside, ) : null, - borderRadius: currentSelectedRating == 3 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null, ), ); case 3: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFFFFF3BF), - border: currentSelectedRating == 4 + border: currentSelectedRating[selectedIndex] == 4 ? Border.all( width: 2, color: const Color(0xFFB5911B), strokeAlign: BorderSide.strokeAlignOutside, ) : null, - borderRadius: currentSelectedRating == 4 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null, ), ); case 4: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFF62E833), - border: currentSelectedRating == 5 + border: currentSelectedRating[selectedIndex] == 5 ? Border.all( width: 2, color: const Color(0xFF276918), strokeAlign: BorderSide.strokeAlignOutside, ) : null, - borderRadius: currentSelectedRating == 5 ? BorderRadius.circular(2) : null, + borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null, ), ); default: return Container( width: MediaQuery.of(context).size.width / 6, height: 24, - margin: currentSelectedRating == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null, + margin: currentSelectedRating[selectedIndex] == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null, decoration: BoxDecoration( color: const Color(0xFF4DCE20), - border: currentSelectedRating == 6 + border: currentSelectedRating[selectedIndex] == 6 ? Border.all( width: 2, color: const Color(0xFF184D12), strokeAlign: BorderSide.strokeAlignOutside, ) : null, - borderRadius: currentSelectedRating == 6 + borderRadius: currentSelectedRating[selectedIndex] == 6 ? const BorderRadius.horizontal( left: Radius.circular(2), right: Radius.circular(8), @@ -172,13 +173,13 @@ class _SatietyRatingWidgetState extends State { }, onRatingUpdate: (value) { setState(() { - currentSelectedRating = value; + currentSelectedRating[selectedIndex] = value; }); EasyDebounce.debounce( 'change-satiety', const Duration(milliseconds: 250), () { - context.read().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating.toInt() - 1); + context.read().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating[selectedIndex].toInt() - 1); }, ); }, 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 114489e6..b4aee1c5 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,8 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../../../../core/theme/theme_helper.dart'; class WakeupAndRetireWidget extends StatefulWidget { - const WakeupAndRetireWidget({super.key}); - + const WakeupAndRetireWidget({super.key, required this.showRetire}); + final bool showRetire; @override State createState() => _WakeupAndRetireWidgetState(); } @@ -29,65 +29,62 @@ class _WakeupAndRetireWidgetState extends State { showModalBottomSheet( context: context, useRootNavigator: true, - builder: - (context) => SizedBox( - height: MediaQuery.of(context).copyWith().size.height / 3, - child: Column( - children: [ - Expanded( - child: CupertinoDatePicker( - mode: CupertinoDatePickerMode.time, - use24hFormat: true, - initialDateTime: DateTime.now(), - onDateTimeChanged: (newTime) { - if (isWakeup) { - wakeup = newTime; - } else { - retire = newTime; - } - }, - ), - ), - InkWell( - onTap: () { - Navigator.of(context).pop(); - if (isWakeup) { - diaryCubit.fillWakeup( - diaryId: diaryId, - time: - '${wakeup.hour.toString().padLeft(2, '0')}:${wakeup.minute.toString().padLeft(2, '0')}:00', - ); - } else { - diaryCubit.fillRetire( - diaryId: diaryId, - time: - '${retire.hour.toString().padLeft(2, '0')}:${retire.minute.toString().padLeft(2, '0')}:00', - ); - } - }, - borderRadius: BorderRadius.circular(20), - child: Ink( - width: MediaQuery.of(context).size.width - 16, - height: 52, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: appTheme.blueA400, - ), - child: const Center( - child: Text( - 'Сохранить', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), - ), - ], + builder: (context) => SizedBox( + height: MediaQuery.of(context).copyWith().size.height / 3, + child: Column( + children: [ + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + use24hFormat: true, + initialDateTime: DateTime.now(), + onDateTimeChanged: (newTime) { + if (isWakeup) { + wakeup = newTime; + } else { + retire = newTime; + } + }, + ), ), - ), + InkWell( + onTap: () { + Navigator.of(context).pop(); + if (isWakeup) { + diaryCubit.fillWakeup( + diaryId: diaryId, + time: '${wakeup.hour.toString().padLeft(2, '0')}:${wakeup.minute.toString().padLeft(2, '0')}:00', + ); + } else { + diaryCubit.fillRetire( + diaryId: diaryId, + time: '${retire.hour.toString().padLeft(2, '0')}:${retire.minute.toString().padLeft(2, '0')}:00', + ); + } + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + width: MediaQuery.of(context).size.width - 16, + height: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: appTheme.blueA400, + ), + child: const Center( + child: Text( + 'Сохранить', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ), ); } @@ -141,9 +138,9 @@ class _WakeupAndRetireWidgetState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( state.diary.wakeup.toString().substring( - 0, - state.diary.wakeup.toString().length - 3, - ), + 0, + state.diary.wakeup.toString().length - 3, + ), style: const TextStyle( color: Color(0xFF6D6F71), fontSize: 17, @@ -158,65 +155,67 @@ class _WakeupAndRetireWidgetState extends State { ), ), ), - const SizedBox(width: 8), - Expanded( - child: Material( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 12, - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset('assets/icons/retire.svg'), - const SizedBox(width: 8), - const Text( - 'Уход ко сну', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1, - ), - ), - ], - ), - const SizedBox(height: 8), - InkWell( - onTap: () => showTimePicker(state.diary.id, false), - borderRadius: BorderRadius.circular(100), - child: Ink( - height: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: const Color(0xFFF1F2F6), - ), - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - state.diary.retire.toString().substring( - 0, - state.diary.retire.toString().length - 3, - ), - style: const TextStyle( - color: Color(0xFF6D6F71), - fontSize: 17, + if (widget.showRetire) ...[ + const SizedBox(width: 8), + Expanded( + child: Material( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 12, + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset('assets/icons/retire.svg'), + const SizedBox(width: 8), + const Text( + 'Уход ко сну', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500, + height: 1, + ), + ), + ], + ), + const SizedBox(height: 8), + InkWell( + onTap: () => showTimePicker(state.diary.id, false), + borderRadius: BorderRadius.circular(100), + child: Ink( + height: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: const Color(0xFFF1F2F6), + ), + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + state.diary.retire.toString().substring( + 0, + state.diary.retire.toString().length - 3, + ), + style: const TextStyle( + color: Color(0xFF6D6F71), + fontSize: 17, + fontWeight: FontWeight.w500, + ), ), ), ), ), - ), - ], + ], + ), ), ), ), - ), + ] ], ), ); diff --git a/lib/features/order_info/cubit/order_info_cubit.dart b/lib/features/order_info/cubit/order_info_cubit.dart new file mode 100644 index 00000000..4cb4d576 --- /dev/null +++ b/lib/features/order_info/cubit/order_info_cubit.dart @@ -0,0 +1,32 @@ +import 'dart:developer'; + +import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class OrderInfoCubit extends Cubit { + OrderInfoCubit({required this.chatRepository}) : super(OrderInfoState()); + final ChatRepository chatRepository; + + Future getPaymentLink() async { + try { + final plan = state.chosenPlan; + if (plan == null) return null; + final paymentModel = await chatRepository.getPaymentLink(id: plan.id); + return paymentModel.paymentUrl; + } catch (e) { + log(e.toString()); + return null; + } + } + + Future getChosenPlan(int id) async{ + try { + emit(state.copyWith(isLoading: true)); + emit(state.copyWith(isLoading: false)); + } 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 new file mode 100644 index 00000000..717f0058 --- /dev/null +++ b/lib/features/order_info/cubit/order_info_state.dart @@ -0,0 +1,14 @@ +import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'order_info_state.freezed.dart'; + +@freezed +class OrderInfoState with _$OrderInfoState { + + factory OrderInfoState({ + @Default(false) 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 new file mode 100644 index 00000000..4d217ef1 --- /dev/null +++ b/lib/features/order_info/cubit/order_info_state.freezed.dart @@ -0,0 +1,185 @@ +// 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 'order_info_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 _$OrderInfoState { + bool get isLoading => throw _privateConstructorUsedError; + ChosenPlanModel? get chosenPlan => throw _privateConstructorUsedError; + + /// Create a copy of OrderInfoState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $OrderInfoStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OrderInfoStateCopyWith<$Res> { + factory $OrderInfoStateCopyWith( + OrderInfoState value, $Res Function(OrderInfoState) then) = + _$OrderInfoStateCopyWithImpl<$Res, OrderInfoState>; + @useResult + $Res call({bool isLoading, ChosenPlanModel? chosenPlan}); + + $ChosenPlanModelCopyWith<$Res>? get chosenPlan; +} + +/// @nodoc +class _$OrderInfoStateCopyWithImpl<$Res, $Val extends OrderInfoState> + implements $OrderInfoStateCopyWith<$Res> { + _$OrderInfoStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OrderInfoState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? chosenPlan = freezed, + }) { + return _then(_value.copyWith( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + chosenPlan: freezed == chosenPlan + ? _value.chosenPlan + : chosenPlan // ignore: cast_nullable_to_non_nullable + as ChosenPlanModel?, + ) as $Val); + } + + /// Create a copy of OrderInfoState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ChosenPlanModelCopyWith<$Res>? get chosenPlan { + if (_value.chosenPlan == null) { + return null; + } + + return $ChosenPlanModelCopyWith<$Res>(_value.chosenPlan!, (value) { + return _then(_value.copyWith(chosenPlan: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$OrderInfoStateImplCopyWith<$Res> + implements $OrderInfoStateCopyWith<$Res> { + factory _$$OrderInfoStateImplCopyWith(_$OrderInfoStateImpl value, + $Res Function(_$OrderInfoStateImpl) then) = + __$$OrderInfoStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({bool isLoading, ChosenPlanModel? chosenPlan}); + + @override + $ChosenPlanModelCopyWith<$Res>? get chosenPlan; +} + +/// @nodoc +class __$$OrderInfoStateImplCopyWithImpl<$Res> + extends _$OrderInfoStateCopyWithImpl<$Res, _$OrderInfoStateImpl> + implements _$$OrderInfoStateImplCopyWith<$Res> { + __$$OrderInfoStateImplCopyWithImpl( + _$OrderInfoStateImpl _value, $Res Function(_$OrderInfoStateImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderInfoState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? chosenPlan = freezed, + }) { + return _then(_$OrderInfoStateImpl( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + chosenPlan: freezed == chosenPlan + ? _value.chosenPlan + : chosenPlan // ignore: cast_nullable_to_non_nullable + as ChosenPlanModel?, + )); + } +} + +/// @nodoc + +class _$OrderInfoStateImpl implements _OrderInfoState { + _$OrderInfoStateImpl({this.isLoading = false, this.chosenPlan = null}); + + @override + @JsonKey() + final bool isLoading; + @override + @JsonKey() + final ChosenPlanModel? chosenPlan; + + @override + String toString() { + return 'OrderInfoState(isLoading: $isLoading, chosenPlan: $chosenPlan)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$OrderInfoStateImpl && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.chosenPlan, chosenPlan) || + other.chosenPlan == chosenPlan)); + } + + @override + int get hashCode => Object.hash(runtimeType, isLoading, chosenPlan); + + /// Create a copy of OrderInfoState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$OrderInfoStateImplCopyWith<_$OrderInfoStateImpl> get copyWith => + __$$OrderInfoStateImplCopyWithImpl<_$OrderInfoStateImpl>( + this, _$identity); +} + +abstract class _OrderInfoState implements OrderInfoState { + factory _OrderInfoState( + {final bool isLoading, + final ChosenPlanModel? chosenPlan}) = _$OrderInfoStateImpl; + + @override + bool get isLoading; + @override + ChosenPlanModel? get chosenPlan; + + /// Create a copy of OrderInfoState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$OrderInfoStateImplCopyWith<_$OrderInfoStateImpl> get copyWith => + throw _privateConstructorUsedError; +} 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 d077803c..bbddd69d 100644 --- a/lib/features/order_info/presentation/info_by_order_screen.dart +++ b/lib/features/order_info/presentation/info_by_order_screen.dart @@ -1,80 +1,172 @@ +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/loader.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; +import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart'; +import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart'; +import 'package:eatvio_app/features/order_info/presentation/widgets/payment_banner.dart'; import 'package:eatvio_app/features/order_info/presentation/widgets/plan_info.dart'; import 'package:eatvio_app/features/order_info/presentation/widgets/plan_props.dart'; import 'package:eatvio_app/features/order_info/presentation/widgets/plan_state.dart'; import 'package:eatvio_app/features/order_info/presentation/widgets/plan_status_box.dart'; -import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/core/widgets/option_widget.dart'; +import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.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 InfoByOrderScreen extends StatefulWidget { +class InfoByOrderScreen extends StatelessWidget { static const name = 'infoByOrderScreen'; static const path = '/info-by-order-screen'; - final ChosenPlanModel plan; - const InfoByOrderScreen({ - super.key, - required this.plan, - }); - + const InfoByOrderScreen({super.key, this.successfulPayment, required this.planId}); + final bool? successfulPayment; + final int planId; @override - State createState() => _InfoByOrderScreenState(); + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => sl()..getChosenPlan(planId), + child: InfoByOrderScreenContent( + successfulPayment: successfulPayment, + planId: planId, + ), + ); + } } -class _InfoByOrderScreenState extends State { +class InfoByOrderScreenContent extends StatefulWidget { + static const name = 'infoByOrderScreen'; + static const path = '/info-by-order-screen'; + const InfoByOrderScreenContent({ + super.key, + required this.planId, + required this.successfulPayment, + }); + final int planId; + final bool? successfulPayment; + @override + State createState() => _InfoByOrderScreenContentState(); +} + +class _InfoByOrderScreenContentState extends State { @override Widget build(BuildContext context) { final s = S.of(context); - PacketOptions? chosenOption; - if (widget.plan.optionType == 'options1') { - chosenOption = widget.plan.plan.packet1Options; - } else if (widget.plan.optionType == 'options2') { - chosenOption = widget.plan.plan.packet2Options; - } else { - chosenOption = widget.plan.plan.packet3Options; - } - 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: () {}), - ), - body: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 8), - AuthorCard( - author: widget.plan.plan.author, - title: s.plan_seller, - titleStyle: TextStyles.textSSemibold.copyWith(color: appTheme.gray), - backgroundColor: appTheme.whiteA700, + + return BlocBuilder( + builder: (context, state) { + final plan = state.chosenPlan; + if (state.isLoading) { + Scaffold( + appBar: AppBar( + title: Text( + s.infoAboutOrder, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), - const SizedBox(height: 8), - PlanProps(planDuration: widget.plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: PlanStatusValues.paymentFailed), - const SizedBox(height: 8), - PlanInfo( - title: widget.plan.plan.name, - description: widget.plan.plan.details, - imagePath: widget.plan.plan.image, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + GoRouter.of(context).pop(); + }), + ), + body: const Center( + child: Loader(), + ), + ); + } + if (plan == null) { + return Scaffold( + appBar: AppBar( + title: Text( + s.infoAboutOrder, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), - const SizedBox(height: 8), - OptionWidget( - option: chosenOption, - disableDuration: true, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + GoRouter.of(context).pop(); + }), + ), + body: Center( + child: Text( + s.sorry_we_did_not_find_the_plan, + style: TextStyles.textSSemibold.copyWith(color: appTheme.black), ), - const SizedBox(height: 8), - PlanState( - state: widget.plan.chosenPlanState, + ), + ); + } + PacketOptions? chosenOption; + if (plan.optionType == 'options1') { + chosenOption = plan.plan.packet1Options; + } else if (plan.optionType == 'options2') { + chosenOption = plan.plan.packet2Options; + } else { + chosenOption = plan.plan.packet3Options; + } + + PlanStatusValues planStatus; + if (widget.successfulPayment == true) { + planStatus = PlanStatusValues.paid; + } else if (widget.successfulPayment == false) { + planStatus = PlanStatusValues.paymentFailed; + } else if (plan.chosenPlanState == ChosenPlanStateEnum.statePaid) { + planStatus = PlanStatusValues.paid; + } else { + planStatus = PlanStatusValues.pendingPayment; + } + return Scaffold( + backgroundColor: appTheme.gray100, + appBar: AppBar( + title: Text( + s.infoAboutOrder, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), - const SizedBox(height: 30), - ], - ), - )); + 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), + PaymentBanner( + successfulPayment: widget.successfulPayment ?? false, + ), + ], + 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: 30), + ], + ), + )); + }, + ); } } diff --git a/lib/features/order_info/presentation/widgets/payment_banner.dart b/lib/features/order_info/presentation/widgets/payment_banner.dart new file mode 100644 index 00000000..2fcee13c --- /dev/null +++ b/lib/features/order_info/presentation/widgets/payment_banner.dart @@ -0,0 +1,161 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart'; +import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.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 PaymentBanner extends StatefulWidget { + const PaymentBanner({ + super.key, + required this.successfulPayment, + }); + final bool successfulPayment; + + @override + State createState() => _PaymentBannerState(); +} + +class _PaymentBannerState extends State { + bool isLoading = false; + @override + Widget build(BuildContext context) { + final s = S.of(context); + final size = MediaQuery.sizeOf(context); + return MainBackgroundBouble( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Stack(children: [ + Positioned( + left: 0, + top: 0, + bottom: 0, + child: Container( + width: 4, + decoration: BoxDecoration( + color: widget.successfulPayment ? appTheme.blueA400 : appTheme.strongRed, + borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20))), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widget.successfulPayment + ? [ + SizedBox( + width: size.width * 0.6, + child: Text( + s.cunsutation_was_paid_successfully, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + ), + const SizedBox( + height: 8, + ), + SizedBox( + width: size.width * 0.6, + child: Text( + s.wait_for_start_day, + style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black), + ), + ), + const SizedBox( + height: 16, + ), + isLoading + ? const Center(child: Loader()) + : CustomOutlinedButton( + text: s.go_to_diary, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: + ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: () async { + GoRouter.of(context).pushReplacement('/diary-screen'); + }, + ), + isLoading + ? const Center(child: Loader()) + : CustomOutlinedButton( + text: s.back_to_chat, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: + ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: () { + GoRouter.of(context).pop(); + }, + ) + ] + : [ + SizedBox( + width: size.width * 0.6, + child: Text( + s.smth_went_wrong_payment_failed, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + ), + const SizedBox( + height: 8, + ), + SizedBox( + width: size.width * 0.6, + child: Text( + s.try_again_2, + style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black), + ), + ), + const SizedBox( + height: 16, + ), + isLoading + ? const Center(child: Loader()) + : CustomOutlinedButton( + text: s.pay, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: + ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: () async { + final planId = context.read().state.chosenPlan?.id; + setState(() { + isLoading = true; + }); + final paymentLink = await context.read().getPaymentLink(); + setState(() { + isLoading = false; + }); + if (paymentLink != null && planId != null) { + if (!context.mounted) return; + GoRouter.of(context).pushReplacement( + PaymentScreen.path, + extra: {'url': paymentLink, 'planId': planId}, + ); + } + }, + ) + ], + ), + ) + ]) + // ? 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/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 97e92111..846b1ed4 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -59,6 +59,7 @@ class MessageLookup extends MessageLookupByLibrary { "average_pressure": MessageLookupByLibrary.simpleMessage( "Средние показатели давления:", ), + "back_to_chat": MessageLookupByLibrary.simpleMessage("Вернуться в чат"), "bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"), "by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"), "calls_for_bullying": MessageLookupByLibrary.simpleMessage( @@ -71,6 +72,9 @@ class MessageLookup extends MessageLookupByLibrary { "can_not_load_data_please_try_again": MessageLookupByLibrary.simpleMessage( "Не удалось загрузить данные. Попробуйте обновить страницу", ), + "can_not_see_next_day": MessageLookupByLibrary.simpleMessage( + "Просмотр последующего дня возможен только при прохождении диеты", + ), "cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"), "cancel_order": MessageLookupByLibrary.simpleMessage("Отменить заказ"), "carb_type": MessageLookupByLibrary.simpleMessage("Мучное"), @@ -166,6 +170,9 @@ class MessageLookup extends MessageLookupByLibrary { ), "country": MessageLookupByLibrary.simpleMessage("Страна"), "create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"), + "cunsutation_was_paid_successfully": MessageLookupByLibrary.simpleMessage( + "Поздравляем, консультация успешно оплачена", + ), "customer_price": MessageLookupByLibrary.simpleMessage( "Цена для покупателя", ), @@ -285,6 +292,7 @@ class MessageLookup extends MessageLookupByLibrary { "from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"), "gallery": MessageLookupByLibrary.simpleMessage("Галерея"), "general_info": MessageLookupByLibrary.simpleMessage("Общая информация"), + "go_to_diary": MessageLookupByLibrary.simpleMessage("Перейти в дневник"), "gr": MessageLookupByLibrary.simpleMessage("гр"), "grams": MessageLookupByLibrary.simpleMessage("гр"), "has_alergy": MessageLookupByLibrary.simpleMessage("Наличие аллергии"), @@ -508,6 +516,9 @@ class MessageLookup extends MessageLookupByLibrary { "slim_type_desc": MessageLookupByLibrary.simpleMessage( "План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;", ), + "smth_went_wrong_payment_failed": MessageLookupByLibrary.simpleMessage( + "Что-то пошло не так, оплата не прошла", + ), "some_error_occurred": MessageLookupByLibrary.simpleMessage( "Упс! Произошла ошибка", ), @@ -517,6 +528,9 @@ class MessageLookup extends MessageLookupByLibrary { "something_went_wrong": MessageLookupByLibrary.simpleMessage( "Упс! Что-то пошло не так", ), + "sorry_we_did_not_find_the_plan": MessageLookupByLibrary.simpleMessage( + "Извините, план где-то потерялся, мы уже ищем его", + ), "spam": MessageLookupByLibrary.simpleMessage("Спам"), "spam_description": MessageLookupByLibrary.simpleMessage( "Отправьте жалобу, если Вы заметили рекламу, размещенную в не предназначенном для этого месте.", @@ -557,6 +571,7 @@ class MessageLookup extends MessageLookupByLibrary { "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), "to_order": MessageLookupByLibrary.simpleMessage("Заказать"), "try_again": MessageLookupByLibrary.simpleMessage("Попробовать снова"), + "try_again_2": MessageLookupByLibrary.simpleMessage("Попробуйте ещё раз"), "type_here_your_complaint": MessageLookupByLibrary.simpleMessage( "Напишите здесь свою жалобу", ), @@ -589,6 +604,12 @@ class MessageLookup extends MessageLookupByLibrary { ), "waist": MessageLookupByLibrary.simpleMessage("Талия"), "waist_circumference": MessageLookupByLibrary.simpleMessage("Обхват талии"), + "wait_for_start_day": MessageLookupByLibrary.simpleMessage( + "Ожидайте начала плана питания", + ), + "want_to_see_tomorrow": MessageLookupByLibrary.simpleMessage( + "Хотите заглянуть в завтра?", + ), "weapon": MessageLookupByLibrary.simpleMessage("Оружие"), "weight": MessageLookupByLibrary.simpleMessage("Вес"), "weight_chart": MessageLookupByLibrary.simpleMessage("График веса"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 0a00d232..2d55c794 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2754,6 +2754,96 @@ class S { String get done { return Intl.message('Завершен', name: 'done', desc: '', args: []); } + + /// `Поздравляем, консультация успешно оплачена` + String get cunsutation_was_paid_successfully { + return Intl.message( + 'Поздравляем, консультация успешно оплачена', + name: 'cunsutation_was_paid_successfully', + desc: '', + args: [], + ); + } + + /// `Ожидайте начала плана питания` + String get wait_for_start_day { + return Intl.message( + 'Ожидайте начала плана питания', + name: 'wait_for_start_day', + desc: '', + args: [], + ); + } + + /// `Перейти в дневник` + String get go_to_diary { + return Intl.message( + 'Перейти в дневник', + name: 'go_to_diary', + desc: '', + args: [], + ); + } + + /// `Вернуться в чат` + String get back_to_chat { + return Intl.message( + 'Вернуться в чат', + name: 'back_to_chat', + desc: '', + args: [], + ); + } + + /// `Что-то пошло не так, оплата не прошла` + String get smth_went_wrong_payment_failed { + return Intl.message( + 'Что-то пошло не так, оплата не прошла', + name: 'smth_went_wrong_payment_failed', + desc: '', + args: [], + ); + } + + /// `Попробуйте ещё раз` + String get try_again_2 { + return Intl.message( + 'Попробуйте ещё раз', + name: 'try_again_2', + desc: '', + args: [], + ); + } + + /// `Извините, план где-то потерялся, мы уже ищем его` + String get sorry_we_did_not_find_the_plan { + return Intl.message( + 'Извините, план где-то потерялся, мы уже ищем его', + name: 'sorry_we_did_not_find_the_plan', + desc: '', + args: [], + ); + } + + /// `Просмотр последующего дня возможен только при прохождении диеты` + String get can_not_see_next_day { + return Intl.message( + 'Просмотр последующего дня возможен только при прохождении диеты', + name: 'can_not_see_next_day', + desc: '', + args: [], + ); + } + + /// `Хотите заглянуть в завтра?` + String get want_to_see_tomorrow { + return Intl.message( + 'Хотите заглянуть в завтра?', + name: 'want_to_see_tomorrow', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index ee829899..3eb3575c 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -337,7 +337,16 @@ "time_to_complete_plan": "Время выполнения плана", "order_created": "Заказ создан", "startDateHasBeenSet": "Выбрана дата старта", - "done": "Завершен" + "done": "Завершен", + "cunsutation_was_paid_successfully": "Поздравляем, консультация успешно оплачена", + "wait_for_start_day": "Ожидайте начала плана питания", + "go_to_diary": "Перейти в дневник", + "back_to_chat": "Вернуться в чат", + "smth_went_wrong_payment_failed": "Что-то пошло не так, оплата не прошла", + "try_again_2": "Попробуйте ещё раз", + "sorry_we_did_not_find_the_plan": "Извините, план где-то потерялся, мы уже ищем его", + "can_not_see_next_day": "Просмотр последующего дня возможен только при прохождении диеты", + "want_to_see_tomorrow": "Хотите заглянуть в завтра?" } diff --git a/pubspec.yaml b/pubspec.yaml index 619dbeda..15b82c27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: eatvio_app description: A new Flutter project. -version: 1.0.50+5 +version: 1.0.50+6 publish_to: 'none' environment: - sdk: ^3.7.0 + sdk: '>=3.3.4 <4.0.0' dependencies: flutter: