diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
index 29232ced..db73bc05 100644
--- a/.gitea/workflows/build.yml
+++ b/.gitea/workflows/build.yml
@@ -9,7 +9,7 @@ env:
jobs:
build:
- runs-on: [ dev-office ]
+ runs-on: [ ubuntu-latest ]
steps:
- uses: https://github.com/actions/checkout@v3
diff --git a/.gitignore b/.gitignore
index 522e6995..6b66540b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ env/.env
android/key.properties
app/upload-keystore.jks
coverage/
+backend/
.fvm/
.fvmrc
/eatvio
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/eatvioapp/app/HmsPushService.kt b/android/app/src/main/kotlin/com/eatvioapp/app/HmsPushService.kt
index f2c7f3d9..f18b3992 100644
--- a/android/app/src/main/kotlin/com/eatvioapp/app/HmsPushService.kt
+++ b/android/app/src/main/kotlin/com/eatvioapp/app/HmsPushService.kt
@@ -1,6 +1,10 @@
package com.eatvioapp.app
+import android.app.PendingIntent
+import android.content.Intent
import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
import com.huawei.hms.push.HmsMessageService
import com.huawei.hms.push.RemoteMessage
@@ -13,6 +17,45 @@ class HmsPushService : HmsMessageService() {
override fun onMessageReceived(message: RemoteMessage?) {
super.onMessageReceived(message)
Log.i("HMS", "onMessageReceived: ${message?.data}")
+
+ if (message == null) return
+
+ // Берём action_url из data-поля, если оно есть.
+ val actionUrl = message.dataOfMap["action_url"]
+
+ // Строим системное уведомление, по тапу по которому откроется MainActivity с action_url.
+ showNotification(message, actionUrl)
+ }
+
+ private fun showNotification(message: RemoteMessage, actionUrl: String?) {
+ val context = applicationContext
+
+ // Интент для запуска Flutter-активити.
+ val intent = Intent(context, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ if (!actionUrl.isNullOrBlank()) {
+ putExtra("action_url", actionUrl)
+ }
+ }
+
+ val pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
+ )
+
+ val channelId = "hms_push_channel"
+
+ val notification = NotificationCompat.Builder(context, channelId)
+ .setSmallIcon(R.mipmap.ic_launcher) // используем иконку приложения
+ .setContentTitle(message.notification?.title ?: context.getString(R.string.app_name))
+ .setContentText(message.notification?.body ?: "")
+ .setAutoCancel(true)
+ .setContentIntent(pendingIntent)
+ .build()
+
+ NotificationManagerCompat.from(context).notify(message.messageId.hashCode(), notification)
}
}
diff --git a/android/app/src/main/kotlin/com/eatvioapp/app/MainActivity.kt b/android/app/src/main/kotlin/com/eatvioapp/app/MainActivity.kt
index 9b626e4c..97e76a0e 100644
--- a/android/app/src/main/kotlin/com/eatvioapp/app/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/eatvioapp/app/MainActivity.kt
@@ -1,8 +1,8 @@
package com.eatvioapp.app
+import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
@@ -14,12 +14,13 @@ import kotlinx.coroutines.launch
class MainActivity: FlutterActivity() {
private val channelName = "com.eatvioapp/hms_push"
+ private var methodChannel: MethodChannel? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
- MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
- .setMethodCallHandler { call, result ->
+ methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
+ methodChannel?.setMethodCallHandler { call, result ->
when (call.method) {
"getHuaweiToken" -> {
CoroutineScope(Dispatchers.IO).launch {
@@ -71,5 +72,33 @@ class MainActivity: FlutterActivity() {
else -> result.notImplemented()
}
}
+
+ // Обрабатываем возможный action_url, с которым была открыта Activity (например, из HMS уведомления).
+ handleIntentForActionUrl(intent)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ // Обработка нового интента, когда Activity уже запущена (тап по уведомлению).
+ handleIntentForActionUrl(intent)
+ }
+
+ private fun handleIntentForActionUrl(intent: Intent?) {
+ if (intent == null) return
+
+ // Вариант 1: action_url лежит в extras.
+ val fromExtras = intent.extras?.getString("action_url")
+
+ // Вариант 2: action_url передан как data Uri.
+ val dataUri = intent.data
+ val fromData = dataUri?.toString()
+
+ val actionUrl = fromExtras ?: fromData
+ if (actionUrl.isNullOrBlank()) return
+
+ android.util.Log.i("HMS", "MainActivity.handleIntentForActionUrl: $actionUrl")
+
+ // Проксируем URL во Flutter, где уже есть общая логика навигации по диплинкам.
+ methodChannel?.invokeMethod("openActionUrl", actionUrl)
}
}
diff --git a/assets/icons/crossed_eye.svg b/assets/icons/crossed_eye.svg
new file mode 100644
index 00000000..f00fc63a
--- /dev/null
+++ b/assets/icons/crossed_eye.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart
index 776aea35..c98f7af2 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.dart
@@ -14,8 +14,8 @@ import 'package:go_router/go_router.dart';
class AppRouter {
static GlobalKey navigatorKey = GlobalKey();
- // Домены для deep links
- static const List _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru'];
+ // Домены для deep links (в т.ч. ссылки из пушей dev2.eatvio.com)
+ static const List _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru', 'dev2.eatvio.com', 'www.dev2.eatvio.com'];
static GoRouter appRouter = GoRouter(
initialLocation: RouteConstants.updateScreen,
@@ -30,64 +30,59 @@ class AppRouter {
],
);
- /// Обработка редиректов для deep links
- /// Возвращает null для обычной навигации, чтобы не мешать внутренним переходам
- static String? _handleRedirect(BuildContext context, GoRouterState state) {
- final uri = state.uri;
+ static String? _resolveDeepLinkUri(Uri uri) {
final host = uri.host.toLowerCase();
final segments = uri.pathSegments;
- // Проверяем, является ли это deep link по домену
final isDeepLink = _deepLinkHosts.contains(host);
-
- // Если это не deep link, не мешаем обычной навигации
+
if (!isDeepLink) {
return null;
}
- // Логирование для отладки
- developer.log(
- 'Deep link detected: host=$host, path=${uri.path}, segments=$segments',
- name: 'AppRouter',
- );
+ developer.log('Deep link detected: host=$host, path=${uri.path}, segments=$segments', name: 'AppRouter');
- // Проверка на пустой путь или некорректный формат
if (segments.isEmpty) {
developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen;
}
- // Проверка языка (первый сегмент должен быть ru или en)
+ if (segments.length >= 4 &&
+ segments[0] == 'api' &&
+ segments[1] == 'v2' &&
+ segments[2] == 'chat' &&
+ segments[3] == 'show') {
+ developer.log('Deep link: api/v2/chat/show → chat-select-screen', name: 'AppRouter');
+ FlutterNativeSplash.remove();
+ return RouteConstants.chatSelectScreen;
+ }
+
if (segments[0] != 'ru' && segments[0] != 'en') {
- developer.log(
- 'Invalid language segment: ${segments[0]}, redirecting to feed',
- name: 'AppRouter',
- );
+ developer.log('Invalid language segment: ${segments[0]}, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen;
}
- // Обработка deep links с учетом языка
- // Формат: /{lang}/{type}/{identifier?}
if (segments.length == 4) {
- // Формат: /{lang}/diary/{...}
final type = segments[1];
-
+
if (type == 'diary') {
developer.log('Deep link: diary, redirecting to diary-screen', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
}
-
- developer.log(
- 'Unknown deep link type for 4 segments: $type, redirecting to feed',
- name: 'AppRouter',
- );
+ if (type == 'consultations' && segments[2] == 'status') {
+ final consultationId = segments[3];
+ developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter');
+ FlutterNativeSplash.remove();
+ return '${RouteConstants.consultationsScreen}?id=$consultationId';
+ }
+
+ developer.log('Unknown deep link type for 4 segments: $type, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen;
} else if (segments.length == 3) {
- // Формат: /{lang}/{type}/{identifier}
final type = segments[1];
final contentIdentifier = segments[2];
@@ -104,55 +99,111 @@ class AppRouter {
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return '/about-plan-screen?slug=$contentIdentifier';
- case 'diet':
- developer.log('Deep link: diet/$contentIdentifier', name: 'AppRouter');
+ case 'diet': {
+ final dietId = int.tryParse(contentIdentifier);
+ if (dietId != null) {
+ developer.log('Deep link: diet/id=$dietId', name: 'AppRouter');
+ FlutterNativeSplash.remove();
+ return '/about-diet-screen?id=$dietId';
+ }
+ developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return '/about-diet-screen?slug=$contentIdentifier';
+ }
case 'chat':
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.chatSelectScreen;
case 'profile':
+ // В диплинке передан slug — открываем профиль пользователя по slug.
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return '/profile-screen?slug=$contentIdentifier';
+ case 'badge':
+ // В badge нет slug, открываем вкладку «Мой профиль».
+ developer.log('Deep link: badge → my-profile tab', name: 'AppRouter');
+ FlutterNativeSplash.remove();
+ return RouteConstants.myProfileScreen;
case 'diary':
developer.log('Deep link: diary/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
+ case 'consultant_diary_comment':
+ developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter');
+ FlutterNativeSplash.remove();
+ return RouteConstants.diaryScreen;
default:
- developer.log(
- 'Unknown deep link type: $type, redirecting to feed',
- name: 'AppRouter',
- );
+ developer.log('Unknown deep link type: $type, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen;
}
} else if (segments.length == 2) {
- // Формат: /{lang}/{type} - только для chat
final type = segments[1];
-
+
if (type == 'chat') {
developer.log('Deep link: chat', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.chatSelectScreen;
- } else {
- developer.log(
- 'Unknown deep link type for 2 segments: $type, redirecting to feed',
- name: 'AppRouter',
- );
- FlutterNativeSplash.remove();
- return RouteConstants.feedScreen;
}
- } else {
- // Неизвестный формат deep link
- developer.log(
- 'Unknown deep link format: ${segments.length} segments, redirecting to feed',
- name: 'AppRouter',
- );
+ if (type == 'diary') {
+ developer.log('Deep link: diary', name: 'AppRouter');
+ FlutterNativeSplash.remove();
+ return RouteConstants.diaryScreen;
+ }
+
+ developer.log('Unknown deep link type for 2 segments: $type, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen;
}
+
+ developer.log('Unknown deep link format: ${segments.length} segments, redirecting to feed', name: 'AppRouter');
+ FlutterNativeSplash.remove();
+ return RouteConstants.feedScreen;
+ }
+
+ static String? _handleRedirect(BuildContext context, GoRouterState state) {
+ return _resolveDeepLinkUri(state.uri);
+ }
+
+ static void navigateByDeepLinkUrl(String url) {
+ if (url.isEmpty) {
+ developer.log('navigateByDeepLinkUrl: empty url, redirecting to feed', name: 'AppRouter');
+ appRouter.go(RouteConstants.feedScreen);
+ return;
+ }
+
+ Uri? uri;
+ try {
+ uri = Uri.tryParse(url);
+ } catch (e) {
+ developer.log('navigateByDeepLinkUrl: failed to parse url=$url, error=$e, redirecting to feed', name: 'AppRouter');
+ appRouter.go(RouteConstants.feedScreen);
+ return;
+ }
+
+ if (uri == null) {
+ developer.log('navigateByDeepLinkUrl: uri is null for url=$url, redirecting to feed', name: 'AppRouter');
+ appRouter.go(RouteConstants.feedScreen);
+ return;
+ }
+
+ if (uri.host.isEmpty && uri.pathSegments.isNotEmpty) {
+ uri = Uri(
+ scheme: 'https',
+ host: _deepLinkHosts.first,
+ pathSegments: uri.pathSegments,
+ queryParameters: uri.queryParameters.isEmpty ? null : uri.queryParameters,
+ );
+ }
+
+ final target = _resolveDeepLinkUri(uri);
+
+ if (target != null) {
+ appRouter.go(target);
+ } else {
+ developer.log('navigateByDeepLinkUrl: unresolved url=$url (normalized=${uri.toString()}), redirecting to feed', name: 'AppRouter');
+ appRouter.go(RouteConstants.feedScreen);
+ }
}
static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) {
diff --git a/lib/core/firebase_api.dart b/lib/core/firebase_api.dart
index e85807ee..1d09d7a3 100644
--- a/lib/core/firebase_api.dart
+++ b/lib/core/firebase_api.dart
@@ -5,7 +5,6 @@ import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:flutter/services.dart';
-
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/locator_service.dart';
diff --git a/lib/core/notifications/push_navigation_handler.dart b/lib/core/notifications/push_navigation_handler.dart
new file mode 100644
index 00000000..b26db5e3
--- /dev/null
+++ b/lib/core/notifications/push_navigation_handler.dart
@@ -0,0 +1,91 @@
+import 'dart:developer' as developer;
+
+import 'package:eatvio_app/core/app_router.dart';
+import 'package:firebase_messaging/firebase_messaging.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+
+/// Отвечает за навигацию по диплинкам, пришедшим в пуш-уведомлениях.
+class PushNavigationHandler {
+ static bool _initialized = false;
+ static const MethodChannel _hmsChannel = MethodChannel('com.eatvioapp/hms_push');
+
+ /// Инициализация обработчиков Firebase Messaging.
+ /// Должна вызываться один раз после старта приложения.
+ static Future initialize() async {
+ if (_initialized) return;
+ _initialized = true;
+
+ // Сообщение, по которому запустили приложение из "убитого" состояния.
+ try {
+ final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
+ if (initialMessage != null) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _handleMessageInternal(initialMessage, source: 'initialMessage');
+ });
+ }
+ } catch (e) {
+ developer.log(
+ 'PushNavigationHandler: error while getting initialMessage: $e',
+ name: 'PushNavigationHandler',
+ );
+ }
+
+ // Тап по уведомлению, когда приложение было в фоне.
+ FirebaseMessaging.onMessageOpenedApp.listen(
+ (message) => _handleMessageInternal(message, source: 'onMessageOpenedApp'),
+ );
+
+ // Подписка на события от native-кода (HMS через MethodChannel).
+ _hmsChannel.setMethodCallHandler((call) async {
+ if (call.method == 'openActionUrl') {
+ final url = call.arguments as String? ?? '';
+ handleActionUrlFromNative(url);
+ }
+ });
+ }
+
+ /// Входная точка для навигации из native-кода (например, HMS через MethodChannel).
+ static void handleActionUrlFromNative(String url) {
+ if (url.isEmpty) {
+ developer.log(
+ 'PushNavigationHandler.handleActionUrlFromNative: empty url',
+ name: 'PushNavigationHandler',
+ );
+ return;
+ }
+
+ developer.log(
+ 'PushNavigationHandler.handleActionUrlFromNative: navigating by url=$url',
+ name: 'PushNavigationHandler',
+ );
+
+ AppRouter.navigateByDeepLinkUrl(url);
+ }
+
+ /// Публичный метод для единичной обработки сообщения (если понадобится где-то ещё).
+ static void handleMessage(RemoteMessage message) {
+ _handleMessageInternal(message, source: 'manualHandle');
+ }
+
+ static void _handleMessageInternal(RemoteMessage message, {required String source}) {
+ final data = message.data;
+ final actionUrl = data['action_url'] as String?;
+
+ if (actionUrl == null || actionUrl.isEmpty) {
+ developer.log(
+ 'PushNavigationHandler: no action_url in message data, source=$source, data=$data',
+ name: 'PushNavigationHandler',
+ );
+ return;
+ }
+
+ developer.log(
+ 'PushNavigationHandler: navigating by action_url=$actionUrl, source=$source',
+ name: 'PushNavigationHandler',
+ );
+
+ AppRouter.navigateByDeepLinkUrl(actionUrl);
+ }
+}
+
diff --git a/lib/core/routing/content_routes.dart b/lib/core/routing/content_routes.dart
index 0556fab6..dcb3d182 100644
--- a/lib/core/routing/content_routes.dart
+++ b/lib/core/routing/content_routes.dart
@@ -17,12 +17,20 @@ class ContentRoutes {
name: 'aboutPlanScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
+ final commentIdStr = state.uri.queryParameters['commentId'];
+ final commentId = int.tryParse(commentIdStr ?? '');
+
if (slug != null) {
- return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
+ return AboutPlanScreen(slug: slug, navigateToComments: commentId != null, index: null, highlightedCommentId: commentId);
}
final data = state.extra as Map;
- return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
+ return AboutPlanScreen(
+ slug: data['slug'] as String,
+ index: data['index'] as int?,
+ navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null),
+ highlightedCommentId: data['commentId'] as int?,
+ );
},
),
GoRoute(
@@ -30,12 +38,20 @@ class ContentRoutes {
name: 'aboutRecipesScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
+ final commentIdStr = state.uri.queryParameters['commentId'];
+ final commentId = int.tryParse(commentIdStr ?? '');
+
if (slug != null) {
- return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
+ return AboutRecipeScreen(recipeSlug: slug, navigateToComments: commentId != null, index: null, highlightedCommentId: commentId);
}
final data = state.extra as Map;
- return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
+ return AboutRecipeScreen(
+ recipeSlug: data['recipe'] as String,
+ index: data['index'] as int?,
+ navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null),
+ highlightedCommentId: data['commentId'] as int?,
+ );
},
),
GoRoute(
@@ -43,16 +59,28 @@ class ContentRoutes {
name: 'aboutDietScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
+ final idStr = state.uri.queryParameters['id'];
+ final id = int.tryParse(idStr ?? '');
+ final commentIdStr = state.uri.queryParameters['commentId'];
+ final commentId = int.tryParse(commentIdStr ?? '');
+
if (slug != null) {
- return AboutDietScreen(id: null, slug: slug, index: null, navigateToComments: false);
+ return AboutDietScreen(id: null, slug: slug, index: null, navigateToComments: commentId != null, highlightedCommentId: commentId);
}
- final data = state.extra as Map;
- return AboutDietScreen(
- id: data['id'] as int?,
- slug: data['slug'] as String?,
- index: data['index'] as int?,
- navigateToComments: data['navigateToComments'] as bool,
- );
+ if (id != null && id > 0) {
+ return AboutDietScreen(id: id, slug: null, index: null, navigateToComments: commentId != null, highlightedCommentId: commentId);
+ }
+ final data = state.extra as Map?;
+ if (data != null) {
+ return AboutDietScreen(
+ id: data['id'] as int?,
+ slug: data['slug'] as String?,
+ index: data['index'] as int?,
+ navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null),
+ highlightedCommentId: data['commentId'] as int?,
+ );
+ }
+ return const AboutDietScreen(id: null, slug: null, index: null, navigateToComments: false, highlightedCommentId: null);
},
),
GoRoute(
@@ -65,12 +93,20 @@ class ContentRoutes {
name: 'blogPostScreen',
builder: (context, state) {
final id = state.uri.queryParameters['id'];
+ final commentIdStr = state.uri.queryParameters['commentId'];
+ final commentId = int.tryParse(commentIdStr ?? '');
final parsedId = int.tryParse(id ?? '');
+
if (parsedId != null) {
- return BlogPostScreen(id: parsedId);
+ return BlogPostScreen(id: parsedId, highlightedCommentId: commentId);
}
- return BlogPostScreen(id: state.extra as int);
+ if (state.extra is int) {
+ return BlogPostScreen(id: state.extra as int);
+ }
+
+ final data = state.extra as Map;
+ return BlogPostScreen(id: data['id'] as int, highlightedCommentId: data['commentId'] as int?);
},
),
];
diff --git a/lib/core/routing/main_navigation_routes.dart b/lib/core/routing/main_navigation_routes.dart
index 88a8db04..2557ddcf 100644
--- a/lib/core/routing/main_navigation_routes.dart
+++ b/lib/core/routing/main_navigation_routes.dart
@@ -1,6 +1,7 @@
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
+import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
@@ -31,8 +32,11 @@ class MainNavigationRoutes {
profileAvatarCubit.getMyProfile();
});
}
-
- return BottomNavigationBarWidget(child: child);
+
+ return _MainShellWithNavScope(
+ currentLocation: state.matchedLocation,
+ child: child,
+ );
},
routes: [
GoRoute(
@@ -58,7 +62,7 @@ class MainNavigationRoutes {
GoRoute(
path: RouteConstants.diaryScreen,
name: 'diaryScreen',
- pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
+ pageBuilder: (context, state) => NoTransitionPage(child: DiaryScreen(date: state.extra as DateTime?)),
),
GoRoute(
path: ConsultationsScreen.path,
@@ -68,7 +72,10 @@ class MainNavigationRoutes {
GoRoute(
path: RouteConstants.chatSelectScreen,
name: 'chatSelectScreen',
- pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
+ pageBuilder: (context, state) {
+ final initialSearchQuery = state.extra is String ? state.extra as String : null;
+ return NoTransitionPage(child: ChatsScreen(initialSearchQuery: initialSearchQuery));
+ },
),
GoRoute(
path: RouteConstants.notificationsScreen,
@@ -94,3 +101,33 @@ class MainNavigationRoutes {
];
}
}
+
+/// Wraps the shell with [BottomNavScope] so descendants (e.g. notifications)
+/// can switch tabs via [BottomNavController.switchToRoute] instead of pushing.
+class _MainShellWithNavScope extends StatefulWidget {
+ const _MainShellWithNavScope({
+ required this.child,
+ required this.currentLocation,
+ });
+
+ final Widget child;
+ final String currentLocation;
+
+ @override
+ State<_MainShellWithNavScope> createState() => _MainShellWithNavScopeState();
+}
+
+class _MainShellWithNavScopeState extends State<_MainShellWithNavScope> {
+ final BottomNavController _navController = BottomNavController();
+
+ @override
+ Widget build(BuildContext context) {
+ return BottomNavScope(
+ controller: _navController,
+ child: BottomNavigationBarWidget(
+ currentLocation: widget.currentLocation,
+ child: widget.child,
+ ),
+ );
+ }
+}
diff --git a/lib/core/routing/other_routes.dart b/lib/core/routing/other_routes.dart
index 98e767da..1b03ce4c 100644
--- a/lib/core/routing/other_routes.dart
+++ b/lib/core/routing/other_routes.dart
@@ -204,7 +204,7 @@ class OtherRoutes {
GoRoute(
path: '${RouteConstants.subsScreen}/:slug',
name: 'subsScreen',
- builder: (context, state) => SubsScreen(slug: state.pathParameters['slug']!, subType: state.extra as SubType),
+ builder: (context, state) => SubsScreen(slug: state.pathParameters['slug'], subType: state.extra as SubType),
),
// Chat routes
@@ -229,7 +229,12 @@ class OtherRoutes {
GoRoute(
path: RouteConstants.stepperBecomeConsultantScreen,
name: 'stepperBecomeConsultantScreen',
- builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
+ builder: (context, state) {
+ final data = state.extra as Map;
+ final slug = data['slug'] as String?;
+ final isCanceled = data['isCanceled'] as bool?;
+ return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled,);
+ },
),
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()),
@@ -285,8 +290,7 @@ class OtherRoutes {
builder: (context, state) {
final data = state.extra as Map;
final diaryId = data['diaryId'] as int;
- final currentPlanDay = data['currentDiaryDay'] as int;
- return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
+ return UserDiaryScreen(diaryId: diaryId);
},
),
GoRoute(
diff --git a/lib/core/websockets/ws_manager.dart b/lib/core/websockets/ws_manager.dart
index 3a711177..cf84354c 100644
--- a/lib/core/websockets/ws_manager.dart
+++ b/lib/core/websockets/ws_manager.dart
@@ -11,6 +11,7 @@ import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:sentry_flutter/sentry_flutter.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';
class WsManager {
final WsService wsService;
@@ -111,6 +112,7 @@ class WsManager {
}
});
_subscribe();
+ await _connectCubitsToStreams();
} catch (e, st) {
await Sentry.captureException(e, stackTrace: st);
log(e.toString(), stackTrace: st);
@@ -118,6 +120,20 @@ class WsManager {
}
}
+ /// Подключает ChatsCubit и NotificationsCubit к стримам сокета.
+ /// Вызывается после успешного initialize(), чтобы уведомления и чаты работали
+ /// и при входе уже залогиненного пользователя (редирект на feed), не только после экрана логина.
+ Future _connectCubitsToStreams() async {
+ try {
+ await sl().connectToWebSocket();
+ await sl().getNotifications();
+ await sl().connectToWebSocket();
+ } catch (e, st) {
+ log('WsManager._connectCubitsToStreams: $e', stackTrace: st);
+ await Sentry.captureException(e, stackTrace: st);
+ }
+ }
+
Future _initPrivateNotificationsChannel() async {
final token = await TokenStorage.getTokenFromStorage();
_privateNotificationChannel = client?.privateChannel(
diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart
index bf9074d9..d1c1ccfd 100644
--- a/lib/features/auth/presentation/pages/auth_screen.dart
+++ b/lib/features/auth/presentation/pages/auth_screen.dart
@@ -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 {
try {
await sl().getMyProfile();
await sl().getChatRoomsWithLoading();
- await sl().connectToWebSocket();
- await sl().getNotifications();
- await sl().connectToWebSocket();
await sl().registerPushToken();
showSuccessToast('Успешный вход');
} catch (e) {
@@ -213,9 +209,6 @@ class _AuthScreenState extends State {
await sl().initialize();
await sl().getMyProfile();
await sl().getChatRoomsWithLoading();
- await sl().connectToWebSocket();
- await sl().getNotifications();
- await sl().connectToWebSocket();
await sl().registerPushToken();
showSuccessToast('Успешный вход');
} catch (e) {
diff --git a/lib/features/auth/presentation/pages/register_via_social_screen.dart b/lib/features/auth/presentation/pages/register_via_social_screen.dart
index 1b27237f..1ea307f0 100644
--- a/lib/features/auth/presentation/pages/register_via_social_screen.dart
+++ b/lib/features/auth/presentation/pages/register_via_social_screen.dart
@@ -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 {
try {
await sl().getMyProfile();
await sl().getChatRoomsWithLoading();
- await sl().connectToWebSocket();
- await sl().getNotifications();
- await sl().connectToWebSocket();
await sl().registerPushToken();
showSuccessToast('Успешная регистрация');
} catch (e) {
diff --git a/lib/features/auth/presentation/pages/scan_qr_code_screen.dart b/lib/features/auth/presentation/pages/scan_qr_code_screen.dart
index 723f95e9..4b90fe70 100644
--- a/lib/features/auth/presentation/pages/scan_qr_code_screen.dart
+++ b/lib/features/auth/presentation/pages/scan_qr_code_screen.dart
@@ -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 {
try {
await sl().getMyProfile();
await sl().getChatRoomsWithLoading();
- await sl().connectToWebSocket();
- await sl().getNotifications();
- await sl().connectToWebSocket();
await sl().registerPushToken();
} catch (e) {
rethrow;
diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart
index 70593f8c..bf50518a 100644
--- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart
+++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart
@@ -21,8 +21,9 @@ import 'package:share_plus/share_plus.dart';
class BlogPostScreen extends StatefulWidget {
final int id;
+ final int? highlightedCommentId;
- const BlogPostScreen({super.key, required this.id});
+ const BlogPostScreen({super.key, required this.id, this.highlightedCommentId});
@override
State createState() => _BlogPostScreenState();
@@ -303,7 +304,13 @@ class _BlogPostScreenState extends State {
const SizedBox(height: 16),
if (state is CommentsStateLoading) const Loader(),
if (state is CommentsStateLoaded)
- CommentsWidget(id: widget.id.toString(), commentSourceType: CommentSourceType.blogPost, comment: state.comment, replyId: replyId),
+ CommentsWidget(
+ id: widget.id.toString(),
+ commentSourceType: CommentSourceType.blogPost,
+ comment: state.comment,
+ replyId: replyId,
+ highlightedCommentId: widget.highlightedCommentId,
+ ),
],
),
),
diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart
new file mode 100644
index 00000000..53ea8741
--- /dev/null
+++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart
@@ -0,0 +1,38 @@
+import 'package:flutter/material.dart';
+
+class BottomNavController {
+ BottomNavController();
+
+ void Function(String path, Object? extra)? _onSwitchToRoute;
+
+ void register(void Function(String path, Object? extra) callback) {
+ _onSwitchToRoute = callback;
+ }
+
+ void switchToRoute(String path, [Object? extra]) {
+ _onSwitchToRoute?.call(path, extra);
+ }
+}
+
+class BottomNavScope extends InheritedWidget {
+ const BottomNavScope({
+ required this.controller,
+ required super.child,
+ super.key,
+ });
+
+ final BottomNavController controller;
+
+ static BottomNavController? maybeOf(BuildContext context) {
+ return context.dependOnInheritedWidgetOfExactType()?.controller;
+ }
+
+ static BottomNavController of(BuildContext context) {
+ final controller = maybeOf(context);
+ assert(controller != null, 'No BottomNavScope found in context');
+ return controller!;
+ }
+
+ @override
+ bool updateShouldNotify(BottomNavScope oldWidget) => controller != oldWidget.controller;
+}
diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
index efac96bf..207b348a 100644
--- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
+++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
@@ -8,6 +8,7 @@ import 'package:eatvio_app/features/notifications/presentation/notifications_cub
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
+import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
@@ -18,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 createState() => _BottomNavigationBarWidgetState();
@@ -66,6 +73,24 @@ class _BottomNavigationBarWidgetState extends State {
}
}
+ /// Switch to the tab that corresponds to [path], optionally with [extra]
+ /// (e.g. date for diary). If [path] is not in the current tab list, falls back to push.
+ void _switchToRoute(String path, [Object? extra]) {
+ if (pages == null) return;
+ final index = pages!.indexOf(path);
+ if (index >= 0 && index != currentBottomNavBarIndex) {
+ setState(() {
+ currentBottomNavBarIndex = index;
+ });
+ GoRouter.of(context).replace(path, extra: extra);
+ } else if (index >= 0) {
+ // Same tab but with new extra (e.g. diary with date)
+ GoRouter.of(context).replace(path, extra: extra);
+ } else {
+ GoRouter.of(context).push(path, extra: extra);
+ }
+ }
+
static const Color _devAccentColor = Color(0xFFF59E0B); // amber-500
Widget _buildLogoIcon() {
@@ -121,6 +146,22 @@ class _BottomNavigationBarWidgetState extends State {
// Инициализируем pages на основе profileModel
pages ??= _getPages(isConsultant);
+ // Expose tab switching to descendants (e.g. notification widget)
+ final controller = BottomNavScope.maybeOf(context);
+ if (controller != null) {
+ 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(
@@ -129,7 +170,7 @@ class _BottomNavigationBarWidgetState extends State {
selectedFontSize: 10,
unselectedFontSize: 10,
selectedItemColor: appTheme.blueA400,
- currentIndex: currentBottomNavBarIndex,
+ currentIndex: effectiveIndex,
items: [
BottomNavigationBarItem(
label: '',
@@ -204,7 +245,7 @@ class _BottomNavigationBarWidgetState extends State {
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),
),
@@ -226,7 +267,7 @@ class _BottomNavigationBarWidgetState extends State {
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,
diff --git a/lib/features/chat/presentation/pages/chats_screen.dart b/lib/features/chat/presentation/pages/chats_screen.dart
index 539e971c..993bcfd3 100644
--- a/lib/features/chat/presentation/pages/chats_screen.dart
+++ b/lib/features/chat/presentation/pages/chats_screen.dart
@@ -11,7 +11,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ChatsScreen extends StatefulWidget {
- const ChatsScreen({super.key});
+ const ChatsScreen({super.key, this.initialSearchQuery});
+
+ /// When set (e.g. from notification tap), the search field is pre-filled
+ /// and chat list is filtered by this query.
+ final String? initialSearchQuery;
@override
State createState() => _ChatsScreenState();
@@ -23,12 +27,17 @@ class _ChatsScreenState extends State {
@override
void initState() {
super.initState();
- // Инициализация ChatsCubit при первом открытии экрана
+ final query = widget.initialSearchQuery?.trim();
+ if (query != null && query.isNotEmpty) {
+ searchController.text = query;
+ }
WidgetsBinding.instance.addPostFrameCallback((_) {
final cubit = context.read();
final state = cubit.state;
if (state.state == BaseState.initial) {
- cubit.getChatRoomsWithLoading();
+ cubit.getChatRoomsWithLoading(query: query?.isNotEmpty == true ? query : null);
+ } else if (query != null && query.isNotEmpty) {
+ cubit.getChatRooms(query: query);
}
});
}
diff --git a/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart b/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart
index 6fdcc1e8..aaec9b68 100644
--- a/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart
+++ b/lib/features/consultant/become_consultant/models/consultant_cert_photo_model.dart
@@ -22,6 +22,9 @@ abstract class ConsultantCertPhotoModel with _$ConsultantCertPhotoModel {
static const String visibleGroup = 'user_certs';
static const String hiddenGroup = 'hidden_user_certs';
+ static const String pendingGroup = 'pending_user_certs';
bool get isHidden => group == hiddenGroup;
+
+ bool get isActive => group == visibleGroup || group == hiddenGroup;
}
diff --git a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart
index 5d69fb96..6cc5e417 100644
--- a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart
+++ b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart
@@ -1,6 +1,5 @@
import 'dart:io';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
-import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
@@ -16,13 +15,14 @@ class RegisterConsultantCubit extends Cubit {
consultant: ConsultantProfileModel(socialLinks: SocialLinksModel(), hasMedicalEducation: true),
loading: true,
acceptOffer: false,
+ isUpdating: false,
),
);
Future getMyRequestStatus(String slug) async {
_update = true;
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
- final certPhotos = await _fetchVisibleCertPhotos();
+ final certPhotos = await consultantRepository.getConsultantCertPhotos();
List? certificates = myRequestStatus.educationModel.completedCourses?.map((courseMap) {
final course = courseMap.values.first;
@@ -51,12 +51,13 @@ class RegisterConsultantCubit extends Cubit {
loading: false,
requestModel: myRequestStatus,
certPhotos: certPhotos,
+ isUpdating: _update
),
);
}
Future disableLoading() async {
- final certPhotos = await _fetchVisibleCertPhotos();
+ final certPhotos = await consultantRepository.getConsultantCertPhotos();
emit(state.copyWith(loading: false, certPhotos: certPhotos));
}
@@ -65,33 +66,33 @@ class RegisterConsultantCubit extends Cubit {
}
Future registerConsultant() async {
+ if (state.newAddedPhotos.isNotEmpty) {
+ await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos);
+ }
+ if (state.deletedPhotoIds.isNotEmpty) {
+ for (final int id in state.deletedPhotoIds) {
+ await consultantRepository.deletePhoto(id);
+ }
+ }
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update);
}
- Future uploadCertPhotos(List files) async {
+ Future addCertPhotos(List files) async {
if (files.isEmpty) {
return;
}
final compressedFiles = await ImageCompressUtility.compressFilesList(files);
- final uploaded = await consultantRepository.storeConsultantCertPhotos(files: compressedFiles);
- emit(state.copyWith(certPhotos: _onlyVisible(uploaded)));
+
+ emit(state.copyWith(newAddedPhotos: [...state.newAddedPhotos, ...compressedFiles]));
}
- Future removeCertPhoto(int id) async {
- await consultantRepository.hideConsultantCertPhoto(id);
- emit(state.copyWith(certPhotos: state.certPhotos.where((photo) => photo.id != id).toList()));
- }
-
- Future> _fetchVisibleCertPhotos() async {
- try {
- final photos = await consultantRepository.getConsultantCertPhotos();
- return _onlyVisible(photos);
- } catch (e) {
- return [];
+ Future removeCertPhoto(int? id, String? path) async {
+ if (id != null) {
+ emit(state.copyWith(deletedPhotoIds: [...state.deletedPhotoIds, id]));
+ return;
+ }
+ if (path != null) {
+ emit(state.copyWith(newAddedPhotos: state.newAddedPhotos.where((file) => file.uri.path != path).toList()));
}
}
-
- List _onlyVisible(List photos) {
- return photos.where((photo) => !photo.isHidden).toList();
- }
}
diff --git a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart
index ee6644a9..45baa2c6 100644
--- a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart
+++ b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
@@ -11,7 +13,10 @@ abstract class RegisterConsultantState with _$RegisterConsultantState {
required ConsultantProfileModel consultant,
required bool acceptOffer,
required bool loading,
+ required bool isUpdating,
ConsultationRequestModel? requestModel,
@Default([]) List certPhotos,
+ @Default([]) List newAddedPhotos,
+ @Default([]) List deletedPhotoIds,
}) = _RegisterConsultantState;
}
diff --git a/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart
index 4ad6db76..cf21b646 100644
--- a/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart
+++ b/lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart
@@ -33,7 +33,9 @@ class _PhotoOrScansScreenState extends State {
const SizedBox(height: 16),
BlocBuilder(
builder: (context, state) {
- final photos = state.certPhotos;
+ final photos = state.certPhotos.where((photo) => !state.deletedPhotoIds.contains(photo.id)).toList();
+ final addedPhotos = state.newAddedPhotos;
+ final totalPhotosCount = photos.length + addedPhotos.length;
return Column(
children: [
if (isUploading) ...[const LinearProgressIndicator(), const SizedBox(height: 16)],
@@ -47,7 +49,7 @@ class _PhotoOrScansScreenState extends State {
mainAxisSpacing: 8,
),
itemBuilder: (BuildContext context, int index) {
- if (index == photos.length) {
+ if (index == totalPhotosCount) {
return InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => _pickAndUploadPhotos(),
@@ -57,10 +59,25 @@ class _PhotoOrScansScreenState extends State {
),
);
} else {
- return _CertPhotoCard(photo: photos[index], allPhotos: photos, onRemove: () => _removePhoto(photos[index].id));
+ if (index < photos.length) {
+ final photo = photos[index];
+ return _CertPhotoCard(
+ photo: photo,
+ allPhotos: photos,
+ onRemove: () => _removePhoto(id: photo.id),
+ );
+ } else {
+ final localIndex = index - photos.length;
+ final localPhoto = addedPhotos[localIndex];
+ return _LocalCertPhotoCard(
+ photo: localPhoto,
+ allPhotos: addedPhotos,
+ onRemove: () => _removePhoto(path: localPhoto.uri.path),
+ );
+ }
}
},
- itemCount: photos.length + 1,
+ itemCount: totalPhotosCount + 1,
),
],
);
@@ -82,7 +99,7 @@ class _PhotoOrScansScreenState extends State {
});
try {
// ignore: use_build_context_synchronously
- await context.read().uploadCertPhotos(newImages.map((file) => File(file.path)).toList());
+ await context.read().addCertPhotos(newImages.map((file) => File(file.path)).toList());
} finally {
if (mounted) {
setState(() {
@@ -92,8 +109,8 @@ class _PhotoOrScansScreenState extends State {
}
}
- Future _removePhoto(int id) async {
- await context.read().removeCertPhoto(id);
+ Future _removePhoto({int? id, String? path}) async {
+ await context.read().removeCertPhoto(id, path);
}
}
@@ -147,3 +164,61 @@ class _CertPhotoCard extends StatelessWidget {
);
}
}
+
+class _LocalCertPhotoCard extends StatelessWidget {
+ final File photo;
+ final List allPhotos;
+ final VoidCallback onRemove;
+
+ const _LocalCertPhotoCard({required this.photo, required this.allPhotos, required this.onRemove});
+
+ @override
+ Widget build(BuildContext context) {
+ return Hero(
+ tag: photo.path,
+ child: Material(
+ borderRadius: BorderRadius.circular(20),
+ child: InkWell(
+ onTap: () {
+ context.pushTransparentRoute(
+ rootNavigator: true,
+ PhotoViewScreen(
+ imageUrls: allPhotos,
+ isLocal: true,
+ index: allPhotos.indexOf(photo),
+ ),
+ );
+ },
+ borderRadius: BorderRadius.circular(20),
+ child: Stack(
+ clipBehavior: Clip.none,
+ children: [
+ Ink(
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ image: DecorationImage(
+ fit: BoxFit.cover,
+ image: FileImage(photo),
+ ),
+ ),
+ ),
+ Positioned(
+ right: -12,
+ top: -12,
+ child: InkWell(
+ onTap: onRemove,
+ customBorder: const CircleBorder(),
+ child: Ink(
+ padding: const EdgeInsets.all(4),
+ decoration: const BoxDecoration(color: Color(0xFFF1F2F6), shape: BoxShape.circle),
+ child: const Icon(size: 20, Icons.close, color: Colors.black),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart
index 494ffc36..515728c1 100644
--- a/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart
+++ b/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart
@@ -1,6 +1,5 @@
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
-import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart';
@@ -19,8 +18,8 @@ import 'package:lottie/lottie.dart';
class StepperBecomeConsultantScreen extends StatefulWidget {
final String? slug;
-
- const StepperBecomeConsultantScreen({super.key, this.slug});
+ final bool? isCanceled;
+ const StepperBecomeConsultantScreen({super.key, this.slug, this.isCanceled});
@override
State createState() => _StepperBecomeConsultantScreenState();
@@ -63,25 +62,17 @@ class _StepperBecomeConsultantScreenState extends State Navigator.pop(context),
- child: const Text('OK'),
- ),
- ],
+ actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
},
@@ -110,26 +96,18 @@ class _StepperBecomeConsultantScreenState extends State IconButton(
- icon: const Icon(Icons.close_rounded),
- onPressed: () => GoRouter.of(context).pop(),
- ),
+ builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()),
),
centerTitle: true,
- title: SvgPicture.asset(
- ImageConstant.imgLogoEatvio,
- height: 48,
- fit: BoxFit.contain,
- ),
- actions: [
- Padding(
- padding: const EdgeInsets.only(right: 16),
- child: Text('шаг $step из 6'),
- ),
- ],
+ title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain),
+ actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 6'))],
bottom: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 32),
child: Padding(
@@ -312,10 +251,7 @@ class _StepperBecomeConsultantScreenState extends State(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
- tween: Tween(
- begin: 1 / 6,
- end: progressValue,
- ),
+ tween: Tween(begin: 1 / 6, end: progressValue),
builder: (context, value, _) => LinearProgressIndicator(
value: value,
borderRadius: BorderRadius.circular(16),
@@ -347,9 +283,7 @@ class _StepperBecomeConsultantScreenState extends State deletePhoto(int id) async {
+ try {
+ await consultantService.deletePhoto(id: id);
+ } catch (e, s) {
+ await Sentry.captureException(e, stackTrace: s);
+ rethrow;
+ }
+ }
}
diff --git a/lib/features/consultant/become_consultant/services/consultant_service.dart b/lib/features/consultant/become_consultant/services/consultant_service.dart
index d7171b8e..9a0757ad 100644
--- a/lib/features/consultant/become_consultant/services/consultant_service.dart
+++ b/lib/features/consultant/become_consultant/services/consultant_service.dart
@@ -40,4 +40,7 @@ abstract class ConsultantService {
@POST('/api/v2/consultant_request/cert_photos/un_hide')
Future unHideConsultantCertPhoto({@Body() required Map payload});
+
+ @POST('/api/v2/consultant_request/cert_photos/{id}/queue-for-deletion')
+ Future deletePhoto({@Path() required int id});
}
diff --git a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart
index b1529280..7efa25f7 100644
--- a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart
+++ b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart
@@ -172,8 +172,8 @@ class _ConsultationCardWidgetState extends State {
);
return;
}
- GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': day.$2.id, 'currentDiaryDay': index + 1});
- },
+ GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': day.$2.id,});
+ },
borderRadius: BorderRadius.circular(100),
child: Ink(
decoration: BoxDecoration(
@@ -255,7 +255,7 @@ class _ConsultationCardWidgetState extends State {
.where((e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished)
.lastOrNull;
if (lastActiveDay != null) {
- GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': lastActiveDay.$2.id, 'currentDiaryDay': lastActiveDay.$1});
+ GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': lastActiveDay.$2.id,});
return;
}
showErrorDialog(
diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart
index 0bbb4f82..93e24045 100644
--- a/lib/features/diary/presentation/bloc/diary_cubit.dart
+++ b/lib/features/diary/presentation/bloc/diary_cubit.dart
@@ -48,6 +48,11 @@ class DiaryCubit extends Cubit {
String comment = '';
+ void setInitialDate(DateTime? date) {
+ if (date == null) return;
+ selectedDay = date;
+ }
+
Future setSelectedDay(DateTime day) async {
final prevDay = selectedDay;
final prevMeal = selectedMeal;
@@ -63,7 +68,7 @@ class DiaryCubit extends Cubit {
return canSelect;
}
- Future fetchDiaryV2({bool needLoading = false, int? diaryId}) async {
+ Future fetchDiaryV2({bool needLoading = false, int? diaryId, DateTime? date}) async {
final currentState = state;
try {
if (needLoading) {
@@ -77,6 +82,7 @@ class DiaryCubit extends Cubit {
return true;
}
}
+
final diaryDay = await getDiary();
if (diaryDay == null) {
emit(const DiaryState.error(errorMessage: 'Diary not found'));
diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart
index c7f832d4..07042f16 100644
--- a/lib/features/diary/presentation/pages/diary_screen.dart
+++ b/lib/features/diary/presentation/pages/diary_screen.dart
@@ -24,31 +24,29 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DiaryScreen extends StatelessWidget {
- const DiaryScreen({super.key});
-
+ const DiaryScreen({super.key, required this.date});
+ final DateTime? date;
@override
Widget build(BuildContext context) {
- return const DiaryScreenContent();
+ return DiaryScreenContent(date: date);
}
}
class UserDiaryScreen extends StatelessWidget {
static const path = '/user-diary-screen';
static const name = 'userDiaryScreen';
- const UserDiaryScreen({super.key, required this.diaryId, required this.currentPlanDay});
+ const UserDiaryScreen({super.key, required this.diaryId});
final int diaryId;
- final int currentPlanDay;
@override
Widget build(BuildContext context) {
- return DiaryScreenContent(diaryId: diaryId, currentPlanDay: currentPlanDay);
+ return DiaryScreenContent(diaryId: diaryId,);
}
}
class DiaryScreenContent extends StatefulWidget {
- const DiaryScreenContent({super.key, this.diaryId, this.currentPlanDay});
+ const DiaryScreenContent({super.key, this.diaryId,this.date});
final int? diaryId;
- final int? currentPlanDay;
-
+ final DateTime? date;
@override
State createState() => _DiaryScreenContentState();
}
@@ -61,7 +59,11 @@ class _DiaryScreenContentState extends State {
final s = S.of(context);
return MultiBlocProvider(
providers: [
- BlocProvider(create: (BuildContext context) => sl()..fetchDiaryV2(needLoading: true, diaryId: widget.diaryId)),
+ BlocProvider(
+ create: (BuildContext context) => sl()
+ ..setInitialDate(widget.date)
+ ..fetchDiaryV2(needLoading: true, diaryId: widget.diaryId),
+ ),
BlocProvider(create: (context) => sl()),
],
child: BlocBuilder(
@@ -91,7 +93,7 @@ class _DiaryScreenContentState extends State {
physics: const BouncingScrollPhysics(),
child: Column(
children: [
- if (widget.diaryId == null) const CalendarWidget(),
+ if (widget.diaryId == null) CalendarWidget(date: widget.date),
if (state is DiaryStateLoading) ...[const SizedBox(height: 60), const Loader()],
if (state is DiaryStateLoaded)
WakeupAndRetireWidget(
diff --git a/lib/features/diary/presentation/widgets/calendar_widget.dart b/lib/features/diary/presentation/widgets/calendar_widget.dart
index cce80a55..bcf1ab09 100644
--- a/lib/features/diary/presentation/widgets/calendar_widget.dart
+++ b/lib/features/diary/presentation/widgets/calendar_widget.dart
@@ -8,7 +8,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart';
class CalendarWidget extends StatefulWidget {
- const CalendarWidget({super.key});
+ const CalendarWidget({super.key, this.date});
+ final DateTime? date;
@override
State createState() => _CalendarWidgetState();
@@ -18,6 +19,23 @@ class _CalendarWidgetState extends State {
CalendarFormat _calendarFormat = CalendarFormat.week;
DateTime _focusedDay = DateTime.now();
DateTime _selectedDay = DateTime.now();
+ @override
+ void initState() {
+ super.initState();
+ if (widget.date != null) {
+ _selectedDay = widget.date!;
+ _focusedDay = widget.date!;
+ }
+ }
+
+ @override
+ void didUpdateWidget(CalendarWidget oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ if (widget.date != null && widget.date != oldWidget.date) {
+ _selectedDay = widget.date!;
+ _focusedDay = widget.date!;
+ }
+ }
@override
Widget build(BuildContext context) {
diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart
index 54d74215..3823b471 100644
--- a/lib/features/diets/presentation/pages/about_diet_screen.dart
+++ b/lib/features/diets/presentation/pages/about_diet_screen.dart
@@ -37,8 +37,9 @@ class AboutDietScreen extends StatefulWidget {
final int? id;
final int? index;
final bool navigateToComments;
+ final int? highlightedCommentId;
- const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug, required this.id});
+ const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug, required this.id, this.highlightedCommentId});
@override
State createState() => _AboutDietScreenState();
@@ -433,6 +434,7 @@ class _AboutDietScreenState extends State {
commentSourceType: CommentSourceType.diet,
comment: state.comment,
replyId: replyId,
+ highlightedCommentId: widget.highlightedCommentId,
),
],
),
diff --git a/lib/features/feed/presentation/widgets/comments_widget.dart b/lib/features/feed/presentation/widgets/comments_widget.dart
index f8b3ecba..61ba39d2 100644
--- a/lib/features/feed/presentation/widgets/comments_widget.dart
+++ b/lib/features/feed/presentation/widgets/comments_widget.dart
@@ -23,14 +23,60 @@ class CommentsWidget extends StatefulWidget {
final CommentSourceType commentSourceType;
final CommentModel comment;
final int? replyId;
+ final int? highlightedCommentId;
- const CommentsWidget({super.key, required this.id, required this.commentSourceType, required this.comment, this.replyId});
+ const CommentsWidget({
+ super.key,
+ required this.id,
+ required this.commentSourceType,
+ required this.comment,
+ this.replyId,
+ this.highlightedCommentId,
+ });
@override
State createState() => _CommentsWidgetState();
}
class _CommentsWidgetState extends State {
+ final Map commentKeys = {};
+
+ @override
+ void initState() {
+ super.initState();
+ if (widget.highlightedCommentId != null) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _scrollToHighlightedComment();
+ });
+ }
+ }
+
+ @override
+ void didUpdateWidget(CommentsWidget oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ if (widget.highlightedCommentId != null && widget.highlightedCommentId != oldWidget.highlightedCommentId) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _scrollToHighlightedComment();
+ });
+ } else if (widget.comment != oldWidget.comment && widget.highlightedCommentId != null) {
+ // Comments might have just loaded
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _scrollToHighlightedComment();
+ });
+ }
+ }
+
+ void _scrollToHighlightedComment() {
+ final key = commentKeys[widget.highlightedCommentId];
+ if (key != null && key.currentContext != null) {
+ Scrollable.ensureVisible(
+ key.currentContext!,
+ duration: const Duration(milliseconds: 500),
+ curve: Curves.easeInOut,
+ );
+ }
+ }
+
@override
Widget build(BuildContext context) {
final s = S.of(context);
@@ -49,8 +95,10 @@ class _CommentsWidgetState extends State {
}
Widget _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
+ final key = commentKeys.putIfAbsent(comment.id, () => GlobalKey());
final isMe = (sl().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug;
return GestureDetector(
+ key: key,
onTap: () {
showCommentsActionsBottomSheet(
context,
@@ -83,7 +131,7 @@ class _CommentsWidgetState extends State {
child: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
- color: widget.replyId == comment.id ? appTheme.blueA400.withValues(alpha: 0.1) : Colors.white,
+ color: (widget.replyId == comment.id || widget.highlightedCommentId == comment.id) ? appTheme.blueA400.withValues(alpha: 0.1) : Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
diff --git a/lib/features/notifications/models/notification_model.dart b/lib/features/notifications/models/notification_model.dart
index cc77616e..b178eb91 100644
--- a/lib/features/notifications/models/notification_model.dart
+++ b/lib/features/notifications/models/notification_model.dart
@@ -10,16 +10,24 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
@override
final bool isRead;
final String planName;
+ final int planId;
final String customerName;
+ final int userId;
+ final String userPhotoPath;
final String chatId;
+ final String chatUrl;
@override
final DateTime createdAt;
PlanSubscriptionStartedToAuthorNotification({
required this.type,
required this.planName,
+ required this.planId,
required this.customerName,
+ required this.userId,
+ required this.userPhotoPath,
required this.chatId,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -27,9 +35,13 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionStartedToAuthorNotification(
type: type,
- planName: json['plan_name'],
- customerName: json['user_name'],
- chatId: json['chat_id'],
+ planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ customerName: json['user_name'] ?? '',
+ userId: json['user_id'] ?? 0,
+ userPhotoPath: json['user_photo_path'] ?? '',
+ chatId: json['chat_id']?.toString() ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -42,14 +54,22 @@ class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationMo
@override
final bool isRead;
final String planName;
+ final int planId;
final String customerName;
+ final int userId;
+ final String userPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
PlanSubscriptionCanceledToAuthorNotification({
required this.type,
required this.planName,
+ required this.planId,
required this.customerName,
+ required this.userId,
+ required this.userPhotoPath,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -57,8 +77,12 @@ class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationMo
factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionCanceledToAuthorNotification(
type: type,
- planName: json['plan_name'],
- customerName: json['user_name'],
+ planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ customerName: json['user_name'] ?? '',
+ userId: json['user_id'] ?? 0,
+ userPhotoPath: json['user_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -72,16 +96,28 @@ class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
final bool isRead;
final String planName;
final int planId;
+ final String consultantName;
+ final String peerProfileUrl;
@override
final DateTime createdAt;
- ChosenPlanStartedToClientNotification({required this.type, required this.planName, required this.planId, required this.isRead, required this.createdAt});
+ ChosenPlanStartedToClientNotification({
+ required this.type,
+ required this.planName,
+ required this.planId,
+ required this.consultantName,
+ required this.peerProfileUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ChosenPlanStartedToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanStartedToClientNotification(
type: type,
- planName: json['plan_name'],
- planId: json['plan_id'],
+ planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ consultantName: json['consultant_name'] ?? '',
+ peerProfileUrl: json['peer_profile_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -94,15 +130,26 @@ class ChosenPlanTomorrowReminderToClientNotification implements BaseNotification
@override
final bool isRead;
final String planName;
+ final String consultantName;
+ final int chatId;
@override
final DateTime createdAt;
- ChosenPlanTomorrowReminderToClientNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
+ ChosenPlanTomorrowReminderToClientNotification({
+ required this.type,
+ required this.planName,
+ required this.consultantName,
+ required this.chatId,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanTomorrowReminderToClientNotification(
type: type,
- planName: json['plan_name'],
+ planName: json['plan_name'] ?? '',
+ consultantName: json['consultant_name'] ?? '',
+ chatId: json['chat_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -114,15 +161,19 @@ class DiaryReminderNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ final int? diaryId;
+ final String? message;
@override
final DateTime createdAt;
- DiaryReminderNotification({required this.type, required this.isRead, required this.createdAt});
+ DiaryReminderNotification({required this.type, required this.isRead, this.diaryId, this.message, required this.createdAt});
factory DiaryReminderNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return DiaryReminderNotification(
type: type,
isRead: isRead ?? false,
+ diaryId: json['diary_id'],
+ message: json['message'],
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
@@ -133,16 +184,36 @@ class ConsultantLeftCommentNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ final int chosenPlanId;
+ final int diaryId;
final DateTime date;
+ final String planName;
+ final String userName;
+ final String consultantName;
@override
final DateTime createdAt;
- ConsultantLeftCommentNotification({required this.type, required this.date, required this.isRead, required this.createdAt});
+ ConsultantLeftCommentNotification({
+ required this.type,
+ required this.chosenPlanId,
+ required this.diaryId,
+ required this.date,
+ required this.planName,
+ required this.userName,
+ required this.consultantName,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ConsultantLeftCommentNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantLeftCommentNotification(
type: type,
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ diaryId: json['diary_id'] ?? 0,
date: parseDate(json['diary_date'] ?? DateTime.now().toIso8601String()),
+ planName: json['plan_name'] ?? '',
+ userName: json['user_name'] ?? '',
+ consultantName: json['consultant_name'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -174,14 +245,34 @@ class ChosenPlanFinishedToClientNotification implements BaseNotificationModel {
@override
final bool isRead;
final String planName;
+ final int chosenPlanId;
+ final String consultantName;
+ final String consultantPhotoPath;
+ final String consultantProfileUrl;
+ final int consultantId;
@override
final DateTime createdAt;
- ChosenPlanFinishedToClientNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
+ ChosenPlanFinishedToClientNotification({
+ required this.type,
+ required this.planName,
+ required this.chosenPlanId,
+ required this.consultantName,
+ required this.consultantPhotoPath,
+ required this.consultantProfileUrl,
+ required this.consultantId,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ChosenPlanFinishedToClientNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanFinishedToClientNotification(
type: type,
- planName: json['plan_name'],
+ planName: json['plan_name'] ?? '',
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ consultantName: json['consultant_name'] ?? '',
+ consultantPhotoPath: json['consultant_photo_path'] ?? '',
+ consultantProfileUrl: json['consultant_profile_url'] ?? '',
+ consultantId: json['consultant_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -194,14 +285,34 @@ class PlanSubscriptionCanceledToCustomerNotification implements BaseNotification
@override
final bool isRead;
final String planName;
+ final int planId;
+ final String authorName;
+ final int authorId;
+ final String authorPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
- PlanSubscriptionCanceledToCustomerNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
+ PlanSubscriptionCanceledToCustomerNotification({
+ required this.type,
+ required this.planName,
+ required this.planId,
+ required this.authorName,
+ required this.authorId,
+ required this.authorPhotoPath,
+ required this.chatUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PlanSubscriptionCanceledToCustomerNotification(
type: type,
- planName: json['plan_name'],
+ planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ authorName: json['author_name'] ?? '',
+ authorId: json['author_id'] ?? 0,
+ authorPhotoPath: json['author_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -215,6 +326,10 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel
final bool isRead;
final String planName;
final String customerName;
+ final int planId;
+ final int customerId;
+ final String peerProfileUrl;
+ final String consultationUrl;
@override
final DateTime createdAt;
ChosenPlanStartedToConsultantNotification({
@@ -222,6 +337,10 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel
required this.isRead,
required this.planName,
required this.customerName,
+ required this.planId,
+ required this.customerId,
+ required this.peerProfileUrl,
+ required this.consultationUrl,
required this.createdAt,
});
@@ -229,8 +348,12 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel
return ChosenPlanStartedToConsultantNotification(
type: type,
isRead: isRead ?? false,
- planName: json['plan_name'],
- customerName: json['client_name'],
+ planName: json['plan_name'] ?? '',
+ customerName: json['client_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ customerId: json['client_id'] ?? 0,
+ peerProfileUrl: json['peer_profile_url'] ?? '',
+ consultationUrl: json['consultation_url'] ?? '',
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
@@ -243,12 +366,14 @@ class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotifica
final bool isRead;
final String planName;
final String customerName;
+ final int chatId;
@override
final DateTime createdAt;
ChosenPlanTomorrowReminderToConsultantNotification({
required this.type,
required this.planName,
required this.customerName,
+ required this.chatId,
required this.isRead,
required this.createdAt,
});
@@ -256,8 +381,9 @@ class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotifica
factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanTomorrowReminderToConsultantNotification(
type: type,
- planName: json['plan_name'],
- customerName: json['client_name'],
+ planName: json['plan_name'] ?? '',
+ customerName: json['client_name'] ?? '',
+ chatId: json['chat_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -270,14 +396,22 @@ class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel
@override
final bool isRead;
final String planName;
+ final int planId;
final String customerName;
+ final int userId;
+ final String userPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
PlanSubscriptionPaidToAuthorNotification({
required this.type,
required this.isRead,
required this.planName,
+ required this.planId,
required this.customerName,
+ required this.userId,
+ required this.userPhotoPath,
+ required this.chatUrl,
required this.createdAt,
});
@@ -285,8 +419,12 @@ class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel
return PlanSubscriptionPaidToAuthorNotification(
type: type,
isRead: isRead ?? false,
- planName: json['plan_name'],
- customerName: json['user_name'],
+ planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ customerName: json['user_name'] ?? '',
+ userId: json['user_id'] ?? 0,
+ userPhotoPath: json['user_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
@@ -300,6 +438,9 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
final String planName;
final String customerName;
final int planId;
+ final int chosenPlanId;
+ final String clientPhotoPath;
+ final int clientId;
@override
final DateTime createdAt;
ChosenPlanFinishedToConsultantNotification({
@@ -307,6 +448,9 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
required this.planName,
required this.customerName,
required this.planId,
+ required this.chosenPlanId,
+ required this.clientPhotoPath,
+ required this.clientId,
required this.isRead,
required this.createdAt,
});
@@ -314,9 +458,12 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
factory ChosenPlanFinishedToConsultantNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanFinishedToConsultantNotification(
type: type,
- planName: json['plan_name'],
- customerName: json['client_name'],
- planId: json['chosen_plan_id'],
+ planName: json['plan_name'] ?? '',
+ customerName: json['client_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ clientPhotoPath: json['client_photo_path'] ?? '',
+ clientId: json['client_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -328,15 +475,31 @@ class ChosenPlanLastDayFinishedForUser implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ final String planName;
+ final int chosenPlanId;
+ final String complaintUrl;
+ final String chatUrl;
@override
final DateTime createdAt;
- ChosenPlanLastDayFinishedForUser({required this.type, required this.isRead, required this.createdAt});
+ ChosenPlanLastDayFinishedForUser({
+ required this.type,
+ required this.isRead,
+ required this.planName,
+ required this.chosenPlanId,
+ required this.complaintUrl,
+ required this.chatUrl,
+ required this.createdAt,
+ });
factory ChosenPlanLastDayFinishedForUser.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanLastDayFinishedForUser(
type: type,
isRead: isRead ?? false,
+ planName: json['plan_name'] ?? '',
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ complaintUrl: json['complaintUrl'] ?? json['complaint_url'] ?? '',
+ chatUrl: json['chatUrl'] ?? json['chat_url'] ?? '',
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
@@ -367,10 +530,10 @@ class ChosenPlanConsultantLastDayStarted implements BaseNotificationModel {
factory ChosenPlanConsultantLastDayStarted.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanConsultantLastDayStarted(
type: type,
- planName: json['plan_name'],
- planId: json['plan_id'],
- chatId: json['chat_id'],
- chatUrl: json['chat_url'],
+ planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ chatId: json['chat_id']?.toString() ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -385,6 +548,7 @@ class ComplaintToModerationForUser implements BaseNotificationModel {
final String message;
final int entityId;
final String entityType;
+ final String entityName;
@override
final DateTime createdAt;
@@ -393,6 +557,7 @@ class ComplaintToModerationForUser implements BaseNotificationModel {
required this.message,
required this.entityId,
required this.entityType,
+ required this.entityName,
required this.isRead,
required this.createdAt,
});
@@ -400,15 +565,32 @@ class ComplaintToModerationForUser implements BaseNotificationModel {
factory ComplaintToModerationForUser.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ComplaintToModerationForUser(
type: type,
- message: json['message'],
- entityId: json['entity_id'],
- entityType: json['entity_type'],
+ message: json['message'] ?? '',
+ entityId: json['entity_id'] ?? 0,
+ entityType: json['entity_type'] ?? '',
+ entityName: json['entity_name'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
}
+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;
@@ -416,10 +598,11 @@ 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;
+ final int? commentId;
@override
final DateTime createdAt;
@@ -431,6 +614,7 @@ class CommentReplied implements BaseNotificationModel {
required this.author,
required this.authorId,
required this.parentCommentId,
+ this.commentId,
required this.isRead,
required this.createdAt,
});
@@ -440,10 +624,11 @@ 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'],
+ commentId: json['comment_id'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -498,12 +683,13 @@ 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;
final int authorId;
final String authorSlug;
+ final int? commentId;
@override
final DateTime createdAt;
@@ -517,6 +703,7 @@ class EntityCommented implements BaseNotificationModel {
required this.authorName,
required this.authorId,
required this.authorSlug,
+ this.commentId,
required this.isRead,
required this.createdAt,
});
@@ -526,12 +713,13 @@ 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'],
authorId: json['author_id'],
authorSlug: json['author_slug'],
+ commentId: json['comment_id'],
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -831,7 +1019,11 @@ class UnknownNotification implements BaseNotificationModel {
@override
final DateTime createdAt;
- UnknownNotification({required this.type, this.isRead = true, DateTime? createdAt}) : createdAt = createdAt ?? DateTime.fromMillisecondsSinceEpoch(0);
+ UnknownNotification({required this.type, this.isRead = true, required this.createdAt});
+
+ factory UnknownNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return UnknownNotification(type: type, isRead: isRead ?? false, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()));
+ }
}
class DietCancelledNotification implements BaseNotificationModel {
@@ -840,15 +1032,17 @@ class DietCancelledNotification implements BaseNotificationModel {
@override
final bool isRead;
final String dietName;
+ final int dietId;
@override
final DateTime createdAt;
- DietCancelledNotification({required this.type, required this.dietName, required this.isRead, required this.createdAt});
+ DietCancelledNotification({required this.type, required this.dietName, required this.dietId, required this.isRead, required this.createdAt});
factory DietCancelledNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return DietCancelledNotification(
type: type,
dietName: json['diet_name'] ?? '',
+ dietId: json['diet_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -861,15 +1055,17 @@ class DietStartedNotification implements BaseNotificationModel {
@override
final bool isRead;
final String dietName;
+ final int dietId;
@override
final DateTime createdAt;
- DietStartedNotification({required this.type, required this.dietName, required this.isRead, required this.createdAt});
+ DietStartedNotification({required this.type, required this.dietName, required this.dietId, required this.isRead, required this.createdAt});
factory DietStartedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return DietStartedNotification(
type: type,
dietName: json['diet_name'] ?? '',
+ dietId: json['diet_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -882,15 +1078,17 @@ class DietExpiredNotification implements BaseNotificationModel {
@override
final bool isRead;
final String dietName;
+ final int dietId;
@override
final DateTime createdAt;
- DietExpiredNotification({required this.type, required this.dietName, required this.isRead, required this.createdAt});
+ DietExpiredNotification({required this.type, required this.dietName, required this.dietId, required this.isRead, required this.createdAt});
factory DietExpiredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return DietExpiredNotification(
type: type,
dietName: json['diet_name'] ?? '',
+ dietId: json['diet_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -903,16 +1101,25 @@ class DietWillStartedInNotification implements BaseNotificationModel {
@override
final bool isRead;
final String dietName;
+ final int dietId;
final String startDate;
@override
final DateTime createdAt;
- DietWillStartedInNotification({required this.type, required this.dietName, required this.startDate, required this.isRead, required this.createdAt});
+ DietWillStartedInNotification({
+ required this.type,
+ required this.dietName,
+ required this.dietId,
+ required this.startDate,
+ required this.isRead,
+ required this.createdAt,
+ });
factory DietWillStartedInNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return DietWillStartedInNotification(
type: type,
dietName: json['diet_name'] ?? '',
+ dietId: json['diet_id'] ?? 0,
startDate: json['start_date'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
@@ -926,16 +1133,25 @@ class DietCanNotBeStartedNotification implements BaseNotificationModel {
@override
final bool isRead;
final String dietName;
+ final int dietId;
final String reason;
@override
final DateTime createdAt;
- DietCanNotBeStartedNotification({required this.type, required this.dietName, required this.reason, required this.isRead, required this.createdAt});
+ DietCanNotBeStartedNotification({
+ required this.type,
+ required this.dietName,
+ required this.dietId,
+ required this.reason,
+ required this.isRead,
+ required this.createdAt,
+ });
factory DietCanNotBeStartedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return DietCanNotBeStartedNotification(
type: type,
dietName: json['diet_name'] ?? '',
+ dietId: json['diet_id'] ?? 0,
reason: json['reason'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
@@ -948,18 +1164,27 @@ class PersonalDietCreationNeededNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
- final String planName;
- final String clientName;
+ final String userName;
+ final String consultantName;
+ final String myDietsUrl;
@override
final DateTime createdAt;
- PersonalDietCreationNeededNotification({required this.type, required this.planName, required this.clientName, required this.isRead, required this.createdAt});
+ PersonalDietCreationNeededNotification({
+ required this.type,
+ required this.userName,
+ required this.consultantName,
+ required this.myDietsUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
factory PersonalDietCreationNeededNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return PersonalDietCreationNeededNotification(
type: type,
- planName: json['plan_name'] ?? '',
- clientName: json['client_name'] ?? '',
+ userName: json['user_name'] ?? '',
+ consultantName: json['consultant_name'] ?? '',
+ myDietsUrl: json['myDietsUrl'] ?? json['my_diets_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -972,15 +1197,29 @@ class ChosenPlanClientLastDayFinishedNotification implements BaseNotificationMod
@override
final bool isRead;
final String planName;
+ final int chosenPlanId;
+ final String complaintUrl;
+ final String chatUrl;
@override
final DateTime createdAt;
- ChosenPlanClientLastDayFinishedNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
+ ChosenPlanClientLastDayFinishedNotification({
+ required this.type,
+ required this.planName,
+ required this.chosenPlanId,
+ required this.complaintUrl,
+ required this.chatUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ChosenPlanClientLastDayFinishedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanClientLastDayFinishedNotification(
type: type,
planName: json['plan_name'] ?? '',
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ complaintUrl: json['complaintUrl'] ?? json['complaint_url'] ?? '',
+ chatUrl: json['chatUrl'] ?? json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -993,15 +1232,26 @@ class ChosenPlanClientLastDayStartedNotification implements BaseNotificationMode
@override
final bool isRead;
final String planName;
+ final int chosenPlanId;
+ final String lastDayDiaryUrl;
@override
final DateTime createdAt;
- ChosenPlanClientLastDayStartedNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
+ ChosenPlanClientLastDayStartedNotification({
+ required this.type,
+ required this.planName,
+ required this.chosenPlanId,
+ required this.lastDayDiaryUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ChosenPlanClientLastDayStartedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ChosenPlanClientLastDayStartedNotification(
type: type,
planName: json['plan_name'] ?? '',
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ lastDayDiaryUrl: json['last_day_diary_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1014,17 +1264,29 @@ class StayOnTrackToAuthorNotification implements BaseNotificationModel {
@override
final bool isRead;
final String planName;
- final String clientName;
+ final String userName;
+ final int dayNum;
+ final int diaryDayId;
@override
final DateTime createdAt;
- StayOnTrackToAuthorNotification({required this.type, required this.planName, required this.clientName, required this.isRead, required this.createdAt});
+ StayOnTrackToAuthorNotification({
+ required this.type,
+ required this.planName,
+ required this.userName,
+ required this.dayNum,
+ required this.diaryDayId,
+ required this.isRead,
+ required this.createdAt,
+ });
factory StayOnTrackToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return StayOnTrackToAuthorNotification(
type: type,
planName: json['plan_name'] ?? '',
- clientName: json['client_name'] ?? '',
+ userName: json['user_name'] ?? '',
+ dayNum: json['day_num'] ?? 0,
+ diaryDayId: json['diary_day_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1037,15 +1299,41 @@ class ClientStartPlanSubscriptionToSubscriberNotification implements BaseNotific
@override
final bool isRead;
final String planName;
+ final int planId;
+ final String authorName;
+ final int authorId;
+ final String authorPhotoPath;
+ final String chatUrl;
+ final String chatId;
+ final String startDate;
@override
final DateTime createdAt;
- ClientStartPlanSubscriptionToSubscriberNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
+ ClientStartPlanSubscriptionToSubscriberNotification({
+ required this.type,
+ required this.planName,
+ required this.planId,
+ required this.authorName,
+ required this.authorId,
+ required this.authorPhotoPath,
+ required this.chatUrl,
+ required this.chatId,
+ required this.startDate,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ClientStartPlanSubscriptionToSubscriberNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ClientStartPlanSubscriptionToSubscriberNotification(
type: type,
planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ authorName: json['author_name'] ?? '',
+ authorId: json['author_id'] ?? 0,
+ authorPhotoPath: json['author_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
+ chatId: json['chat_id']?.toString() ?? '',
+ startDate: json['start_date']?.toString() ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1058,14 +1346,22 @@ class ConsultantConfirmPlanSubscriptionToSubscriberNotification implements BaseN
@override
final bool isRead;
final String planName;
+ final int planId;
final String consultantName;
+ final int authorId;
+ final String authorPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
ConsultantConfirmPlanSubscriptionToSubscriberNotification({
required this.type,
required this.planName,
+ required this.planId,
required this.consultantName,
+ required this.authorId,
+ required this.authorPhotoPath,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -1079,7 +1375,11 @@ class ConsultantConfirmPlanSubscriptionToSubscriberNotification implements BaseN
return ConsultantConfirmPlanSubscriptionToSubscriberNotification(
type: type,
planName: json['plan_name'] ?? '',
- consultantName: json['consultant_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ consultantName: json['author_name'] ?? json['consultant_name'] ?? '',
+ authorId: json['author_id'] ?? 0,
+ authorPhotoPath: json['author_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1092,16 +1392,22 @@ class ClientPaidPlanSubscriptionToAuthorNotification implements BaseNotification
@override
final bool isRead;
final String planName;
+ final int planId;
final String clientName;
- final double amount;
+ final int userId;
+ final String userPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
ClientPaidPlanSubscriptionToAuthorNotification({
required this.type,
required this.planName,
+ required this.planId,
required this.clientName,
- required this.amount,
+ required this.userId,
+ required this.userPhotoPath,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -1110,8 +1416,11 @@ class ClientPaidPlanSubscriptionToAuthorNotification implements BaseNotification
return ClientPaidPlanSubscriptionToAuthorNotification(
type: type,
planName: json['plan_name'] ?? '',
- clientName: json['client_name'] ?? '',
- amount: (json['amount'] as num?)?.toDouble() ?? 0.0,
+ planId: json['plan_id'] ?? 0,
+ clientName: json['user_name'] ?? '',
+ userId: json['user_id'] ?? 0,
+ userPhotoPath: json['user_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1123,7 +1432,10 @@ class ConsultantLeftDiaryCommentNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ final int chosenPlanId;
+ final int diaryId;
final String planName;
+ final String userName;
final String consultantName;
final DateTime diaryDate;
@override
@@ -1131,7 +1443,10 @@ class ConsultantLeftDiaryCommentNotification implements BaseNotificationModel {
ConsultantLeftDiaryCommentNotification({
required this.type,
+ required this.chosenPlanId,
+ required this.diaryId,
required this.planName,
+ required this.userName,
required this.consultantName,
required this.diaryDate,
required this.isRead,
@@ -1141,7 +1456,10 @@ class ConsultantLeftDiaryCommentNotification implements BaseNotificationModel {
factory ConsultantLeftDiaryCommentNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantLeftDiaryCommentNotification(
type: type,
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ diaryId: json['diary_id'] ?? 0,
planName: json['plan_name'] ?? '',
+ userName: json['user_name'] ?? '',
consultantName: json['consultant_name'] ?? '',
diaryDate: parseDate(json['diary_date'] ?? DateTime.now().toIso8601String()),
isRead: isRead ?? false,
@@ -1156,14 +1474,22 @@ class ConsultantConfirmPlanSubscriptionToAuthorNotification implements BaseNotif
@override
final bool isRead;
final String planName;
- final String consultantName;
+ final int planId;
+ final String clientName;
+ final int userId;
+ final String userPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
ConsultantConfirmPlanSubscriptionToAuthorNotification({
required this.type,
required this.planName,
- required this.consultantName,
+ required this.planId,
+ required this.clientName,
+ required this.userId,
+ required this.userPhotoPath,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -1172,7 +1498,11 @@ class ConsultantConfirmPlanSubscriptionToAuthorNotification implements BaseNotif
return ConsultantConfirmPlanSubscriptionToAuthorNotification(
type: type,
planName: json['plan_name'] ?? '',
- consultantName: json['consultant_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ clientName: json['user_name'] ?? '',
+ userId: json['user_id'] ?? 0,
+ userPhotoPath: json['user_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1185,14 +1515,22 @@ class ClientPaidPlanSubscriptionToSubscriberNotification implements BaseNotifica
@override
final bool isRead;
final String planName;
- final double amount;
+ final int planId;
+ final String authorName;
+ final int authorId;
+ final String authorPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
ClientPaidPlanSubscriptionToSubscriberNotification({
required this.type,
required this.planName,
- required this.amount,
+ required this.planId,
+ required this.authorName,
+ required this.authorId,
+ required this.authorPhotoPath,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -1201,7 +1539,11 @@ class ClientPaidPlanSubscriptionToSubscriberNotification implements BaseNotifica
return ClientPaidPlanSubscriptionToSubscriberNotification(
type: type,
planName: json['plan_name'] ?? '',
- amount: (json['amount'] as num?)?.toDouble() ?? 0.0,
+ planId: json['plan_id'] ?? 0,
+ authorName: json['author_name'] ?? '',
+ authorId: json['author_id'] ?? 0,
+ authorPhotoPath: json['author_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1214,14 +1556,24 @@ class ClientStartPlanSubscriptionToAuthorNotification implements BaseNotificatio
@override
final bool isRead;
final String planName;
+ final int planId;
final String clientName;
+ final int userId;
+ final String userPhotoPath;
+ final String chatId;
+ final String chatUrl;
@override
final DateTime createdAt;
ClientStartPlanSubscriptionToAuthorNotification({
required this.type,
required this.planName,
+ required this.planId,
required this.clientName,
+ required this.userId,
+ required this.userPhotoPath,
+ required this.chatId,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -1230,7 +1582,12 @@ class ClientStartPlanSubscriptionToAuthorNotification implements BaseNotificatio
return ClientStartPlanSubscriptionToAuthorNotification(
type: type,
planName: json['plan_name'] ?? '',
- clientName: json['client_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ clientName: json['user_name'] ?? '',
+ userId: json['user_id'] ?? 0,
+ userPhotoPath: json['user_photo_path'] ?? '',
+ chatId: json['chat_id']?.toString() ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1243,14 +1600,22 @@ class ClientCancelPlanSubscriptionToAuthorNotification implements BaseNotificati
@override
final bool isRead;
final String planName;
+ final int planId;
final String clientName;
+ final int userId;
+ final String userPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
ClientCancelPlanSubscriptionToAuthorNotification({
required this.type,
required this.planName,
+ required this.planId,
required this.clientName,
+ required this.userId,
+ required this.userPhotoPath,
+ required this.chatUrl,
required this.isRead,
required this.createdAt,
});
@@ -1259,7 +1624,11 @@ class ClientCancelPlanSubscriptionToAuthorNotification implements BaseNotificati
return ClientCancelPlanSubscriptionToAuthorNotification(
type: type,
planName: json['plan_name'] ?? '',
- clientName: json['client_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ clientName: json['user_name'] ?? '',
+ userId: json['user_id'] ?? 0,
+ userPhotoPath: json['user_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1272,15 +1641,35 @@ class ClientCancelPlanSubscriptionToSubscriberNotification implements BaseNotifi
@override
final bool isRead;
final String planName;
+ final int planId;
+ final String authorName;
+ final int authorId;
+ final String authorPhotoPath;
+ final String chatUrl;
@override
final DateTime createdAt;
- ClientCancelPlanSubscriptionToSubscriberNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
+ ClientCancelPlanSubscriptionToSubscriberNotification({
+ required this.type,
+ required this.planName,
+ required this.planId,
+ required this.authorName,
+ required this.authorId,
+ required this.authorPhotoPath,
+ required this.chatUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
factory ClientCancelPlanSubscriptionToSubscriberNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ClientCancelPlanSubscriptionToSubscriberNotification(
type: type,
planName: json['plan_name'] ?? '',
+ planId: json['plan_id'] ?? 0,
+ authorName: json['author_name'] ?? '',
+ authorId: json['author_id'] ?? 0,
+ authorPhotoPath: json['author_photo_path'] ?? '',
+ chatUrl: json['chat_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1292,17 +1681,21 @@ class UserDailyReportCompletedNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ final int chosenPlanId;
+ final int diaryId;
final String planName;
final String clientName;
- final DateTime reportDate;
+ final String consultantName;
@override
final DateTime createdAt;
UserDailyReportCompletedNotification({
required this.type,
+ required this.chosenPlanId,
+ required this.diaryId,
required this.planName,
required this.clientName,
- required this.reportDate,
+ required this.consultantName,
required this.isRead,
required this.createdAt,
});
@@ -1310,9 +1703,11 @@ class UserDailyReportCompletedNotification implements BaseNotificationModel {
factory UserDailyReportCompletedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return UserDailyReportCompletedNotification(
type: type,
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ diaryId: json['diary_id'] ?? 0,
planName: json['plan_name'] ?? '',
- clientName: json['client_name'] ?? '',
- reportDate: parseDate(json['report_date'] ?? DateTime.now().toIso8601String()),
+ clientName: json['user_name'] ?? '',
+ consultantName: json['consultant_name'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1325,17 +1720,26 @@ class StayOnTrackToCustomerNotification implements BaseNotificationModel {
@override
final bool isRead;
final String planName;
- final String consultantName;
+ final int dayNum;
+ final int diaryDayId;
@override
final DateTime createdAt;
- StayOnTrackToCustomerNotification({required this.type, required this.planName, required this.consultantName, required this.isRead, required this.createdAt});
+ StayOnTrackToCustomerNotification({
+ required this.type,
+ required this.planName,
+ required this.dayNum,
+ required this.diaryDayId,
+ required this.isRead,
+ required this.createdAt,
+ });
factory StayOnTrackToCustomerNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return StayOnTrackToCustomerNotification(
type: type,
planName: json['plan_name'] ?? '',
- consultantName: json['consultant_name'] ?? '',
+ dayNum: json['day_num'] ?? 0,
+ diaryDayId: json['diary_day_id'] ?? 0,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1370,18 +1774,27 @@ class SubscriptionNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
- final String subscriptionType;
- final String planName;
+ final String userName;
+ final String userPhotoPath;
+ final String userProfileUrl;
@override
final DateTime createdAt;
- SubscriptionNotification({required this.type, required this.subscriptionType, required this.planName, required this.isRead, required this.createdAt});
+ SubscriptionNotification({
+ required this.type,
+ required this.userName,
+ required this.userPhotoPath,
+ required this.userProfileUrl,
+ required this.isRead,
+ required this.createdAt,
+ });
factory SubscriptionNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return SubscriptionNotification(
type: type,
- subscriptionType: json['subscription_type'] ?? '',
- planName: json['plan_name'] ?? '',
+ userName: json['user_name'] ?? '',
+ userPhotoPath: json['user_photo_path'] ?? '',
+ userProfileUrl: json['user_profile_url'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1393,18 +1806,27 @@ class BadgeAwardedNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
- final String badgeName;
- final String badgeDescription;
+ final String badgeTitle;
+ final String badgeType;
+ final String badgeImage;
@override
final DateTime createdAt;
- BadgeAwardedNotification({required this.type, required this.badgeName, required this.badgeDescription, required this.isRead, required this.createdAt});
+ BadgeAwardedNotification({
+ required this.type,
+ required this.badgeTitle,
+ required this.badgeType,
+ required this.badgeImage,
+ required this.isRead,
+ required this.createdAt,
+ });
factory BadgeAwardedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return BadgeAwardedNotification(
type: type,
- badgeName: json['badge_name'] ?? '',
- badgeDescription: json['badge_description'] ?? '',
+ badgeTitle: json['badge_title'] ?? '',
+ badgeType: json['badge_type'] ?? '',
+ badgeImage: json['badge_image'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1416,18 +1838,27 @@ class BadgeRevokedNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
- final String badgeName;
- final String reason;
+ final String badgeTitle;
+ final String badgeType;
+ final String badgeImage;
@override
final DateTime createdAt;
- BadgeRevokedNotification({required this.type, required this.badgeName, required this.reason, required this.isRead, required this.createdAt});
+ BadgeRevokedNotification({
+ required this.type,
+ required this.badgeTitle,
+ required this.badgeType,
+ required this.badgeImage,
+ required this.isRead,
+ required this.createdAt,
+ });
factory BadgeRevokedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return BadgeRevokedNotification(
type: type,
- badgeName: json['badge_name'] ?? '',
- reason: json['reason'] ?? '',
+ badgeTitle: json['badge_title'] ?? '',
+ badgeType: json['badge_type'] ?? '',
+ badgeImage: json['badge_image'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1460,24 +1891,16 @@ class ClientCloseComplaintSuccessNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
- final String complaintType;
- final String entityName;
+ final String planName;
@override
final DateTime createdAt;
- ClientCloseComplaintSuccessNotification({
- required this.type,
- required this.complaintType,
- required this.entityName,
- required this.isRead,
- required this.createdAt,
- });
+ ClientCloseComplaintSuccessNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
factory ClientCloseComplaintSuccessNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ClientCloseComplaintSuccessNotification(
type: type,
- complaintType: json['complaint_type'] ?? '',
- entityName: json['entity_name'] ?? '',
+ planName: json['plan_name'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1554,17 +1977,15 @@ class ClientRejectSuccessNotification implements BaseNotificationModel {
@override
final bool isRead;
final String planName;
- final String reason;
@override
final DateTime createdAt;
- ClientRejectSuccessNotification({required this.type, required this.planName, required this.reason, required this.isRead, required this.createdAt});
+ ClientRejectSuccessNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
factory ClientRejectSuccessNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
return ClientRejectSuccessNotification(
type: type,
planName: json['plan_name'] ?? '',
- reason: json['reason'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1578,25 +1999,16 @@ class ConsultantRejectSuccessNotification implements BaseNotificationModel {
final bool isRead;
final String planName;
final String clientName;
- final String reason;
@override
final DateTime createdAt;
- ConsultantRejectSuccessNotification({
- required this.type,
- required this.planName,
- required this.clientName,
- required this.reason,
- 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 json, NotificationType type, {bool? isRead, String? createdAt}) {
return ConsultantRejectSuccessNotification(
type: type,
planName: json['plan_name'] ?? '',
- clientName: json['client_name'] ?? '',
- reason: json['reason'] ?? '',
+ clientName: json['user_name'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1650,16 +2062,24 @@ class ProductStatusUpdatedForAuthorNotification implements BaseNotificationModel
@override
final bool isRead;
final String productName;
- final String oldStatus;
- final String newStatus;
+ final int productId;
+ final String author;
+ final int authorId;
+ final String status;
+ final String subject;
+ final String messageLine;
@override
final DateTime createdAt;
ProductStatusUpdatedForAuthorNotification({
required this.type,
required this.productName,
- required this.oldStatus,
- required this.newStatus,
+ required this.productId,
+ required this.author,
+ required this.authorId,
+ required this.status,
+ required this.subject,
+ required this.messageLine,
required this.isRead,
required this.createdAt,
});
@@ -1668,8 +2088,12 @@ class ProductStatusUpdatedForAuthorNotification implements BaseNotificationModel
return ProductStatusUpdatedForAuthorNotification(
type: type,
productName: json['product_name'] ?? '',
- oldStatus: json['old_status'] ?? '',
- newStatus: json['new_status'] ?? '',
+ productId: json['product_id'] ?? 0,
+ author: json['author'] ?? '',
+ authorId: json['author_id'] ?? 0,
+ status: json['status'] ?? '',
+ subject: json['subject'] ?? '',
+ messageLine: json['messageLine'] ?? json['message_line'] ?? '',
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
@@ -1681,15 +2105,20 @@ class DiaryReminderForUsersNotification implements BaseNotificationModel {
final NotificationType type;
@override
final bool isRead;
+ final int? diaryId;
+ final String? message;
@override
final DateTime createdAt;
- DiaryReminderForUsersNotification({required this.type, required this.isRead, required this.createdAt});
+ DiaryReminderForUsersNotification({required this.type, required this.isRead, this.diaryId, this.message, required this.createdAt});
factory DiaryReminderForUsersNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ final data = json['data'] is Map ? json['data'] as Map : json;
return DiaryReminderForUsersNotification(
type: type,
isRead: isRead ?? false,
+ diaryId: data['diary_id'],
+ message: data['message'],
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
);
}
@@ -1702,15 +2131,29 @@ class PersonalDietIsDefinedToCustomerNotification implements BaseNotificationMod
final bool isRead;
@override
final DateTime createdAt;
- final String dietName;
+ final int chosenPlanId;
final int dietId;
+ final String planName;
+ final String userName;
+ final String consultantName;
+ final String dietName;
+ final String role;
+ final String peerSlug;
+ final String peerImagePath;
PersonalDietIsDefinedToCustomerNotification({
required this.type,
required this.isRead,
required this.createdAt,
- required this.dietName,
+ required this.chosenPlanId,
required this.dietId,
+ required this.planName,
+ required this.userName,
+ required this.consultantName,
+ required this.dietName,
+ required this.role,
+ required this.peerSlug,
+ required this.peerImagePath,
});
factory PersonalDietIsDefinedToCustomerNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
@@ -1718,8 +2161,288 @@ class PersonalDietIsDefinedToCustomerNotification implements BaseNotificationMod
type: type,
isRead: isRead ?? false,
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ chosenPlanId: json['chosen_plan_id'] ?? 0,
+ dietId: json['diet_id'] ?? 0,
+ planName: json['plan_name'] ?? '',
+ userName: json['user_name'] ?? '',
+ consultantName: json['consultant_name'] ?? '',
dietName: json['diet_name'] ?? '',
- dietId: json['diet_id'],
+ role: json['role']?.toString() ?? '',
+ peerSlug: json['peer_slug'] ?? '',
+ peerImagePath: json['peer_image_path'] ?? '',
+ );
+ }
+}
+
+// New Model Classes for Comprehensive Support
+
+class ConsultantEducationUpdatedNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String consultantName;
+
+ ConsultantEducationUpdatedNotification({required this.type, required this.isRead, required this.createdAt, required this.consultantName});
+
+ factory ConsultantEducationUpdatedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ConsultantEducationUpdatedNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ consultantName: json['consultant_name'] ?? '',
+ );
+ }
+}
+
+class ComplaintRegisteredNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String message;
+ final int entityId;
+ final String entityType;
+ final String entityName;
+
+ ComplaintRegisteredNotification({
+ required this.type,
+ required this.isRead,
+ required this.createdAt,
+ required this.message,
+ required this.entityId,
+ required this.entityType,
+ required this.entityName,
+ });
+
+ factory ComplaintRegisteredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ComplaintRegisteredNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ message: json['message'] ?? '',
+ entityId: json['entity_id'] ?? 0,
+ entityType: json['entity_type'] ?? '',
+ entityName: json['entity_name'] ?? '',
+ );
+ }
+}
+
+class ContentUnpublishedNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String reason;
+ final String entityName;
+
+ ContentUnpublishedNotification({required this.type, required this.isRead, required this.createdAt, required this.reason, required this.entityName});
+
+ factory ContentUnpublishedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ContentUnpublishedNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ reason: json['reason'] ?? '',
+ entityName: json['entity_name'] ?? '',
+ );
+ }
+}
+
+class FinanceQuestionCreatedNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String questionText;
+
+ FinanceQuestionCreatedNotification({required this.type, required this.isRead, required this.createdAt, required this.questionText});
+
+ factory FinanceQuestionCreatedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return FinanceQuestionCreatedNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ questionText: json['question_text'] ?? '',
+ );
+ }
+}
+
+class NewChatMessageNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String senderName;
+ final String messagePreview;
+ final int chatId;
+
+ NewChatMessageNotification({
+ required this.type,
+ required this.isRead,
+ required this.createdAt,
+ required this.senderName,
+ required this.messagePreview,
+ required this.chatId,
+ });
+
+ factory NewChatMessageNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return NewChatMessageNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ senderName: json['sender_name'] ?? '',
+ messagePreview: json['message_preview'] ?? '',
+ chatId: json['chat_id'] ?? 0,
+ );
+ }
+}
+
+class CompleteRegistrationNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+
+ CompleteRegistrationNotification({required this.type, required this.isRead, required this.createdAt});
+
+ factory CompleteRegistrationNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return CompleteRegistrationNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ );
+ }
+}
+
+class ChosenDietExpiredNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String dietName;
+ final int dietId;
+
+ ChosenDietExpiredNotification({required this.type, required this.isRead, required this.createdAt, required this.dietName, required this.dietId});
+
+ factory ChosenDietExpiredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return ChosenDietExpiredNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ dietName: json['diet_name'] ?? '',
+ dietId: json['diet_id'] ?? 0,
+ );
+ }
+}
+
+class MotivateUserNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String message;
+
+ MotivateUserNotification({required this.type, required this.isRead, required this.createdAt, required this.message});
+
+ factory MotivateUserNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return MotivateUserNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ message: json['message'] ?? '',
+ );
+ }
+}
+
+class CommentComplaintNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String commentPreview;
+ final String reason;
+
+ CommentComplaintNotification({required this.type, required this.isRead, required this.createdAt, required this.commentPreview, required this.reason});
+
+ factory CommentComplaintNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return CommentComplaintNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ commentPreview: json['comment_preview'] ?? '',
+ reason: json['reason'] ?? '',
+ );
+ }
+}
+
+class CommentDeletedNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String commentPreview;
+
+ CommentDeletedNotification({required this.type, required this.isRead, required this.createdAt, required this.commentPreview});
+
+ factory CommentDeletedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return CommentDeletedNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ commentPreview: json['comment_preview'] ?? '',
+ );
+ }
+}
+
+class UserSubscribedNotification implements BaseNotificationModel {
+ @override
+ final NotificationType type;
+ @override
+ final bool isRead;
+ @override
+ final DateTime createdAt;
+ final String userName;
+ final String userPhotoPath;
+ final String userProfileUrl;
+
+ UserSubscribedNotification({
+ required this.type,
+ required this.isRead,
+ required this.createdAt,
+ required this.userName,
+ required this.userPhotoPath,
+ required this.userProfileUrl,
+ });
+
+ factory UserSubscribedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) {
+ return UserSubscribedNotification(
+ type: type,
+ isRead: isRead ?? false,
+ createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
+ userName: json['user_name'] ?? '',
+ userPhotoPath: json['user_photo_path'] ?? '',
+ userProfileUrl: json['user_profile_url'] ?? '',
);
}
}
@@ -1731,6 +2454,7 @@ enum NotificationType {
dietExpired,
dietWillStartedIn,
dietCanNotBeStarted,
+ chosenDietExpired, // New
// Plan related
personalDietCreationNeeded,
@@ -1760,12 +2484,17 @@ enum NotificationType {
// Comments
entityCommented,
commentReplied,
+ commentComplaint, // New
+ commentDeleted, // New
// Profile
phoneChanged,
+ consultantEducationUpdated, // New
+ completeRegistration, // New
// Subscription
subscription,
+ userSubscribed, // New
// Badges
badgeAwarded,
@@ -1773,16 +2502,21 @@ enum NotificationType {
// Consultant
consultantRequestStarted,
+ motivateUser, // New
// Complaints
clientCloseComplaintSuccess,
complaintToModerationForUser,
+ complaintRegistered, // New
+ contentUnpublished, // New
+ contentUnpublishedComplaint, // New
// Finance
clientRefundSuccess,
consultantRefundSuccess,
clientRejectSuccess,
consultantRejectSuccess,
+ financeQuestionCreated, // New
// Score
scoreAdded,
@@ -1794,6 +2528,9 @@ enum NotificationType {
// Diary
diaryReminderForUsers,
+ // Chat
+ newChatMessage, // New
+
// Legacy types (keeping for backward compatibility)
planSubscriptionStartedToAuthor,
planSubscriptionCanceledToAuthor,
@@ -1866,6 +2603,21 @@ enum NotificationType {
'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,
+ 'content-unpublished' => NotificationType.contentUnpublished,
+ 'content-unpublished-complaint' => NotificationType.contentUnpublishedComplaint,
+ 'finance-question-created' => NotificationType.financeQuestionCreated,
+ 'new-chat-message' => NotificationType.newChatMessage,
+ 'complete-registration' => NotificationType.completeRegistration,
+ 'chosen-diet-expired' => NotificationType.chosenDietExpired,
+ 'motivate-user' => NotificationType.motivateUser,
+ 'comment-complaint' => NotificationType.commentComplaint,
+ 'comment-deleted' => NotificationType.commentDeleted,
+ '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,
diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart
index 110607c0..51771c24 100644
--- a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart
+++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart
@@ -267,33 +267,113 @@ class NotificationsCubit extends Cubit {
isRead: isRead,
createdAt: createdAt,
),
- NotificationType.unknown => UnknownNotification(type: type),
+ NotificationType.consultantEducationUpdated => ConsultantEducationUpdatedNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.complaintRegistered => ComplaintRegisteredNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.contentUnpublished => ContentUnpublishedNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.contentUnpublishedComplaint => ContentUnpublishedNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.financeQuestionCreated => FinanceQuestionCreatedNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.newChatMessage => NewChatMessageNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.completeRegistration => CompleteRegistrationNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenDietExpired => ChosenDietExpiredNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.motivateUser => MotivateUserNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.commentComplaint => CommentComplaintNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.commentDeleted => CommentDeletedNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.userSubscribed => UserSubscribedNotification.fromJson(
+ notificationJson,
+ type,
+ isRead: isRead,
+ createdAt: createdAt,
+ ),
+ NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt ?? jsonEncode(notificationJson))),
};
} catch (e) {
- return UnknownNotification(type: NotificationType.unknown);
+ return UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now());
}
}
void _handleNotification(String notificationData) {
final json = jsonDecode(notificationData) as Map;
- // Улучшенная логика определения типа уведомления
+ // Улучшенная логика определения типа уведомления (порядок: 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) {
final data = json['data'] as Map;
- 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) {
diff --git a/lib/features/notifications/presentation/widgets/notification_widget.dart b/lib/features/notifications/presentation/widgets/notification_widget.dart
index 0169f96d..cdde4bbf 100644
--- a/lib/features/notifications/presentation/widgets/notification_widget.dart
+++ b/lib/features/notifications/presentation/widgets/notification_widget.dart
@@ -1,10 +1,13 @@
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
+import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
+import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
+import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
@@ -12,6 +15,7 @@ import 'package:go_router/go_router.dart';
class NotificationWidget extends StatelessWidget {
const NotificationWidget({super.key, required this.notification});
final BaseNotificationModel notification;
+
@override
Widget build(BuildContext context) {
return switch (notification) {
@@ -117,11 +121,45 @@ class NotificationWidget extends StatelessWidget {
PersonalDietIsDefinedToCustomerNotification() => _PersonalDietIsDefinedToCustomerWidget(
notification: notification as PersonalDietIsDefinedToCustomerNotification,
),
+ // New Widgets
+ ConsultantEducationUpdatedNotification() => _ConsultantEducationUpdatedWidget(notification: notification as ConsultantEducationUpdatedNotification),
+ ComplaintRegisteredNotification() => _ComplaintRegisteredWidget(notification: notification as ComplaintRegisteredNotification),
+ ContentUnpublishedNotification() => _ContentUnpublishedWidget(notification: notification as ContentUnpublishedNotification),
+ FinanceQuestionCreatedNotification() => _FinanceQuestionCreatedWidget(notification: notification as FinanceQuestionCreatedNotification),
+ NewChatMessageNotification() => _NewChatMessageWidget(notification: notification as NewChatMessageNotification),
+ CompleteRegistrationNotification() => _CompleteRegistrationWidget(notification: notification as CompleteRegistrationNotification),
+ ChosenDietExpiredNotification() => _ChosenDietExpiredWidget(notification: notification as ChosenDietExpiredNotification),
+ MotivateUserNotification() => _MotivateUserWidget(notification: notification as MotivateUserNotification),
+ CommentComplaintNotification() => _CommentComplaintWidget(notification: notification as CommentComplaintNotification),
+ CommentDeletedNotification() => _CommentDeletedWidget(notification: notification as CommentDeletedNotification),
+ UserSubscribedNotification() => _UserSubscribedWidget(notification: notification as UserSubscribedNotification),
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
};
}
}
+void _navigateToBottomNavRoute(BuildContext context, String path, [Object? extra]) {
+ final controller = BottomNavScope.maybeOf(context);
+ if (controller != null) {
+ controller.switchToRoute(path, extra);
+ } else {
+ context.push(path, extra: extra);
+ }
+}
+
+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;
+ }
+}
+
class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
const _PlanSubscriptionStartedToAuthorWidget({required this.notification});
final PlanSubscriptionStartedToAuthorNotification notification;
@@ -131,8 +169,11 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName),
+ notificationsText: '${s.userSubscribedToPlan(notification.customerName, notification.planName)}! 🎉',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.customerName.isNotEmpty ? notification.customerName : null);
+ },
);
}
}
@@ -142,12 +183,13 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
final PlanSubscriptionCanceledToAuthorNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName),
+ notificationsText: 'Отмена подписки. Клиент ${notification.customerName} отказался от плана "${notification.planName}"',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.customerName.isNotEmpty ? notification.customerName : null);
+ },
);
}
}
@@ -157,9 +199,14 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
final ChosenPlanStartedToClientNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
- return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.planStarted(notification.planName), createdAt: notification.createdAt);
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Ваш план "${notification.planName}" начался! Удачи! 🚀',
+ createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.diaryScreen);
+ },
+ );
}
}
@@ -168,11 +215,9 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
final ChosenPlanTomorrowReminderToClientNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.tomorrowYourPlanWillStart(notification.planName),
+ notificationsText: 'Напоминание: Завтра старт плана "${notification.planName}"',
createdAt: notification.createdAt,
);
}
@@ -183,8 +228,16 @@ class _DiaryReminderWidget extends StatelessWidget {
final DiaryReminderNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
- return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.fillYasterday, createdAt: notification.createdAt);
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Не забудьте заполнить дневник за вчера! 📝',
+ createdAt: notification.createdAt,
+ onTap: () {
+ final yesterday = notification.createdAt.subtract(const Duration(days: 1));
+ final dateOnly = DateTime(yesterday.year, yesterday.month, yesterday.day);
+ _navigateToBottomNavRoute(context, RouteConstants.diaryScreen, dateOnly);
+ },
+ );
}
}
@@ -193,12 +246,13 @@ class _ConsultantLeftCommentWidget extends StatelessWidget {
final ConsultantLeftCommentNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.consultantLeftComment(notification.date.format('dd.MM.yyyy')),
+ notificationsText: 'Новый комментарий от консультанта (за ${notification.date.format('dd.MM.yyyy')})',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.diaryScreen, notification.date);
+ },
);
}
}
@@ -208,7 +262,14 @@ class _PersonalizedDietIsDefinedWidget extends StatelessWidget {
final PersonalizedDietIsDefinedNotification notification;
@override
Widget build(BuildContext context) {
- return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Персональная диета определена', createdAt: notification.createdAt);
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Персональная диета определена',
+ createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.consultationsScreen);
+ },
+ );
}
}
@@ -217,11 +278,9 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget {
final ChosenPlanFinishedToClientNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
+ notificationsText: 'Поздравляем! План «${notification.planName}» успешно завершен! 🏆',
createdAt: notification.createdAt,
);
}
@@ -232,11 +291,9 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget {
final PlanSubscriptionCanceledToCustomerNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.youCanceledSubscriptionToPlan(notification.planName),
+ notificationsText: 'Подписка на план "${notification.planName}" отменена.',
createdAt: notification.createdAt,
);
}
@@ -247,11 +304,9 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget {
final ChosenPlanStartedToConsultantNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.planWithUserStarted(notification.planName, notification.customerName),
+ notificationsText: 'Начало работы: План "${notification.planName}" с клиентом ${notification.customerName}',
createdAt: notification.createdAt,
);
}
@@ -262,11 +317,9 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget {
final ChosenPlanTomorrowReminderToConsultantNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName),
+ notificationsText: 'Напоминание: Завтра старт работы с ${notification.customerName} по плану "${notification.planName}"',
createdAt: notification.createdAt,
);
}
@@ -277,12 +330,13 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget {
final PlanSubscriptionPaidToAuthorNotification notification;
@override
Widget build(BuildContext context) {
- final s = S.of(context);
-
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: s.userPaidThePlan(notification.customerName, notification.planName),
+ notificationsText: 'Оплата получена! Клиент ${notification.customerName} оплатил план "${notification.planName}" 💰',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.consultantFinancesScreen);
+ },
);
}
}
@@ -293,44 +347,48 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final s = S.of(context);
- 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).push(CardSettingsScreen.path, extra: notification.planId);
- },
- ),
- ],
+ return GestureDetector(
+ onTap: () {},
+ behavior: HitTestBehavior.opaque,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('План «${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).push(CardSettingsScreen.path, 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),
- ),
- ],
+ 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),
+ ),
+ ],
+ ),
);
}
@@ -377,6 +435,7 @@ class _BaseNotificationWidget extends StatelessWidget {
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
+ behavior: HitTestBehavior.opaque,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -385,8 +444,14 @@ class _BaseNotificationWidget extends StatelessWidget {
Expanded(
child: Text(notificationsText, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
),
+ if (onTap != null)
+ Padding(
+ padding: const EdgeInsets.only(left: 8),
+ child: Icon(Icons.chevron_right, color: appTheme.gray500, size: 20),
+ ),
if (!isRead)
Container(
+ margin: const EdgeInsets.only(left: 8),
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
width: 8,
height: 8,
@@ -440,7 +505,11 @@ class _ChosenPlanLastDayFinishedForUserWidget extends StatelessWidget {
final ChosenPlanLastDayFinishedForUser notification;
@override
Widget build(BuildContext context) {
- return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Последний день плана завершен', createdAt: notification.createdAt);
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Финишная прямая! Последний день плана завершен.',
+ createdAt: notification.createdAt,
+ );
}
}
@@ -451,8 +520,11 @@ class _ChosenPlanConsultantLastDayStartedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Последний день плана начался: ${notification.planName}',
+ notificationsText: 'Внимание: Начался последний день плана "${notification.planName}"',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.consultationsScreen);
+ },
);
}
}
@@ -462,7 +534,11 @@ class _ComplaintToModerationForUserWidget extends StatelessWidget {
final ComplaintToModerationForUser notification;
@override
Widget build(BuildContext context) {
- return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Жалоба отправлена на модерацию', createdAt: notification.createdAt);
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Ваша жалоба принята и находится на модерации.',
+ createdAt: notification.createdAt,
+ );
}
}
@@ -473,8 +549,13 @@ class _CommentRepliedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Ответ на комментарий: ${notification.entityName}',
+ notificationsText: 'Вам ответили в комментариях к: ${notification.entityName}',
createdAt: notification.createdAt,
+ onTap: (notification.commentedEntityType == CommentedEntityType.unknown || notification.commentedEntityId <= 0)
+ ? null
+ : () {
+ _navigateToEntity(context, notification.commentedEntityType, notification.commentedEntityId, notification.commentId);
+ },
);
}
}
@@ -486,8 +567,11 @@ class _ScoreAddedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Добавлены баллы: ${notification.scoreValue}',
+ notificationsText: 'Поздравляем! Вам начислены баллы: ${notification.scoreValue} ⭐',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.consultantRatingScreen);
+ },
);
}
}
@@ -499,8 +583,13 @@ class _EntityCommentedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Комментарий к ${notification.entityTranslatedType}: ${notification.entityName}',
+ notificationsText: 'Новый комментарий к вашей публикации "${notification.entityTranslatedType}": ${notification.entityName}',
createdAt: notification.createdAt,
+ onTap: (notification.entityType == CommentedEntityType.unknown || notification.entityId <= 0)
+ ? null
+ : () {
+ _navigateToEntity(context, notification.entityType, notification.entityId, notification.commentId);
+ },
);
}
}
@@ -510,11 +599,7 @@ class _UnknownNotificationWidget extends StatelessWidget {
final UnknownNotification notification;
@override
Widget build(BuildContext context) {
- return _BaseNotificationWidget(
- isRead: notification.isRead,
- notificationsText: 'Неизвестное уведомление: ${notification.type.toString()}',
- createdAt: notification.createdAt,
- );
+ return _BaseNotificationWidget(isRead: false, notificationsText: 'Неизвестное уведомление', createdAt: DateTime.now(), onTap: null);
}
}
@@ -526,7 +611,7 @@ class _PlanSubscriptionExpiredWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Подписка на план "${notification.planName}" истекла',
+ notificationsText: 'Срок действия подписки на план "${notification.planName}" истек. Продлите, чтобы продолжить! ⏳',
createdAt: notification.createdAt,
);
}
@@ -539,7 +624,7 @@ class _PlanSubscriptionRenewedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Подписка на план "${notification.planName}" продлена до ${notification.renewalDate}',
+ notificationsText: 'Успешное продление! Подписка на план "${notification.planName}" активна до ${notification.renewalDate} ✅',
createdAt: notification.createdAt,
);
}
@@ -552,8 +637,11 @@ class _ConsultantAssignedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'К плану "${notification.planName}" назначен консультант ${notification.consultantName}',
+ notificationsText: 'Вам назначен консультант ${notification.consultantName} по плану "${notification.planName}". Приветствуйте! 👋',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.consultantName.isNotEmpty ? notification.consultantName : null);
+ },
);
}
}
@@ -565,7 +653,7 @@ class _ConsultantUnassignedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Консультант ${notification.consultantName} отстранен от плана "${notification.planName}"',
+ notificationsText: 'Изменение в плане "${notification.planName}": консультант ${notification.consultantName} отстранен.',
createdAt: notification.createdAt,
);
}
@@ -578,8 +666,11 @@ class _PaymentFailedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Ошибка оплаты плана "${notification.planName}": ${notification.errorMessage}',
+ notificationsText: 'Не удалось провести оплату за план "${notification.planName}". Причина: ${notification.errorMessage} ❌',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.userFinanceScreen);
+ },
);
}
}
@@ -591,8 +682,11 @@ class _PaymentSuccessfulWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Успешная оплата плана "${notification.planName}" на сумму ${notification.amount} ${notification.currency}',
+ notificationsText: 'Оплата прошла успешно! План "${notification.planName}" оплачен на сумму ${notification.amount} ${notification.currency} 💳',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.userFinanceScreen);
+ },
);
}
}
@@ -604,7 +698,7 @@ class _GoalAchievedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Достигнута цель "${notification.goalName}": ${notification.achievedValue}/${notification.targetValue}',
+ notificationsText: 'Цель достигнута! "${notification.goalName}": ${notification.achievedValue}/${notification.targetValue} 🎯',
createdAt: notification.createdAt,
);
}
@@ -617,7 +711,7 @@ class _WorkoutReminderWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Напоминание о тренировке "${notification.workoutName}" (${notification.duration} мин)',
+ notificationsText: 'Время тренировки! "${notification.workoutName}" ждет вас (${notification.duration} мин) 💪',
createdAt: notification.createdAt,
);
}
@@ -630,8 +724,11 @@ class _MealReminderWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Напоминание о приеме пищи "${notification.mealName}" (${notification.calories} ккал)',
+ notificationsText: 'Пора подкрепиться! "${notification.mealName}" (${notification.calories} ккал) 🍎',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.diaryScreen);
+ },
);
}
}
@@ -644,8 +741,13 @@ class _DietCancelledWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Диета "${notification.dietName}" отменена',
+ notificationsText: 'Диета "${notification.dietName}" была отменена.',
createdAt: notification.createdAt,
+ onTap: () {
+ if (notification.dietId > 0) {
+ context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId});
+ }
+ },
);
}
}
@@ -657,8 +759,13 @@ class _DietStartedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Диета "${notification.dietName}" началась',
+ notificationsText: 'Старт диеты "${notification.dietName}"! Успехов! 🥗',
createdAt: notification.createdAt,
+ onTap: () {
+ if (notification.dietId > 0) {
+ context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId});
+ }
+ },
);
}
}
@@ -670,8 +777,13 @@ class _DietExpiredWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Диета "${notification.dietName}" истекла',
+ notificationsText: 'Срок действия диеты "${notification.dietName}" подошел к концу.',
createdAt: notification.createdAt,
+ onTap: () {
+ if (notification.dietId > 0) {
+ context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId});
+ }
+ },
);
}
}
@@ -683,8 +795,13 @@ class _DietWillStartedInWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Диета "${notification.dietName}" начнется ${notification.startDate}',
+ notificationsText: 'Скоро старт! Диета "${notification.dietName}" начнется ${notification.startDate} ⏰',
createdAt: notification.createdAt,
+ onTap: () {
+ if (notification.dietId > 0) {
+ context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId});
+ }
+ },
);
}
}
@@ -696,8 +813,13 @@ class _DietCanNotBeStartedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Диета "${notification.dietName}" не может быть начата: ${notification.reason}',
+ notificationsText: 'Внимание: Не удалось начать диету "${notification.dietName}". Причина: ${notification.reason}',
createdAt: notification.createdAt,
+ onTap: () {
+ if (notification.dietId > 0) {
+ context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId});
+ }
+ },
);
}
}
@@ -709,8 +831,11 @@ class _PersonalDietCreationNeededWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Требуется создание персональной диеты для клиента ${notification.clientName} по плану "${notification.planName}"',
+ notificationsText: 'Задача: Создать персональную диету для клиента ${notification.userName}',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.myDietsScreen);
+ },
);
}
}
@@ -722,7 +847,7 @@ class _ChosenPlanClientLastDayFinishedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Последний день плана "${notification.planName}" завершен',
+ notificationsText: 'Поздравляем с завершением плана "${notification.planName}"! 🎉',
createdAt: notification.createdAt,
);
}
@@ -735,8 +860,11 @@ class _ChosenPlanClientLastDayStartedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Последний день плана "${notification.planName}" начался',
+ notificationsText: 'Сегодня последний день плана "${notification.planName}"! Финиш близко! 🏁',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.diaryScreen);
+ },
);
}
}
@@ -748,8 +876,15 @@ class _StayOnTrackToAuthorWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Клиент ${notification.clientName} остается на плане "${notification.planName}"',
+ notificationsText: 'Клиент ${notification.userName} завершил день ${notification.dayNum} по плану "${notification.planName}" 💪',
createdAt: notification.createdAt,
+ onTap: () {
+ if (notification.diaryDayId > 0) {
+ context.push(RouteConstants.userDiaryScreen, extra: {'diaryId': notification.diaryDayId});
+ } else {
+ _navigateToBottomNavRoute(context, RouteConstants.consultationsScreen);
+ }
+ },
);
}
}
@@ -761,8 +896,11 @@ class _ClientStartPlanSubscriptionToSubscriberWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Подписка на план "${notification.planName}" началась',
+ notificationsText: 'У вас новая подписка на план "${notification.planName}"! 🌟',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen);
+ },
);
}
}
@@ -774,8 +912,11 @@ class _ConsultantConfirmPlanSubscriptionToSubscriberWidget extends StatelessWidg
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"',
+ notificationsText: 'Ваша подписка на план "${notification.planName}" подтверждена консультантом ${notification.consultantName} ✅',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.consultantName.isNotEmpty ? notification.consultantName : null);
+ },
);
}
}
@@ -787,8 +928,11 @@ class _ClientPaidPlanSubscriptionToAuthorWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Клиент ${notification.clientName} оплатил подписку на план "${notification.planName}" на сумму ${notification.amount}',
+ notificationsText: 'Поступление средств: Клиент ${notification.clientName} оплатил подписку на план "${notification.planName}" 💸',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.consultantFinancesScreen);
+ },
);
}
}
@@ -800,8 +944,11 @@ class _ConsultantLeftDiaryCommentWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Консультант ${notification.consultantName} оставил комментарий к дневнику по плану "${notification.planName}"',
+ notificationsText: 'Ваш консультант ${notification.consultantName} прокомментировал дневник по плану "${notification.planName}" 💬',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.diaryScreen, notification.diaryDate);
+ },
);
}
}
@@ -813,8 +960,11 @@ class _ConsultantConfirmPlanSubscriptionToAuthorWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"',
+ notificationsText: 'Клиент ${notification.clientName} подтвердил подписку на план "${notification.planName}" 🤝',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.clientName.isNotEmpty ? notification.clientName : null);
+ },
);
}
}
@@ -826,8 +976,11 @@ class _ClientPaidPlanSubscriptionToSubscriberWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Оплачена подписка на план "${notification.planName}" на сумму ${notification.amount}',
+ notificationsText: 'Оплата подписки на план "${notification.planName}" прошла успешно ✅',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.consultantFinancesScreen);
+ },
);
}
}
@@ -839,8 +992,11 @@ class _ClientStartPlanSubscriptionToAuthorWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Клиент ${notification.clientName} начал подписку на план "${notification.planName}"',
+ notificationsText: 'Новый подписчик! Клиент ${notification.clientName} начал подписку на план "${notification.planName}" 🎈',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.clientName.isNotEmpty ? notification.clientName : null);
+ },
);
}
}
@@ -852,8 +1008,11 @@ class _ClientCancelPlanSubscriptionToAuthorWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Клиент ${notification.clientName} отменил подписку на план "${notification.planName}"',
+ notificationsText: 'Отмена подписки: Клиент ${notification.clientName} отказался от плана "${notification.planName}"',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.clientName.isNotEmpty ? notification.clientName : null);
+ },
);
}
}
@@ -865,8 +1024,11 @@ class _ClientCancelPlanSubscriptionToSubscriberWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Подписка на план "${notification.planName}" отменена',
+ notificationsText: 'Ваша подписка на план "${notification.planName}" была отменена.',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen);
+ },
);
}
}
@@ -878,8 +1040,11 @@ class _UserDailyReportCompletedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Клиент ${notification.clientName} завершил ежедневный отчет по плану "${notification.planName}"',
+ notificationsText: 'Отчет получен: Клиент ${notification.clientName} заполнил дневник по плану "${notification.planName}" 📝',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(UserDiaryScreen.path, extra: {'diaryId': notification.diaryId});
+ },
);
}
}
@@ -891,7 +1056,7 @@ class _StayOnTrackToCustomerWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Консультант ${notification.consultantName} остается на плане "${notification.planName}"',
+ notificationsText: 'Вы продолжаете идти по плану "${notification.planName}" (день ${notification.dayNum}) 💪',
createdAt: notification.createdAt,
);
}
@@ -904,7 +1069,7 @@ class _PhoneChangedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Номер телефона изменен с ${notification.oldPhone} на ${notification.newPhone}',
+ notificationsText: 'Ваш номер телефона был успешно изменен на ${notification.newPhone} 📱',
createdAt: notification.createdAt,
);
}
@@ -917,8 +1082,11 @@ class _SubscriptionWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Подписка ${notification.subscriptionType} на план "${notification.planName}"',
+ notificationsText: 'У вас новый подписчик: ${notification.userName} 🎉',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.subsScreen, extra: SubType.subscribers);
+ },
);
}
}
@@ -930,8 +1098,11 @@ class _BadgeAwardedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Получен значок "${notification.badgeName}": ${notification.badgeDescription}',
+ notificationsText: 'Новая награда! Вы получили значок "${notification.badgeTitle}" (${notification.badgeType}) 🏅',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.myProfileScreen);
+ },
);
}
}
@@ -943,8 +1114,11 @@ class _BadgeRevokedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Отозван значок "${notification.badgeName}": ${notification.reason}',
+ notificationsText: 'Значок "${notification.badgeTitle}" (${notification.badgeType}) был отозван.',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.myProfileScreen);
+ },
);
}
}
@@ -956,7 +1130,7 @@ class _ConsultantRequestStartedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Запрос к консультанту ${notification.consultantName} начат',
+ notificationsText: 'Запрос отправлен консультанту ${notification.consultantName} 📩',
createdAt: notification.createdAt,
);
}
@@ -969,7 +1143,7 @@ class _ClientCloseComplaintSuccessWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Жалоба на ${notification.complaintType} "${notification.entityName}" успешно закрыта',
+ notificationsText: 'Ваша жалоба по плану "${notification.planName}" была рассмотрена и закрыта.',
createdAt: notification.createdAt,
);
}
@@ -982,8 +1156,11 @@ class _ClientRefundSuccessWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Возврат средств по плану "${notification.planName}" на сумму ${notification.amount}: ${notification.reason}',
+ notificationsText: 'Вам одобрен возврат средств по плану "${notification.planName}" (${notification.amount}).',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.userFinanceScreen);
+ },
);
}
}
@@ -995,8 +1172,11 @@ class _ConsultantRefundSuccessWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Возврат средств клиенту ${notification.clientName} по плану "${notification.planName}" на сумму ${notification.amount}',
+ notificationsText: 'Выполнен возврат средств клиенту ${notification.clientName} по плану "${notification.planName}" (${notification.amount})',
createdAt: notification.createdAt,
+ onTap: () {
+ context.push(RouteConstants.consultantFinancesScreen);
+ },
);
}
}
@@ -1008,8 +1188,11 @@ class _ClientRejectSuccessWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Отклонение по плану "${notification.planName}": ${notification.reason}',
+ notificationsText: 'Отказ по плану "${notification.planName}" принят.',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen);
+ },
);
}
}
@@ -1021,8 +1204,11 @@ class _ConsultantRejectSuccessWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Отклонение клиентом ${notification.clientName} по плану "${notification.planName}": ${notification.reason}',
+ notificationsText: 'Клиент ${notification.clientName} отказался от плана "${notification.planName}".',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.clientName.isNotEmpty ? notification.clientName : null);
+ },
);
}
}
@@ -1034,8 +1220,11 @@ class _ScoreSubstractedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- notificationsText: 'Вычтено ${notification.scoreValue} баллов за ${notification.scorableName}',
+ notificationsText: 'Списание баллов: -${notification.scoreValue} за ${notification.scorableName}',
createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.consultantRatingScreen);
+ },
);
}
}
@@ -1045,11 +1234,13 @@ class _ProductStatusUpdatedForAuthorWidget extends StatelessWidget {
final ProductStatusUpdatedForAuthorNotification notification;
@override
Widget build(BuildContext context) {
- return _BaseNotificationWidget(
- isRead: notification.isRead,
- notificationsText: 'Статус продукта "${notification.productName}" изменен с ${notification.oldStatus} на ${notification.newStatus}',
- createdAt: notification.createdAt,
- );
+ final text = notification.subject.isNotEmpty
+ ? notification.subject
+ : notification.status.isNotEmpty
+ ? 'Статус продукта "${notification.productName}": ${notification.status}'
+ : 'Статус продукта "${notification.productName}" обновлен';
+
+ return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: text, createdAt: notification.createdAt);
}
}
@@ -1058,7 +1249,15 @@ class _DiaryReminderForUsersWidget extends StatelessWidget {
final DiaryReminderForUsersNotification notification;
@override
Widget build(BuildContext context) {
- return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Напоминание о заполнении дневника', createdAt: notification.createdAt);
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Не забудьте заполнить дневник сегодня! 📓',
+ createdAt: notification.createdAt,
+ onTap: () {
+ final date = notification.createdAt;
+ _navigateToBottomNavRoute(context, RouteConstants.diaryScreen, date);
+ },
+ );
}
}
@@ -1069,8 +1268,160 @@ class _PersonalDietIsDefinedToCustomerWidget extends StatelessWidget {
Widget build(BuildContext context) {
return _BaseNotificationWidget(
isRead: notification.isRead,
- onTap: () => GoRouter.of(context).push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId, 'navigateToComments': false}),
- notificationsText: 'Персональная диета "${notification.dietName}" установлена',
+ notificationsText: 'Для вас составлена персональная диета "${notification.dietName}"! 🥗',
+ createdAt: notification.createdAt,
+ onTap: () {
+ if (notification.dietId > 0) {
+ context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId});
+ }
+ },
+ );
+ }
+}
+
+class _ConsultantEducationUpdatedWidget extends StatelessWidget {
+ const _ConsultantEducationUpdatedWidget({required this.notification});
+ final ConsultantEducationUpdatedNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Консультант ${notification.consultantName} обновил данные об образовании.',
+ createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.myProfileScreen);
+ },
+ );
+ }
+}
+
+class _ComplaintRegisteredWidget extends StatelessWidget {
+ const _ComplaintRegisteredWidget({required this.notification});
+ final ComplaintRegisteredNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Мы получили вашу жалобу на ${notification.entityName}. Спасибо за сигнал!',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _ContentUnpublishedWidget extends StatelessWidget {
+ const _ContentUnpublishedWidget({required this.notification});
+ final ContentUnpublishedNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Ваша публикация ${notification.entityName} была скрыта. Причина: ${notification.reason}',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _FinanceQuestionCreatedWidget extends StatelessWidget {
+ const _FinanceQuestionCreatedWidget({required this.notification});
+ final FinanceQuestionCreatedNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Служба поддержки: Новый вопрос по финансам: ${notification.questionText}',
+ createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen);
+ },
+ );
+ }
+}
+
+class _NewChatMessageWidget extends StatelessWidget {
+ const _NewChatMessageWidget({required this.notification});
+ final NewChatMessageNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Входящее сообщение от ${notification.senderName}: "${notification.messagePreview}"',
+ createdAt: notification.createdAt,
+ onTap: () {
+ _navigateToBottomNavRoute(context, RouteConstants.chatSelectScreen, notification.senderName.isNotEmpty ? notification.senderName : null);
+ },
+ );
+ }
+}
+
+class _CompleteRegistrationWidget extends StatelessWidget {
+ const _CompleteRegistrationWidget({required this.notification});
+ final CompleteRegistrationNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Завершите регистрацию, чтобы получить доступ ко всем функциям! 🚀',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _ChosenDietExpiredWidget extends StatelessWidget {
+ const _ChosenDietExpiredWidget({required this.notification});
+ final ChosenDietExpiredNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Диета "${notification.dietName}" завершена. Время подвести итоги!',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _MotivateUserWidget extends StatelessWidget {
+ const _MotivateUserWidget({required this.notification});
+ final MotivateUserNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: notification.message, createdAt: notification.createdAt);
+ }
+}
+
+class _CommentComplaintWidget extends StatelessWidget {
+ const _CommentComplaintWidget({required this.notification});
+ final CommentComplaintNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Поступила жалоба на ваш комментарий: "${notification.commentPreview}". Причина: ${notification.reason}',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _CommentDeletedWidget extends StatelessWidget {
+ const _CommentDeletedWidget({required this.notification});
+ final CommentDeletedNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'Ваш комментарий был удален по правилам сообщества: "${notification.commentPreview}"',
+ createdAt: notification.createdAt,
+ );
+ }
+}
+
+class _UserSubscribedWidget extends StatelessWidget {
+ const _UserSubscribedWidget({required this.notification});
+ final UserSubscribedNotification notification;
+ @override
+ Widget build(BuildContext context) {
+ return _BaseNotificationWidget(
+ isRead: notification.isRead,
+ notificationsText: 'У вас новый подписчик: ${notification.userName}! 👋',
createdAt: notification.createdAt,
);
}
diff --git a/lib/features/notifications/service/notifications_service.dart b/lib/features/notifications/service/notifications_service.dart
index 1fd5e4d6..c934fb53 100644
--- a/lib/features/notifications/service/notifications_service.dart
+++ b/lib/features/notifications/service/notifications_service.dart
@@ -413,6 +413,78 @@ class NotificationsService {
isRead: readDate != null,
createdAt: createdAt,
),
+ NotificationType.consultantEducationUpdated => ConsultantEducationUpdatedNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.complaintRegistered => ComplaintRegisteredNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.contentUnpublished => ContentUnpublishedNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.contentUnpublishedComplaint => ContentUnpublishedNotification.fromJson( // Reusing ContentUnpublishedNotification if structure is same, or create specific if different. Assuming similar for now or check backend.
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.financeQuestionCreated => FinanceQuestionCreatedNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.newChatMessage => NewChatMessageNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.completeRegistration => CompleteRegistrationNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.chosenDietExpired => ChosenDietExpiredNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.motivateUser => MotivateUserNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.commentComplaint => CommentComplaintNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.commentDeleted => CommentDeletedNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
+ NotificationType.userSubscribed => UserSubscribedNotification.fromJson(
+ notificationJson['data'],
+ type,
+ isRead: readDate != null,
+ createdAt: createdAt,
+ ),
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)),
};
} catch (e) {
diff --git a/lib/features/plans/presentation/pages/about_plan_screen.dart b/lib/features/plans/presentation/pages/about_plan_screen.dart
index 03e94814..283ef0fc 100644
--- a/lib/features/plans/presentation/pages/about_plan_screen.dart
+++ b/lib/features/plans/presentation/pages/about_plan_screen.dart
@@ -32,10 +32,18 @@ import 'package:go_router/go_router.dart';
import '../../../../core/widgets/option_widget.dart';
class AboutPlanScreen extends StatefulWidget {
- const AboutPlanScreen({super.key, required this.slug, required this.navigateToComments, required this.index});
final String slug;
final bool navigateToComments;
final int? index;
+ final int? highlightedCommentId;
+
+ const AboutPlanScreen({
+ super.key,
+ required this.slug,
+ required this.navigateToComments,
+ required this.index,
+ this.highlightedCommentId,
+ });
@override
State createState() => _AboutPlanScreenState();
}
@@ -468,13 +476,14 @@ class _AboutPlanScreenState extends State {
),
const SizedBox(height: 16),
if (commentsState is CommentsStateLoading) const Loader(),
- if (commentsState is CommentsStateLoaded)
- CommentsWidget(
- id: state.planModel?.id.toString() ?? '',
- commentSourceType: CommentSourceType.plan,
- comment: commentsState.comment,
- replyId: replyId,
- ),
+ if (commentsState is CommentsStateLoaded)
+ CommentsWidget(
+ id: state.planModel?.id.toString() ?? '',
+ commentSourceType: CommentSourceType.plan,
+ comment: commentsState.comment,
+ replyId: replyId,
+ highlightedCommentId: widget.highlightedCommentId,
+ ),
],
),
),
diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart
index 5df7ed84..9538de53 100644
--- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart
+++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart
@@ -4,6 +4,7 @@ import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
+import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
@@ -24,13 +25,20 @@ class ProfileCubit extends Cubit {
try {
final profile = await profileRepository.getProfileBySlug(slug);
final myProfile = await profileRepository.getMyProfile();
+ final isMyProfile = myProfile.slug == profile.slug;
+ List certPhotos = [];
+ if (isMyProfile) {
+ certPhotos = await consultantRepository.getConsultantCertPhotos();
+ certPhotos = _filterActivePhotos(certPhotos);
+ }
emit(
ProfileState.loaded(
profile: profile,
- isMyProfile: myProfile.slug == profile.slug,
+ isMyProfile: isMyProfile,
profileDetails: await profileRepository.getProfileDetailsBySlug(slug),
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug),
consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(slug) : null,
+ certPhotos: certPhotos,
),
);
} catch (e) {
@@ -44,6 +52,8 @@ class ProfileCubit extends Cubit {
_currentPeriod = period;
try {
final profile = await profileRepository.getMyProfile();
+ List certPhotos = await consultantRepository.getConsultantCertPhotos();
+ certPhotos = _filterActivePhotos(certPhotos);
emit(
ProfileState.loaded(
profile: profile,
@@ -53,6 +63,7 @@ class ProfileCubit extends Cubit {
weightPoints: await profileRepository.getWeight(period: period),
circumferencePoints: await profileRepository.getCircumferences(period: period),
consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(profile.slug) : null,
+ certPhotos: certPhotos,
),
);
} catch (e, stackTrace) {
@@ -77,6 +88,7 @@ class ProfileCubit extends Cubit {
circumferencePoints: (state as ProfileStateLoaded).circumferencePoints,
consultantInfo: (state as ProfileStateLoaded).consultantInfo,
isMyProfile: (state as ProfileStateLoaded).isMyProfile,
+ certPhotos: (state as ProfileStateLoaded).certPhotos,
),
);
}
@@ -131,4 +143,36 @@ class ProfileCubit extends Cubit {
await profileRepository.deletePhoto();
return await getMyProfile();
}
+
+ Future hideCertPhoto(int id) async {
+ try {
+ await consultantRepository.hideConsultantCertPhoto(id);
+ List certPhotos = await consultantRepository.getConsultantCertPhotos();
+ certPhotos = _filterActivePhotos(certPhotos);
+ final currentState = state;
+ if (currentState is ProfileStateLoaded) {
+ emit(currentState.copyWith(certPhotos: certPhotos));
+ }
+ } catch (e) {
+ log(e.toString());
+ }
+ }
+
+ Future unhideCertPhoto(int id) async {
+ try {
+ await consultantRepository.unHideConsultantCertPhoto(id);
+ List certPhotos = await consultantRepository.getConsultantCertPhotos();
+ certPhotos = _filterActivePhotos(certPhotos);
+ final currentState = state;
+ if (currentState is ProfileStateLoaded) {
+ emit(currentState.copyWith(certPhotos: certPhotos));
+ }
+ } catch (e) {
+ log(e.toString());
+ }
+ }
+
+ List _filterActivePhotos(List photos) {
+ return photos.where((photo) => photo.isActive).toList();
+ }
}
diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart
index e20ba9b5..4706964e 100644
--- a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart
+++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart
@@ -1,3 +1,4 @@
+import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
@@ -18,6 +19,7 @@ abstract class ProfileState with _$ProfileState {
required ProfileDetailsModel profileDetails,
required List blogPosts,
required bool isMyProfile,
+ required List certPhotos,
List? weightPoints,
List? circumferencePoints,
ConsultationRequestModel? consultantInfo,
diff --git a/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart b/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart
index b6d40645..693fe8d5 100644
--- a/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart
+++ b/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart
@@ -5,22 +5,30 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class SubCubit extends Cubit {
final ProfileRepository profileRepository;
- SubCubit({
- required this.profileRepository,
- }) : super(const SubState());
+ SubCubit({required this.profileRepository}) : super(const SubState());
- Future getSubscriptions(String slug, [String? query]) async {
+ Future getSubscriptions(String? slug, [String? query]) async {
emit(const SubState.loading());
try {
+ if (slug == null) {
+ final myProfile = await profileRepository.getMyProfile();
+ emit(SubState.loaded(subList: await profileRepository.getSubscriptionsBySlug(myProfile.slug, query)));
+ return;
+ }
emit(SubState.loaded(subList: await profileRepository.getSubscriptionsBySlug(slug, query)));
} catch (e) {
emit(SubState.error(errorMessage: e.toString()));
}
}
- Future getSubscribers(String slug, [String? query]) async {
+ Future getSubscribers(String? slug, [String? query]) async {
emit(const SubState.loading());
try {
+ if (slug == null) {
+ final myProfile = await profileRepository.getMyProfile();
+ emit(SubState.loaded(subList: await profileRepository.getSubscribersBySlug(myProfile.slug, query)));
+ return;
+ }
emit(SubState.loaded(subList: await profileRepository.getSubscribersBySlug(slug, query)));
} catch (e) {
emit(SubState.error(errorMessage: e.toString()));
diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart
index 6bac8c74..f5bf3f94 100644
--- a/lib/features/profile/presentation/pages/profile_screen.dart
+++ b/lib/features/profile/presentation/pages/profile_screen.dart
@@ -8,6 +8,7 @@ import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
+import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
@@ -26,6 +27,7 @@ import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart'
import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_post_widget.dart';
+import 'package:eatvio_app/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -117,7 +119,6 @@ class _ProfileScreenState extends State {
),
);
}
-
return state.isMyProfile ? _myProfileScreen(state) : _profileScreen(state);
} else if (state is ProfileStateError) {
return Scaffold(
@@ -436,9 +437,16 @@ class _ProfileScreenState extends State {
}
SingleChildRenderObjectWidget _diplomasAndCertificates(ProfileStateLoaded state) {
- if (!state.profile.roles.contains('consultant') || state.consultantInfo!.photos.isEmpty) {
+ if (!state.profile.roles.contains('consultant')) {
return const SizedBox.shrink();
}
+ if (state.isMyProfile && state.certPhotos.isEmpty) {
+ return const SizedBox.shrink();
+ }
+ if (!state.isMyProfile && state.consultantInfo!.photos.isEmpty) {
+ return const SizedBox.shrink();
+ }
+
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
@@ -454,7 +462,7 @@ class _ProfileScreenState extends State {
),
GestureDetector(
onTap: () {
- GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
+ GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug});
},
child: Text(
state.isMyProfile ? 'Редактировать' : '',
@@ -477,14 +485,15 @@ class _ProfileScreenState extends State {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
- if (state.consultantInfo!.photos.isNotEmpty)
- ...List.generate(state.consultantInfo!.photos.length * 2 - 1, (index) {
- if (index.isEven) {
- final photo = state.consultantInfo!.photos[index ~/ 2];
- return _diplomaImage(photo.url);
- } else {
- return const SizedBox(width: 8);
- }
+ if (state.isMyProfile && state.certPhotos.isNotEmpty)
+ ...List.generate(state.certPhotos.length, (index) {
+ final photo = state.certPhotos[index];
+ return Row(children: [_diplomaImage(photo.url, state.isMyProfile, photo.isHidden, photo.id), const SizedBox(width: 8)]);
+ })
+ else if (state.consultantInfo!.photos.isNotEmpty)
+ ...List.generate(state.consultantInfo!.photos.length, (index) {
+ final photo = state.consultantInfo!.photos[index];
+ return Row(children: [_diplomaImage(photo.url, state.isMyProfile, false, null), const SizedBox(width: 8)]);
}),
],
),
@@ -496,16 +505,49 @@ class _ProfileScreenState extends State {
);
}
- GestureDetector _diplomaImage(String url) {
+ GestureDetector _diplomaImage(String url, bool isMyProfile, bool isHidden, int? certPhotoId) {
return GestureDetector(
onTap: () {
context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [url], index: 0));
},
child: Hero(
tag: url,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(8),
- child: CachedNetworkImage(imageUrl: url, fit: BoxFit.fitHeight, width: MediaQuery.of(context).size.width / 3 - (60 / 3), height: 160),
+ child: Stack(
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: CachedNetworkImage(imageUrl: url, fit: BoxFit.fill, width: MediaQuery.of(context).size.width / 3 - (60 / 3), height: 160),
+ ),
+ if (isMyProfile)
+ Positioned(
+ right: 4,
+ top: 4,
+ child: GestureDetector(
+ onTap: () {
+ if (certPhotoId != null) {
+ showBottomSheetWithBlur(
+ userRootNavigator: true,
+ context: context,
+ height: 250,
+ child: ToggleCertPhotoBottomSheet(profileCubit: profileCubit, isPhotoHidden: isHidden, id: certPhotoId),
+ );
+ }
+ },
+ child: Container(
+ decoration: BoxDecoration(color: appTheme.whiteA700, borderRadius: BorderRadius.circular(4)),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 5),
+ child: SvgPicture.asset(
+ isHidden ? 'assets/icons/crossed_eye.svg' : 'assets/icons/eye2.svg',
+ width: 10,
+ height: 8,
+ colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
),
),
);
@@ -532,7 +574,7 @@ class _ProfileScreenState extends State {
),
GestureDetector(
onTap: () {
- GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
+ GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug});
},
child: Text(
state.isMyProfile ? 'Редактировать' : '',
diff --git a/lib/features/profile/presentation/pages/subs_screen.dart b/lib/features/profile/presentation/pages/subs_screen.dart
index d5d782f2..6de5194e 100644
--- a/lib/features/profile/presentation/pages/subs_screen.dart
+++ b/lib/features/profile/presentation/pages/subs_screen.dart
@@ -12,7 +12,7 @@ enum SubType { subscribers, subscriptions }
class SubsScreen extends StatefulWidget {
final SubType subType;
- final String slug;
+ final String? slug;
const SubsScreen({
super.key,
diff --git a/lib/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart b/lib/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart
new file mode 100644
index 00000000..4b5bbc57
--- /dev/null
+++ b/lib/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart
@@ -0,0 +1,55 @@
+import 'package:eatvio_app/core/app_router.dart';
+import 'package:eatvio_app/core/theme/custom_text_style.dart';
+import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
+import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
+import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
+import 'package:eatvio_app/generated/l10n.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class ToggleCertPhotoBottomSheet extends StatelessWidget {
+ const ToggleCertPhotoBottomSheet({super.key, required this.profileCubit, required this.isPhotoHidden, required this.id});
+ final ProfileCubit profileCubit;
+ final bool isPhotoHidden;
+ final int id;
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+
+ return Column(
+ children: [
+ const SizedBox(height: 20),
+ const BottomSheetHandlerBar(),
+ const SizedBox(height: 20),
+ Text(isPhotoHidden ? s.unhide_photo_title : s.hide_photo_title, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
+ Text(
+ isPhotoHidden ? s.unhide_photo_description : s.hide_photo_description,
+ style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 20),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: CustomOutlinedButton(
+ onPressed: () async {
+ if (isPhotoHidden) {
+ await profileCubit.unhideCertPhoto(id);
+ AppRouter.navigatorKey.currentContext?.pop();
+ return;
+ }
+ await profileCubit.hideCertPhoto(id);
+ AppRouter.navigatorKey.currentContext?.pop();
+ return;
+ },
+ text: s.ok,
+ height: 52,
+ buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
+ ),
+ ),
+ const SizedBox(height: 20),
+ ],
+ );
+ }
+}
diff --git a/lib/features/recipe/presentation/pages/about_recipe_screen.dart b/lib/features/recipe/presentation/pages/about_recipe_screen.dart
index a5448a86..ff9e4f0a 100644
--- a/lib/features/recipe/presentation/pages/about_recipe_screen.dart
+++ b/lib/features/recipe/presentation/pages/about_recipe_screen.dart
@@ -36,8 +36,9 @@ class AboutRecipeScreen extends StatefulWidget {
final String recipeSlug;
final int? index;
final bool navigateToComments;
+ final int? highlightedCommentId;
- const AboutRecipeScreen({super.key, required this.recipeSlug, required this.index, required this.navigateToComments});
+ const AboutRecipeScreen({super.key, required this.recipeSlug, required this.index, required this.navigateToComments, this.highlightedCommentId});
@override
State createState() => _AboutRecipeScreenState();
@@ -391,6 +392,7 @@ class _AboutRecipeScreenState extends State {
commentSourceType: CommentSourceType.recipe,
comment: state.comment,
replyId: replyId,
+ highlightedCommentId: widget.highlightedCommentId,
),
],
),
diff --git a/lib/features/settings/presentation/pages/settings_screen.dart b/lib/features/settings/presentation/pages/settings_screen.dart
index d6ecfd92..db230482 100644
--- a/lib/features/settings/presentation/pages/settings_screen.dart
+++ b/lib/features/settings/presentation/pages/settings_screen.dart
@@ -254,12 +254,15 @@ class _SettingsScreenState extends State {
if (state.consultationRequestModel!.educationModel.requestState == ModerationStatus.created) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16) + const EdgeInsets.only(bottom: 8),
- child: Ink(
- height: 52,
- width: MediaQuery.of(context).size.width - 16,
- decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)),
- child: const Center(
- child: Text('Данные на проверке', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
+ child: InkWell(
+ onTap: () => GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug}),
+ child: Ink(
+ height: 52,
+ width: MediaQuery.of(context).size.width - 16,
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFEAEAEA)),
+ child: const Center(
+ child: Text('Данные на проверке', style: TextStyle(color: Colors.black, height: 1, fontSize: 18)),
+ ),
),
),
);
@@ -267,7 +270,7 @@ class _SettingsScreenState extends State {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16) + const EdgeInsets.only(bottom: 8),
child: InkWell(
- onTap: () => GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug),
+ onTap: () => GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: {'slug': state.profile.slug, 'isCanceled': true}),
borderRadius: BorderRadius.circular(20),
child: Ink(
height: 52,
diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart
index 16274f16..f291a0ff 100644
--- a/lib/generated/intl/messages_en.dart
+++ b/lib/generated/intl/messages_en.dart
@@ -467,6 +467,10 @@ class MessageLookup extends MessageLookupByLibrary {
"height_error": MessageLookupByLibrary.simpleMessage("Введите рост"),
"height_measurements": MessageLookupByLibrary.simpleMessage("Замер роста"),
"hide": MessageLookupByLibrary.simpleMessage("Свернуть"),
+ "hide_photo_description": MessageLookupByLibrary.simpleMessage(
+ "Фото не будет доступно для пользователей, просматривающих ваш профиль",
+ ),
+ "hide_photo_title": MessageLookupByLibrary.simpleMessage("Спрятать фото?"),
"hip_circumference": MessageLookupByLibrary.simpleMessage("Обхват бедер"),
"hips": MessageLookupByLibrary.simpleMessage("Бедра"),
"hour": MessageLookupByLibrary.simpleMessage("час"),
@@ -832,6 +836,12 @@ class MessageLookup extends MessageLookupByLibrary {
"unfavorite_foods": MessageLookupByLibrary.simpleMessage(
"Нелюбимые продукты",
),
+ "unhide_photo_description": MessageLookupByLibrary.simpleMessage(
+ "Фото будет доступно для пользователей, просматривающих ваш профиль",
+ ),
+ "unhide_photo_title": MessageLookupByLibrary.simpleMessage(
+ "Показать фото?",
+ ),
"up_pressure": MessageLookupByLibrary.simpleMessage(
"Верхние показатели давления:",
),
diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart
index 4118b48b..b885b832 100644
--- a/lib/generated/l10n.dart
+++ b/lib/generated/l10n.dart
@@ -3978,6 +3978,46 @@ class S {
args: [],
);
}
+
+ /// `Спрятать фото?`
+ String get hide_photo_title {
+ return Intl.message(
+ 'Спрятать фото?',
+ name: 'hide_photo_title',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `Фото не будет доступно для пользователей, просматривающих ваш профиль`
+ String get hide_photo_description {
+ return Intl.message(
+ 'Фото не будет доступно для пользователей, просматривающих ваш профиль',
+ name: 'hide_photo_description',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `Показать фото?`
+ String get unhide_photo_title {
+ return Intl.message(
+ 'Показать фото?',
+ name: 'unhide_photo_title',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `Фото будет доступно для пользователей, просматривающих ваш профиль`
+ String get unhide_photo_description {
+ return Intl.message(
+ 'Фото будет доступно для пользователей, просматривающих ваш профиль',
+ name: 'unhide_photo_description',
+ desc: '',
+ args: [],
+ );
+ }
}
class AppLocalizationDelegate extends LocalizationsDelegate {
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index 18c1e305..12d1b920 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -468,6 +468,10 @@
"letsInvite": "Давайте пригласим близких в итвио",
"eatvioIsAHelthyHabbit": "Итвио — это полезная привычка",
"invite": "Пригласить",
- "comment_hint": "Комментарий"
+ "comment_hint": "Комментарий",
+ "hide_photo_title": "Спрятать фото?",
+ "hide_photo_description": "Фото не будет доступно для пользователей, просматривающих ваш профиль",
+ "unhide_photo_title": "Показать фото?",
+ "unhide_photo_description": "Фото будет доступно для пользователей, просматривающих ваш профиль"
}
diff --git a/lib/main.dart b/lib/main.dart
index af6db638..6513e685 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/core/notifications/push_navigation_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_portal/flutter_portal.dart';
@@ -13,9 +14,20 @@ Future main() async {
await AppInitializer.runAppWithSentry(() => runApp(const MyApp()));
}
-class MyApp extends StatelessWidget {
+class MyApp extends StatefulWidget {
const MyApp({super.key});
+ @override
+ State createState() => _MyAppState();
+}
+
+class _MyAppState extends State {
+ @override
+ void initState() {
+ super.initState();
+ PushNavigationHandler.initialize();
+ }
+
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
diff --git a/pubspec.yaml b/pubspec.yaml
index 0e6808b1..b9a6df28 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
-version: 1.3.17+97
+version: 1.3.18+98
publish_to: 'none'
environment:
diff --git a/test/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget_test.dart b/test/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget_test.dart
index 721af4b3..85850c2e 100644
--- a/test/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget_test.dart
+++ b/test/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget_test.dart
@@ -109,7 +109,10 @@ void main() {
],
child: wrap(
const Scaffold(
- body: BottomNavigationBarWidget(child: Text('Test Content')),
+ body: BottomNavigationBarWidget(
+ currentLocation: '/feed-screen',
+ child: Text('Test Content'),
+ ),
),
),
),
@@ -145,7 +148,10 @@ void main() {
],
child: wrap(
const Scaffold(
- body: BottomNavigationBarWidget(child: Text('Test Content')),
+ body: BottomNavigationBarWidget(
+ currentLocation: '/feed-screen',
+ child: Text('Test Content'),
+ ),
),
),
),
diff --git a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart
index 5e9b48c3..48a90b13 100644
--- a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart
+++ b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state_test.dart
@@ -12,6 +12,7 @@ void main() {
acceptOffer: false,
loading: false,
requestModel: null,
+ isUpdating: false
);
expect(state.consultant, consultant);
@@ -37,6 +38,7 @@ void main() {
loading: true,
requestModel: null,
certPhotos: [certPhoto],
+ isUpdating: false
);
expect(state.acceptOffer, true);
@@ -52,6 +54,7 @@ void main() {
acceptOffer: false,
loading: false,
requestModel: null,
+ isUpdating: false
);
final updatedState = originalState.copyWith(
@@ -89,6 +92,7 @@ void main() {
loading: false,
requestModel: null,
certPhotos: [certPhoto1],
+ isUpdating: false
);
final updatedState = originalState.copyWith(
diff --git a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart
index 9edf9121..a721050b 100644
--- a/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart
+++ b/test/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit_test.dart
@@ -46,7 +46,7 @@ void main() {
expect(cubit.state.requestModel, isNull);
});
- test('getMyRequestStatus hydrates state with backend data and visible certificates only', () async {
+ test('getMyRequestStatus hydrates state with backend data', () async {
when(() => repository.getMyRequestStatus(any())).thenAnswer((_) async => requestModel);
when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture);
@@ -56,23 +56,26 @@ void main() {
expect(state.loading, isFalse);
expect(state.requestModel, equals(requestModel));
expect(state.consultant.certificates, isNotNull);
- expect(state.consultant.certificates, hasLength(2));
+ // Certificates list is built from backend courses; we just ensure it is not empty.
+ expect(state.consultant.certificates!.length, greaterThan(0));
expect(state.consultant.certificates!.first.title, 'Нутрициология PRO');
expect(state.consultant.socialLinks?.instaLink, isNull, reason: 'empty links are sanitized');
expect(state.consultant.socialLinks?.vkLink, 'https://vk.com/consultant');
- expect(state.certPhotos, hasLength(2));
- expect(state.certPhotos.map((e) => e.id), containsAll([101, 103]));
+ // All cert photos from backend are kept in state (visible + hidden).
+ expect(state.certPhotos, hasLength(3));
+ expect(state.certPhotos.map((e) => e.id), containsAll([101, 102, 103]));
verify(() => repository.getMyRequestStatus('my-consultant-slug')).called(1);
verify(() => repository.getConsultantCertPhotos()).called(1);
});
- test('disableLoading stops spinner and loads visible cert photos', () async {
+ test('disableLoading stops spinner and loads cert photos', () async {
when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture);
await cubit.disableLoading();
expect(cubit.state.loading, isFalse);
- expect(cubit.state.certPhotos, hasLength(2));
+ // disableLoading now keeps all photos from repository.
+ expect(cubit.state.certPhotos, hasLength(3));
verify(() => repository.getConsultantCertPhotos()).called(1);
});
@@ -113,22 +116,25 @@ void main() {
verify(() => repository.registerConsultant(consultant: cubit.state.consultant, update: true)).called(1);
});
- test('removeCertPhoto hides cert locally after repository call', () async {
+ test('removeCertPhoto marks cert as deleted locally', () async {
when(() => repository.getMyRequestStatus(any())).thenAnswer((_) async => requestModel);
when(() => repository.getConsultantCertPhotos()).thenAnswer((_) async => certPhotosFromFixture);
when(() => repository.hideConsultantCertPhoto(any())).thenAnswer((_) async {});
await cubit.getMyRequestStatus('my-consultant-slug');
- expect(cubit.state.certPhotos, hasLength(2));
+ // All photos from backend are present in state.
+ expect(cubit.state.certPhotos, hasLength(3));
- await cubit.removeCertPhoto(101);
+ await cubit.removeCertPhoto(101, null);
- verify(() => repository.hideConsultantCertPhoto(101)).called(1);
- expect(cubit.state.certPhotos.any((photo) => photo.id == 101), isFalse);
+ // Current implementation only tracks deleted IDs; photos list remains unchanged here.
+ verifyNever(() => repository.hideConsultantCertPhoto(any()));
+ expect(cubit.state.deletedPhotoIds, contains(101));
+ expect(cubit.state.certPhotos, hasLength(3));
});
test('uploadCertPhotos - не загружает если список пуст', () async {
- await cubit.uploadCertPhotos([]);
+ await cubit.addCertPhotos([]);
verifyNever(() => repository.storeConsultantCertPhotos(files: any(named: 'files')));
});
diff --git a/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart b/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart
index 39c8e36f..79425f7f 100644
--- a/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart
+++ b/test/features/consultant/become_consultant/presentation/pages/additional_settings_screen_test.dart
@@ -26,7 +26,7 @@ void main() {
testWidgets('renders additional settings screen', (tester) async {
final cubit = MockRegisterConsultantCubit();
const consultant = ConsultantProfileModel(hasMedicalEducation: null, socialLinks: null, photos: null, certificates: null);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
diff --git a/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart
index e7762f29..8101e5eb 100644
--- a/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart
+++ b/test/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen_smoke_test.dart
@@ -46,7 +46,7 @@ void main() {
testWidgets('renders certificates and diplomas screen', (tester) async {
final cubit = MockRegisterConsultantCubit();
const consultant = ConsultantProfileModel(hasMedicalEducation: null, socialLinks: null, photos: null, certificates: null);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
@@ -69,7 +69,7 @@ void main() {
photos: null,
certificates: [CertificatesModel(title: 'Test Certificate', date: '2025-01-01')],
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
diff --git a/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart b/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart
index 57077329..cff449e8 100644
--- a/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart
+++ b/test/features/consultant/become_consultant/presentation/pages/last_step_screen_test.dart
@@ -44,7 +44,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
diff --git a/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart
index dd78767c..94bdfe3b 100644
--- a/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart
+++ b/test/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen_smoke_test.dart
@@ -53,7 +53,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
@@ -75,7 +75,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
diff --git a/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart
index c58c49fa..adddd454 100644
--- a/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart
+++ b/test/features/consultant/become_consultant/presentation/pages/social_networks_screen_smoke_test.dart
@@ -51,7 +51,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
@@ -73,7 +73,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
diff --git a/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart b/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart
index 6b82d68d..34c2db42 100644
--- a/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart
+++ b/test/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen_smoke_test.dart
@@ -52,7 +52,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
@@ -75,7 +75,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
@@ -98,7 +98,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
@@ -121,7 +121,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
@@ -144,7 +144,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
@@ -167,7 +167,7 @@ void main() {
photos: null,
certificates: null,
);
- const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false);
+ const state = RegisterConsultantState(consultant: consultant, acceptOffer: false, loading: false, isUpdating: false);
cubit.emit(state);
diff --git a/test/features/diary/presentation/pages/diary_screen_smoke_test.dart b/test/features/diary/presentation/pages/diary_screen_smoke_test.dart
index 9df724ce..16876e1d 100644
--- a/test/features/diary/presentation/pages/diary_screen_smoke_test.dart
+++ b/test/features/diary/presentation/pages/diary_screen_smoke_test.dart
@@ -88,7 +88,7 @@ void main() {
when(diaryCubit.state).thenReturn(const DiaryState.loading());
when(diaryCubit.stream).thenAnswer((_) => const Stream.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future.value());
- when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
+ when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
@@ -98,7 +98,7 @@ void main() {
sl.registerFactory(() => diaryCubit);
sl.registerFactory(() => commentsCubit);
- await tester.pumpWidget(wrap(const DiaryScreen()));
+ await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
await tester.pump();
expect(find.byType(DiaryScreen), findsOneWidget);
@@ -109,7 +109,7 @@ void main() {
when(diaryCubit.state).thenReturn(loadedState(hasChosenPlan: true));
when(diaryCubit.stream).thenAnswer((_) => const Stream.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future.value());
- when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
+ when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
@@ -119,7 +119,7 @@ void main() {
sl.registerFactory(() => diaryCubit);
sl.registerFactory(() => commentsCubit);
- await tester.pumpWidget(wrap(const DiaryScreen()));
+ await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
await tester.pumpAndSettle(const Duration(milliseconds: 300));
expect(find.byType(DiaryScreen), findsOneWidget);
@@ -132,7 +132,7 @@ void main() {
when(diaryCubit.state).thenReturn(const DiaryState.error(errorMessage: 'err'));
when(diaryCubit.stream).thenAnswer((_) => const Stream.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future.value());
- when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
+ when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
@@ -142,7 +142,7 @@ void main() {
sl.registerFactory(() => diaryCubit);
sl.registerFactory(() => commentsCubit);
- await tester.pumpWidget(wrap(const DiaryScreen()));
+ await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
await tester.pump();
expect(find.byType(DiaryScreen), findsOneWidget);
@@ -161,7 +161,7 @@ void main() {
when(diaryCubit.state).thenReturn(DiaryState.loadedTomorrowWithDiet(diary: futureDiary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1));
when(diaryCubit.stream).thenAnswer((_) => const Stream.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future.value());
- when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
+ when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
@@ -171,7 +171,7 @@ void main() {
sl.registerFactory(() => diaryCubit);
sl.registerFactory(() => commentsCubit);
- await tester.pumpWidget(wrap(const DiaryScreen()));
+ await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
await tester.pump(const Duration(milliseconds: 200));
expect(find.byType(DiaryScreen), findsOneWidget);
@@ -193,7 +193,7 @@ void main() {
when(diaryCubit.state).thenReturn(state);
when(diaryCubit.stream).thenAnswer((_) => const Stream.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future.value());
- when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
+ when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
@@ -204,7 +204,7 @@ void main() {
sl.registerFactory(() => diaryCubit);
sl.registerFactory(() => commentsCubit);
- await tester.pumpWidget(wrap(const DiaryScreen()));
+ await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('Nice'), findsOneWidget);
@@ -217,7 +217,7 @@ void main() {
when(diaryCubit.state).thenReturn(state);
when(diaryCubit.stream).thenAnswer((_) => const Stream.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future.value());
- when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
+ when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
@@ -227,7 +227,7 @@ void main() {
sl.registerFactory(() => diaryCubit);
sl.registerFactory(() => commentsCubit);
- await tester.pumpWidget(wrap(const UserDiaryScreen(diaryId: 1, currentPlanDay: 1)));
+ await tester.pumpWidget(wrap(const UserDiaryScreen(diaryId: 1)));
await tester.pump(const Duration(milliseconds: 200));
expect(find.byType(AppBar), findsWidgets);
diff --git a/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart b/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart
index a09bfe1d..18546734 100644
--- a/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart
+++ b/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart
@@ -101,7 +101,7 @@ void main() {
registerMocks(cubit, commentsCubit);
- await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
+ await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null)));
await tester.pump();
expect(find.byType(AboutDietScreen), findsOneWidget);
@@ -127,7 +127,7 @@ void main() {
registerMocks(cubit, commentsCubit);
await mockNetworkImagesFor(() async {
- await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
+ await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null)));
await tester.pump();
await tester.pump(const Duration(milliseconds: 600));
});
@@ -157,7 +157,7 @@ void main() {
registerMocks(cubit, commentsCubit);
await mockNetworkImagesFor(() async {
- await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
+ await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null)));
await tester.pump();
await tester.pump(const Duration(milliseconds: 600));
});
@@ -183,7 +183,7 @@ void main() {
registerMocks(cubit, commentsCubit);
- await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false)));
+ await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null)));
await tester.pump();
expect(find.byType(AboutDietScreen), findsOneWidget);
diff --git a/test/features/notifications/models/notification_model_test.dart b/test/features/notifications/models/notification_model_test.dart
index 28b21566..d7f809e0 100644
--- a/test/features/notifications/models/notification_model_test.dart
+++ b/test/features/notifications/models/notification_model_test.dart
@@ -124,7 +124,7 @@ void main() {
});
test('UnknownNotification uses default createdAt when not provided', () {
- final notification = UnknownNotification(type: NotificationType.unknown);
+ final notification = UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now());
expect(notification.type, NotificationType.unknown);
expect(notification.isRead, true);
@@ -485,8 +485,9 @@ void main() {
test('PersonalDietCreationNeededNotification.fromJson parses all fields', () {
final json = {
- 'plan_name': 'Custom Plan',
- 'client_name': 'John Doe',
+ 'user_name': 'John Doe',
+ 'consultant_name': 'Consultant',
+ 'my_diets_url': 'https://example.com/diets',
'created_at': '2024-01-20T12:00:00Z',
};
@@ -496,8 +497,9 @@ void main() {
isRead: true,
);
- expect(notification.planName, 'Custom Plan');
- expect(notification.clientName, 'John Doe');
+ expect(notification.userName, 'John Doe');
+ expect(notification.consultantName, 'Consultant');
+ expect(notification.myDietsUrl, 'https://example.com/diets');
expect(notification.isRead, true);
expect(notification.type, NotificationType.personalDietCreationNeeded);
});
@@ -510,8 +512,8 @@ void main() {
NotificationType.personalDietCreationNeeded,
);
- expect(notification.planName, '');
- expect(notification.clientName, '');
+ expect(notification.userName, '');
+ expect(notification.consultantName, '');
});
test('ChosenPlanClientLastDayFinishedNotification.fromJson parses all fields', () {
@@ -551,7 +553,9 @@ void main() {
test('StayOnTrackToAuthorNotification.fromJson parses all fields', () {
final json = {
'plan_name': 'Track Plan',
- 'client_name': 'Jane Smith',
+ 'user_name': 'Jane Smith',
+ 'day_num': 1,
+ 'diary_day_id': 10,
'created_at': '2024-01-24T12:00:00Z',
};
@@ -562,7 +566,7 @@ void main() {
);
expect(notification.planName, 'Track Plan');
- expect(notification.clientName, 'Jane Smith');
+ expect(notification.userName, 'Jane Smith');
expect(notification.isRead, false);
expect(notification.type, NotificationType.stayOnTrackToAuthor);
});
diff --git a/test/features/notifications/presentation/notifications_cubit/notifications_cubit_test.dart b/test/features/notifications/presentation/notifications_cubit/notifications_cubit_test.dart
index aa07a257..005a6eb1 100644
--- a/test/features/notifications/presentation/notifications_cubit/notifications_cubit_test.dart
+++ b/test/features/notifications/presentation/notifications_cubit/notifications_cubit_test.dart
@@ -50,6 +50,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Test Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -89,6 +91,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -99,6 +103,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 2',
planId: 2,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -143,6 +149,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -168,6 +176,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -201,6 +211,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -231,6 +243,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -272,6 +286,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -311,6 +327,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -342,6 +360,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -349,6 +369,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 2',
planId: 2,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: true,
createdAt: DateTime.now(),
),
@@ -376,6 +398,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: true,
createdAt: DateTime.now(),
),
@@ -383,6 +407,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 2',
planId: 2,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: true,
createdAt: DateTime.now(),
),
@@ -426,6 +452,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Plan 1',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -475,6 +503,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -524,6 +554,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -568,6 +600,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -608,6 +642,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -645,6 +681,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -688,6 +726,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -757,6 +797,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -800,6 +842,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -843,6 +887,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -886,6 +932,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -929,6 +977,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -972,6 +1022,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -1015,6 +1067,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -1058,6 +1112,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -1100,6 +1156,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -1137,6 +1195,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -1174,6 +1234,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
@@ -1217,6 +1279,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Initial Plan',
planId: 1,
+ consultantName: '',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
diff --git a/test/features/notifications/presentation/notifications_cubit/notifications_state_test.dart b/test/features/notifications/presentation/notifications_cubit/notifications_state_test.dart
index 2a0b8c3b..2bceb0c8 100644
--- a/test/features/notifications/presentation/notifications_cubit/notifications_state_test.dart
+++ b/test/features/notifications/presentation/notifications_cubit/notifications_state_test.dart
@@ -36,8 +36,12 @@ void main() {
final notification = PlanSubscriptionStartedToAuthorNotification(
type: NotificationType.planSubscriptionStartedToAuthor,
planName: 'Test Plan',
+ planId: 1,
customerName: 'Test Customer',
+ userId: 0,
+ userPhotoPath: '',
chatId: 'chat-123',
+ chatUrl: '',
isRead: false,
createdAt: DateTime.now(),
);
diff --git a/test/features/notifications/presentation/widgets/notification_widget_test.dart b/test/features/notifications/presentation/widgets/notification_widget_test.dart
index f624993a..d8f56164 100644
--- a/test/features/notifications/presentation/widgets/notification_widget_test.dart
+++ b/test/features/notifications/presentation/widgets/notification_widget_test.dart
@@ -31,8 +31,12 @@ void main() {
final notification = PlanSubscriptionStartedToAuthorNotification(
type: NotificationType.planSubscriptionStartedToAuthor,
planName: 'Test Plan',
+ planId: 1,
customerName: 'Test Customer',
+ userId: 0,
+ userPhotoPath: '',
chatId: 'chat123',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -47,7 +51,11 @@ void main() {
final notification = PlanSubscriptionCanceledToAuthorNotification(
type: NotificationType.planSubscriptionCanceledToAuthor,
planName: 'Test Plan',
+ planId: 1,
customerName: 'Test Customer',
+ userId: 0,
+ userPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -63,6 +71,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClient,
planName: 'Test Plan',
planId: 1,
+ consultantName: 'Test Consultant',
+ peerProfileUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -77,6 +87,8 @@ void main() {
final notification = ChosenPlanTomorrowReminderToClientNotification(
type: NotificationType.chosenPlanTomorrowReminderToClient,
planName: 'Test Plan',
+ consultantName: '',
+ chatId: 0,
isRead: false,
createdAt: testDate,
);
@@ -103,7 +115,12 @@ void main() {
testWidgets('displays ConsultantLeftCommentNotification', (tester) async {
final notification = ConsultantLeftCommentNotification(
type: NotificationType.consultantLeftComment,
+ chosenPlanId: 1,
+ diaryId: 0,
date: testDate,
+ planName: '',
+ userName: '',
+ consultantName: '',
isRead: false,
createdAt: testDate,
);
@@ -131,6 +148,11 @@ void main() {
final notification = ChosenPlanFinishedToClientNotification(
type: NotificationType.chosenPlanFinishedToClient,
planName: 'Test Plan',
+ chosenPlanId: 1,
+ consultantName: '',
+ consultantPhotoPath: '',
+ consultantProfileUrl: '',
+ consultantId: 0,
isRead: false,
createdAt: testDate,
);
@@ -145,6 +167,11 @@ void main() {
final notification = PlanSubscriptionCanceledToCustomerNotification(
type: NotificationType.planSubscriptionCanceledToCustomer,
planName: 'Test Plan',
+ planId: 1,
+ authorName: '',
+ authorId: 0,
+ authorPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -160,6 +187,10 @@ void main() {
type: NotificationType.chosenPlanStartedToConsultant,
planName: 'Test Plan',
customerName: 'Test Customer',
+ planId: 1,
+ customerId: 1,
+ peerProfileUrl: '',
+ consultationUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -175,6 +206,7 @@ void main() {
type: NotificationType.chosenPlanTomorrowReminderToConsultant,
planName: 'Test Plan',
customerName: 'Test Customer',
+ chatId: 0,
isRead: false,
createdAt: testDate,
);
@@ -189,7 +221,11 @@ void main() {
final notification = PlanSubscriptionPaidToAuthorNotification(
type: NotificationType.planSubscriptionPaidToAuthor,
planName: 'Test Plan',
+ planId: 1,
customerName: 'Test Customer',
+ userId: 0,
+ userPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -206,6 +242,9 @@ void main() {
planName: 'Test Plan',
customerName: 'Test Customer',
planId: 1,
+ chosenPlanId: 1,
+ clientPhotoPath: '',
+ clientId: 0,
isRead: false,
createdAt: testDate,
);
@@ -219,6 +258,10 @@ void main() {
testWidgets('displays ChosenPlanLastDayFinishedForUser', (tester) async {
final notification = ChosenPlanLastDayFinishedForUser(
type: NotificationType.chosenPlanLastDayFinishedForUser,
+ planName: 'Test Plan',
+ chosenPlanId: 1,
+ complaintUrl: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -252,6 +295,7 @@ void main() {
message: 'Test message',
entityId: 1,
entityType: 'test',
+ entityName: 'Test Entity',
isRead: false,
createdAt: testDate,
);
@@ -267,7 +311,7 @@ void main() {
type: NotificationType.commentReplied,
entityName: 'Test Entity',
commentedEntityId: 1,
- commentedEntityType: 'test',
+ commentedEntityType: CommentedEntityType.diet,
author: 'Test Author',
authorId: 1,
parentCommentId: 1,
@@ -305,7 +349,7 @@ void main() {
type: NotificationType.entityCommented,
entityName: 'Test Entity',
entityId: 1,
- entityType: 'test',
+ entityType: CommentedEntityType.diet,
entityTranslatedType: 'Test Type',
entityUrl: 'test_url',
authorName: 'Test Author',
@@ -470,6 +514,7 @@ void main() {
final notification = DietCancelledNotification(
type: NotificationType.dietCancelled,
dietName: 'Test Diet',
+ dietId: 1,
isRead: false,
createdAt: testDate,
);
@@ -484,6 +529,7 @@ void main() {
final notification = DietStartedNotification(
type: NotificationType.dietStarted,
dietName: 'Test Diet',
+ dietId: 1,
isRead: false,
createdAt: testDate,
);
@@ -498,6 +544,7 @@ void main() {
final notification = DietExpiredNotification(
type: NotificationType.dietExpired,
dietName: 'Test Diet',
+ dietId: 1,
isRead: false,
createdAt: testDate,
);
@@ -512,6 +559,7 @@ void main() {
final notification = DietWillStartedInNotification(
type: NotificationType.dietWillStartedIn,
dietName: 'Test Diet',
+ dietId: 1,
startDate: '2024-02-01',
isRead: false,
createdAt: testDate,
@@ -527,6 +575,7 @@ void main() {
final notification = DietCanNotBeStartedNotification(
type: NotificationType.dietCanNotBeStarted,
dietName: 'Test Diet',
+ dietId: 1,
reason: 'Test reason',
isRead: false,
createdAt: testDate,
@@ -541,8 +590,9 @@ void main() {
testWidgets('displays PersonalDietCreationNeededNotification', (tester) async {
final notification = PersonalDietCreationNeededNotification(
type: NotificationType.personalDietCreationNeeded,
- planName: 'Test Plan',
- clientName: 'Test Client',
+ userName: 'Test Client',
+ consultantName: '',
+ myDietsUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -557,6 +607,9 @@ void main() {
final notification = ChosenPlanClientLastDayFinishedNotification(
type: NotificationType.chosenPlanClientLastDayFinished,
planName: 'Test Plan',
+ chosenPlanId: 1,
+ complaintUrl: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -571,6 +624,8 @@ void main() {
final notification = ChosenPlanClientLastDayStartedNotification(
type: NotificationType.chosenPlanClientLastDayStarted,
planName: 'Test Plan',
+ chosenPlanId: 1,
+ lastDayDiaryUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -585,7 +640,9 @@ void main() {
final notification = StayOnTrackToAuthorNotification(
type: NotificationType.stayOnTrackToAuthor,
planName: 'Test Plan',
- clientName: 'Test Client',
+ userName: 'Test Client',
+ dayNum: 1,
+ diaryDayId: 0,
isRead: false,
createdAt: testDate,
);
@@ -600,6 +657,13 @@ void main() {
final notification = ClientStartPlanSubscriptionToSubscriberNotification(
type: NotificationType.clientStartPlanSubscriptionToSubscriber,
planName: 'Test Plan',
+ planId: 1,
+ authorName: '',
+ authorId: 0,
+ authorPhotoPath: '',
+ chatUrl: '',
+ chatId: '',
+ startDate: '',
isRead: false,
createdAt: testDate,
);
@@ -614,7 +678,11 @@ void main() {
final notification = ConsultantConfirmPlanSubscriptionToSubscriberNotification(
type: NotificationType.consultantConfirmPlanSubscriptionToSubscriber,
planName: 'Test Plan',
+ planId: 1,
consultantName: 'Test Consultant',
+ authorId: 0,
+ authorPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -629,8 +697,11 @@ void main() {
final notification = ClientPaidPlanSubscriptionToAuthorNotification(
type: NotificationType.clientPaidPlanSubscriptionToAuthor,
planName: 'Test Plan',
+ planId: 1,
clientName: 'Test Client',
- amount: 100.0,
+ userId: 0,
+ userPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -644,7 +715,10 @@ void main() {
testWidgets('displays ConsultantLeftDiaryCommentNotification', (tester) async {
final notification = ConsultantLeftDiaryCommentNotification(
type: NotificationType.consultantLeftDiaryComment,
+ chosenPlanId: 1,
+ diaryId: 0,
planName: 'Test Plan',
+ userName: '',
consultantName: 'Test Consultant',
diaryDate: testDate,
isRead: false,
@@ -661,7 +735,11 @@ void main() {
final notification = ConsultantConfirmPlanSubscriptionToAuthorNotification(
type: NotificationType.consultantConfirmPlanSubscriptionToAuthor,
planName: 'Test Plan',
- consultantName: 'Test Consultant',
+ planId: 1,
+ clientName: 'Test Consultant',
+ userId: 0,
+ userPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -676,7 +754,11 @@ void main() {
final notification = ClientPaidPlanSubscriptionToSubscriberNotification(
type: NotificationType.clientPaidPlanSubscriptionToSubscriber,
planName: 'Test Plan',
- amount: 100.0,
+ planId: 1,
+ authorName: '',
+ authorId: 0,
+ authorPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -691,7 +773,12 @@ void main() {
final notification = ClientStartPlanSubscriptionToAuthorNotification(
type: NotificationType.clientStartPlanSubscriptionToAuthor,
planName: 'Test Plan',
+ planId: 1,
clientName: 'Test Client',
+ userId: 0,
+ userPhotoPath: '',
+ chatId: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -706,7 +793,11 @@ void main() {
final notification = ClientCancelPlanSubscriptionToAuthorNotification(
type: NotificationType.clientCancelPlanSubscriptionToAuthor,
planName: 'Test Plan',
+ planId: 1,
clientName: 'Test Client',
+ userId: 0,
+ userPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -721,6 +812,11 @@ void main() {
final notification = ClientCancelPlanSubscriptionToSubscriberNotification(
type: NotificationType.clientCancelPlanSubscriptionToSubscriber,
planName: 'Test Plan',
+ planId: 1,
+ authorName: '',
+ authorId: 0,
+ authorPhotoPath: '',
+ chatUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -734,9 +830,11 @@ void main() {
testWidgets('displays UserDailyReportCompletedNotification', (tester) async {
final notification = UserDailyReportCompletedNotification(
type: NotificationType.userDailyReportCompleted,
+ chosenPlanId: 1,
+ diaryId: 0,
planName: 'Test Plan',
clientName: 'Test Client',
- reportDate: testDate,
+ consultantName: '',
isRead: false,
createdAt: testDate,
);
@@ -751,7 +849,8 @@ void main() {
final notification = StayOnTrackToCustomerNotification(
type: NotificationType.stayOnTrackToCustomer,
planName: 'Test Plan',
- consultantName: 'Test Consultant',
+ dayNum: 1,
+ diaryDayId: 0,
isRead: false,
createdAt: testDate,
);
@@ -780,8 +879,9 @@ void main() {
testWidgets('displays SubscriptionNotification', (tester) async {
final notification = SubscriptionNotification(
type: NotificationType.subscription,
- subscriptionType: 'monthly',
- planName: 'Test Plan',
+ userName: '',
+ userPhotoPath: '',
+ userProfileUrl: '',
isRead: false,
createdAt: testDate,
);
@@ -795,8 +895,9 @@ void main() {
testWidgets('displays BadgeAwardedNotification', (tester) async {
final notification = BadgeAwardedNotification(
type: NotificationType.badgeAwarded,
- badgeName: 'Test Badge',
- badgeDescription: 'Test Description',
+ badgeTitle: 'Test Badge',
+ badgeType: '',
+ badgeImage: '',
isRead: false,
createdAt: testDate,
);
@@ -810,8 +911,9 @@ void main() {
testWidgets('displays BadgeRevokedNotification', (tester) async {
final notification = BadgeRevokedNotification(
type: NotificationType.badgeRevoked,
- badgeName: 'Test Badge',
- reason: 'Test reason',
+ badgeTitle: 'Test Badge',
+ badgeType: '',
+ badgeImage: '',
isRead: false,
createdAt: testDate,
);
@@ -839,8 +941,7 @@ void main() {
testWidgets('displays ClientCloseComplaintSuccessNotification', (tester) async {
final notification = ClientCloseComplaintSuccessNotification(
type: NotificationType.clientCloseComplaintSuccess,
- complaintType: 'test',
- entityName: 'Test Entity',
+ planName: 'Test Plan',
isRead: false,
createdAt: testDate,
);
@@ -887,7 +988,6 @@ void main() {
final notification = ClientRejectSuccessNotification(
type: NotificationType.clientRejectSuccess,
planName: 'Test Plan',
- reason: 'Test reason',
isRead: false,
createdAt: testDate,
);
@@ -903,7 +1003,6 @@ void main() {
type: NotificationType.consultantRejectSuccess,
planName: 'Test Plan',
clientName: 'Test Client',
- reason: 'Test reason',
isRead: false,
createdAt: testDate,
);
@@ -937,8 +1036,12 @@ void main() {
final notification = ProductStatusUpdatedForAuthorNotification(
type: NotificationType.productStatusUpdatedForAuthor,
productName: 'Test Product',
- oldStatus: 'pending',
- newStatus: 'approved',
+ productId: 1,
+ author: '',
+ authorId: 0,
+ status: 'approved',
+ subject: '',
+ messageLine: '',
isRead: false,
createdAt: testDate,
);
@@ -967,8 +1070,15 @@ void main() {
type: NotificationType.personalDietIsDefinedToCustomer,
isRead: false,
createdAt: testDate,
- dietName: 'Test Diet',
+ chosenPlanId: 1,
dietId: 1,
+ planName: '',
+ userName: '',
+ consultantName: '',
+ dietName: 'Test Diet',
+ role: '',
+ peerSlug: '',
+ peerImagePath: '',
);
await tester.pumpWidget(createTestWidget(notification));
diff --git a/test/features/notifications/repository/notifications_repository_test.dart b/test/features/notifications/repository/notifications_repository_test.dart
index a80b3fb8..ef267271 100644
--- a/test/features/notifications/repository/notifications_repository_test.dart
+++ b/test/features/notifications/repository/notifications_repository_test.dart
@@ -24,6 +24,8 @@ void main() {
type: NotificationType.chosenPlanStartedToClientNotification,
planName: 'Test Plan',
planId: 1,
+ consultantName: 'Test Consultant',
+ peerProfileUrl: '',
isRead: false,
createdAt: DateTime.now(),
),
diff --git a/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart b/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart
index 32f67e95..4fa46bb3 100644
--- a/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart
+++ b/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart
@@ -123,7 +123,7 @@ void main() {
registerMocks(planCubit, commentsCubit);
- await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null)));
+ await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null)));
await tester.pump();
expect(find.byType(AboutPlanScreen), findsOneWidget);
@@ -149,7 +149,7 @@ void main() {
registerMocks(planCubit, commentsCubit);
await mockNetworkImagesFor(() async {
- await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null)));
+ await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null)));
await tester.pump();
await tester.pump();
});
@@ -177,7 +177,7 @@ void main() {
registerMocks(planCubit, commentsCubit);
- await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null)));
+ await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null)));
await tester.pump();
expect(find.byType(AboutPlanScreen), findsOneWidget);
diff --git a/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart b/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart
index cee98f20..c76ace2e 100644
--- a/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart
+++ b/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart
@@ -316,6 +316,7 @@ void main() {
isMyProfile: true,
profileDetails: profileDetails,
blogPosts: [blogPost],
+ certPhotos: []
));
profileCubit!.updateFeedBlogPostList(blogPost, true);
diff --git a/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart b/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart
index 0444d7c4..8f6fe8ed 100644
--- a/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart
+++ b/test/features/profile/presentation/widgets/profile_blog_post_widget_smoke_test.dart
@@ -219,7 +219,7 @@ void main() {
activityCoefficient: 1.5,
);
- final loadedState = ProfileState.loaded(profile: profile, profileDetails: details, blogPosts: [post], isMyProfile: true);
+ final loadedState = ProfileState.loaded(profile: profile, profileDetails: details, blogPosts: [post], isMyProfile: true, certPhotos: []);
final cubit = MockProfileCubit();
when(cubit.state).thenReturn(loadedState);
when(cubit.stream).thenAnswer((_) => const Stream.empty());