feat(notifications): added json parsing for notifications and tested impplementation of notifications layoute

This commit is contained in:
Username1548
2025-05-21 21:45:22 +07:00
parent c1e2b1c312
commit 272637078a
8 changed files with 203 additions and 116 deletions

View File

@@ -103,7 +103,15 @@ class AppRouter {
return const NoTransitionPage(child: ProfileScreen());
},
),
GoRoute(path: '/feed-screen', name: 'feedScreen', pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen())),
GoRoute(
path: '/feed-screen',
name: 'feedScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
redirect: (context, state) async {
await sl<WsManager>().initialize();
return null;
},
),
GoRoute(path: '/diary-screen', name: 'diaryScreen', pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen())),
GoRoute(
path: ConsultationsScreen.path,

View File

@@ -64,6 +64,8 @@ import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
import 'package:eatvio_app/features/plans/services/plans_service.dart';
@@ -97,7 +99,7 @@ Future<void> init() async {
// Cubit singletons
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl()));
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()));
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
sl.registerLazySingleton<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
@@ -161,6 +163,7 @@ Future<void> init() async {
sl.registerLazySingleton<ConsultationsRepository>(() => ConsultationsRepository(consultationService: sl()));
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
// Services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
@@ -188,6 +191,7 @@ Future<void> init() async {
sl.registerLazySingleton<ConsultationService>(() => ConsultationService(sl()));
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
// External
sl.registerLazySingleton(

View File

@@ -1,5 +1,3 @@
import 'dart:ffi';
import 'package:eatvio_app/core/models/pagination_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/my_finance/models/card_model.dart';

View File

@@ -1,5 +1,3 @@
import 'package:freezed_annotation/freezed_annotation.dart';
abstract class BaseNotificationModel {
abstract final NotificationType type;
}
@@ -18,12 +16,13 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
required this.chatId,
});
factory PlanSubscriptionStartedToAuthorNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory PlanSubscriptionStartedToAuthorNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return PlanSubscriptionStartedToAuthorNotification(
type: type,
planName: json['plan_name'],
customerName: json['user_name'],
chatId: json['chat_id'],
);
}
}
@@ -39,12 +38,12 @@ class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationMo
required this.customerName,
});
factory PlanSubscriptionCanceledToAuthorNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory PlanSubscriptionCanceledToAuthorNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return PlanSubscriptionCanceledToAuthorNotification(
type: type,
planName: json['plan_name'],
customerName: json['user_name'],
);
}
}
@@ -60,12 +59,12 @@ class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
required this.planId,
});
factory ChosenPlanStartedToClientNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory ChosenPlanStartedToClientNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory ChosenPlanStartedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return ChosenPlanStartedToClientNotification(
type: type,
planName: json['plan_name'],
planId: json['plan_id'],
);
}
}
@@ -79,12 +78,11 @@ class ChosenPlanTomorrowReminderToClientNotification implements BaseNotification
required this.planName,
});
factory ChosenPlanTomorrowReminderToClientNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory ChosenPlanTomorrowReminderToClientNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return ChosenPlanTomorrowReminderToClientNotification(
type: type,
planName: json['plan_name'],
);
}
}
@@ -94,12 +92,10 @@ class DiaryReminderNotification implements BaseNotificationModel {
DiaryReminderNotification({required this.type});
factory DiaryReminderNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory DiaryReminderNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return DiaryReminderNotification(
type: type,
);
}
}
@@ -113,12 +109,8 @@ class ConsultantLeftCommentNotification implements BaseNotificationModel {
required this.date,
});
factory ConsultantLeftCommentNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory ConsultantLeftCommentNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory ConsultantLeftCommentNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return ConsultantLeftCommentNotification(type: type, date: json['diary_date']);
}
}
@@ -128,12 +120,10 @@ class PersonalizedDietIsDefinedNotification implements BaseNotificationModel {
PersonalizedDietIsDefinedNotification({required this.type});
factory PersonalizedDietIsDefinedNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory PersonalizedDietIsDefinedNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory PersonalizedDietIsDefinedNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return PersonalizedDietIsDefinedNotification(
type: type,
);
}
}
@@ -146,12 +136,8 @@ class ChosenPlanFinishedToClientNotification implements BaseNotificationModel {
required this.planName,
});
factory ChosenPlanFinishedToClientNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory ChosenPlanFinishedToClientNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory ChosenPlanFinishedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return ChosenPlanFinishedToClientNotification(type: type, planName: json['plan_name']);
}
}
@@ -164,12 +150,11 @@ class PlanSubscriptionCanceledToCustomerNotification implements BaseNotification
required this.planName,
});
factory PlanSubscriptionCanceledToCustomerNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory PlanSubscriptionCanceledToCustomerNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return PlanSubscriptionCanceledToCustomerNotification(
type: type,
planName: json['plan_name'],
);
}
}
@@ -184,12 +169,12 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel
required this.customerName,
});
factory ChosenPlanStartedToConsultantNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory ChosenPlanStartedToConsultantNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory ChosenPlanStartedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return ChosenPlanStartedToConsultantNotification(
type: type,
planName: json['plan_name'],
customerName: json['client_name'],
);
}
}
@@ -204,12 +189,12 @@ class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotifica
required this.customerName,
});
factory ChosenPlanTomorrowReminderToConsultantNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory ChosenPlanTomorrowReminderToConsultantNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return ChosenPlanTomorrowReminderToConsultantNotification(
type: type,
planName: json['plan_name'],
customerName: json['client_name'],
);
}
}
@@ -224,12 +209,12 @@ class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel
required this.customerName,
});
factory PlanSubscriptionPaidToAuthorNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory PlanSubscriptionPaidToAuthorNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory PlanSubscriptionPaidToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return PlanSubscriptionPaidToAuthorNotification(
type: type,
planName: json['plan_name'],
customerName: json['user_name'],
);
}
}
@@ -246,12 +231,13 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
required this.planId,
});
factory ChosenPlanFinishedToConsultantNotification.fromRest(Map<String, dynamic> json) {
throw UnimplementedError();
}
factory ChosenPlanFinishedToConsultantNotification.fromWebSocket(Map<String, dynamic> json) {
throw UnimplementedError();
factory ChosenPlanFinishedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
return ChosenPlanFinishedToConsultantNotification(
type: type,
planName: json['plan_name'],
customerName: json['client_name'],
planId: json['plan_id'],
);
}
}

