diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index c017f047..706fca26 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -13,6 +13,8 @@ import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.da import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart'; import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart'; +import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart'; @@ -465,7 +467,27 @@ class _ChatScreenState extends State { const SizedBox(width: 8), InkWell( customBorder: const CircleBorder(), - onTap: () { + onTap: () async { + // Проверяем, заблокирован ли пользователь + final blockedUsersCubit = sl(); + await blockedUsersCubit.getBlockedUsers(); + final blockedState = blockedUsersCubit.state; + + if (blockedState is BlockedUsersStateLoaded) { + final isBlocked = blockedState.profilesList.any((profile) => profile.slug == state.chatRoom.users.receiver.slug); + if (isBlocked) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Вы не можете отправить сообщение заблокированному пользователю'), + backgroundColor: Colors.red, + ), + ); + } + return; + } + } + if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) { chatRoomCubit.sendMessage( data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(), diff --git a/lib/features/chat/presentation/pages/empty_chat_screen.dart b/lib/features/chat/presentation/pages/empty_chat_screen.dart index 7b543f86..d6a87c46 100644 --- a/lib/features/chat/presentation/pages/empty_chat_screen.dart +++ b/lib/features/chat/presentation/pages/empty_chat_screen.dart @@ -12,6 +12,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart'; +import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart'; class EmptyChatScreen extends StatefulWidget { final Author author; @@ -133,21 +135,45 @@ class _EmptyChatScreenState extends State { InkWell( customBorder: const CircleBorder(), onTap: () async { + // Проверяем, заблокирован ли пользователь + final blockedUsersCubit = sl(); + await blockedUsersCubit.getBlockedUsers(); + final blockedState = blockedUsersCubit.state; + + if (blockedState is BlockedUsersStateLoaded) { + final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug); + if (isBlocked) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Вы не можете отправить сообщение заблокированному пользователю'), + backgroundColor: Colors.red, + ), + ); + } + return; + } + } + if (textEditingController.text.isNotEmpty) { await sl().sendMessage( data: textEditingController.text.trim(), slug: widget.author.authorSlug, ); - setState(() { - textEditingController.clear(); - }); + if (mounted) { + setState(() { + textEditingController.clear(); + }); + } final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); - AppRouter.navigatorKey.currentContext?.pushReplacementNamed( - 'chatScreen', - extra: { - 'uuid': chatIdOrError, - }, - ); + if (context.mounted) { + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'uuid': chatIdOrError, + }, + ); + } } }, child: Ink( @@ -175,20 +201,44 @@ class _EmptyChatScreenState extends State { CupertinoActionSheetAction( onPressed: () async { Navigator.of(context).pop(); + + // Проверяем, заблокирован ли пользователь + final blockedUsersCubit = sl(); + await blockedUsersCubit.getBlockedUsers(); + final blockedState = blockedUsersCubit.state; + + if (blockedState is BlockedUsersStateLoaded) { + final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug); + if (isBlocked) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Вы не можете отправить сообщение заблокированному пользователю'), + backgroundColor: Colors.red, + ), + ); + } + return; + } + } + final picker = ImagePicker(); - XFile? image = await picker.pickImage(source: ImageSource.camera); - if (image != null) { + List? images = await picker.pickMultiImage(); + if (images.isNotEmpty) { + List files = images.map((image) => File(image.path)).toList(); await sl().sendMessage( - files: [File(image.path)], + files: files, slug: widget.author.authorSlug, ); final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); - AppRouter.navigatorKey.currentContext?.pushReplacementNamed( - 'chatScreen', - extra: { - 'uuid': chatIdOrError, - }, - ); + if (mounted) { + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'uuid': chatIdOrError, + }, + ); + } } }, child: const Row( @@ -211,12 +261,14 @@ class _EmptyChatScreenState extends State { slug: widget.author.authorSlug, ); final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); - AppRouter.navigatorKey.currentContext?.pushReplacementNamed( - 'chatScreen', - extra: { - 'uuid': chatIdOrError, - }, - ); + if (mounted) { + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'uuid': chatIdOrError, + }, + ); + } } }, child: const Row( @@ -261,12 +313,14 @@ class _EmptyChatScreenState extends State { slug: widget.author.authorSlug, ); final chatIdOrError = await sl().getChatByUserSlug(widget.author.authorSlug); - AppRouter.navigatorKey.currentContext?.pushReplacementNamed( - 'chatScreen', - extra: { - 'uuid': chatIdOrError, - }, - ); + if (mounted) { + AppRouter.navigatorKey.currentContext?.pushReplacementNamed( + 'chatScreen', + extra: { + 'uuid': chatIdOrError, + }, + ); + } } }, child: const Row( diff --git a/lib/features/notifications/models/notification_model.dart b/lib/features/notifications/models/notification_model.dart index 96626d5d..31d74e10 100644 --- a/lib/features/notifications/models/notification_model.dart +++ b/lib/features/notifications/models/notification_model.dart @@ -538,6 +538,297 @@ class EntityCommented implements BaseNotificationModel { } } +class PlanSubscriptionExpiredNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String customerName; + @override + final DateTime createdAt; + + PlanSubscriptionExpiredNotification({ + required this.type, + required this.planName, + required this.customerName, + required this.isRead, + required this.createdAt, + }); + + factory PlanSubscriptionExpiredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return PlanSubscriptionExpiredNotification( + type: type, + planName: json['plan_name'], + customerName: json['user_name'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class PlanSubscriptionRenewedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String customerName; + final String renewalDate; + @override + final DateTime createdAt; + + PlanSubscriptionRenewedNotification({ + required this.type, + required this.planName, + required this.customerName, + required this.renewalDate, + required this.isRead, + required this.createdAt, + }); + + factory PlanSubscriptionRenewedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return PlanSubscriptionRenewedNotification( + type: type, + planName: json['plan_name'], + customerName: json['user_name'], + renewalDate: json['renewal_date'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantAssignedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String consultantName; + final String planName; + final int planId; + @override + final DateTime createdAt; + + ConsultantAssignedNotification({ + required this.type, + required this.consultantName, + required this.planName, + required this.planId, + required this.isRead, + required this.createdAt, + }); + + factory ConsultantAssignedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantAssignedNotification( + type: type, + consultantName: json['consultant_name'], + planName: json['plan_name'], + planId: json['plan_id'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ConsultantUnassignedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String consultantName; + final String planName; + final int planId; + @override + final DateTime createdAt; + + ConsultantUnassignedNotification({ + required this.type, + required this.consultantName, + required this.planName, + required this.planId, + required this.isRead, + required this.createdAt, + }); + + factory ConsultantUnassignedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantUnassignedNotification( + type: type, + consultantName: json['consultant_name'], + planName: json['plan_name'], + planId: json['plan_id'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class PaymentFailedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final String errorMessage; + final String paymentMethod; + @override + final DateTime createdAt; + + PaymentFailedNotification({ + required this.type, + required this.planName, + required this.errorMessage, + required this.paymentMethod, + required this.isRead, + required this.createdAt, + }); + + factory PaymentFailedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return PaymentFailedNotification( + type: type, + planName: json['plan_name'], + errorMessage: json['error_message'], + paymentMethod: json['payment_method'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class PaymentSuccessfulNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String planName; + final double amount; + final String currency; + final String transactionId; + @override + final DateTime createdAt; + + PaymentSuccessfulNotification({ + required this.type, + required this.planName, + required this.amount, + required this.currency, + required this.transactionId, + required this.isRead, + required this.createdAt, + }); + + factory PaymentSuccessfulNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return PaymentSuccessfulNotification( + type: type, + planName: json['plan_name'], + amount: (json['amount'] as num).toDouble(), + currency: json['currency'], + transactionId: json['transaction_id'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class GoalAchievedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String goalName; + final String goalType; + final double achievedValue; + final double targetValue; + @override + final DateTime createdAt; + + GoalAchievedNotification({ + required this.type, + required this.goalName, + required this.goalType, + required this.achievedValue, + required this.targetValue, + required this.isRead, + required this.createdAt, + }); + + factory GoalAchievedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return GoalAchievedNotification( + type: type, + goalName: json['goal_name'], + goalType: json['goal_type'], + achievedValue: (json['achieved_value'] as num).toDouble(), + targetValue: (json['target_value'] as num).toDouble(), + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class WorkoutReminderNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String workoutName; + final String workoutType; + final int duration; + @override + final DateTime createdAt; + + WorkoutReminderNotification({ + required this.type, + required this.workoutName, + required this.workoutType, + required this.duration, + required this.isRead, + required this.createdAt, + }); + + factory WorkoutReminderNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return WorkoutReminderNotification( + type: type, + workoutName: json['workout_name'], + workoutType: json['workout_type'], + duration: json['duration'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class MealReminderNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + final String mealName; + final String mealType; + final int calories; + @override + final DateTime createdAt; + + MealReminderNotification({ + required this.type, + required this.mealName, + required this.mealType, + required this.calories, + required this.isRead, + required this.createdAt, + }); + + factory MealReminderNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return MealReminderNotification( + type: type, + mealName: json['meal_name'], + mealType: json['meal_type'], + calories: json['calories'], + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + class UnknownNotification implements BaseNotificationModel { @override final NotificationType type; @@ -570,6 +861,16 @@ enum NotificationType { commentReplied, scoreAdded, entityCommented, + // Дополнительные типы уведомлений + planSubscriptionExpired, + planSubscriptionRenewed, + consultantAssigned, + consultantUnassigned, + paymentFailed, + paymentSuccessful, + goalAchieved, + workoutReminder, + mealReminder, unknown; static NotificationType getTypeFromString(String type) => switch (type) { @@ -593,6 +894,16 @@ enum NotificationType { 'App\\Notifications\\Comment\\Replied' => NotificationType.commentReplied, 'App\\Notifications\\Score\\ScoreAdded' => NotificationType.scoreAdded, 'App\\Notifications\\Comment\\EntityCommented' => NotificationType.entityCommented, + // Дополнительные маппинги + 'App\\Notifications\\Plan\\PlanSubscriptionExpired' => NotificationType.planSubscriptionExpired, + 'App\\Notifications\\Plan\\PlanSubscriptionRenewed' => NotificationType.planSubscriptionRenewed, + 'App\\Notifications\\Plan\\ConsultantAssigned' => NotificationType.consultantAssigned, + 'App\\Notifications\\Plan\\ConsultantUnassigned' => NotificationType.consultantUnassigned, + 'App\\Notifications\\Payment\\PaymentFailed' => NotificationType.paymentFailed, + 'App\\Notifications\\Payment\\PaymentSuccessful' => NotificationType.paymentSuccessful, + 'App\\Notifications\\Goal\\GoalAchieved' => NotificationType.goalAchieved, + 'App\\Notifications\\Workout\\WorkoutReminder' => NotificationType.workoutReminder, + 'App\\Notifications\\Meal\\MealReminder' => NotificationType.mealReminder, _ => NotificationType.unknown, }; } diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart index d4e6e17d..df921b4a 100644 --- a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart @@ -113,6 +113,16 @@ class NotificationsCubit extends Cubit { NotificationType.commentReplied => CommentReplied.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.entityCommented => EntityCommented.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + // Новые типы уведомлений + NotificationType.planSubscriptionExpired => PlanSubscriptionExpiredNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.planSubscriptionRenewed => PlanSubscriptionRenewedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.consultantAssigned => ConsultantAssignedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.consultantUnassigned => ConsultantUnassignedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.paymentFailed => PaymentFailedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.paymentSuccessful => PaymentSuccessfulNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.goalAchieved => GoalAchievedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.workoutReminder => WorkoutReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), + NotificationType.mealReminder => MealReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.unknown => UnknownNotification(type: type), }; } catch (e) { diff --git a/lib/features/notifications/presentation/widgets/notification_widget.dart b/lib/features/notifications/presentation/widgets/notification_widget.dart index 18c02a13..eff047da 100644 --- a/lib/features/notifications/presentation/widgets/notification_widget.dart +++ b/lib/features/notifications/presentation/widgets/notification_widget.dart @@ -47,6 +47,16 @@ class NotificationWidget extends StatelessWidget { CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied), ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded), EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented), + // Новые типы уведомлений + PlanSubscriptionExpiredNotification() => _PlanSubscriptionExpiredWidget(notification: notification as PlanSubscriptionExpiredNotification), + PlanSubscriptionRenewedNotification() => _PlanSubscriptionRenewedWidget(notification: notification as PlanSubscriptionRenewedNotification), + ConsultantAssignedNotification() => _ConsultantAssignedWidget(notification: notification as ConsultantAssignedNotification), + ConsultantUnassignedNotification() => _ConsultantUnassignedWidget(notification: notification as ConsultantUnassignedNotification), + PaymentFailedNotification() => _PaymentFailedWidget(notification: notification as PaymentFailedNotification), + PaymentSuccessfulNotification() => _PaymentSuccessfulWidget(notification: notification as PaymentSuccessfulNotification), + GoalAchievedNotification() => _GoalAchievedWidget(notification: notification as GoalAchievedNotification), + WorkoutReminderNotification() => _WorkoutReminderWidget(notification: notification as WorkoutReminderNotification), + MealReminderNotification() => _MealReminderWidget(notification: notification as MealReminderNotification), UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification), }; } @@ -443,3 +453,121 @@ class _UnknownNotificationWidget extends StatelessWidget { ); } } + +// Новые виджеты для дополнительных типов уведомлений +class _PlanSubscriptionExpiredWidget extends StatelessWidget { + const _PlanSubscriptionExpiredWidget({required this.notification}); + final PlanSubscriptionExpiredNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Подписка на план "${notification.planName}" истекла', + createdAt: notification.createdAt, + ); + } +} + +class _PlanSubscriptionRenewedWidget extends StatelessWidget { + const _PlanSubscriptionRenewedWidget({required this.notification}); + final PlanSubscriptionRenewedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Подписка на план "${notification.planName}" продлена до ${notification.renewalDate}', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantAssignedWidget extends StatelessWidget { + const _ConsultantAssignedWidget({required this.notification}); + final ConsultantAssignedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'К плану "${notification.planName}" назначен консультант ${notification.consultantName}', + createdAt: notification.createdAt, + ); + } +} + +class _ConsultantUnassignedWidget extends StatelessWidget { + const _ConsultantUnassignedWidget({required this.notification}); + final ConsultantUnassignedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Консультант ${notification.consultantName} отстранен от плана "${notification.planName}"', + createdAt: notification.createdAt, + ); + } +} + +class _PaymentFailedWidget extends StatelessWidget { + const _PaymentFailedWidget({required this.notification}); + final PaymentFailedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Ошибка оплаты плана "${notification.planName}": ${notification.errorMessage}', + createdAt: notification.createdAt, + ); + } +} + +class _PaymentSuccessfulWidget extends StatelessWidget { + const _PaymentSuccessfulWidget({required this.notification}); + final PaymentSuccessfulNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Успешная оплата плана "${notification.planName}" на сумму ${notification.amount} ${notification.currency}', + createdAt: notification.createdAt, + ); + } +} + +class _GoalAchievedWidget extends StatelessWidget { + const _GoalAchievedWidget({required this.notification}); + final GoalAchievedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Достигнута цель "${notification.goalName}": ${notification.achievedValue}/${notification.targetValue}', + createdAt: notification.createdAt, + ); + } +} + +class _WorkoutReminderWidget extends StatelessWidget { + const _WorkoutReminderWidget({required this.notification}); + final WorkoutReminderNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Напоминание о тренировке "${notification.workoutName}" (${notification.duration} мин)', + createdAt: notification.createdAt, + ); + } +} + +class _MealReminderWidget extends StatelessWidget { + const _MealReminderWidget({required this.notification}); + final MealReminderNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Напоминание о приеме пищи "${notification.mealName}" (${notification.calories} ккал)', + createdAt: notification.createdAt, + ); + } +} diff --git a/lib/features/notifications/service/notifications_service.dart b/lib/features/notifications/service/notifications_service.dart index 61f545ab..7fdcb4fe 100644 --- a/lib/features/notifications/service/notifications_service.dart +++ b/lib/features/notifications/service/notifications_service.dart @@ -119,6 +119,61 @@ class NotificationsService { NotificationType.commentReplied => CommentReplied.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), NotificationType.entityCommented => EntityCommented.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt), + // Новые типы уведомлений + NotificationType.planSubscriptionExpired => PlanSubscriptionExpiredNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.planSubscriptionRenewed => PlanSubscriptionRenewedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantAssigned => ConsultantAssignedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.consultantUnassigned => ConsultantUnassignedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.paymentFailed => PaymentFailedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.paymentSuccessful => PaymentSuccessfulNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.goalAchieved => GoalAchievedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.workoutReminder => WorkoutReminderNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.mealReminder => MealReminderNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)), }; } catch (e) { diff --git a/lib/features/profile/models/profile_model.dart b/lib/features/profile/models/profile_model.dart index f798021f..03fd57f3 100644 --- a/lib/features/profile/models/profile_model.dart +++ b/lib/features/profile/models/profile_model.dart @@ -19,6 +19,7 @@ abstract class ProfileModel with _$ProfileModel { @JsonKey(name: "updated_at") DateTime? updatedAt, @JsonKey(name: "is_public_profile", fromJson: parseBool) required bool isPublicProfile, @JsonKey(name: "is_blocked_by_me", fromJson: parseBool) required bool isBlockedByMe, + @JsonKey(name: "is_blocked_by_user", fromJson: parseBool, defaultValue: false) required bool isBlockedByUser, @JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe, @JsonKey(name: "slug") required String slug, @JsonKey(name: "profile_photo_url") required String profilePhotoUrl, diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index f3329074..990beed1 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -93,6 +93,39 @@ class _ProfileScreenState extends State { } isFirstFetch = false; } + + // Проверяем, заблокировал ли пользователь меня + if (state.profile.isBlockedByUser) { + return Scaffold( + appBar: AppBar(), + body: Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.block, + size: 64, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'Пользователь ограничил доступ к своей странице', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ), + ); + } + return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state); } else if (state is ProfileStateError) { return Scaffold( @@ -174,10 +207,10 @@ class _ProfileScreenState extends State { ), _subscriptionsAndSubscribers(state), if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _socialLinks(state), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _education(state), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _diplomasAndCertificates(state), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans) _activePlans(state), + if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _socialLinks(state), + if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _education(state), + if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _diplomasAndCertificates(state), + if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans && !state.profile.isBlockedByMe) _activePlans(state), _blogPosts(state), ], ), diff --git a/test/features/profile/services/profile_service_test.dart b/test/features/profile/services/profile_service_test.dart index 106da096..8c666344 100644 --- a/test/features/profile/services/profile_service_test.dart +++ b/test/features/profile/services/profile_service_test.dart @@ -17,6 +17,7 @@ void main() { (_) async => await Future.value( const ProfileModel( id: 0, + isBlockedByUser: false, name: 'Alexander Avanesyan', email: 'mso7nueh@mail.ru', phoneConfirmationState: '',