import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'package:eatvio_app/core/websockets/ws_manager.dart'; import 'package:eatvio_app/features/notifications/models/notification_model.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart'; import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class NotificationsCubit extends Cubit { final List _notifications = []; final WsManager wsManager; final NotificationsRepository notificationsRepository; StreamSubscription? _newNotificationsSubscription; NotificationsCubit({required this.wsManager, required this.notificationsRepository}) : super(const NotificationsState.empty()); Future connectToWebSocket() async { await _newNotificationsSubscription?.cancel(); _newNotificationsSubscription = wsManager.newNotificationsStream?.listen((event) { _handleNotification(event.data); }); } BaseNotificationModel _parseNotification(Map notificationJson, NotificationType type) { try { final createdAt = notificationJson['created_at']; final isRead = notificationJson['read_at'] != null; return switch (type) { NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanConsultantLastDayStarted => ChosenPlanConsultantLastDayStarted.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanTomorrowReminderToConsultantNotification => ChosenPlanTomorrowReminderToConsultantNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), // New notification types NotificationType.complaintToModerationForUser => ComplaintToModerationForUser.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), 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), // New notification types NotificationType.dietCancelled => DietCancelledNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.dietStarted => DietStartedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.dietExpired => DietExpiredNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.dietWillStartedIn => DietWillStartedInNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.dietCanNotBeStarted => DietCanNotBeStartedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.personalDietCreationNeeded => PersonalDietCreationNeededNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanClientLastDayFinished => ChosenPlanClientLastDayFinishedNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanClientLastDayStarted => ChosenPlanClientLastDayStartedNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.stayOnTrackToAuthor => StayOnTrackToAuthorNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.chosenPlanStartedToClient => ChosenPlanStartedToClientNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientStartPlanSubscriptionToSubscriber => ClientStartPlanSubscriptionToSubscriberNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.consultantConfirmPlanSubscriptionToSubscriber => ConsultantConfirmPlanSubscriptionToSubscriberNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientPaidPlanSubscriptionToAuthor => ClientPaidPlanSubscriptionToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanTomorrowReminderToClient => ChosenPlanTomorrowReminderToClientNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanTomorrowReminderToConsultant => ChosenPlanTomorrowReminderToConsultantNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanStartedToConsultant => ChosenPlanStartedToConsultantNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.consultantLeftDiaryComment => ConsultantLeftDiaryCommentNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.consultantConfirmPlanSubscriptionToAuthor => ConsultantConfirmPlanSubscriptionToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientPaidPlanSubscriptionToSubscriber => ClientPaidPlanSubscriptionToSubscriberNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientStartPlanSubscriptionToAuthor => ClientStartPlanSubscriptionToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientCancelPlanSubscriptionToAuthor => ClientCancelPlanSubscriptionToAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientCancelPlanSubscriptionToSubscriber => ClientCancelPlanSubscriptionToSubscriberNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.userDailyReportCompleted => UserDailyReportCompletedNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.stayOnTrackToCustomer => StayOnTrackToCustomerNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.phoneChanged => PhoneChangedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.subscription => SubscriptionNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.badgeAwarded => BadgeAwardedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.badgeRevoked => BadgeRevokedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.consultantRequestStarted => ConsultantRequestStartedNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientCloseComplaintSuccess => ClientCloseComplaintSuccessNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.clientRefundSuccess => ClientRefundSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.consultantRefundSuccess => ConsultantRefundSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.clientRejectSuccess => ClientRejectSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.consultantRejectSuccess => ConsultantRejectSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.scoreSubstracted => ScoreSubstractedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.productStatusUpdatedForAuthor => ProductStatusUpdatedForAuthorNotification.fromJson( notificationJson, type, isRead: isRead, createdAt: createdAt, ), NotificationType.diaryReminderForUsers => DiaryReminderForUsersNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt), NotificationType.unknown => UnknownNotification(type: type), }; } catch (e) { return UnknownNotification(type: NotificationType.unknown); } } void _handleNotification(String notificationData) { final json = jsonDecode(notificationData) as Map; // Улучшенная логика определения типа уведомления String? stringifiedType; // Сначала проверяем поле 'type' в корне JSON if (json.containsKey('type') && json['type'] != null) { stringifiedType = json['type'].toString(); log('Found type in root: $stringifiedType'); } // Затем проверяем поле 'notification_class_name' в корне JSON 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', проверяем внутри него else if (json.containsKey('data') && json['data'] is Map) { final data = json['data'] as Map; 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) { stringifiedType = data['notification_class_name'].toString(); log('Found notification_class_name in data: $stringifiedType'); } } // Если тип не найден, используем unknown final type = stringifiedType != null ? NotificationType.getTypeFromString(stringifiedType) : NotificationType.unknown; log('Final type: $type, stringifiedType: $stringifiedType'); final notification = _parseNotification(json, type); _notifications.insert(0, notification); final currentState = state; if (currentState is NotificationsStateLoaded) { emit(currentState.copyWith(notifications: _notifications, notificationsCount: _notifications.length, hasUnreadNotifications: true)); } } Future getNotifications() async { try { final result = await notificationsRepository.getNotifications(page: 1); if (result != null) { final notifications = result['notifications'] as List; final pagination = result['pagination'] as Map; _notifications.clear(); _notifications.addAll(notifications); final currentPage = pagination['current_page'] as int; final lastPage = pagination['last_page'] as int; final hasNextPage = currentPage < lastPage; emit( NotificationsState.loaded( notifications: _notifications, notificationsCount: _notifications.length, hasUnreadNotifications: notifications.where((elem) => elem.isRead == false).isNotEmpty, currentPage: currentPage, hasNextPage: hasNextPage, ), ); } } catch (e, st) { log(e.toString(), stackTrace: st); } } Future loadMoreNotifications() async { final currentState = state; if (currentState is NotificationsStateLoaded && currentState.hasNextPage && !currentState.isLoadingMore) { try { emit(currentState.copyWith(isLoadingMore: true)); final nextPage = currentState.currentPage + 1; final result = await notificationsRepository.getNotifications(page: nextPage); if (result != null) { final newNotifications = result['notifications'] as List; final pagination = result['pagination'] as Map; _notifications.addAll(newNotifications); final currentPage = pagination['current_page'] as int; final lastPage = pagination['last_page'] as int; final hasNextPage = currentPage < lastPage; emit( currentState.copyWith( notifications: _notifications, notificationsCount: _notifications.length, currentPage: currentPage, hasNextPage: hasNextPage, isLoadingMore: false, ), ); } else { emit(currentState.copyWith(isLoadingMore: false)); } } catch (e, st) { log(e.toString(), stackTrace: st); emit(currentState.copyWith(isLoadingMore: false)); } } } Future markNotificationsAsRead() async { final currentState = state; if (currentState is NotificationsStateLoaded) { try { emit(currentState.copyWith(isLoading: true)); await notificationsRepository.markNotificationsAsRead(); await getNotifications(); emit(currentState.copyWith(isLoading: false, hasUnreadNotifications: false)); } catch (e, st) { log(e.toString(), stackTrace: st); emit(currentState.copyWith(isLoading: false)); } } } }