forked from eatvio/eatvio_app
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3892bf00aa | ||
|
|
89268e1132 | ||
|
|
08eeea6ba4 | ||
|
|
58d99424a0 | ||
|
|
c6cdb5bf01 | ||
|
|
8b1d08b2aa | ||
|
|
6f666a8859 | ||
|
|
c540cbe6df | ||
|
|
bb97c395dd |
@@ -9,7 +9,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: [ dev-office ]
|
runs-on: [ ubuntu-latest ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: https://github.com/actions/checkout@v3
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,6 +21,7 @@ env/.env
|
|||||||
android/key.properties
|
android/key.properties
|
||||||
app/upload-keystore.jks
|
app/upload-keystore.jks
|
||||||
coverage/
|
coverage/
|
||||||
|
backend/
|
||||||
.fvm/
|
.fvm/
|
||||||
.fvmrc
|
.fvmrc
|
||||||
/eatvio
|
/eatvio
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package com.eatvioapp.app
|
package com.eatvioapp.app
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Intent
|
||||||
import android.util.Log
|
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.HmsMessageService
|
||||||
import com.huawei.hms.push.RemoteMessage
|
import com.huawei.hms.push.RemoteMessage
|
||||||
|
|
||||||
@@ -13,6 +17,45 @@ class HmsPushService : HmsMessageService() {
|
|||||||
override fun onMessageReceived(message: RemoteMessage?) {
|
override fun onMessageReceived(message: RemoteMessage?) {
|
||||||
super.onMessageReceived(message)
|
super.onMessageReceived(message)
|
||||||
Log.i("HMS", "onMessageReceived: ${message?.data}")
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.eatvioapp.app
|
package com.eatvioapp.app
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
@@ -14,12 +14,13 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
private val channelName = "com.eatvioapp/hms_push"
|
private val channelName = "com.eatvioapp/hms_push"
|
||||||
|
private var methodChannel: MethodChannel? = null
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
||||||
.setMethodCallHandler { call, result ->
|
methodChannel?.setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getHuaweiToken" -> {
|
"getHuaweiToken" -> {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
@@ -71,5 +72,33 @@ class MainActivity: FlutterActivity() {
|
|||||||
else -> result.notImplemented()
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import 'package:go_router/go_router.dart';
|
|||||||
class AppRouter {
|
class AppRouter {
|
||||||
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
// Домены для deep links
|
// Домены для deep links (в т.ч. ссылки из пушей dev2.eatvio.com)
|
||||||
static const List<String> _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru'];
|
static const List<String> _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru', 'dev2.eatvio.com', 'www.dev2.eatvio.com'];
|
||||||
|
|
||||||
static GoRouter appRouter = GoRouter(
|
static GoRouter appRouter = GoRouter(
|
||||||
initialLocation: RouteConstants.updateScreen,
|
initialLocation: RouteConstants.updateScreen,
|
||||||
@@ -30,48 +30,41 @@ class AppRouter {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Обработка редиректов для deep links
|
static String? _resolveDeepLinkUri(Uri uri) {
|
||||||
/// Возвращает null для обычной навигации, чтобы не мешать внутренним переходам
|
|
||||||
static String? _handleRedirect(BuildContext context, GoRouterState state) {
|
|
||||||
final uri = state.uri;
|
|
||||||
final host = uri.host.toLowerCase();
|
final host = uri.host.toLowerCase();
|
||||||
final segments = uri.pathSegments;
|
final segments = uri.pathSegments;
|
||||||
|
|
||||||
// Проверяем, является ли это deep link по домену
|
|
||||||
final isDeepLink = _deepLinkHosts.contains(host);
|
final isDeepLink = _deepLinkHosts.contains(host);
|
||||||
|
|
||||||
// Если это не deep link, не мешаем обычной навигации
|
|
||||||
if (!isDeepLink) {
|
if (!isDeepLink) {
|
||||||
return null;
|
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) {
|
if (segments.isEmpty) {
|
||||||
developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter');
|
developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter');
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.feedScreen;
|
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') {
|
if (segments[0] != 'ru' && segments[0] != 'en') {
|
||||||
developer.log(
|
developer.log('Invalid language segment: ${segments[0]}, redirecting to feed', name: 'AppRouter');
|
||||||
'Invalid language segment: ${segments[0]}, redirecting to feed',
|
|
||||||
name: 'AppRouter',
|
|
||||||
);
|
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.feedScreen;
|
return RouteConstants.feedScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка deep links с учетом языка
|
|
||||||
// Формат: /{lang}/{type}/{identifier?}
|
|
||||||
if (segments.length == 4) {
|
if (segments.length == 4) {
|
||||||
// Формат: /{lang}/diary/{...}
|
|
||||||
final type = segments[1];
|
final type = segments[1];
|
||||||
|
|
||||||
if (type == 'diary') {
|
if (type == 'diary') {
|
||||||
@@ -79,15 +72,17 @@ class AppRouter {
|
|||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.diaryScreen;
|
return RouteConstants.diaryScreen;
|
||||||
}
|
}
|
||||||
|
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(
|
developer.log('Unknown deep link type for 4 segments: $type, redirecting to feed', name: 'AppRouter');
|
||||||
'Unknown deep link type for 4 segments: $type, redirecting to feed',
|
|
||||||
name: 'AppRouter',
|
|
||||||
);
|
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.feedScreen;
|
return RouteConstants.feedScreen;
|
||||||
} else if (segments.length == 3) {
|
} else if (segments.length == 3) {
|
||||||
// Формат: /{lang}/{type}/{identifier}
|
|
||||||
final type = segments[1];
|
final type = segments[1];
|
||||||
final contentIdentifier = segments[2];
|
final contentIdentifier = segments[2];
|
||||||
|
|
||||||
@@ -104,55 +99,111 @@ class AppRouter {
|
|||||||
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter');
|
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter');
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return '/about-plan-screen?slug=$contentIdentifier';
|
return '/about-plan-screen?slug=$contentIdentifier';
|
||||||
case 'diet':
|
case 'diet': {
|
||||||
developer.log('Deep link: diet/$contentIdentifier', name: 'AppRouter');
|
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();
|
FlutterNativeSplash.remove();
|
||||||
return '/about-diet-screen?slug=$contentIdentifier';
|
return '/about-diet-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
case 'chat':
|
case 'chat':
|
||||||
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
|
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.chatSelectScreen;
|
return RouteConstants.chatSelectScreen;
|
||||||
case 'profile':
|
case 'profile':
|
||||||
|
// В диплинке передан slug — открываем профиль пользователя по slug.
|
||||||
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
|
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return '/profile-screen?slug=$contentIdentifier';
|
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':
|
case 'diary':
|
||||||
developer.log('Deep link: diary/$contentIdentifier', name: 'AppRouter');
|
developer.log('Deep link: diary/$contentIdentifier', name: 'AppRouter');
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.diaryScreen;
|
return RouteConstants.diaryScreen;
|
||||||
|
case 'consultant_diary_comment':
|
||||||
|
developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter');
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
return RouteConstants.diaryScreen;
|
||||||
default:
|
default:
|
||||||
developer.log(
|
developer.log('Unknown deep link type: $type, redirecting to feed', name: 'AppRouter');
|
||||||
'Unknown deep link type: $type, redirecting to feed',
|
|
||||||
name: 'AppRouter',
|
|
||||||
);
|
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.feedScreen;
|
return RouteConstants.feedScreen;
|
||||||
}
|
}
|
||||||
} else if (segments.length == 2) {
|
} else if (segments.length == 2) {
|
||||||
// Формат: /{lang}/{type} - только для chat
|
|
||||||
final type = segments[1];
|
final type = segments[1];
|
||||||
|
|
||||||
if (type == 'chat') {
|
if (type == 'chat') {
|
||||||
developer.log('Deep link: chat', name: 'AppRouter');
|
developer.log('Deep link: chat', name: 'AppRouter');
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.chatSelectScreen;
|
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 {
|
if (type == 'diary') {
|
||||||
// Неизвестный формат deep link
|
developer.log('Deep link: diary', name: 'AppRouter');
|
||||||
developer.log(
|
FlutterNativeSplash.remove();
|
||||||
'Unknown deep link format: ${segments.length} segments, redirecting to feed',
|
return RouteConstants.diaryScreen;
|
||||||
name: 'AppRouter',
|
}
|
||||||
);
|
|
||||||
|
developer.log('Unknown deep link type for 2 segments: $type, redirecting to feed', name: 'AppRouter');
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
return RouteConstants.feedScreen;
|
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}) {
|
static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) {
|
||||||
|
|||||||
@@ -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/app_initializer.dart';
|
||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
|||||||
91
lib/core/notifications/push_navigation_handler.dart
Normal file
91
lib/core/notifications/push_navigation_handler.dart
Normal file
@@ -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<void> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -17,12 +17,20 @@ class ContentRoutes {
|
|||||||
name: 'aboutPlanScreen',
|
name: 'aboutPlanScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
final commentIdStr = state.uri.queryParameters['commentId'];
|
||||||
|
final commentId = int.tryParse(commentIdStr ?? '');
|
||||||
|
|
||||||
if (slug != null) {
|
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<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
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(
|
GoRoute(
|
||||||
@@ -30,12 +38,20 @@ class ContentRoutes {
|
|||||||
name: 'aboutRecipesScreen',
|
name: 'aboutRecipesScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
final commentIdStr = state.uri.queryParameters['commentId'];
|
||||||
|
final commentId = int.tryParse(commentIdStr ?? '');
|
||||||
|
|
||||||
if (slug != null) {
|
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<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
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(
|
GoRoute(
|
||||||
@@ -43,16 +59,28 @@ class ContentRoutes {
|
|||||||
name: 'aboutDietScreen',
|
name: 'aboutDietScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
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) {
|
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<String, dynamic>;
|
if (id != null && id > 0) {
|
||||||
return AboutDietScreen(
|
return AboutDietScreen(id: id, slug: null, index: null, navigateToComments: commentId != null, highlightedCommentId: commentId);
|
||||||
id: data['id'] as int?,
|
}
|
||||||
slug: data['slug'] as String?,
|
final data = state.extra as Map<String, dynamic>?;
|
||||||
index: data['index'] as int?,
|
if (data != null) {
|
||||||
navigateToComments: data['navigateToComments'] as bool,
|
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(
|
GoRoute(
|
||||||
@@ -65,12 +93,20 @@ class ContentRoutes {
|
|||||||
name: 'blogPostScreen',
|
name: 'blogPostScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final id = state.uri.queryParameters['id'];
|
final id = state.uri.queryParameters['id'];
|
||||||
|
final commentIdStr = state.uri.queryParameters['commentId'];
|
||||||
|
final commentId = int.tryParse(commentIdStr ?? '');
|
||||||
final parsedId = int.tryParse(id ?? '');
|
final parsedId = int.tryParse(id ?? '');
|
||||||
|
|
||||||
if (parsedId != null) {
|
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<String, dynamic>;
|
||||||
|
return BlogPostScreen(id: data['id'] as int, highlightedCommentId: data['commentId'] as int?);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
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_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/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/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/chat/presentation/pages/chats_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
||||||
@@ -32,7 +33,10 @@ class MainNavigationRoutes {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return BottomNavigationBarWidget(child: child);
|
return _MainShellWithNavScope(
|
||||||
|
currentLocation: state.matchedLocation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -58,7 +62,7 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.diaryScreen,
|
path: RouteConstants.diaryScreen,
|
||||||
name: 'diaryScreen',
|
name: 'diaryScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
|
pageBuilder: (context, state) => NoTransitionPage(child: DiaryScreen(date: state.extra as DateTime?)),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: ConsultationsScreen.path,
|
path: ConsultationsScreen.path,
|
||||||
@@ -68,7 +72,10 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.chatSelectScreen,
|
path: RouteConstants.chatSelectScreen,
|
||||||
name: '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(
|
GoRoute(
|
||||||
path: RouteConstants.notificationsScreen,
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class OtherRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '${RouteConstants.subsScreen}/:slug',
|
path: '${RouteConstants.subsScreen}/:slug',
|
||||||
name: 'subsScreen',
|
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
|
// Chat routes
|
||||||
@@ -285,8 +285,7 @@ class OtherRoutes {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final data = state.extra as Map<String, int>;
|
final data = state.extra as Map<String, int>;
|
||||||
final diaryId = data['diaryId'] as int;
|
final diaryId = data['diaryId'] as int;
|
||||||
final currentPlanDay = data['currentDiaryDay'] as int;
|
return UserDiaryScreen(diaryId: diaryId);
|
||||||
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:eatvio_app/core/websockets/ws_service.dart';
|
|||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.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/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
|
|
||||||
class WsManager {
|
class WsManager {
|
||||||
final WsService wsService;
|
final WsService wsService;
|
||||||
@@ -111,6 +112,7 @@ class WsManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
_subscribe();
|
_subscribe();
|
||||||
|
await _connectCubitsToStreams();
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await Sentry.captureException(e, stackTrace: st);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
@@ -118,6 +120,20 @@ class WsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Подключает ChatsCubit и NotificationsCubit к стримам сокета.
|
||||||
|
/// Вызывается после успешного initialize(), чтобы уведомления и чаты работали
|
||||||
|
/// и при входе уже залогиненного пользователя (редирект на feed), не только после экрана логина.
|
||||||
|
Future<void> _connectCubitsToStreams() async {
|
||||||
|
try {
|
||||||
|
await sl<ChatsCubit>().connectToWebSocket();
|
||||||
|
await sl<NotificationsCubit>().getNotifications();
|
||||||
|
await sl<NotificationsCubit>().connectToWebSocket();
|
||||||
|
} catch (e, st) {
|
||||||
|
log('WsManager._connectCubitsToStreams: $e', stackTrace: st);
|
||||||
|
await Sentry.captureException(e, stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _initPrivateNotificationsChannel() async {
|
Future<void> _initPrivateNotificationsChannel() async {
|
||||||
final token = await TokenStorage.getTokenFromStorage();
|
final token = await TokenStorage.getTokenFromStorage();
|
||||||
_privateNotificationChannel = client?.privateChannel(
|
_privateNotificationChannel = client?.privateChannel(
|
||||||
|
|||||||
@@ -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/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/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/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:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@@ -80,9 +79,6 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
try {
|
try {
|
||||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||||
await sl<ChatsCubit>().connectToWebSocket();
|
|
||||||
await sl<NotificationsCubit>().getNotifications();
|
|
||||||
await sl<NotificationsCubit>().connectToWebSocket();
|
|
||||||
await sl<FirebaseApi>().registerPushToken();
|
await sl<FirebaseApi>().registerPushToken();
|
||||||
showSuccessToast('Успешный вход');
|
showSuccessToast('Успешный вход');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -213,9 +209,6 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
await sl<WsManager>().initialize();
|
await sl<WsManager>().initialize();
|
||||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||||
await sl<ChatsCubit>().connectToWebSocket();
|
|
||||||
await sl<NotificationsCubit>().getNotifications();
|
|
||||||
await sl<NotificationsCubit>().connectToWebSocket();
|
|
||||||
await sl<FirebaseApi>().registerPushToken();
|
await sl<FirebaseApi>().registerPushToken();
|
||||||
showSuccessToast('Успешный вход');
|
showSuccessToast('Успешный вход');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -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/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/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/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/firebase_api.dart';
|
||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
import 'package:eatvio_app/core/utils/validation_utils.dart';
|
import 'package:eatvio_app/core/utils/validation_utils.dart';
|
||||||
@@ -74,9 +73,6 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
|||||||
try {
|
try {
|
||||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||||
await sl<ChatsCubit>().connectToWebSocket();
|
|
||||||
await sl<NotificationsCubit>().getNotifications();
|
|
||||||
await sl<NotificationsCubit>().connectToWebSocket();
|
|
||||||
await sl<FirebaseApi>().registerPushToken();
|
await sl<FirebaseApi>().registerPushToken();
|
||||||
showSuccessToast('Успешная регистрация');
|
showSuccessToast('Успешная регистрация');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -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/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/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/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:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
|
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
|
||||||
@@ -76,9 +75,6 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
|||||||
try {
|
try {
|
||||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||||
await sl<ChatsCubit>().connectToWebSocket();
|
|
||||||
await sl<NotificationsCubit>().getNotifications();
|
|
||||||
await sl<NotificationsCubit>().connectToWebSocket();
|
|
||||||
await sl<FirebaseApi>().registerPushToken();
|
await sl<FirebaseApi>().registerPushToken();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
|
|
||||||
class BlogPostScreen extends StatefulWidget {
|
class BlogPostScreen extends StatefulWidget {
|
||||||
final int id;
|
final int id;
|
||||||
|
final int? highlightedCommentId;
|
||||||
|
|
||||||
const BlogPostScreen({super.key, required this.id});
|
const BlogPostScreen({super.key, required this.id, this.highlightedCommentId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BlogPostScreen> createState() => _BlogPostScreenState();
|
State<BlogPostScreen> createState() => _BlogPostScreenState();
|
||||||
@@ -303,7 +304,13 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (state is CommentsStateLoading) const Loader(),
|
if (state is CommentsStateLoading) const Loader(),
|
||||||
if (state is CommentsStateLoaded)
|
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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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<BottomNavScope>()?.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;
|
||||||
|
}
|
||||||
@@ -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/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_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/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_cubit.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.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';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class BottomNavigationBarWidget extends StatefulWidget {
|
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
|
@override
|
||||||
State<BottomNavigationBarWidget> createState() => _BottomNavigationBarWidgetState();
|
State<BottomNavigationBarWidget> createState() => _BottomNavigationBarWidgetState();
|
||||||
@@ -66,6 +73,24 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
static const Color _devAccentColor = Color(0xFFF59E0B); // amber-500
|
||||||
|
|
||||||
Widget _buildLogoIcon() {
|
Widget _buildLogoIcon() {
|
||||||
@@ -121,6 +146,22 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
// Инициализируем pages на основе profileModel
|
// Инициализируем pages на основе profileModel
|
||||||
pages ??= _getPages(isConsultant);
|
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(
|
return Scaffold(
|
||||||
body: widget.child,
|
body: widget.child,
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
@@ -129,7 +170,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
selectedFontSize: 10,
|
selectedFontSize: 10,
|
||||||
unselectedFontSize: 10,
|
unselectedFontSize: 10,
|
||||||
selectedItemColor: appTheme.blueA400,
|
selectedItemColor: appTheme.blueA400,
|
||||||
currentIndex: currentBottomNavBarIndex,
|
currentIndex: effectiveIndex,
|
||||||
items: [
|
items: [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
label: '',
|
label: '',
|
||||||
@@ -204,7 +245,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
ImageConstant.icnChat,
|
ImageConstant.icnChat,
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
colorFilter: pages![currentBottomNavBarIndex] == '/chat-select-screen'
|
colorFilter: pages![effectiveIndex] == '/chat-select-screen'
|
||||||
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
|
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
|
||||||
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
|
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
|
||||||
),
|
),
|
||||||
@@ -226,7 +267,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
padding: const EdgeInsets.only(bottom: 2),
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
ImageConstant.icnBell,
|
ImageConstant.icnBell,
|
||||||
colorFilter: pages![currentBottomNavBarIndex] == '/notifications-screen'
|
colorFilter: pages![effectiveIndex] == '/notifications-screen'
|
||||||
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
|
? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)
|
||||||
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
|
: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
|
||||||
width: 20,
|
width: 20,
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class ChatsScreen extends StatefulWidget {
|
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
|
@override
|
||||||
State<ChatsScreen> createState() => _ChatsScreenState();
|
State<ChatsScreen> createState() => _ChatsScreenState();
|
||||||
@@ -23,12 +27,17 @@ class _ChatsScreenState extends State<ChatsScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Инициализация ChatsCubit при первом открытии экрана
|
final query = widget.initialSearchQuery?.trim();
|
||||||
|
if (query != null && query.isNotEmpty) {
|
||||||
|
searchController.text = query;
|
||||||
|
}
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final cubit = context.read<ChatsCubit>();
|
final cubit = context.read<ChatsCubit>();
|
||||||
final state = cubit.state;
|
final state = cubit.state;
|
||||||
if (state.state == BaseState.initial) {
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
);
|
);
|
||||||
return;
|
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),
|
borderRadius: BorderRadius.circular(100),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
@@ -255,7 +255,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
.where((e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished)
|
.where((e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished)
|
||||||
.lastOrNull;
|
.lastOrNull;
|
||||||
if (lastActiveDay != null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
showErrorDialog(
|
showErrorDialog(
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
|
|
||||||
String comment = '';
|
String comment = '';
|
||||||
|
|
||||||
|
void setInitialDate(DateTime? date) {
|
||||||
|
if (date == null) return;
|
||||||
|
selectedDay = date;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> setSelectedDay(DateTime day) async {
|
Future<bool> setSelectedDay(DateTime day) async {
|
||||||
final prevDay = selectedDay;
|
final prevDay = selectedDay;
|
||||||
final prevMeal = selectedMeal;
|
final prevMeal = selectedMeal;
|
||||||
@@ -63,7 +68,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return canSelect;
|
return canSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId}) async {
|
Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId, DateTime? date}) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
try {
|
try {
|
||||||
if (needLoading) {
|
if (needLoading) {
|
||||||
@@ -77,6 +82,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final diaryDay = await getDiary();
|
final diaryDay = await getDiary();
|
||||||
if (diaryDay == null) {
|
if (diaryDay == null) {
|
||||||
emit(const DiaryState.error(errorMessage: 'Diary not found'));
|
emit(const DiaryState.error(errorMessage: 'Diary not found'));
|
||||||
|
|||||||
@@ -24,31 +24,29 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class DiaryScreen extends StatelessWidget {
|
class DiaryScreen extends StatelessWidget {
|
||||||
const DiaryScreen({super.key});
|
const DiaryScreen({super.key, required this.date});
|
||||||
|
final DateTime? date;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const DiaryScreenContent();
|
return DiaryScreenContent(date: date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserDiaryScreen extends StatelessWidget {
|
class UserDiaryScreen extends StatelessWidget {
|
||||||
static const path = '/user-diary-screen';
|
static const path = '/user-diary-screen';
|
||||||
static const name = 'userDiaryScreen';
|
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 diaryId;
|
||||||
final int currentPlanDay;
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DiaryScreenContent(diaryId: diaryId, currentPlanDay: currentPlanDay);
|
return DiaryScreenContent(diaryId: diaryId,);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiaryScreenContent extends StatefulWidget {
|
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? diaryId;
|
||||||
final int? currentPlanDay;
|
final DateTime? date;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DiaryScreenContent> createState() => _DiaryScreenContentState();
|
State<DiaryScreenContent> createState() => _DiaryScreenContentState();
|
||||||
}
|
}
|
||||||
@@ -61,7 +59,11 @@ class _DiaryScreenContentState extends State<DiaryScreenContent> {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<DiaryCubit>(create: (BuildContext context) => sl<DiaryCubit>()..fetchDiaryV2(needLoading: true, diaryId: widget.diaryId)),
|
BlocProvider<DiaryCubit>(
|
||||||
|
create: (BuildContext context) => sl<DiaryCubit>()
|
||||||
|
..setInitialDate(widget.date)
|
||||||
|
..fetchDiaryV2(needLoading: true, diaryId: widget.diaryId),
|
||||||
|
),
|
||||||
BlocProvider(create: (context) => sl<ConsultantCommentsCubit>()),
|
BlocProvider(create: (context) => sl<ConsultantCommentsCubit>()),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<DiaryCubit, DiaryState>(
|
child: BlocBuilder<DiaryCubit, DiaryState>(
|
||||||
@@ -91,7 +93,7 @@ class _DiaryScreenContentState extends State<DiaryScreenContent> {
|
|||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
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 DiaryStateLoading) ...[const SizedBox(height: 60), const Loader()],
|
||||||
if (state is DiaryStateLoaded)
|
if (state is DiaryStateLoaded)
|
||||||
WakeupAndRetireWidget(
|
WakeupAndRetireWidget(
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:table_calendar/table_calendar.dart';
|
import 'package:table_calendar/table_calendar.dart';
|
||||||
|
|
||||||
class CalendarWidget extends StatefulWidget {
|
class CalendarWidget extends StatefulWidget {
|
||||||
const CalendarWidget({super.key});
|
const CalendarWidget({super.key, this.date});
|
||||||
|
final DateTime? date;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CalendarWidget> createState() => _CalendarWidgetState();
|
State<CalendarWidget> createState() => _CalendarWidgetState();
|
||||||
@@ -18,6 +19,23 @@ class _CalendarWidgetState extends State<CalendarWidget> {
|
|||||||
CalendarFormat _calendarFormat = CalendarFormat.week;
|
CalendarFormat _calendarFormat = CalendarFormat.week;
|
||||||
DateTime _focusedDay = DateTime.now();
|
DateTime _focusedDay = DateTime.now();
|
||||||
DateTime _selectedDay = 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ class AboutDietScreen extends StatefulWidget {
|
|||||||
final int? id;
|
final int? id;
|
||||||
final int? index;
|
final int? index;
|
||||||
final bool navigateToComments;
|
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
|
@override
|
||||||
State<AboutDietScreen> createState() => _AboutDietScreenState();
|
State<AboutDietScreen> createState() => _AboutDietScreenState();
|
||||||
@@ -433,6 +434,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
commentSourceType: CommentSourceType.diet,
|
commentSourceType: CommentSourceType.diet,
|
||||||
comment: state.comment,
|
comment: state.comment,
|
||||||
replyId: replyId,
|
replyId: replyId,
|
||||||
|
highlightedCommentId: widget.highlightedCommentId,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,14 +23,60 @@ class CommentsWidget extends StatefulWidget {
|
|||||||
final CommentSourceType commentSourceType;
|
final CommentSourceType commentSourceType;
|
||||||
final CommentModel comment;
|
final CommentModel comment;
|
||||||
final int? replyId;
|
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
|
@override
|
||||||
State<CommentsWidget> createState() => _CommentsWidgetState();
|
State<CommentsWidget> createState() => _CommentsWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CommentsWidgetState extends State<CommentsWidget> {
|
class _CommentsWidgetState extends State<CommentsWidget> {
|
||||||
|
final Map<int, GlobalKey> 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
@@ -49,8 +95,10 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
|
Widget _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
|
||||||
|
final key = commentKeys.putIfAbsent(comment.id, () => GlobalKey());
|
||||||
final isMe = (sl<ProfileAvatarCubit>().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug;
|
final isMe = (sl<ProfileAvatarCubit>().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
key: key,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showCommentsActionsBottomSheet(
|
showCommentsActionsBottomSheet(
|
||||||
context,
|
context,
|
||||||
@@ -83,7 +131,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -267,33 +267,113 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
isRead: isRead,
|
isRead: isRead,
|
||||||
createdAt: createdAt,
|
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) {
|
} catch (e) {
|
||||||
return UnknownNotification(type: NotificationType.unknown);
|
return UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleNotification(String notificationData) {
|
void _handleNotification(String notificationData) {
|
||||||
final json = jsonDecode(notificationData) as Map<String, dynamic>;
|
final json = jsonDecode(notificationData) as Map<String, dynamic>;
|
||||||
|
|
||||||
// Улучшенная логика определения типа уведомления
|
// Улучшенная логика определения типа уведомления (порядок: database_type → type → notification_class_name)
|
||||||
String? stringifiedType;
|
String? stringifiedType;
|
||||||
|
|
||||||
// Сначала проверяем поле 'type' в корне JSON
|
// Сначала проверяем поле 'database_type' в корне JSON
|
||||||
if (json.containsKey('type') && json['type'] != null) {
|
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();
|
stringifiedType = json['type'].toString();
|
||||||
log('Found type in root: $stringifiedType');
|
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) {
|
else if (json.containsKey('notification_class_name') && json['notification_class_name'] != null) {
|
||||||
stringifiedType = json['notification_class_name'].toString();
|
stringifiedType = json['notification_class_name'].toString();
|
||||||
log('Found notification_class_name in root: $stringifiedType');
|
log('Found notification_class_name in root: $stringifiedType');
|
||||||
}
|
}
|
||||||
// Если есть поле 'data', проверяем внутри него
|
// Если есть поле 'data', проверяем внутри него в том же порядке
|
||||||
else if (json.containsKey('data') && json['data'] is Map<String, dynamic>) {
|
else if (json.containsKey('data') && json['data'] is Map<String, dynamic>) {
|
||||||
final data = json['data'] as Map<String, dynamic>;
|
final data = json['data'] as Map<String, dynamic>;
|
||||||
if (data.containsKey('type') && data['type'] != null) {
|
if (data.containsKey('database_type') && data['database_type'] != null) {
|
||||||
|
stringifiedType = data['database_type'].toString();
|
||||||
|
log('Found database_type in data: $stringifiedType');
|
||||||
|
} else if (data.containsKey('type') && data['type'] != null) {
|
||||||
stringifiedType = data['type'].toString();
|
stringifiedType = data['type'].toString();
|
||||||
log('Found type in data: $stringifiedType');
|
log('Found type in data: $stringifiedType');
|
||||||
} else if (data.containsKey('notification_class_name') && data['notification_class_name'] != null) {
|
} else if (data.containsKey('notification_class_name') && data['notification_class_name'] != null) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -413,6 +413,78 @@ class NotificationsService {
|
|||||||
isRead: readDate != null,
|
isRead: readDate != null,
|
||||||
createdAt: createdAt,
|
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)),
|
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -32,10 +32,18 @@ import 'package:go_router/go_router.dart';
|
|||||||
import '../../../../core/widgets/option_widget.dart';
|
import '../../../../core/widgets/option_widget.dart';
|
||||||
|
|
||||||
class AboutPlanScreen extends StatefulWidget {
|
class AboutPlanScreen extends StatefulWidget {
|
||||||
const AboutPlanScreen({super.key, required this.slug, required this.navigateToComments, required this.index});
|
|
||||||
final String slug;
|
final String slug;
|
||||||
final bool navigateToComments;
|
final bool navigateToComments;
|
||||||
final int? index;
|
final int? index;
|
||||||
|
final int? highlightedCommentId;
|
||||||
|
|
||||||
|
const AboutPlanScreen({
|
||||||
|
super.key,
|
||||||
|
required this.slug,
|
||||||
|
required this.navigateToComments,
|
||||||
|
required this.index,
|
||||||
|
this.highlightedCommentId,
|
||||||
|
});
|
||||||
@override
|
@override
|
||||||
State<AboutPlanScreen> createState() => _AboutPlanScreenState();
|
State<AboutPlanScreen> createState() => _AboutPlanScreenState();
|
||||||
}
|
}
|
||||||
@@ -468,13 +476,14 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (commentsState is CommentsStateLoading) const Loader(),
|
if (commentsState is CommentsStateLoading) const Loader(),
|
||||||
if (commentsState is CommentsStateLoaded)
|
if (commentsState is CommentsStateLoaded)
|
||||||
CommentsWidget(
|
CommentsWidget(
|
||||||
id: state.planModel?.id.toString() ?? '',
|
id: state.planModel?.id.toString() ?? '',
|
||||||
commentSourceType: CommentSourceType.plan,
|
commentSourceType: CommentSourceType.plan,
|
||||||
comment: commentsState.comment,
|
comment: commentsState.comment,
|
||||||
replyId: replyId,
|
replyId: replyId,
|
||||||
),
|
highlightedCommentId: widget.highlightedCommentId,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,22 +5,30 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
class SubCubit extends Cubit<SubState> {
|
class SubCubit extends Cubit<SubState> {
|
||||||
final ProfileRepository profileRepository;
|
final ProfileRepository profileRepository;
|
||||||
|
|
||||||
SubCubit({
|
SubCubit({required this.profileRepository}) : super(const SubState());
|
||||||
required this.profileRepository,
|
|
||||||
}) : super(const SubState());
|
|
||||||
|
|
||||||
Future<void> getSubscriptions(String slug, [String? query]) async {
|
Future<void> getSubscriptions(String? slug, [String? query]) async {
|
||||||
emit(const SubState.loading());
|
emit(const SubState.loading());
|
||||||
try {
|
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)));
|
emit(SubState.loaded(subList: await profileRepository.getSubscriptionsBySlug(slug, query)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(SubState.error(errorMessage: e.toString()));
|
emit(SubState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getSubscribers(String slug, [String? query]) async {
|
Future<void> getSubscribers(String? slug, [String? query]) async {
|
||||||
emit(const SubState.loading());
|
emit(const SubState.loading());
|
||||||
try {
|
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)));
|
emit(SubState.loaded(subList: await profileRepository.getSubscribersBySlug(slug, query)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(SubState.error(errorMessage: e.toString()));
|
emit(SubState.error(errorMessage: e.toString()));
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ enum SubType { subscribers, subscriptions }
|
|||||||
|
|
||||||
class SubsScreen extends StatefulWidget {
|
class SubsScreen extends StatefulWidget {
|
||||||
final SubType subType;
|
final SubType subType;
|
||||||
final String slug;
|
final String? slug;
|
||||||
|
|
||||||
const SubsScreen({
|
const SubsScreen({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ class AboutRecipeScreen extends StatefulWidget {
|
|||||||
final String recipeSlug;
|
final String recipeSlug;
|
||||||
final int? index;
|
final int? index;
|
||||||
final bool navigateToComments;
|
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
|
@override
|
||||||
State<AboutRecipeScreen> createState() => _AboutRecipeScreenState();
|
State<AboutRecipeScreen> createState() => _AboutRecipeScreenState();
|
||||||
@@ -391,6 +392,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
|||||||
commentSourceType: CommentSourceType.recipe,
|
commentSourceType: CommentSourceType.recipe,
|
||||||
comment: state.comment,
|
comment: state.comment,
|
||||||
replyId: replyId,
|
replyId: replyId,
|
||||||
|
highlightedCommentId: widget.highlightedCommentId,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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/app_router.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_portal/flutter_portal.dart';
|
import 'package:flutter_portal/flutter_portal.dart';
|
||||||
@@ -13,9 +14,20 @@ Future<void> main() async {
|
|||||||
await AppInitializer.runAppWithSentry(() => runApp(const MyApp()));
|
await AppInitializer.runAppWithSentry(() => runApp(const MyApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyApp> createState() => _MyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyAppState extends State<MyApp> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
PushNavigationHandler.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
|
|||||||
@@ -109,7 +109,10 @@ void main() {
|
|||||||
],
|
],
|
||||||
child: wrap(
|
child: wrap(
|
||||||
const Scaffold(
|
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(
|
child: wrap(
|
||||||
const Scaffold(
|
const Scaffold(
|
||||||
body: BottomNavigationBarWidget(child: Text('Test Content')),
|
body: BottomNavigationBarWidget(
|
||||||
|
currentLocation: '/feed-screen',
|
||||||
|
child: Text('Test Content'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ void main() {
|
|||||||
when(diaryCubit.state).thenReturn(const DiaryState.loading());
|
when(diaryCubit.state).thenReturn(const DiaryState.loading());
|
||||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.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();
|
final commentsCubit = MockConsultantCommentsCubit();
|
||||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||||
@@ -98,7 +98,7 @@ void main() {
|
|||||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||||
|
|
||||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
expect(find.byType(DiaryScreen), findsOneWidget);
|
||||||
@@ -109,7 +109,7 @@ void main() {
|
|||||||
when(diaryCubit.state).thenReturn(loadedState(hasChosenPlan: true));
|
when(diaryCubit.state).thenReturn(loadedState(hasChosenPlan: true));
|
||||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.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();
|
final commentsCubit = MockConsultantCommentsCubit();
|
||||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
|
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
|
||||||
@@ -119,7 +119,7 @@ void main() {
|
|||||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||||
|
|
||||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
|
||||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
expect(find.byType(DiaryScreen), findsOneWidget);
|
||||||
@@ -132,7 +132,7 @@ void main() {
|
|||||||
when(diaryCubit.state).thenReturn(const DiaryState.error(errorMessage: 'err'));
|
when(diaryCubit.state).thenReturn(const DiaryState.error(errorMessage: 'err'));
|
||||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.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();
|
final commentsCubit = MockConsultantCommentsCubit();
|
||||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||||
@@ -142,7 +142,7 @@ void main() {
|
|||||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||||
|
|
||||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
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.state).thenReturn(DiaryState.loadedTomorrowWithDiet(diary: futureDiary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1));
|
||||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.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();
|
final commentsCubit = MockConsultantCommentsCubit();
|
||||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||||
@@ -171,7 +171,7 @@ void main() {
|
|||||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||||
|
|
||||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
|
||||||
await tester.pump(const Duration(milliseconds: 200));
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
expect(find.byType(DiaryScreen), findsOneWidget);
|
expect(find.byType(DiaryScreen), findsOneWidget);
|
||||||
@@ -193,7 +193,7 @@ void main() {
|
|||||||
when(diaryCubit.state).thenReturn(state);
|
when(diaryCubit.state).thenReturn(state);
|
||||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.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();
|
final commentsCubit = MockConsultantCommentsCubit();
|
||||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
|
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
|
||||||
@@ -204,7 +204,7 @@ void main() {
|
|||||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
||||||
|
|
||||||
await tester.pumpWidget(wrap(const DiaryScreen()));
|
await tester.pumpWidget(wrap(DiaryScreen(date: DateTime(2025, 1, 1))));
|
||||||
await tester.pump(const Duration(milliseconds: 200));
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
expect(find.text('Nice'), findsOneWidget);
|
expect(find.text('Nice'), findsOneWidget);
|
||||||
@@ -217,7 +217,7 @@ void main() {
|
|||||||
when(diaryCubit.state).thenReturn(state);
|
when(diaryCubit.state).thenReturn(state);
|
||||||
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
|
||||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.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();
|
final commentsCubit = MockConsultantCommentsCubit();
|
||||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||||
@@ -227,7 +227,7 @@ void main() {
|
|||||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
|
sl.registerFactory<ConsultantCommentsCubit>(() => 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));
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
expect(find.byType(AppBar), findsWidgets);
|
expect(find.byType(AppBar), findsWidgets);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ void main() {
|
|||||||
|
|
||||||
registerMocks(cubit, commentsCubit);
|
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();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.byType(AboutDietScreen), findsOneWidget);
|
expect(find.byType(AboutDietScreen), findsOneWidget);
|
||||||
@@ -127,7 +127,7 @@ void main() {
|
|||||||
registerMocks(cubit, commentsCubit);
|
registerMocks(cubit, commentsCubit);
|
||||||
|
|
||||||
await mockNetworkImagesFor(() async {
|
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();
|
||||||
await tester.pump(const Duration(milliseconds: 600));
|
await tester.pump(const Duration(milliseconds: 600));
|
||||||
});
|
});
|
||||||
@@ -157,7 +157,7 @@ void main() {
|
|||||||
registerMocks(cubit, commentsCubit);
|
registerMocks(cubit, commentsCubit);
|
||||||
|
|
||||||
await mockNetworkImagesFor(() async {
|
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();
|
||||||
await tester.pump(const Duration(milliseconds: 600));
|
await tester.pump(const Duration(milliseconds: 600));
|
||||||
});
|
});
|
||||||
@@ -183,7 +183,7 @@ void main() {
|
|||||||
|
|
||||||
registerMocks(cubit, commentsCubit);
|
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();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.byType(AboutDietScreen), findsOneWidget);
|
expect(find.byType(AboutDietScreen), findsOneWidget);
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('UnknownNotification uses default createdAt when not provided', () {
|
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.type, NotificationType.unknown);
|
||||||
expect(notification.isRead, true);
|
expect(notification.isRead, true);
|
||||||
@@ -485,8 +485,9 @@ void main() {
|
|||||||
|
|
||||||
test('PersonalDietCreationNeededNotification.fromJson parses all fields', () {
|
test('PersonalDietCreationNeededNotification.fromJson parses all fields', () {
|
||||||
final json = {
|
final json = {
|
||||||
'plan_name': 'Custom Plan',
|
'user_name': 'John Doe',
|
||||||
'client_name': 'John Doe',
|
'consultant_name': 'Consultant',
|
||||||
|
'my_diets_url': 'https://example.com/diets',
|
||||||
'created_at': '2024-01-20T12:00:00Z',
|
'created_at': '2024-01-20T12:00:00Z',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -496,8 +497,9 @@ void main() {
|
|||||||
isRead: true,
|
isRead: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(notification.planName, 'Custom Plan');
|
expect(notification.userName, 'John Doe');
|
||||||
expect(notification.clientName, 'John Doe');
|
expect(notification.consultantName, 'Consultant');
|
||||||
|
expect(notification.myDietsUrl, 'https://example.com/diets');
|
||||||
expect(notification.isRead, true);
|
expect(notification.isRead, true);
|
||||||
expect(notification.type, NotificationType.personalDietCreationNeeded);
|
expect(notification.type, NotificationType.personalDietCreationNeeded);
|
||||||
});
|
});
|
||||||
@@ -510,8 +512,8 @@ void main() {
|
|||||||
NotificationType.personalDietCreationNeeded,
|
NotificationType.personalDietCreationNeeded,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(notification.planName, '');
|
expect(notification.userName, '');
|
||||||
expect(notification.clientName, '');
|
expect(notification.consultantName, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ChosenPlanClientLastDayFinishedNotification.fromJson parses all fields', () {
|
test('ChosenPlanClientLastDayFinishedNotification.fromJson parses all fields', () {
|
||||||
@@ -551,7 +553,9 @@ void main() {
|
|||||||
test('StayOnTrackToAuthorNotification.fromJson parses all fields', () {
|
test('StayOnTrackToAuthorNotification.fromJson parses all fields', () {
|
||||||
final json = {
|
final json = {
|
||||||
'plan_name': 'Track Plan',
|
'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',
|
'created_at': '2024-01-24T12:00:00Z',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -562,7 +566,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(notification.planName, 'Track Plan');
|
expect(notification.planName, 'Track Plan');
|
||||||
expect(notification.clientName, 'Jane Smith');
|
expect(notification.userName, 'Jane Smith');
|
||||||
expect(notification.isRead, false);
|
expect(notification.isRead, false);
|
||||||
expect(notification.type, NotificationType.stayOnTrackToAuthor);
|
expect(notification.type, NotificationType.stayOnTrackToAuthor);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -89,6 +91,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -99,6 +103,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 2',
|
planName: 'Plan 2',
|
||||||
planId: 2,
|
planId: 2,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -143,6 +149,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -168,6 +176,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -201,6 +211,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -231,6 +243,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -272,6 +286,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -311,6 +327,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -342,6 +360,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -349,6 +369,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 2',
|
planName: 'Plan 2',
|
||||||
planId: 2,
|
planId: 2,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: true,
|
isRead: true,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -376,6 +398,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: true,
|
isRead: true,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -383,6 +407,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 2',
|
planName: 'Plan 2',
|
||||||
planId: 2,
|
planId: 2,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: true,
|
isRead: true,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -426,6 +452,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Plan 1',
|
planName: 'Plan 1',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -475,6 +503,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -524,6 +554,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -568,6 +600,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -608,6 +642,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -645,6 +681,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -688,6 +726,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -757,6 +797,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -800,6 +842,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -843,6 +887,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -886,6 +932,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -929,6 +977,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -972,6 +1022,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -1015,6 +1067,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -1058,6 +1112,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -1100,6 +1156,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -1137,6 +1195,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -1174,6 +1234,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -1217,6 +1279,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Initial Plan',
|
planName: 'Initial Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -36,8 +36,12 @@ void main() {
|
|||||||
final notification = PlanSubscriptionStartedToAuthorNotification(
|
final notification = PlanSubscriptionStartedToAuthorNotification(
|
||||||
type: NotificationType.planSubscriptionStartedToAuthor,
|
type: NotificationType.planSubscriptionStartedToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
customerName: 'Test Customer',
|
customerName: 'Test Customer',
|
||||||
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
chatId: 'chat-123',
|
chatId: 'chat-123',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,8 +31,12 @@ void main() {
|
|||||||
final notification = PlanSubscriptionStartedToAuthorNotification(
|
final notification = PlanSubscriptionStartedToAuthorNotification(
|
||||||
type: NotificationType.planSubscriptionStartedToAuthor,
|
type: NotificationType.planSubscriptionStartedToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
customerName: 'Test Customer',
|
customerName: 'Test Customer',
|
||||||
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
chatId: 'chat123',
|
chatId: 'chat123',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -47,7 +51,11 @@ void main() {
|
|||||||
final notification = PlanSubscriptionCanceledToAuthorNotification(
|
final notification = PlanSubscriptionCanceledToAuthorNotification(
|
||||||
type: NotificationType.planSubscriptionCanceledToAuthor,
|
type: NotificationType.planSubscriptionCanceledToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
customerName: 'Test Customer',
|
customerName: 'Test Customer',
|
||||||
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -63,6 +71,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClient,
|
type: NotificationType.chosenPlanStartedToClient,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: 'Test Consultant',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -77,6 +87,8 @@ void main() {
|
|||||||
final notification = ChosenPlanTomorrowReminderToClientNotification(
|
final notification = ChosenPlanTomorrowReminderToClientNotification(
|
||||||
type: NotificationType.chosenPlanTomorrowReminderToClient,
|
type: NotificationType.chosenPlanTomorrowReminderToClient,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
consultantName: '',
|
||||||
|
chatId: 0,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -103,7 +115,12 @@ void main() {
|
|||||||
testWidgets('displays ConsultantLeftCommentNotification', (tester) async {
|
testWidgets('displays ConsultantLeftCommentNotification', (tester) async {
|
||||||
final notification = ConsultantLeftCommentNotification(
|
final notification = ConsultantLeftCommentNotification(
|
||||||
type: NotificationType.consultantLeftComment,
|
type: NotificationType.consultantLeftComment,
|
||||||
|
chosenPlanId: 1,
|
||||||
|
diaryId: 0,
|
||||||
date: testDate,
|
date: testDate,
|
||||||
|
planName: '',
|
||||||
|
userName: '',
|
||||||
|
consultantName: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -131,6 +148,11 @@ void main() {
|
|||||||
final notification = ChosenPlanFinishedToClientNotification(
|
final notification = ChosenPlanFinishedToClientNotification(
|
||||||
type: NotificationType.chosenPlanFinishedToClient,
|
type: NotificationType.chosenPlanFinishedToClient,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
chosenPlanId: 1,
|
||||||
|
consultantName: '',
|
||||||
|
consultantPhotoPath: '',
|
||||||
|
consultantProfileUrl: '',
|
||||||
|
consultantId: 0,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -145,6 +167,11 @@ void main() {
|
|||||||
final notification = PlanSubscriptionCanceledToCustomerNotification(
|
final notification = PlanSubscriptionCanceledToCustomerNotification(
|
||||||
type: NotificationType.planSubscriptionCanceledToCustomer,
|
type: NotificationType.planSubscriptionCanceledToCustomer,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
|
authorName: '',
|
||||||
|
authorId: 0,
|
||||||
|
authorPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -160,6 +187,10 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToConsultant,
|
type: NotificationType.chosenPlanStartedToConsultant,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
customerName: 'Test Customer',
|
customerName: 'Test Customer',
|
||||||
|
planId: 1,
|
||||||
|
customerId: 1,
|
||||||
|
peerProfileUrl: '',
|
||||||
|
consultationUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -175,6 +206,7 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanTomorrowReminderToConsultant,
|
type: NotificationType.chosenPlanTomorrowReminderToConsultant,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
customerName: 'Test Customer',
|
customerName: 'Test Customer',
|
||||||
|
chatId: 0,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -189,7 +221,11 @@ void main() {
|
|||||||
final notification = PlanSubscriptionPaidToAuthorNotification(
|
final notification = PlanSubscriptionPaidToAuthorNotification(
|
||||||
type: NotificationType.planSubscriptionPaidToAuthor,
|
type: NotificationType.planSubscriptionPaidToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
customerName: 'Test Customer',
|
customerName: 'Test Customer',
|
||||||
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -206,6 +242,9 @@ void main() {
|
|||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
customerName: 'Test Customer',
|
customerName: 'Test Customer',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
chosenPlanId: 1,
|
||||||
|
clientPhotoPath: '',
|
||||||
|
clientId: 0,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -219,6 +258,10 @@ void main() {
|
|||||||
testWidgets('displays ChosenPlanLastDayFinishedForUser', (tester) async {
|
testWidgets('displays ChosenPlanLastDayFinishedForUser', (tester) async {
|
||||||
final notification = ChosenPlanLastDayFinishedForUser(
|
final notification = ChosenPlanLastDayFinishedForUser(
|
||||||
type: NotificationType.chosenPlanLastDayFinishedForUser,
|
type: NotificationType.chosenPlanLastDayFinishedForUser,
|
||||||
|
planName: 'Test Plan',
|
||||||
|
chosenPlanId: 1,
|
||||||
|
complaintUrl: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -252,6 +295,7 @@ void main() {
|
|||||||
message: 'Test message',
|
message: 'Test message',
|
||||||
entityId: 1,
|
entityId: 1,
|
||||||
entityType: 'test',
|
entityType: 'test',
|
||||||
|
entityName: 'Test Entity',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -267,7 +311,7 @@ void main() {
|
|||||||
type: NotificationType.commentReplied,
|
type: NotificationType.commentReplied,
|
||||||
entityName: 'Test Entity',
|
entityName: 'Test Entity',
|
||||||
commentedEntityId: 1,
|
commentedEntityId: 1,
|
||||||
commentedEntityType: 'test',
|
commentedEntityType: CommentedEntityType.diet,
|
||||||
author: 'Test Author',
|
author: 'Test Author',
|
||||||
authorId: 1,
|
authorId: 1,
|
||||||
parentCommentId: 1,
|
parentCommentId: 1,
|
||||||
@@ -305,7 +349,7 @@ void main() {
|
|||||||
type: NotificationType.entityCommented,
|
type: NotificationType.entityCommented,
|
||||||
entityName: 'Test Entity',
|
entityName: 'Test Entity',
|
||||||
entityId: 1,
|
entityId: 1,
|
||||||
entityType: 'test',
|
entityType: CommentedEntityType.diet,
|
||||||
entityTranslatedType: 'Test Type',
|
entityTranslatedType: 'Test Type',
|
||||||
entityUrl: 'test_url',
|
entityUrl: 'test_url',
|
||||||
authorName: 'Test Author',
|
authorName: 'Test Author',
|
||||||
@@ -470,6 +514,7 @@ void main() {
|
|||||||
final notification = DietCancelledNotification(
|
final notification = DietCancelledNotification(
|
||||||
type: NotificationType.dietCancelled,
|
type: NotificationType.dietCancelled,
|
||||||
dietName: 'Test Diet',
|
dietName: 'Test Diet',
|
||||||
|
dietId: 1,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -484,6 +529,7 @@ void main() {
|
|||||||
final notification = DietStartedNotification(
|
final notification = DietStartedNotification(
|
||||||
type: NotificationType.dietStarted,
|
type: NotificationType.dietStarted,
|
||||||
dietName: 'Test Diet',
|
dietName: 'Test Diet',
|
||||||
|
dietId: 1,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -498,6 +544,7 @@ void main() {
|
|||||||
final notification = DietExpiredNotification(
|
final notification = DietExpiredNotification(
|
||||||
type: NotificationType.dietExpired,
|
type: NotificationType.dietExpired,
|
||||||
dietName: 'Test Diet',
|
dietName: 'Test Diet',
|
||||||
|
dietId: 1,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -512,6 +559,7 @@ void main() {
|
|||||||
final notification = DietWillStartedInNotification(
|
final notification = DietWillStartedInNotification(
|
||||||
type: NotificationType.dietWillStartedIn,
|
type: NotificationType.dietWillStartedIn,
|
||||||
dietName: 'Test Diet',
|
dietName: 'Test Diet',
|
||||||
|
dietId: 1,
|
||||||
startDate: '2024-02-01',
|
startDate: '2024-02-01',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
@@ -527,6 +575,7 @@ void main() {
|
|||||||
final notification = DietCanNotBeStartedNotification(
|
final notification = DietCanNotBeStartedNotification(
|
||||||
type: NotificationType.dietCanNotBeStarted,
|
type: NotificationType.dietCanNotBeStarted,
|
||||||
dietName: 'Test Diet',
|
dietName: 'Test Diet',
|
||||||
|
dietId: 1,
|
||||||
reason: 'Test reason',
|
reason: 'Test reason',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
@@ -541,8 +590,9 @@ void main() {
|
|||||||
testWidgets('displays PersonalDietCreationNeededNotification', (tester) async {
|
testWidgets('displays PersonalDietCreationNeededNotification', (tester) async {
|
||||||
final notification = PersonalDietCreationNeededNotification(
|
final notification = PersonalDietCreationNeededNotification(
|
||||||
type: NotificationType.personalDietCreationNeeded,
|
type: NotificationType.personalDietCreationNeeded,
|
||||||
planName: 'Test Plan',
|
userName: 'Test Client',
|
||||||
clientName: 'Test Client',
|
consultantName: '',
|
||||||
|
myDietsUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -557,6 +607,9 @@ void main() {
|
|||||||
final notification = ChosenPlanClientLastDayFinishedNotification(
|
final notification = ChosenPlanClientLastDayFinishedNotification(
|
||||||
type: NotificationType.chosenPlanClientLastDayFinished,
|
type: NotificationType.chosenPlanClientLastDayFinished,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
chosenPlanId: 1,
|
||||||
|
complaintUrl: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -571,6 +624,8 @@ void main() {
|
|||||||
final notification = ChosenPlanClientLastDayStartedNotification(
|
final notification = ChosenPlanClientLastDayStartedNotification(
|
||||||
type: NotificationType.chosenPlanClientLastDayStarted,
|
type: NotificationType.chosenPlanClientLastDayStarted,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
chosenPlanId: 1,
|
||||||
|
lastDayDiaryUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -585,7 +640,9 @@ void main() {
|
|||||||
final notification = StayOnTrackToAuthorNotification(
|
final notification = StayOnTrackToAuthorNotification(
|
||||||
type: NotificationType.stayOnTrackToAuthor,
|
type: NotificationType.stayOnTrackToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
clientName: 'Test Client',
|
userName: 'Test Client',
|
||||||
|
dayNum: 1,
|
||||||
|
diaryDayId: 0,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -600,6 +657,13 @@ void main() {
|
|||||||
final notification = ClientStartPlanSubscriptionToSubscriberNotification(
|
final notification = ClientStartPlanSubscriptionToSubscriberNotification(
|
||||||
type: NotificationType.clientStartPlanSubscriptionToSubscriber,
|
type: NotificationType.clientStartPlanSubscriptionToSubscriber,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
|
authorName: '',
|
||||||
|
authorId: 0,
|
||||||
|
authorPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
|
chatId: '',
|
||||||
|
startDate: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -614,7 +678,11 @@ void main() {
|
|||||||
final notification = ConsultantConfirmPlanSubscriptionToSubscriberNotification(
|
final notification = ConsultantConfirmPlanSubscriptionToSubscriberNotification(
|
||||||
type: NotificationType.consultantConfirmPlanSubscriptionToSubscriber,
|
type: NotificationType.consultantConfirmPlanSubscriptionToSubscriber,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
consultantName: 'Test Consultant',
|
consultantName: 'Test Consultant',
|
||||||
|
authorId: 0,
|
||||||
|
authorPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -629,8 +697,11 @@ void main() {
|
|||||||
final notification = ClientPaidPlanSubscriptionToAuthorNotification(
|
final notification = ClientPaidPlanSubscriptionToAuthorNotification(
|
||||||
type: NotificationType.clientPaidPlanSubscriptionToAuthor,
|
type: NotificationType.clientPaidPlanSubscriptionToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
clientName: 'Test Client',
|
clientName: 'Test Client',
|
||||||
amount: 100.0,
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -644,7 +715,10 @@ void main() {
|
|||||||
testWidgets('displays ConsultantLeftDiaryCommentNotification', (tester) async {
|
testWidgets('displays ConsultantLeftDiaryCommentNotification', (tester) async {
|
||||||
final notification = ConsultantLeftDiaryCommentNotification(
|
final notification = ConsultantLeftDiaryCommentNotification(
|
||||||
type: NotificationType.consultantLeftDiaryComment,
|
type: NotificationType.consultantLeftDiaryComment,
|
||||||
|
chosenPlanId: 1,
|
||||||
|
diaryId: 0,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
userName: '',
|
||||||
consultantName: 'Test Consultant',
|
consultantName: 'Test Consultant',
|
||||||
diaryDate: testDate,
|
diaryDate: testDate,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
@@ -661,7 +735,11 @@ void main() {
|
|||||||
final notification = ConsultantConfirmPlanSubscriptionToAuthorNotification(
|
final notification = ConsultantConfirmPlanSubscriptionToAuthorNotification(
|
||||||
type: NotificationType.consultantConfirmPlanSubscriptionToAuthor,
|
type: NotificationType.consultantConfirmPlanSubscriptionToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
consultantName: 'Test Consultant',
|
planId: 1,
|
||||||
|
clientName: 'Test Consultant',
|
||||||
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -676,7 +754,11 @@ void main() {
|
|||||||
final notification = ClientPaidPlanSubscriptionToSubscriberNotification(
|
final notification = ClientPaidPlanSubscriptionToSubscriberNotification(
|
||||||
type: NotificationType.clientPaidPlanSubscriptionToSubscriber,
|
type: NotificationType.clientPaidPlanSubscriptionToSubscriber,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
amount: 100.0,
|
planId: 1,
|
||||||
|
authorName: '',
|
||||||
|
authorId: 0,
|
||||||
|
authorPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -691,7 +773,12 @@ void main() {
|
|||||||
final notification = ClientStartPlanSubscriptionToAuthorNotification(
|
final notification = ClientStartPlanSubscriptionToAuthorNotification(
|
||||||
type: NotificationType.clientStartPlanSubscriptionToAuthor,
|
type: NotificationType.clientStartPlanSubscriptionToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
clientName: 'Test Client',
|
clientName: 'Test Client',
|
||||||
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
|
chatId: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -706,7 +793,11 @@ void main() {
|
|||||||
final notification = ClientCancelPlanSubscriptionToAuthorNotification(
|
final notification = ClientCancelPlanSubscriptionToAuthorNotification(
|
||||||
type: NotificationType.clientCancelPlanSubscriptionToAuthor,
|
type: NotificationType.clientCancelPlanSubscriptionToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
clientName: 'Test Client',
|
clientName: 'Test Client',
|
||||||
|
userId: 0,
|
||||||
|
userPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -721,6 +812,11 @@ void main() {
|
|||||||
final notification = ClientCancelPlanSubscriptionToSubscriberNotification(
|
final notification = ClientCancelPlanSubscriptionToSubscriberNotification(
|
||||||
type: NotificationType.clientCancelPlanSubscriptionToSubscriber,
|
type: NotificationType.clientCancelPlanSubscriptionToSubscriber,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
|
planId: 1,
|
||||||
|
authorName: '',
|
||||||
|
authorId: 0,
|
||||||
|
authorPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -734,9 +830,11 @@ void main() {
|
|||||||
testWidgets('displays UserDailyReportCompletedNotification', (tester) async {
|
testWidgets('displays UserDailyReportCompletedNotification', (tester) async {
|
||||||
final notification = UserDailyReportCompletedNotification(
|
final notification = UserDailyReportCompletedNotification(
|
||||||
type: NotificationType.userDailyReportCompleted,
|
type: NotificationType.userDailyReportCompleted,
|
||||||
|
chosenPlanId: 1,
|
||||||
|
diaryId: 0,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
clientName: 'Test Client',
|
clientName: 'Test Client',
|
||||||
reportDate: testDate,
|
consultantName: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -751,7 +849,8 @@ void main() {
|
|||||||
final notification = StayOnTrackToCustomerNotification(
|
final notification = StayOnTrackToCustomerNotification(
|
||||||
type: NotificationType.stayOnTrackToCustomer,
|
type: NotificationType.stayOnTrackToCustomer,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
consultantName: 'Test Consultant',
|
dayNum: 1,
|
||||||
|
diaryDayId: 0,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -780,8 +879,9 @@ void main() {
|
|||||||
testWidgets('displays SubscriptionNotification', (tester) async {
|
testWidgets('displays SubscriptionNotification', (tester) async {
|
||||||
final notification = SubscriptionNotification(
|
final notification = SubscriptionNotification(
|
||||||
type: NotificationType.subscription,
|
type: NotificationType.subscription,
|
||||||
subscriptionType: 'monthly',
|
userName: '',
|
||||||
planName: 'Test Plan',
|
userPhotoPath: '',
|
||||||
|
userProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -795,8 +895,9 @@ void main() {
|
|||||||
testWidgets('displays BadgeAwardedNotification', (tester) async {
|
testWidgets('displays BadgeAwardedNotification', (tester) async {
|
||||||
final notification = BadgeAwardedNotification(
|
final notification = BadgeAwardedNotification(
|
||||||
type: NotificationType.badgeAwarded,
|
type: NotificationType.badgeAwarded,
|
||||||
badgeName: 'Test Badge',
|
badgeTitle: 'Test Badge',
|
||||||
badgeDescription: 'Test Description',
|
badgeType: '',
|
||||||
|
badgeImage: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -810,8 +911,9 @@ void main() {
|
|||||||
testWidgets('displays BadgeRevokedNotification', (tester) async {
|
testWidgets('displays BadgeRevokedNotification', (tester) async {
|
||||||
final notification = BadgeRevokedNotification(
|
final notification = BadgeRevokedNotification(
|
||||||
type: NotificationType.badgeRevoked,
|
type: NotificationType.badgeRevoked,
|
||||||
badgeName: 'Test Badge',
|
badgeTitle: 'Test Badge',
|
||||||
reason: 'Test reason',
|
badgeType: '',
|
||||||
|
badgeImage: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -839,8 +941,7 @@ void main() {
|
|||||||
testWidgets('displays ClientCloseComplaintSuccessNotification', (tester) async {
|
testWidgets('displays ClientCloseComplaintSuccessNotification', (tester) async {
|
||||||
final notification = ClientCloseComplaintSuccessNotification(
|
final notification = ClientCloseComplaintSuccessNotification(
|
||||||
type: NotificationType.clientCloseComplaintSuccess,
|
type: NotificationType.clientCloseComplaintSuccess,
|
||||||
complaintType: 'test',
|
planName: 'Test Plan',
|
||||||
entityName: 'Test Entity',
|
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -887,7 +988,6 @@ void main() {
|
|||||||
final notification = ClientRejectSuccessNotification(
|
final notification = ClientRejectSuccessNotification(
|
||||||
type: NotificationType.clientRejectSuccess,
|
type: NotificationType.clientRejectSuccess,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
reason: 'Test reason',
|
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -903,7 +1003,6 @@ void main() {
|
|||||||
type: NotificationType.consultantRejectSuccess,
|
type: NotificationType.consultantRejectSuccess,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
clientName: 'Test Client',
|
clientName: 'Test Client',
|
||||||
reason: 'Test reason',
|
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -937,8 +1036,12 @@ void main() {
|
|||||||
final notification = ProductStatusUpdatedForAuthorNotification(
|
final notification = ProductStatusUpdatedForAuthorNotification(
|
||||||
type: NotificationType.productStatusUpdatedForAuthor,
|
type: NotificationType.productStatusUpdatedForAuthor,
|
||||||
productName: 'Test Product',
|
productName: 'Test Product',
|
||||||
oldStatus: 'pending',
|
productId: 1,
|
||||||
newStatus: 'approved',
|
author: '',
|
||||||
|
authorId: 0,
|
||||||
|
status: 'approved',
|
||||||
|
subject: '',
|
||||||
|
messageLine: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
);
|
);
|
||||||
@@ -967,8 +1070,15 @@ void main() {
|
|||||||
type: NotificationType.personalDietIsDefinedToCustomer,
|
type: NotificationType.personalDietIsDefinedToCustomer,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
dietName: 'Test Diet',
|
chosenPlanId: 1,
|
||||||
dietId: 1,
|
dietId: 1,
|
||||||
|
planName: '',
|
||||||
|
userName: '',
|
||||||
|
consultantName: '',
|
||||||
|
dietName: 'Test Diet',
|
||||||
|
role: '',
|
||||||
|
peerSlug: '',
|
||||||
|
peerImagePath: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(notification));
|
await tester.pumpWidget(createTestWidget(notification));
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ void main() {
|
|||||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
planId: 1,
|
planId: 1,
|
||||||
|
consultantName: 'Test Consultant',
|
||||||
|
peerProfileUrl: '',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ void main() {
|
|||||||
|
|
||||||
registerMocks(planCubit, commentsCubit);
|
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();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
||||||
@@ -149,7 +149,7 @@ void main() {
|
|||||||
registerMocks(planCubit, commentsCubit);
|
registerMocks(planCubit, commentsCubit);
|
||||||
|
|
||||||
await mockNetworkImagesFor(() async {
|
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();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
});
|
});
|
||||||
@@ -177,7 +177,7 @@ void main() {
|
|||||||
|
|
||||||
registerMocks(planCubit, commentsCubit);
|
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();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
||||||
|
|||||||
Reference in New Issue
Block a user