Compare commits

...

2 Commits

9 changed files with 151 additions and 94 deletions

View File

@@ -135,12 +135,12 @@ class _ChatScreenState extends State<ChatScreen> {
} else if (!amIConsultant && } else if (!amIConsultant &&
(chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet || chosenPlan.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) { (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet || chosenPlan.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) {
bannerType = BannerType.payment; bannerType = BannerType.payment;
} else if (chosenOption?.hasPersonalDiet == true && amIConsultant) {
bannerType = BannerType.setPersonalDiet;
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateDone && amIConsultant) { } else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateDone && amIConsultant) {
bannerType = BannerType.collectCharge; bannerType = BannerType.collectCharge;
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateLastDayFinished && !amIConsultant) { } else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateLastDayFinished && !amIConsultant) {
bannerType = BannerType.finishPlan; bannerType = BannerType.finishPlan;
} else if (chosenOption?.hasPersonalDiet == true && amIConsultant && chosenPlan.chosenPlanState != ChosenPlanStateEnum.stateDone) {
bannerType = BannerType.setPersonalDiet;
} }
return Scaffold( return Scaffold(

View File

@@ -29,7 +29,7 @@ class AuthorCard extends StatelessWidget {
child: Stack( child: Stack(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 24), padding: const EdgeInsets.only(left: 16, top: 16, bottom: 24, right: 16),
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
@@ -68,20 +68,23 @@ class AuthorCard extends StatelessWidget {
if (author.authorPlaceInRating != null) if (author.authorPlaceInRating != null)
InfoBouble( InfoBouble(
radius: 12, radius: 12,
backgroundColor: appTheme.whiteA700, backgroundColor: appTheme.cardBackground,
children: [ children: [
Text('${author.authorPlaceInRating}-й в рейтинге', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400)), Text('${author.authorPlaceInRating}-й в рейтинге', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400)),
], ],
), ),
] else ] else
GestureDetector( Expanded(
onTap: () { child: GestureDetector(
GoRouter.of(context).pushNamed(UserDetailsScreen.name, extra: customerProfile!.slug); onTap: () {
}, GoRouter.of(context).pushNamed(UserDetailsScreen.name, extra: customerProfile!.slug);
child: InfoBouble( },
radius: 12, child: InfoBouble(
backgroundColor: appTheme.whiteA700, radius: 12,
children: [Text('Анкета пользователя', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400))], backgroundColor: appTheme.cardBackground,
mainAxisAlignment: MainAxisAlignment.center,
children: [Text('Анкета пользователя', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400))],
),
), ),
), ),
], ],

View File

@@ -2,20 +2,25 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class InfoBouble extends StatelessWidget { class InfoBouble extends StatelessWidget {
const InfoBouble({super.key, required this.children, this.backgroundColor, this.radius = 100, this.padding}); const InfoBouble({
super.key,
required this.children,
this.backgroundColor,
this.radius = 100,
this.padding,
this.mainAxisAlignment = MainAxisAlignment.start,
});
final List<Widget> children; final List<Widget> children;
final Color? backgroundColor; final Color? backgroundColor;
final double radius; final double radius;
final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? padding;
final MainAxisAlignment mainAxisAlignment;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 12, vertical: 8.5), padding: padding ?? const EdgeInsets.symmetric(horizontal: 12, vertical: 8.5),
decoration: BoxDecoration( decoration: BoxDecoration(color: backgroundColor ?? appTheme.gray50, borderRadius: BorderRadius.circular(radius)),
color: backgroundColor ?? appTheme.gray50, child: Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: mainAxisAlignment, children: children),
borderRadius: BorderRadius.circular(radius),
),
child: Row(mainAxisSize: MainAxisSize.min, children: children),
); );
} }
} }

View File

