From 7e64b24a8c9f41f2b10043ec29c20b1df4e7a90c Mon Sep 17 00:00:00 2001 From: Username1548 Date: Tue, 15 Jul 2025 21:49:47 +0700 Subject: [PATCH] added layout for suggestions page --- .../widgets/chose_plan_date_widget.dart | 2 +- .../presentation/consultation_widget.dart | 320 +++++++++++++++++- .../personal_diet_suggestion_screen.dart | 3 + .../personal_diet_suggestion_service.dart | 7 +- 4 files changed, 318 insertions(+), 14 deletions(-) diff --git a/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart b/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart index 1b138f93..2744ba74 100644 --- a/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart +++ b/lib/features/chat/presentation/widgets/chose_plan_date_widget.dart @@ -67,7 +67,7 @@ class _ChosePlanDateWidget extends State { target: Alignment.bottomLeft, ), portalFollower: Padding( - padding: const EdgeInsetsGeometry.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( diff --git a/lib/features/personal_diet_suggestion/presentation/consultation_widget.dart b/lib/features/personal_diet_suggestion/presentation/consultation_widget.dart index 77042d13..3f47db11 100644 --- a/lib/features/personal_diet_suggestion/presentation/consultation_widget.dart +++ b/lib/features/personal_diet_suggestion/presentation/consultation_widget.dart @@ -1,34 +1,86 @@ +import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_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 ConsultationWidget extends StatelessWidget { - const ConsultationWidget({super.key, required this.consultationModel}); + const ConsultationWidget({super.key, required this.consultationModel, required this.dietName}); final ConsultationModel consultationModel; + final String dietName; @override Widget build(BuildContext context) { final s = S.of(context); return Container( + padding: const EdgeInsets.all(16), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(12), color: appTheme.whiteA700, ), child: Column( children: [ - ListTile( - leading: Container( - decoration: const BoxDecoration( - shape: BoxShape.circle, - ), - child: Image.network('$baseUrl/${consultationModel.userProfilePhotoUrl}'), + if (consultationModel.personalDietId == null) + Row( + children: [ + CircleAvatar( + radius: 30, + backgroundImage: NetworkImage(baseUrl + consultationModel.userProfilePhotoUrl), + ), + const SizedBox(width: 12), + Column( + mainAxisSize: MainAxisSize.min, + spacing: 4, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + consultationModel.userName, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + Text( + consultationModel.planTitle, + style: TextStyles.textMMedium.copyWith(color: const Color(0xFF88949F)), + ), + Text( + 'Старт: ${consultationModel.userName}', + style: TextStyles.textMMedium.copyWith(color: const Color(0xFF88949F)), + ), + ], + ) + ], + ) + else + Row( + children: [ + CircleAvatar( + radius: 30, + backgroundImage: NetworkImage(baseUrl + consultationModel.userProfilePhotoUrl), + ), + const SizedBox(width: 12), + Column( + mainAxisSize: MainAxisSize.min, + spacing: 4, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + consultationModel.userName, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + Text( + consultationModel.planTitle, + style: TextStyles.textMMedium.copyWith(color: const Color(0xFF88949F)), + ), + ], + ) + ], ), - ), const SizedBox( height: 12, ), @@ -39,7 +91,42 @@ class ConsultationWidget extends StatelessWidget { buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.dividerColor)), onPressed: () async { - context.read().reattachDietToConsultation(consultationId: consultationModel.id); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + height: null, + child: SuggestDietBottomSheet( + userName: consultationModel.userName, + dietName: dietName, + onTap: () async { + final result = await context.read().attachDietToConsultation(consultationId: consultationModel.id); + AppRouter.navigatorKey.currentContext?.pop(); + if (result) { + if (!context.mounted) return; + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + height: null, + child: SuggestionSuccessBottomSheet( + userName: consultationModel.userName, + dietName: dietName, + ), + ); + return; + } + if (!context.mounted) return; + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + height: null, + child: SuggestionFailureBottomSheet( + userName: consultationModel.userName, + dietName: dietName, + ), + ); + }, + ), + ); }, ) else @@ -49,7 +136,42 @@ class ConsultationWidget extends StatelessWidget { buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), onPressed: () async { - context.read().attachDietToConsultation(consultationId: consultationModel.id); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + height: null, + child: SuggestDietBottomSheet( + userName: consultationModel.userName, + dietName: dietName, + onTap: () async { + AppRouter.navigatorKey.currentContext?.pop(); + final result = await context.read().attachDietToConsultation(consultationId: consultationModel.id); + if (result) { + if (!context.mounted) return; + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + height: null, + child: SuggestionSuccessBottomSheet( + userName: consultationModel.userName, + dietName: dietName, + ), + ); + return; + } + if (!context.mounted) return; + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + height: null, + child: SuggestionFailureBottomSheet( + userName: consultationModel.userName, + dietName: dietName, + ), + ); + }, + ), + ); }, ) ], @@ -57,3 +179,179 @@ class ConsultationWidget extends StatelessWidget { ); } } + +class SuggestDietBottomSheet extends StatefulWidget { + const SuggestDietBottomSheet({ + super.key, + required this.userName, + required this.dietName, + required this.onTap, + }); + final String userName; + final String dietName; + final Future Function() onTap; + @override + State createState() => _SuggestDietBottomSheet(); +} + +class _SuggestDietBottomSheet extends State { + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 8, + ), + const BottomSheetHandlerBar(), + const SizedBox( + height: 20, + ), + Text('Предложение диеты', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), + const SizedBox( + height: 16, + ), + Text( + 'Предложить пользователю ${widget.userName} «${widget.dietName}»', + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + textAlign: TextAlign.center, + ), + const SizedBox( + height: 8, + ), + CustomOutlinedButton( + text: s.toOffer, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: widget.onTap, + ), + const SizedBox( + height: 40, + ), + ], + ), + ); + } +} + +class SuggestionSuccessBottomSheet extends StatefulWidget { + const SuggestionSuccessBottomSheet({ + super.key, + required this.userName, + required this.dietName, + }); + final String userName; + final String dietName; + + @override + State createState() => _SuggestionSuccessBottomSheet(); +} + +class _SuggestionSuccessBottomSheet extends State { + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 8, + ), + const BottomSheetHandlerBar(), + const SizedBox( + height: 20, + ), + Text('Предложение диеты', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), + const SizedBox( + height: 16, + ), + Text( + '«${widget.dietName}» предложена пользователю ${widget.userName}', + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + textAlign: TextAlign.center, + ), + const SizedBox( + height: 8, + ), + CustomOutlinedButton( + text: s.ok, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: () async { + AppRouter.navigatorKey.currentContext?.pop(); + }, + ), + const SizedBox( + height: 40, + ), + ], + ), + ); + } +} + +class SuggestionFailureBottomSheet extends StatefulWidget { + const SuggestionFailureBottomSheet({ + super.key, + required this.userName, + required this.dietName, + }); + final String userName; + final String dietName; + + @override + State createState() => _SuggestionFailureBottomSheet(); +} + +class _SuggestionFailureBottomSheet extends State { + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 8, + ), + const BottomSheetHandlerBar(), + const SizedBox( + height: 20, + ), + Text('Предложение диеты', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), + const SizedBox( + height: 16, + ), + Text( + 'Не удалось предложить диету ${widget.dietName} пользователю ${widget.userName}', + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + textAlign: TextAlign.center, + ), + const SizedBox( + height: 8, + ), + CustomOutlinedButton( + text: s.ok, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: () async { + AppRouter.navigatorKey.currentContext?.pop(); + }, + ), + const SizedBox( + height: 40, + ), + ], + ), + ); + } +} diff --git a/lib/features/personal_diet_suggestion/presentation/personal_diet_suggestion_screen.dart b/lib/features/personal_diet_suggestion/presentation/personal_diet_suggestion_screen.dart index 2db01fb3..dad76406 100644 --- a/lib/features/personal_diet_suggestion/presentation/personal_diet_suggestion_screen.dart +++ b/lib/features/personal_diet_suggestion/presentation/personal_diet_suggestion_screen.dart @@ -31,6 +31,7 @@ class PersonalDietSuggestionScreenContent extends StatelessWidget { return BlocBuilder( builder: (context, state) { return Scaffold( + backgroundColor: appTheme.gray100, appBar: AppBar( centerTitle: true, title: Text( @@ -46,6 +47,7 @@ class PersonalDietSuggestionScreenContent extends StatelessWidget { ...List.generate(state.consultationsWithDiet.length, (index) { return ConsultationWidget( consultationModel: state.consultationsWithDiet[index], + dietName: state.dietName, ); }), const SizedBox( @@ -54,6 +56,7 @@ class PersonalDietSuggestionScreenContent extends StatelessWidget { ...List.generate(state.consultationsWithoutDiet.length, (index) { return ConsultationWidget( consultationModel: state.consultationsWithoutDiet[index], + dietName: state.dietName, ); }) ], diff --git a/lib/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart b/lib/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart index 2325bb72..3f65005d 100644 --- a/lib/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart +++ b/lib/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart @@ -7,8 +7,11 @@ class PersonalDietSuggestionService { Future getConsultationWithPersonalDiets() async { try { - final response = - await dio.get('/api/consultations', queryParameters: {'filters[plan][has_personal_diet]': '1', 'sort[]': 'start_date:desc'}); + final response = await dio.get('/api/consultations', queryParameters: { + 'filters[plan][has_personal_diet][\$personal_diet]': '1', + 'filters[last_state][\$notIn][]': ['done', 'charge_was_collected', 'complained', 'last_day_finished', 'canceled_by_the_initiator', 'inited'], + 'sort[]': 'start_date:desc', + }); return ConsultationPaginationModel.fromJson(response.data); } catch (e) { rethrow;