View File

@@ -1,22 +1,55 @@
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<NotificationsState> {
final List<BaseNotificationModel> _notifications = [];
final WsManager wsManager;
NotificationsCubit({required this.wsManager}) : super(const NotificationsState.empty()) {
final NotificationsRepository notificationsRepository;
NotificationsCubit({
required this.wsManager,
required this.notificationsRepository,
}) : super(const NotificationsState.empty()) {
wsManager.newNotificationsStream?.listen((event) {
_handleNotification(event.data);
});
}
BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) {
try {
return switch (type) {
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson, type),
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(notificationJson, type),
NotificationType.chosenPlanTomorrowReminderToClientNotification =>
ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson, type),
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type),
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type),
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(notificationJson, type),
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(notificationJson, type),
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(notificationJson, type),
NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
ChosenPlanTomorrowReminderToConsultantNotification.fromJson(notificationJson, type),
NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(notificationJson, type),
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(notificationJson, type),
NotificationType.unknown => UnknownNotification(type: type),
};
} catch (e) {
return UnknownNotification(type: NotificationType.unknown);
}
}
void _handleNotification(String notificationData) {
// final data = jsonDecode(notificationData) as Map<String, dynamic>;
// final notification = NotificationModel.fromJson(data);
// _notifications.add(notification);
final json = jsonDecode(notificationData) as Map<String, dynamic>;
final stringifiedType = json['notification_class_name'];
final type = NotificationType.getTypeFromString(stringifiedType);
final notification = _parseNotification(json, type);
_notifications.insert(0, notification);
emit(
NotificationsState.loaded(
notifications: _notifications,
@@ -24,4 +57,50 @@ class NotificationsCubit extends Cubit<NotificationsState> {
),
);
}
Future<void> getNotifications() async {
try {
final notifications = await notificationsRepository.getNotifications(page: 1);
_notifications.clear();
_notifications.addAll(notifications ?? []);
emit(
NotificationsState.loaded(
notifications: _notifications,
notificationsCount: _notifications.length,
),
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
}
}
}
final qwe = {
'event': 'IlluminateNotificationsEventsBroadcastNotificationCreated',
'channel': 'private-message-events.36',
'data': {
"plan_name": "Little Cheeseburger",
"plan_id": 18,
"user_name": "\u041b\u044f\u043f\u0438\u0441 \u041b\u0430\u043f\u0448\u0430",
"user_id": 53,
"user_photo_path": "https://dev2.eatvio.com/storage/profile-photos/X2aHwD6IOIFxEK8HM2d2oLuqio5BmocDLeWoH3Gs.jpg",
"chat_id": "3d14615d-1388-4833-8165-b40daa78f96a",
"chat_url": "https://dev2.eatvio.com/chat/3d14615d-1388-4833-8165-b40daa78f96a",
"notification_class_name": "App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor",
"database_type": "client-start-plan-subscription-to-author",
"id": "65cbea6d-1734-406e-b5da-79db270aed90",
"type": "client-start-plan-subscription-to-author"
}
};
final qwe2 = {
"plan_name": "Little Cheeseburger",
"plan_id": 18,
"user_name": "\u041b\u044f\u043f\u0438\u0441 \u041b\u0430\u043f\u0448\u0430",
"user_id": 53,
"user_photo_path": "https://dev2.eatvio.com/storage/profile-photos/X2aHwD6IOIFxEK8HM2d2oLuqio5BmocDLeWoH3Gs.jpg",
"chat_url": "https://dev2.eatvio.com/chat/3d14615d-1388-4833-8165-b40daa78f96a",
"notification_class_name": "App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor",
"database_type": "client-cancel-plan-subscription-to-author",
"id": "cf86d0e7-8ebb-4360-9b8a-b6df97e12aed",
"type": "client-cancel-plan-subscription-to-author"
};

View File

@@ -1,3 +1,4 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
import 'package:flutter/material.dart';
@@ -6,9 +7,15 @@ class NotificationWidget extends StatelessWidget {
final BaseNotificationModel notification;
@override
Widget build(BuildContext context) {
// return (notification.type) {
// NotificationType.userSubscribedNotification => const _UserSubscribedNotification(),
// };
return Container();
return Container(
width: MediaQuery.sizeOf(context).width,
height: 60,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: appTheme.green,
borderRadius: BorderRadius.circular(18),
),
child: Center(child: Text(notification.type.name)),
);
}
}