@@ -316,7 +316,7 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
type: type, type: type,
planName: json['plan_name'], planName: json['plan_name'],
customerName: json['client_name'], customerName: json['client_name'],
planId: json['plan_id'], planId: json['chosen_plan_id'],
isRead: isRead ?? false, isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
); );
@@ -1695,6 +1695,35 @@ class DiaryReminderForUsersNotification implements BaseNotificationModel {
} }
} }
class PersonalDietIsDefinedToCustomerNotification implements BaseNotificationModel {
@override
final NotificationType type;
@override
final bool isRead;
@override
final DateTime createdAt;
final String dietName;
final int dietId;
PersonalDietIsDefinedToCustomerNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.dietName,
required this.dietId,
});
factory PersonalDietIsDefinedToCustomerNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return PersonalDietIsDefinedToCustomerNotification(
type: type,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
dietName: json['diet_name'] ?? '',
dietId: json['diet_id'],
);
}
}
enum NotificationType { enum NotificationType {
// Diet related // Diet related
dietCancelled, dietCancelled,
@@ -1726,6 +1755,7 @@ enum NotificationType {
userDailyReportCompleted, userDailyReportCompleted,
chosenPlanFinishedToConsultant, chosenPlanFinishedToConsultant,
stayOnTrackToCustomer, stayOnTrackToCustomer,
personalDietIsDefinedToCustomer,
// Comments // Comments
entityCommented, entityCommented,
@@ -1834,6 +1864,7 @@ enum NotificationType {
'chosen-plan-finished-to-consultant' => NotificationType.chosenPlanFinishedToConsultant, 'chosen-plan-finished-to-consultant' => NotificationType.chosenPlanFinishedToConsultant,
'stay-on-track-to-customer' => NotificationType.stayOnTrackToCustomer, 'stay-on-track-to-customer' => NotificationType.stayOnTrackToCustomer,
'diary-reminder-for-users' => NotificationType.diaryReminderForUsers, 'diary-reminder-for-users' => NotificationType.diaryReminderForUsers,
'personal-diet-is-defined-to-customer' => NotificationType.personalDietIsDefinedToCustomer,
// Legacy mapping // Legacy mapping
'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor, 'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor,

View File

@@ -261,6 +261,12 @@ class NotificationsCubit extends Cubit<NotificationsState> {
createdAt: createdAt, createdAt: createdAt,
), ),
NotificationType.diaryReminderForUsers => DiaryReminderForUsersNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.diaryReminderForUsers => DiaryReminderForUsersNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
NotificationType.personalDietIsDefinedToCustomer => PersonalDietIsDefinedToCustomerNotification.fromJson(
notificationJson,
type,
isRead: isRead,
createdAt: createdAt,
),
NotificationType.unknown => UnknownNotification(type: type), NotificationType.unknown => UnknownNotification(type: type),
}; };
} catch (e) { } catch (e) {

View File

@@ -1,3 +1,4 @@
import 'package:eatvio_app/core/routing/route_constants.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/utils/date_time_utils.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart';
@@ -113,7 +114,11 @@ class NotificationWidget extends StatelessWidget {
notification: notification as ProductStatusUpdatedForAuthorNotification, notification: notification as ProductStatusUpdatedForAuthorNotification,
), ),
DiaryReminderForUsersNotification() => _DiaryReminderForUsersWidget(notification: notification as DiaryReminderForUsersNotification), DiaryReminderForUsersNotification() => _DiaryReminderForUsersWidget(notification: notification as DiaryReminderForUsersNotification),
PersonalDietIsDefinedToCustomerNotification() => _PersonalDietIsDefinedToCustomerWidget(
notification: notification as PersonalDietIsDefinedToCustomerNotification,
),
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification), UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
// TODO: Handle this case.
}; };
} }
} }
@@ -364,34 +369,38 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget {
// CardSettingsScreen // CardSettingsScreen
class _BaseNotificationWidget extends StatelessWidget { class _BaseNotificationWidget extends StatelessWidget {
const _BaseNotificationWidget({required this.notificationsText, required this.isRead, required this.createdAt}); const _BaseNotificationWidget({required this.notificationsText, required this.isRead, required this.createdAt, this.onTap});
final String notificationsText; final String notificationsText;
final bool isRead; final bool isRead;
final DateTime createdAt; final DateTime createdAt;
final void Function()? onTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return GestureDetector(
crossAxisAlignment: CrossAxisAlignment.start, onTap: onTap,
children: [ child: Column(
Row( crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Row(
child: Text(notificationsText, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), children: [
), Expanded(
if (!isRead) child: Text(notificationsText, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
Container(
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
width: 8,
height: 8,
), ),
], if (!isRead)
), Container(
const SizedBox(height: 4), decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
Text( width: 8,
'${createdAt.day} ${_getMonthName(createdAt.month)} ${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}', height: 8,
style: TextStyles.textSMedium.copyWith(color: appTheme.gray600), ),
), ],
], ),
const SizedBox(height: 4),
Text(
'${createdAt.day} ${_getMonthName(createdAt.month)} ${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}',
style: TextStyles.textSMedium.copyWith(color: appTheme.gray600),
),
],
),
); );
} }
@@ -1053,3 +1062,17 @@ class _DiaryReminderForUsersWidget extends StatelessWidget {
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Напоминание о заполнении дневника', createdAt: notification.createdAt); return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Напоминание о заполнении дневника', createdAt: notification.createdAt);
} }
} }
class _PersonalDietIsDefinedToCustomerWidget extends StatelessWidget {
const _PersonalDietIsDefinedToCustomerWidget({required this.notification});
final PersonalDietIsDefinedToCustomerNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
onTap: () => GoRouter.of(context).push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId, 'navigateToComments': false}),
notificationsText: 'Персональная диета "${notification.dietName}" установлена',
createdAt: notification.createdAt,
);
}
}

View File

