feat(notifications): added push notifications navigation handling and fixed ws notification parsing

This commit is contained in:
misha
2026-03-04 09:05:00 +03:00
parent 58d99424a0
commit 08eeea6ba4
18 changed files with 424 additions and 239 deletions

View File

@@ -21,7 +21,6 @@ import 'package:eatvio_app/core/di/cubit_registrations.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
@@ -80,9 +79,6 @@ class _AuthScreenState extends State<AuthScreen> {
try {
await sl<ProfileAvatarCubit>().getMyProfile();
await sl<ChatsCubit>().getChatRoomsWithLoading();
await sl<ChatsCubit>().connectToWebSocket();
await sl<NotificationsCubit>().getNotifications();
await sl<NotificationsCubit>().connectToWebSocket();
await sl<FirebaseApi>().registerPushToken();
showSuccessToast('Успешный вход');
} catch (e) {
@@ -213,9 +209,6 @@ class _AuthScreenState extends State<AuthScreen> {
await sl<WsManager>().initialize();
await sl<ProfileAvatarCubit>().getMyProfile();
await sl<ChatsCubit>().getChatRoomsWithLoading();
await sl<ChatsCubit>().connectToWebSocket();
await sl<NotificationsCubit>().getNotifications();
await sl<NotificationsCubit>().connectToWebSocket();
await sl<FirebaseApi>().registerPushToken();
showSuccessToast('Успешный вход');
} catch (e) {

View File

@@ -13,7 +13,6 @@ import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/utils/validation_utils.dart';
@@ -74,9 +73,6 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
try {
await sl<ProfileAvatarCubit>().getMyProfile();
await sl<ChatsCubit>().getChatRoomsWithLoading();
await sl<ChatsCubit>().connectToWebSocket();
await sl<NotificationsCubit>().getNotifications();
await sl<NotificationsCubit>().connectToWebSocket();
await sl<FirebaseApi>().registerPushToken();
showSuccessToast('Успешная регистрация');
} catch (e) {

View File

@@ -10,7 +10,6 @@ import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
@@ -76,9 +75,6 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
try {
await sl<ProfileAvatarCubit>().getMyProfile();
await sl<ChatsCubit>().getChatRoomsWithLoading();
await sl<ChatsCubit>().connectToWebSocket();
await sl<NotificationsCubit>().getNotifications();
await sl<NotificationsCubit>().connectToWebSocket();
await sl<FirebaseApi>().registerPushToken();
} catch (e) {
rethrow;

View File

@@ -19,9 +19,15 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
class BottomNavigationBarWidget extends StatefulWidget {
final Widget child;
const BottomNavigationBarWidget({
super.key,
required this.child,
required this.currentLocation,
});
const BottomNavigationBarWidget({super.key, required this.child});
final Widget child;
/// Текущий путь из GoRouter (matchedLocation), чтобы подсветка таба совпадала с экраном при навигации через go() (диплинки, пуши).
final String currentLocation;
@override
State<BottomNavigationBarWidget> createState() => _BottomNavigationBarWidgetState();
@@ -146,6 +152,16 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
controller.register(_switchToRoute);
}
// Подсветка таба по текущему маршруту (совпадает при навигации через go(), в т.ч. диплинки/пуши).
final pathWithoutQuery = widget.currentLocation.split('?').first;
final indexFromRoute = pages!.indexOf(pathWithoutQuery);
final effectiveIndex = indexFromRoute >= 0 ? indexFromRoute : currentBottomNavBarIndex;
if (indexFromRoute >= 0 && indexFromRoute != currentBottomNavBarIndex) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) setState(() => currentBottomNavBarIndex = indexFromRoute);
});
}
return Scaffold(
body: widget.child,
bottomNavigationBar: BottomNavigationBar(
@@ -154,7 +170,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
selectedFontSize: 10,
unselectedFontSize: 10,
selectedItemColor: appTheme.blueA400,
currentIndex: currentBottomNavBarIndex,
currentIndex: effectiveIndex,
items: [
BottomNavigationBarItem(
label: '',
@@ -229,7 +245,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
ImageConstant.icnChat,
width: 20,
height: 20,
colorFilter: pages![currentBottomNavBarIndex] == '/chat-select-screen'
colorFilter: pages![effectiveIndex] == '/chat-select-screen'
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
),
@@ -251,7 +267,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnBell,
colorFilter: pages![currentBottomNavBarIndex] == '/notifications-screen'
colorFilter: pages![effectiveIndex] == '/notifications-screen'
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: 20,

View File

@@ -166,13 +166,7 @@ class DiaryReminderNotification implements BaseNotificationModel {
@override
final DateTime createdAt;
DiaryReminderNotification({
required this.type,
required this.isRead,
this.diaryId,
this.message,
required this.createdAt,
});
DiaryReminderNotification({required this.type, required this.isRead, this.diaryId, this.message, required this.createdAt});
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return DiaryReminderNotification(
@@ -581,6 +575,22 @@ class ComplaintToModerationForUser implements BaseNotificationModel {
}
}
enum CommentedEntityType {
diet,
blogPost,
unknown;
factory CommentedEntityType.fromJson(String type) {
return switch (type) {
'diet' => CommentedEntityType.diet,
'Modules\\UserGeneratedPublicContent\\Models\\Diet' => CommentedEntityType.diet,
'blog-post' => CommentedEntityType.blogPost,
'Modules\\UserGeneratedPublicContent\\Models\\BlogPost' => CommentedEntityType.blogPost,
_ => CommentedEntityType.unknown,
};
}
}
class CommentReplied implements BaseNotificationModel {
@override
final NotificationType type;
@@ -588,7 +598,7 @@ class CommentReplied implements BaseNotificationModel {
final bool isRead;
final String entityName;
final int commentedEntityId;
final String commentedEntityType;
final CommentedEntityType commentedEntityType;
final String author;
final int authorId;
final int parentCommentId;
@@ -614,7 +624,7 @@ class CommentReplied implements BaseNotificationModel {
type: type,
entityName: json['entity_name'],
commentedEntityId: json['commented_entity_id'],
commentedEntityType: json['commented_entity_type'],
commentedEntityType: CommentedEntityType.fromJson(json['commented_entity_type']),
author: json['author'],
authorId: json['author_id'],
parentCommentId: json['parrent_comment_id'],
@@ -673,7 +683,7 @@ class EntityCommented implements BaseNotificationModel {
final bool isRead;
final String entityName;
final int entityId;
final String entityType;
final CommentedEntityType entityType;
final String entityTranslatedType;
final String entityUrl;
final String authorName;
@@ -703,7 +713,7 @@ class EntityCommented implements BaseNotificationModel {
type: type,
entityName: json['entity_name'],
entityId: json['entity_id'],
entityType: json['entity_type'],
entityType: CommentedEntityType.fromJson(json['entity_type']),
entityTranslatedType: json['entity_traslated_type'],
entityUrl: json['entity_url'],
authorName: json['author_name'],
@@ -1012,11 +1022,7 @@ class UnknownNotification implements BaseNotificationModel {
UnknownNotification({required this.type, this.isRead = true, required this.createdAt});
factory UnknownNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return UnknownNotification(
type: type,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
return UnknownNotification(type: type, isRead: isRead ?? false, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()));
}
}
@@ -1889,12 +1895,7 @@ class ClientCloseComplaintSuccessNotification implements BaseNotificationModel {
@override
final DateTime createdAt;
ClientCloseComplaintSuccessNotification({
required this.type,
required this.planName,
required this.isRead,
required this.createdAt,
});
ClientCloseComplaintSuccessNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
factory ClientCloseComplaintSuccessNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return ClientCloseComplaintSuccessNotification(
@@ -2001,13 +2002,7 @@ class ConsultantRejectSuccessNotification implements BaseNotificationModel {
@override
final DateTime createdAt;
ConsultantRejectSuccessNotification({
required this.type,
required this.planName,
required this.clientName,
required this.isRead,
required this.createdAt,
});
ConsultantRejectSuccessNotification({required this.type, required this.planName, required this.clientName, required this.isRead, required this.createdAt});
factory ConsultantRejectSuccessNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantRejectSuccessNotification(
@@ -2115,13 +2110,7 @@ class DiaryReminderForUsersNotification implements BaseNotificationModel {
@override
final DateTime createdAt;
DiaryReminderForUsersNotification({
required this.type,
required this.isRead,
this.diaryId,
this.message,
required this.createdAt,
});
DiaryReminderForUsersNotification({required this.type, required this.isRead, this.diaryId, this.message, required this.createdAt});
factory DiaryReminderForUsersNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
final data = json['data'] is Map<String, dynamic> ? json['data'] as Map<String, dynamic> : json;
@@ -2196,12 +2185,7 @@ class ConsultantEducationUpdatedNotification implements BaseNotificationModel {
final DateTime createdAt;
final String consultantName;
ConsultantEducationUpdatedNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.consultantName,
});
ConsultantEducationUpdatedNotification({required this.type, required this.isRead, required this.createdAt, required this.consultantName});
factory ConsultantEducationUpdatedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantEducationUpdatedNotification(
@@ -2258,13 +2242,7 @@ class ContentUnpublishedNotification implements BaseNotificationModel {
final String reason;
final String entityName;
ContentUnpublishedNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.reason,
required this.entityName,
});
ContentUnpublishedNotification({required this.type, required this.isRead, required this.createdAt, required this.reason, required this.entityName});
factory ContentUnpublishedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return ContentUnpublishedNotification(
@@ -2286,12 +2264,7 @@ class FinanceQuestionCreatedNotification implements BaseNotificationModel {
final DateTime createdAt;
final String questionText;
FinanceQuestionCreatedNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.questionText,
});
FinanceQuestionCreatedNotification({required this.type, required this.isRead, required this.createdAt, required this.questionText});
factory FinanceQuestionCreatedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return FinanceQuestionCreatedNotification(
@@ -2343,11 +2316,7 @@ class CompleteRegistrationNotification implements BaseNotificationModel {
@override
final DateTime createdAt;
CompleteRegistrationNotification({
required this.type,
required this.isRead,
required this.createdAt,
});
CompleteRegistrationNotification({required this.type, required this.isRead, required this.createdAt});
factory CompleteRegistrationNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return CompleteRegistrationNotification(
@@ -2368,13 +2337,7 @@ class ChosenDietExpiredNotification implements BaseNotificationModel {
final String dietName;
final int dietId;
ChosenDietExpiredNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.dietName,
required this.dietId,
});
ChosenDietExpiredNotification({required this.type, required this.isRead, required this.createdAt, required this.dietName, required this.dietId});
factory ChosenDietExpiredNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenDietExpiredNotification(
@@ -2396,12 +2359,7 @@ class MotivateUserNotification implements BaseNotificationModel {
final DateTime createdAt;
final String message;
MotivateUserNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.message,
});
MotivateUserNotification({required this.type, required this.isRead, required this.createdAt, required this.message});
factory MotivateUserNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return MotivateUserNotification(
@@ -2423,13 +2381,7 @@ class CommentComplaintNotification implements BaseNotificationModel {
final String commentPreview;
final String reason;
CommentComplaintNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.commentPreview,
required this.reason,
});
CommentComplaintNotification({required this.type, required this.isRead, required this.createdAt, required this.commentPreview, required this.reason});
factory CommentComplaintNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return CommentComplaintNotification(
@@ -2451,12 +2403,7 @@ class CommentDeletedNotification implements BaseNotificationModel {
final DateTime createdAt;
final String commentPreview;
CommentDeletedNotification({
required this.type,
required this.isRead,
required this.createdAt,
required this.commentPreview,
});
CommentDeletedNotification({required this.type, required this.isRead, required this.createdAt, required this.commentPreview});
factory CommentDeletedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
return CommentDeletedNotification(
@@ -2655,7 +2602,7 @@ enum NotificationType {
'stay-on-track-to-customer' => NotificationType.stayOnTrackToCustomer,
'diary-reminder-for-users' => NotificationType.diaryReminderForUsers,
'personal-diet-is-defined-to-customer' => NotificationType.personalDietIsDefinedToCustomer,
// New additions
'consultant-education-updated' => NotificationType.consultantEducationUpdated,
'complaint-registered' => NotificationType.complaintRegistered,
@@ -2671,7 +2618,6 @@ enum NotificationType {
'user-subscribed' => NotificationType.userSubscribed,
// 'diary-reminder' => NotificationType.diaryReminder, // Already legacy? Check if it maps to diaryReminderNotification
'meal-reminder' => NotificationType.mealReminder, // Maps to legacy if string matches? No, legacy was App\\...
// Legacy mapping
'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor,
'App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor' => NotificationType.planSubscriptionCanceledToAuthor,

View File

@@ -349,23 +349,31 @@ class NotificationsCubit extends Cubit<NotificationsState> {
void _handleNotification(String notificationData) {
final json = jsonDecode(notificationData) as Map<String, dynamic>;
// Улучшенная логика определения типа уведомления
// Улучшенная логика определения типа уведомления (порядок: database_type → type → notification_class_name)
String? stringifiedType;
// Сначала проверяем поле 'type' в корне JSON
if (json.containsKey('type') && json['type'] != null) {
// Сначала проверяем поле 'database_type' в корне JSON
if (json.containsKey('database_type') && json['database_type'] != null) {
stringifiedType = json['database_type'].toString();
log('Found database_type in root: $stringifiedType');
}
// Затем поле 'type' в корне
else if (json.containsKey('type') && json['type'] != null) {
stringifiedType = json['type'].toString();
log('Found type in root: $stringifiedType');
}
// Затем проверяем поле 'notification_class_name' в корне JSON
// Затем поле 'notification_class_name' в корне
else if (json.containsKey('notification_class_name') && json['notification_class_name'] != null) {
stringifiedType = json['notification_class_name'].toString();
log('Found notification_class_name in root: $stringifiedType');
}
// Если есть поле 'data', проверяем внутри него
// Если есть поле 'data', проверяем внутри него в том же порядке
else if (json.containsKey('data') && json['data'] is Map<String, dynamic>) {
final data = json['data'] as Map<String, dynamic>;
if (data.containsKey('type') && data['type'] != null) {
if (data.containsKey('database_type') && data['database_type'] != null) {
stringifiedType = data['database_type'].toString();
log('Found database_type in data: $stringifiedType');
} else if (data.containsKey('type') && data['type'] != null) {
stringifiedType = data['type'].toString();
log('Found type in data: $stringifiedType');
} else if (data.containsKey('notification_class_name') && data['notification_class_name'] != null) {

View File

@@ -147,17 +147,16 @@ void _navigateToBottomNavRoute(BuildContext context, String path, [Object? extra
}
}
void _navigateToEntity(BuildContext context, String type, int id, int? commentId) {
if (type.contains('BlogPost')) {
context.push(RouteConstants.blogPostScreen, extra: {'id': id, 'highlightedCommentId': commentId});
} else if (type.contains('Recipe')) {
// TODO: aboutRecipesScreen требует recipe slug, в уведомлении только id — навигация отключена
} else if (type.contains('Plan')) {
// TODO: aboutPlanScreen требует slug плана, в уведомлении только id — навигация отключена
} else if (type.contains('Diet')) {
if (id > 0) {
context.push(RouteConstants.aboutDietScreen, extra: {'id': id, 'highlightedCommentId': commentId});
}
void _navigateToEntity(BuildContext context, CommentedEntityType type, int id, int? commentId) {
switch (type) {
case CommentedEntityType.blogPost:
context.push(RouteConstants.blogPostScreen, extra: {'id': id, 'highlightedCommentId': commentId});
case CommentedEntityType.diet:
if (id > 0) {
context.push(RouteConstants.aboutDietScreen, extra: {'id': id, 'highlightedCommentId': commentId});
}
case CommentedEntityType.unknown:
break;
}
}
@@ -184,7 +183,6 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
final PlanSubscriptionCanceledToAuthorNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Отмена подписки. Клиент ${notification.customerName} отказался от плана "${notification.planName}"',
@@ -201,7 +199,6 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
final ChosenPlanStartedToClientNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Ваш план "${notification.planName}" начался! Удачи! 🚀',
@@ -218,7 +215,6 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
final ChosenPlanTomorrowReminderToClientNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Напоминание: Завтра старт плана "${notification.planName}"',
@@ -250,7 +246,6 @@ class _ConsultantLeftCommentWidget extends StatelessWidget {
final ConsultantLeftCommentNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Новый комментарий от консультанта (за ${notification.date.format('dd.MM.yyyy')})',
@@ -283,7 +278,6 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget {
final ChosenPlanFinishedToClientNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Поздравляем! План «${notification.planName}» успешно завершен! 🏆',
@@ -297,7 +291,6 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget {
final PlanSubscriptionCanceledToCustomerNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Подписка на план "${notification.planName}" отменена.',
@@ -311,7 +304,6 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget {
final ChosenPlanStartedToConsultantNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Начало работы: План "${notification.planName}" с клиентом ${notification.customerName}',
@@ -325,7 +317,6 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget {
final ChosenPlanTomorrowReminderToConsultantNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Напоминание: Завтра старт работы с ${notification.customerName} по плану "${notification.planName}"',
@@ -339,7 +330,6 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget {
final PlanSubscriptionPaidToAuthorNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Оплата получена! Клиент ${notification.customerName} оплатил план "${notification.planName}" 💰',
@@ -548,11 +538,6 @@ class _ComplaintToModerationForUserWidget extends StatelessWidget {
isRead: notification.isRead,
notificationsText: 'Ваша жалоба принята и находится на модерации.',
createdAt: notification.createdAt,
onTap: (['Plan', 'Recipe'].contains(notification.entityType) || notification.entityId <= 0 || notification.entityType.isEmpty)
? null
: () {
_navigateToEntity(context, notification.entityType, notification.entityId, null);
},
);
}
}
@@ -566,7 +551,7 @@ class _CommentRepliedWidget extends StatelessWidget {
isRead: notification.isRead,
notificationsText: 'Вам ответили в комментариях к: ${notification.entityName}',
createdAt: notification.createdAt,
onTap: (['Plan', 'Recipe'].contains(notification.commentedEntityType) || notification.commentedEntityId <= 0 || notification.commentId == null)
onTap: (notification.commentedEntityType == CommentedEntityType.unknown || notification.commentedEntityId <= 0)
? null
: () {
_navigateToEntity(context, notification.commentedEntityType, notification.commentedEntityId, notification.commentId);
@@ -600,9 +585,11 @@ class _EntityCommentedWidget extends StatelessWidget {
isRead: notification.isRead,
notificationsText: 'Новый комментарий к вашей публикации "${notification.entityTranslatedType}": ${notification.entityName}',
createdAt: notification.createdAt,
onTap: () {
_navigateToEntity(context, notification.entityType, notification.entityId, notification.commentId);
},
onTap: (notification.entityType == CommentedEntityType.unknown || notification.entityId <= 0)
? null
: () {
_navigateToEntity(context, notification.entityType, notification.entityId, notification.commentId);
},
);
}
}