added layout for suggestions page

This commit is contained in:
Username1548
2025-07-15 21:49:47 +07:00
parent a07d085ba7
commit 7e64b24a8c
4 changed files with 318 additions and 14 deletions

View File

@@ -67,7 +67,7 @@ class _ChosePlanDateWidget extends State<ChosePlanDateWidget> {
target: Alignment.bottomLeft, target: Alignment.bottomLeft,
), ),
portalFollower: Padding( portalFollower: Padding(
padding: const EdgeInsetsGeometry.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Container( child: Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@@ -1,34 +1,86 @@
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.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/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/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.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/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class ConsultationWidget extends StatelessWidget { 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 ConsultationModel consultationModel;
final String dietName;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = S.of(context); final s = S.of(context);
return Container( return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(12),
color: appTheme.whiteA700, color: appTheme.whiteA700,
), ),
child: Column( child: Column(
children: [ children: [
ListTile( if (consultationModel.personalDietId == null)
leading: Container( Row(
decoration: const BoxDecoration( children: [
shape: BoxShape.circle, CircleAvatar(
), radius: 30,
child: Image.network('$baseUrl/${consultationModel.userProfilePhotoUrl}'), 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( const SizedBox(
height: 12, height: 12,
), ),
@@ -39,7 +91,42 @@ class ConsultationWidget extends StatelessWidget {
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.dividerColor)), buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.dividerColor)),
onPressed: () async { onPressed: () async {
context.read<PersonalDietSuggestionCubit>().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<PersonalDietSuggestionCubit>().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 else
@@ -49,7 +136,42 @@ class ConsultationWidget extends StatelessWidget {
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
onPressed: () async { onPressed: () async {
context.read<PersonalDietSuggestionCubit>().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<PersonalDietSuggestionCubit>().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<void> Function() onTap;
@override
State<SuggestDietBottomSheet> createState() => _SuggestDietBottomSheet();
}
class _SuggestDietBottomSheet extends State<SuggestDietBottomSheet> {
@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<SuggestionSuccessBottomSheet> createState() => _SuggestionSuccessBottomSheet();
}
class _SuggestionSuccessBottomSheet extends State<SuggestionSuccessBottomSheet> {
@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<SuggestionFailureBottomSheet> createState() => _SuggestionFailureBottomSheet();
}
class _SuggestionFailureBottomSheet extends State<SuggestionFailureBottomSheet> {
@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,
),
],
),
);
}
}

View File

@@ -31,6 +31,7 @@ class PersonalDietSuggestionScreenContent extends StatelessWidget {
return BlocBuilder<PersonalDietSuggestionCubit, PersonalDietSuggestionState>( return BlocBuilder<PersonalDietSuggestionCubit, PersonalDietSuggestionState>(
builder: (context, state) { builder: (context, state) {
return Scaffold( return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: Text( title: Text(
@@ -46,6 +47,7 @@ class PersonalDietSuggestionScreenContent extends StatelessWidget {
...List.generate(state.consultationsWithDiet.length, (index) { ...List.generate(state.consultationsWithDiet.length, (index) {
return ConsultationWidget( return ConsultationWidget(
consultationModel: state.consultationsWithDiet[index], consultationModel: state.consultationsWithDiet[index],
dietName: state.dietName,
); );
}), }),
const SizedBox( const SizedBox(
@@ -54,6 +56,7 @@ class PersonalDietSuggestionScreenContent extends StatelessWidget {
...List.generate(state.consultationsWithoutDiet.length, (index) { ...List.generate(state.consultationsWithoutDiet.length, (index) {
return ConsultationWidget( return ConsultationWidget(
consultationModel: state.consultationsWithoutDiet[index], consultationModel: state.consultationsWithoutDiet[index],
dietName: state.dietName,
); );
}) })
], ],

View File

@@ -7,8 +7,11 @@ class PersonalDietSuggestionService {
Future<ConsultationPaginationModel> getConsultationWithPersonalDiets() async { Future<ConsultationPaginationModel> getConsultationWithPersonalDiets() async {
try { try {
final response = final response = await dio.get('/api/consultations', queryParameters: {
await dio.get('/api/consultations', queryParameters: {'filters[plan][has_personal_diet]': '1', 'sort[]': 'start_date:desc'}); '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); return ConsultationPaginationModel.fromJson(response.data);
} catch (e) { } catch (e) {
rethrow; rethrow;