@@ -407,6 +407,12 @@ class NotificationsService {
isRead: readDate != null, isRead: readDate != null,
createdAt: createdAt, createdAt: createdAt,
), ),
NotificationType.personalDietIsDefinedToCustomer => PersonalDietIsDefinedToCustomerNotification.fromJson(
notificationJson['data'],
type,
isRead: readDate != null,
createdAt: createdAt,
),
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)), NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)),
}; };
} catch (e) { } catch (e) {

View File

@@ -11,11 +11,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
class PaymentBanner extends StatefulWidget { class PaymentBanner extends StatefulWidget {
const PaymentBanner({ const PaymentBanner({super.key, required this.successfulPayment});
super.key,
required this.successfulPayment,
});
final bool successfulPayment; final bool successfulPayment;
@override @override
@@ -29,8 +25,9 @@ class _PaymentBannerState extends State<PaymentBanner> {
final s = S.of(context); final s = S.of(context);
final size = MediaQuery.sizeOf(context); final size = MediaQuery.sizeOf(context);
return MainBackgroundBouble( return MainBackgroundBouble(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Stack(children: [ child: Stack(
children: [
Positioned( Positioned(
left: 0, left: 0,
top: 0, top: 0,
@@ -38,8 +35,9 @@ class _PaymentBannerState extends State<PaymentBanner> {
child: Container( child: Container(
width: 4, width: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: widget.successfulPayment ? appTheme.blueA400 : appTheme.strongRed, color: widget.successfulPayment ? appTheme.blueA400 : appTheme.strongRed,
borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20))), borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20)),
),
), ),
), ),
Padding( Padding(
@@ -50,78 +48,65 @@ class _PaymentBannerState extends State<PaymentBanner> {
? [ ? [
SizedBox( SizedBox(
width: size.width * 0.6, width: size.width * 0.6,
child: Text( child: Text(s.cunsutation_was_paid_successfully, style: TextStyles.textLSemibold.copyWith(color: appTheme.black)),
s.cunsutation_was_paid_successfully,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 8,
), ),
const SizedBox(height: 8),
SizedBox( SizedBox(
width: size.width * 0.6, width: size.width * 0.6,
child: Text( child: Text(s.wait_for_start_day, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)),
s.wait_for_start_day,
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 16,
), ),
const SizedBox(height: 16),
isLoading isLoading
? const Center(child: Loader()) ? const Center(child: Loader())
: CustomOutlinedButton( : CustomOutlinedButton(
text: s.go_to_diary, text: s.go_to_diary,
height: 52, height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: buttonStyle: ButtonStyle(
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
onPressed: () async { onPressed: () async {
GoRouter.of(context).pushReplacement('/diary-screen'); GoRouter.of(context).pushReplacement('/diary-screen');
}, },
), ),
const SizedBox(height: 10),
isLoading isLoading
? const Center(child: Loader()) ? const Center(child: Loader())
: CustomOutlinedButton( : CustomOutlinedButton(
text: s.back_to_chat, text: s.back_to_chat,
height: 52, height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: buttonStyle: ButtonStyle(
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
onPressed: () { onPressed: () {
GoRouter.of(context).pop(); GoRouter.of(context).pop();
}, },
) ),
] ]
: [ : [
SizedBox( SizedBox(
width: size.width * 0.6, width: size.width * 0.6,
child: Text( child: Text(s.smth_went_wrong_payment_failed, style: TextStyles.textLSemibold.copyWith(color: appTheme.black)),
s.smth_went_wrong_payment_failed,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 8,
), ),
const SizedBox(height: 8),
SizedBox( SizedBox(
width: size.width * 0.6, width: size.width * 0.6,
child: Text( child: Text(s.try_again_2, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)),
s.try_again_2,
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 16,
), ),
const SizedBox(height: 16),
isLoading isLoading
? const Center(child: Loader()) ? const Center(child: Loader())
: CustomOutlinedButton( : CustomOutlinedButton(
text: s.pay, text: s.pay,
height: 52, height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: buttonStyle: ButtonStyle(
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
onPressed: () async { onPressed: () async {
final planId = context.read<OrderInfoCubit>().state.chosenPlan?.id; final planId = context.read<OrderInfoCubit>().state.chosenPlan?.id;
setState(() { setState(() {
@@ -133,16 +118,15 @@ class _PaymentBannerState extends State<PaymentBanner> {
}); });
if (paymentLink != null && planId != null) { if (paymentLink != null && planId != null) {
if (!context.mounted) return; if (!context.mounted) return;
GoRouter.of(context).pushReplacement( GoRouter.of(context).pushReplacement(PaymentScreen.path, extra: {'url': paymentLink, 'planId': planId});
PaymentScreen.path,
extra: {'url': paymentLink, 'planId': planId},
);
} }
}, },
) ),
], ],
), ),
) ),
])); ],
),
);
} }
} }

View File

@@ -49,8 +49,7 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
final List<String> chipsIntervalList = ['1', '3', '6', '12', 'all']; final List<String> chipsIntervalList = ['1', '3', '6', '12', 'all'];
List<WeightModel> weightPoints = []; List<WeightModel> weightPoints = [];
List<CircumferenceModel> circumferencePoints = []; List<CircumferenceModel> circumferencePoints = []; int _selectedChipIndex = 0;
int _selectedChipIndex = 0;
int _selectedIntervalChipIndex = 0; int _selectedIntervalChipIndex = 0;
bool isMoreOptionsVisible = false; bool isMoreOptionsVisible = false;
@override @override