View File

@@ -7,31 +7,36 @@ class NotificationsService {
Future<List<BaseNotificationModel>?> getNotifications({required int page}) async {
try {
final response = await dio.get('api/profile/notifications?page=$page');
final response = await dio.get('/api/profile/notifications?page=$page');
if (response.statusCode == 200) {
final data = response.data['data'] as List<Map<String, dynamic>>;
final data = response.data['data'] as List<dynamic>;
final notifications = data.map((notificationJson) {
final stringifiedType = notificationJson['data']['notification_class_name'];
final type = NotificationType.getTypeFromString(stringifiedType);
return switch (type) {
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromRest(notificationJson),
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromRest(notificationJson),
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromRest(notificationJson),
NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromRest(notificationJson),
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromRest(notificationJson),
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromRest(notificationJson),
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromRest(notificationJson),
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromRest(notificationJson),
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromRest(notificationJson),
NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromRest(notificationJson),
NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
ChosenPlanTomorrowReminderToConsultantNotification.fromRest(notificationJson),
NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromRest(notificationJson),
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromRest(notificationJson),
NotificationType.unknown => UnknownNotification(type: type),
};
try {
final stringifiedType = notificationJson['data']['notification_class_name'];
final type = NotificationType.getTypeFromString(stringifiedType);
return switch (type) {
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson['data'], type),
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type),
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(notificationJson['data'], type),
NotificationType.chosenPlanTomorrowReminderToClientNotification =>
ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson['data'], type),
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson['data'], type),
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson['data'], type),
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(notificationJson['data'], type),
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(notificationJson['data'], type),
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type),
NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(notificationJson['data'], type),
NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
ChosenPlanTomorrowReminderToConsultantNotification.fromJson(notificationJson['data'], type),
NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(notificationJson['data'], type),
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(notificationJson['data'], type),
NotificationType.unknown => UnknownNotification(type: type),
};
} catch (e) {
return UnknownNotification(type: NotificationType.unknown);
}
}).toList();
return notifications;
return notifications.where((notification) => notification.type != NotificationType.unknown).toList();
}
return null;
} catch (e) {

View File

@@ -39,7 +39,7 @@ class MyApp extends StatelessWidget {
return MultiBlocProvider(
providers: [
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()),
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()..getNotifications()),
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
BlocProvider<ProfileAvatarCubit>(create: (context) => sl<ProfileAvatarCubit>()..getMyProfile()),
BlocProvider<ChatsCubit>(create: (context) => sl<ChatsCubit>()..getChatRoomsWithLoading()),