feat(notifications): Issue-191

This commit is contained in:
mso7nueh
2025-08-06 11:28:02 +03:00
parent d53fdd1ccb
commit cd9357c050
8 changed files with 918 additions and 281 deletions

View File

@@ -13,10 +13,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
final WsManager wsManager;
final NotificationsRepository notificationsRepository;
StreamSubscription? _newNotificationsSubscription;
NotificationsCubit({
required this.wsManager,
required this.notificationsRepository,
}) : super(const NotificationsState.empty());
NotificationsCubit({required this.wsManager, required this.notificationsRepository}) : super(const NotificationsState.empty());
Future<void> connectToWebSocket() async {
await _newNotificationsSubscription?.cancel();
@@ -27,22 +24,95 @@ class NotificationsCubit extends Cubit<NotificationsState> {
BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) {
try {
final createdAt = notificationJson['created_at'];
final isRead = notificationJson['read_at'] != null;
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.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson(notificationJson, 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.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.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.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.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.unknown => UnknownNotification(type: type),
};
} catch (e) {
@@ -56,32 +126,80 @@ class NotificationsCubit extends Cubit<NotificationsState> {
final type = NotificationType.getTypeFromString(stringifiedType);
final notification = _parseNotification(json, type);
_notifications.insert(0, notification);
emit(
NotificationsState.loaded(
notifications: _notifications,
notificationsCount: _notifications.length,
hasUnreadNotifications: true,
),
);
final currentState = state;
if (currentState is NotificationsStateLoaded) {
emit(currentState.copyWith(notifications: _notifications, notificationsCount: _notifications.length, hasUnreadNotifications: true));
}
}
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,
hasUnreadNotifications: (notifications ?? []).where((elem) => elem.isRead == false).isNotEmpty,
),
);
final result = await notificationsRepository.getNotifications(page: 1);
if (result != null) {
final notifications = result['notifications'] as List<BaseNotificationModel>;
final pagination = result['pagination'] as Map<String, dynamic>;
_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<void> 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<BaseNotificationModel>;
final pagination = result['pagination'] as Map<String, dynamic>;
_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<void> markNotificationsAsRead() async {
final currentState = state;
if (currentState is NotificationsStateLoaded) {

View File

@@ -14,6 +14,9 @@ abstract class NotificationsState with _$NotificationsState {
required int notificationsCount,
@Default(false) bool isLoading,
@Default(false) bool hasUnreadNotifications,
@Default(1) int currentPage,
@Default(true) bool hasNextPage,
@Default(false) bool isLoadingMore,
}) = NotificationsStateLoaded;
const factory NotificationsState.error({

View File

@@ -10,9 +10,38 @@ import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NotificationsScreen extends StatelessWidget {
class NotificationsScreen extends StatefulWidget {
const NotificationsScreen({super.key});
@override
State<NotificationsScreen> createState() => _NotificationsScreenState();
}
class _NotificationsScreenState extends State<NotificationsScreen> {
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) {
final cubit = context.read<NotificationsCubit>();
final state = cubit.state;
if (state is NotificationsStateLoaded && state.hasNextPage && !state.isLoadingMore) {
cubit.loadMoreNotifications();
}
}
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
@@ -20,62 +49,65 @@ class NotificationsScreen extends StatelessWidget {
builder: (BuildContext context, NotificationsState state) {
return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar(
title: const Text('Уведомления'),
),
appBar: AppBar(title: const Text('Уведомления')),
body: state is NotificationsStateLoaded
? Stack(children: [
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Container(
padding: const EdgeInsets.only(top: 16),
decoration: BoxDecoration(
color: appTheme.whiteA700,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
? Stack(
children: [
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Container(
padding: const EdgeInsets.only(top: 16),
decoration: BoxDecoration(
color: appTheme.whiteA700,
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
),
child: state.isLoading
? const Center(child: Loader())
: CustomScrollView(
controller: _scrollController,
slivers: [
SliverList.separated(
separatorBuilder: (context, index) =>
const Padding(padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: CustomDivider()),
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: NotificationWidget(notification: state.notifications[index]),
);
},
itemCount: state.notifications.length,
),
if (state.isLoadingMore)
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: Loader()),
),
),
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
child: CustomOutlinedButton(
text: s.markAsRead,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
onPressed: () async {
context.read<NotificationsCubit>().markNotificationsAsRead();
},
),
),
child: state.isLoading
? const Center(child: Loader())
: CustomScrollView(
slivers: [
SliverList.separated(
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: CustomDivider(),
),
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: NotificationWidget(
notification: state.notifications[index],
),
);
},
itemCount: state.notifications.length,
)
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
child: CustomOutlinedButton(
text: s.markAsRead,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle:
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
onPressed: () async {
context.read<NotificationsCubit>().markNotificationsAsRead();
},
),
),
)
])
],
)
: const Placeholder(),
);
},

View File

@@ -15,46 +15,39 @@ class NotificationWidget extends StatelessWidget {
Widget build(BuildContext context) {
return switch (notification) {
PlanSubscriptionStartedToAuthorNotification() => _PlanSubscriptionStartedToAuthorWidget(
notification: notification as PlanSubscriptionStartedToAuthorNotification,
),
notification: notification as PlanSubscriptionStartedToAuthorNotification,
),
PlanSubscriptionCanceledToAuthorNotification() => _PlanSubscriptionCanceledToAuthorWidget(
notification: notification as PlanSubscriptionCanceledToAuthorNotification,
),
ChosenPlanStartedToClientNotification() => _ChosenPlanStartedToClientWidget(
notification: notification as ChosenPlanStartedToClientNotification,
),
notification: notification as PlanSubscriptionCanceledToAuthorNotification,
),
ChosenPlanStartedToClientNotification() => _ChosenPlanStartedToClientWidget(notification: notification as ChosenPlanStartedToClientNotification),
ChosenPlanTomorrowReminderToClientNotification() => _ChosenPlanTomorrowReminderToClientWidget(
notification: notification as ChosenPlanTomorrowReminderToClientNotification,
),
DiaryReminderNotification() => _DiaryReminderWidget(
notification: notification as DiaryReminderNotification,
),
ConsultantLeftCommentNotification() => _ConsultantLeftCommentWidget(
notification: notification as ConsultantLeftCommentNotification,
),
PersonalizedDietIsDefinedNotification() => _PersonalizedDietIsDefinedWidget(
notification: notification as PersonalizedDietIsDefinedNotification,
),
ChosenPlanFinishedToClientNotification() => _ChosenPlanFinishedToClientWidget(
notification: notification as ChosenPlanFinishedToClientNotification,
),
notification: notification as ChosenPlanTomorrowReminderToClientNotification,
),
DiaryReminderNotification() => _DiaryReminderWidget(notification: notification as DiaryReminderNotification),
ConsultantLeftCommentNotification() => _ConsultantLeftCommentWidget(notification: notification as ConsultantLeftCommentNotification),
PersonalizedDietIsDefinedNotification() => _PersonalizedDietIsDefinedWidget(notification: notification as PersonalizedDietIsDefinedNotification),
ChosenPlanFinishedToClientNotification() => _ChosenPlanFinishedToClientWidget(notification: notification as ChosenPlanFinishedToClientNotification),
PlanSubscriptionCanceledToCustomerNotification() => _PlanSubscriptionCanceledToCustomerWidget(
notification: notification as PlanSubscriptionCanceledToCustomerNotification,
),
notification: notification as PlanSubscriptionCanceledToCustomerNotification,
),
ChosenPlanStartedToConsultantNotification() => _ChosenPlanStartedToConsultantWidget(
notification: notification as ChosenPlanStartedToConsultantNotification,
),
notification: notification as ChosenPlanStartedToConsultantNotification,
),
ChosenPlanTomorrowReminderToConsultantNotification() => _ChosenPlanTomorrowReminderToConsultantWidget(
notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
),
PlanSubscriptionPaidToAuthorNotification() => _PlanSubscriptionPaidToAuthorWidget(
notification: notification as PlanSubscriptionPaidToAuthorNotification,
),
notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
),
PlanSubscriptionPaidToAuthorNotification() => _PlanSubscriptionPaidToAuthorWidget(notification: notification as PlanSubscriptionPaidToAuthorNotification),
ChosenPlanFinishedToConsultantNotification() => _ChosenPlanFinishedToConsultantWidget(
notification: notification as ChosenPlanFinishedToConsultantNotification,
),
ChosenPlanLastDayFinishedForUser() => Text((notification as ChosenPlanLastDayFinishedForUser).type.toString()),
UnknownNotification() => const SizedBox.shrink(),
notification: notification as ChosenPlanFinishedToConsultantNotification,
),
ChosenPlanLastDayFinishedForUser() => _ChosenPlanLastDayFinishedForUserWidget(notification: notification as ChosenPlanLastDayFinishedForUser),
ChosenPlanConsultantLastDayStarted() => _ChosenPlanConsultantLastDayStartedWidget(notification: notification as ChosenPlanConsultantLastDayStarted),
ComplaintToModerationForUser() => _ComplaintToModerationForUserWidget(notification: notification as ComplaintToModerationForUser),
CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied),
ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded),
EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented),
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
};
}
}
@@ -69,6 +62,7 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName),
createdAt: notification.createdAt,
);
}
}
@@ -83,6 +77,7 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName),
createdAt: notification.createdAt,
);
}
}
@@ -94,10 +89,7 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
Widget build(BuildContext context) {
final s = S.of(context);
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.planStarted(notification.planName),
);
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.planStarted(notification.planName), createdAt: notification.createdAt);
}
}
@@ -111,6 +103,7 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.tomorrowYourPlanWillStart(notification.planName),
createdAt: notification.createdAt,
);
}
}
@@ -121,10 +114,7 @@ class _DiaryReminderWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.fillYasterday,
);
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.fillYasterday, createdAt: notification.createdAt);
}
}
@@ -138,6 +128,7 @@ class _ConsultantLeftCommentWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.consultantLeftComment(notification.date.format('dd.MM.yyyy')),
createdAt: notification.createdAt,
);
}
}
@@ -147,7 +138,7 @@ class _PersonalizedDietIsDefinedWidget extends StatelessWidget {
final PersonalizedDietIsDefinedNotification notification;
@override
Widget build(BuildContext context) {
return Container();
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Персональная диета определена', createdAt: notification.createdAt);
}
}
@@ -161,6 +152,7 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
createdAt: notification.createdAt,
);
}
}
@@ -175,6 +167,7 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.youCanceledSubscriptionToPlan(notification.planName),
createdAt: notification.createdAt,
);
}
}
@@ -189,6 +182,7 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.planWithUserStarted(notification.planName, notification.customerName),
createdAt: notification.createdAt,
);
}
}
@@ -203,6 +197,7 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName),
createdAt: notification.createdAt,
);
}
}
@@ -217,6 +212,7 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.userPaidThePlan(notification.customerName, notification.planName),
createdAt: notification.createdAt,
);
}
}
@@ -227,58 +223,223 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Column(children: [
Text(
'${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 12),
Text(
s.userConfirmedPlanCompletionWithoutSum,
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
const SizedBox(height: 12),
CustomOutlinedButton(
text: s.collectCharge,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
onPressed: () {
GoRouter.of(context).pushNamed(CardSettingsScreen.name, extra: notification.planId);
},
),
const SizedBox(height: 12),
]);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${s.congratsYouHaveFinishedPlan} «${notification.planName}»', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)),
const SizedBox(height: 12),
Text(s.userConfirmedPlanCompletionWithoutSum, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
const SizedBox(height: 12),
CustomOutlinedButton(
text: s.collectCharge,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
onPressed: () {
GoRouter.of(context).pushNamed(CardSettingsScreen.name, extra: notification.planId);
},
),
],
),
),
if (!notification.isRead)
Container(
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
width: 8,
height: 8,
),
],
),
const SizedBox(height: 4),
Text(
'${notification.createdAt.day} ${_getMonthName(notification.createdAt.month)} ${notification.createdAt.hour.toString().padLeft(2, '0')}:${notification.createdAt.minute.toString().padLeft(2, '0')}',
style: TextStyles.textSMedium.copyWith(color: appTheme.gray600),
),
],
);
}
String _getMonthName(int month) {
switch (month) {
case 1:
return 'янв';
case 2:
return 'фев';
case 3:
return 'мар';
case 4:
return 'апр';
case 5:
return 'май';
case 6:
return 'июн';
case 7:
return 'июл';
case 8:
return 'авг';
case 9:
return 'сен';
case 10:
return 'окт';
case 11:
return 'ноя';
case 12:
return 'дек';
default:
return '';
}
}
}
// CardSettingsScreen
class _BaseNotificationWidget extends StatelessWidget {
const _BaseNotificationWidget({required this.notificationsText, required this.isRead});
const _BaseNotificationWidget({required this.notificationsText, required this.isRead, required this.createdAt});
final String notificationsText;
final bool isRead;
final DateTime createdAt;
@override
Widget build(BuildContext context) {
return Row(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.6,
child: Text(
notificationsText,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
),
if (!isRead) ...[
const Spacer(),
Container(
decoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
Row(
children: [
Expanded(
child: Text(notificationsText, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
width: 8,
height: 8,
)
]
if (!isRead)
Container(
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
width: 8,
height: 8,
),
],
),
const SizedBox(height: 4),
Text(
'${createdAt.day} ${_getMonthName(createdAt.month)} ${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}',
style: TextStyles.textSMedium.copyWith(color: appTheme.gray600),
),
],
);
}
String _getMonthName(int month) {
switch (month) {
case 1:
return 'янв';
case 2:
return 'фев';
case 3:
return 'мар';
case 4:
return 'апр';
case 5:
return 'май';
case 6:
return 'июн';
case 7:
return 'июл';
case 8:
return 'авг';
case 9:
return 'сен';
case 10:
return 'окт';
case 11:
return 'ноя';
case 12:
return 'дек';
default:
return '';
}
}
}
class _ChosenPlanLastDayFinishedForUserWidget extends StatelessWidget {
const _ChosenPlanLastDayFinishedForUserWidget({required this.notification});
final ChosenPlanLastDayFinishedForUser notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Последний день плана завершен', createdAt: notification.createdAt);
}
}
class _ChosenPlanConsultantLastDayStartedWidget extends StatelessWidget {
const _ChosenPlanConsultantLastDayStartedWidget({required this.notification});
final ChosenPlanConsultantLastDayStarted notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Последний день плана начался: ${notification.planName}',
createdAt: notification.createdAt,
);
}
}
class _ComplaintToModerationForUserWidget extends StatelessWidget {
const _ComplaintToModerationForUserWidget({required this.notification});
final ComplaintToModerationForUser notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Жалоба отправлена на модерацию', createdAt: notification.createdAt);
}
}
class _CommentRepliedWidget extends StatelessWidget {
const _CommentRepliedWidget({required this.notification});
final CommentReplied notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Ответ на комментарий: ${notification.entityName}',
createdAt: notification.createdAt,
);
}
}
class _ScoreAddedWidget extends StatelessWidget {
const _ScoreAddedWidget({required this.notification});
final ScoreAdded notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Добавлены баллы: ${notification.scoreValue}',
createdAt: notification.createdAt,
);
}
}
class _EntityCommentedWidget extends StatelessWidget {
const _EntityCommentedWidget({required this.notification});
final EntityCommented notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Комментарий к ${notification.entityTranslatedType}: ${notification.entityName}',
createdAt: notification.createdAt,
);
}
}
class _UnknownNotificationWidget extends StatelessWidget {
const _UnknownNotificationWidget({required this.notification});
final UnknownNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: 'Неизвестное уведомление: ${notification.type.toString()}',
createdAt: notification.createdAt,
);
}
}