forked from eatvio/eatvio_app
Compare commits
46 Commits
issue-914
...
push-notif
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8284338aa | ||
|
|
03ec91a595 | ||
|
|
e814683984 | ||
|
|
684415ef5b | ||
|
|
631ceb2b7a | ||
|
|
71a405c63f | ||
|
|
4ca90eb2df | ||
|
|
f17f1ecb57 | ||
|
|
a71bcc752d | ||
|
|
aac085def4 | ||
|
|
b6b13b1db5 | ||
|
|
6629286bf1 | ||
|
|
966bc23f0b | ||
|
|
a13b623ca2 | ||
|
|
ee2dd75a94 | ||
|
|
c450a41715 | ||
|
|
55b6b75134 | ||
|
|
335e36dd18 | ||
| 7106bde4ae | |||
|
|
4f241f7db6 | ||
|
|
3d7755a9a2 | ||
|
|
decc8c63e6 | ||
|
|
ad5b48ef1b | ||
|
|
e8dc0138e2 | ||
|
|
96896671b6 | ||
|
|
5e2f0430d9 | ||
|
|
2da3b10e63 | ||
|
|
5cb121d733 | ||
|
|
109013c25c | ||
|
|
f5d14d213f | ||
|
|
26ae28a705 | ||
|
|
7672477c03 | ||
|
|
07277d6e48 | ||
|
|
9d1bedff26 | ||
|
|
e39638be7f | ||
| 442fc0ec14 | |||
| a8f8cc8eb0 | |||
|
|
3892bf00aa | ||
|
|
89268e1132 | ||
|
|
08eeea6ba4 | ||
|
|
58d99424a0 | ||
|
|
c6cdb5bf01 | ||
|
|
8b1d08b2aa | ||
|
|
6f666a8859 | ||
|
|
c540cbe6df | ||
|
|
bb97c395dd |
@@ -1,37 +1,39 @@
|
|||||||
name: Eatvio App Android Test
|
name: Eatvio App Android Test
|
||||||
|
|
||||||
on: [ pull_request ]
|
on: [pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.41.2"
|
flutter_version: '3.41.2'
|
||||||
java_version: "21.x"
|
java_version: '21.x'
|
||||||
flutter_channel: "stable"
|
flutter_channel: 'stable'
|
||||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
my_local_cache_dir: '/opt/hostedtoolcache/'
|
||||||
ANDROID_SDK_ROOT: "/opt/hostedtoolcache/Android/Sdk"
|
ANDROID_SDK_ROOT: '/opt/hostedtoolcache/Android/Sdk'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: [ ubuntu-latest ]
|
runs-on: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: https://github.com/actions/checkout@v3
|
||||||
|
|
||||||
- name: Set proxy server
|
# Пока что не нужно проксирование
|
||||||
env:
|
# - name: Set proxy server
|
||||||
PROXY_USER: ${{ secrets.PROXY_USER }}
|
# env:
|
||||||
PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }}
|
# PROXY_USER: ${{ secrets.PROXY_USER }}
|
||||||
run: |
|
# PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }}
|
||||||
echo "Setting up proxy..."
|
# run: |
|
||||||
cat <<EOF >/etc/apt/apt.conf.d/proxy.conf
|
# echo "Setting up proxy..."
|
||||||
Acquire::http::Proxy "socks5h://${PROXY_USER}:${PROXY_PASSWORD}@10.55.5.202:19843/";
|
# cat <<EOF >/etc/apt/apt.conf.d/proxy.conf
|
||||||
EOF
|
# Acquire::http::Proxy "socks5h://${PROXY_USER}:${PROXY_PASSWORD}@10.55.5.202:19843/";
|
||||||
|
# EOF
|
||||||
|
|
||||||
- name: Checkout env repository
|
- name: Checkout env repository
|
||||||
uses: https://github.com/actions/checkout@v3
|
uses: https://github.com/actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: mso7nueh/eatvio_app_env
|
repository: eatvio/eatvio_app_env
|
||||||
path: env_repo
|
path: env_repo
|
||||||
token: ${{ vars.MSO7NUEH_TOKEN }}
|
# Здесь может быть токен любого юзера с доступом в gitea
|
||||||
|
token: ${{ vars.READ_USER_GITEA_TOKEN }}
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Copy .env file
|
- name: Copy .env file
|
||||||
@@ -66,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache Android SDK
|
- name: Cache Android SDK
|
||||||
id: cache-sdk
|
id: cache-sdk
|
||||||
uses: "https://github.com/actions/cache@v4"
|
uses: 'https://github.com/actions/cache@v4'
|
||||||
with:
|
with:
|
||||||
path: ${{ env.my_local_cache_dir }}
|
path: ${{ env.my_local_cache_dir }}
|
||||||
key: ${{ runner.os }}-cache-sdk
|
key: ${{ runner.os }}-cache-sdk
|
||||||
@@ -74,14 +76,14 @@ jobs:
|
|||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: https://github.com/actions/setup-java@v3
|
uses: https://github.com/actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.java_version }}
|
java-version: ${{ env.java_version }}
|
||||||
cache: gradle
|
cache: gradle
|
||||||
|
|
||||||
- name: Install utils for Android SDK
|
- name: Install utils for Android SDK
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y openjdk-17-jdk wget jq clang cmake ninja-build libgtk-3-dev
|
apt-get install -y openjdk-17-jdk wget jq clang cmake ninja-build libgtk-3-dev
|
||||||
|
|
||||||
- name: Install Android SDK
|
- name: Install Android SDK
|
||||||
if: steps.cache-sdk.outputs.cache-hit != 'true'
|
if: steps.cache-sdk.outputs.cache-hit != 'true'
|
||||||
@@ -100,15 +102,14 @@ jobs:
|
|||||||
sdkmanager "platform-tools" "platforms;android-35" "platforms;android-34" "platforms;android-33" "platforms;android-31"
|
sdkmanager "platform-tools" "platforms;android-35" "platforms;android-34" "platforms;android-33" "platforms;android-31"
|
||||||
sdkmanager "build-tools;34.0.0"
|
sdkmanager "build-tools;34.0.0"
|
||||||
sdkmanager "ndk;25.1.8937393"
|
sdkmanager "ndk;25.1.8937393"
|
||||||
sdkmanager --install "cmake;3.22.1"
|
sdkmanager --install "cmake;3.22.1"
|
||||||
|
|
||||||
- name: Set up Android environment variables
|
- name: Set up Android environment variables
|
||||||
id: "android-env-setup"
|
id: 'android-env-setup'
|
||||||
run: |
|
run: |
|
||||||
echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
||||||
echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV
|
echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV
|
||||||
echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
|
||||||
- name: Set up Flutter
|
- name: Set up Flutter
|
||||||
run: |
|
run: |
|
||||||
@@ -147,7 +148,7 @@ jobs:
|
|||||||
if [ "$ARCH" = "x86_64" ]; then ARCH="x64"; fi
|
if [ "$ARCH" = "x86_64" ]; then ARCH="x64"; fi
|
||||||
git config --global --add safe.directory "${{ env.my_local_cache_dir }}flutter"
|
git config --global --add safe.directory "${{ env.my_local_cache_dir }}flutter"
|
||||||
flutter config --android-sdk="$ANDROID_HOME"
|
flutter config --android-sdk="$ANDROID_HOME"
|
||||||
flutter doctor -v
|
flutter doctor -v
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -212,12 +213,11 @@ jobs:
|
|||||||
percentage = (covered / total) * 100
|
percentage = (covered / total) * 100
|
||||||
printf "✅ Coverage: %.2f%%\n", percentage
|
printf "✅ Coverage: %.2f%%\n", percentage
|
||||||
print ""
|
print ""
|
||||||
if (percentage < 60) {
|
if (percentage < 70) {
|
||||||
printf "⚠️ Warning: Coverage is below 60%%\n"
|
printf "❌ FAIL: Coverage is below 70%%\n"
|
||||||
} else if (percentage < 80) {
|
exit 1
|
||||||
printf "💛 Good: Coverage is above 60%%\n"
|
|
||||||
} else {
|
} else {
|
||||||
printf "💚 Excellent: Coverage is above 80%%\n"
|
printf "💚 Excellent: Coverage is above 70%%\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,7 @@ jobs:
|
|||||||
APK_NAME="eatvio_app_${TIMESTAMP}.apk"
|
APK_NAME="eatvio_app_${TIMESTAMP}.apk"
|
||||||
mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME
|
mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME
|
||||||
echo "APK build finished $APK_NAME"
|
echo "APK build finished $APK_NAME"
|
||||||
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload APK artifact
|
- name: Upload APK artifact
|
||||||
uses: https://github.com/actions/upload-artifact@v3
|
uses: https://github.com/actions/upload-artifact@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
|
||||||
@@ -55,32 +55,19 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- Main domains -->
|
||||||
<data android:host="eatvio.ru" />
|
<data android:host="eatvio.ru" />
|
||||||
<data android:host="www.eatvio.ru" />
|
<data android:host="www.eatvio.ru" />
|
||||||
|
<data android:host="eatvio.com" />
|
||||||
|
<data android:host="www.eatvio.com" />
|
||||||
|
|
||||||
|
<!-- Development/Subdomains -->
|
||||||
|
<data android:host="*.eatvio.ru" />
|
||||||
|
<data android:host="*.eatvio.com" />
|
||||||
|
|
||||||
<data android:pathPattern="/ru/recipe/.*" />
|
<!-- Simplify patterns to allow navigation within the app for all matching hosts -->
|
||||||
<data android:pathPattern="/en/recipe/.*" />
|
<data android:pathPattern="/.*" />
|
||||||
|
|
||||||
<data android:pathPattern="/ru/blog_post/.*" />
|
|
||||||
<data android:pathPattern="/en/blog_post/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/diet/.*" />
|
|
||||||
<data android:pathPattern="/en/diet/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/plan/.*" />
|
|
||||||
<data android:pathPattern="/en/plan/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/chat/.*" />
|
|
||||||
<data android:pathPattern="/en/chat/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/chat" />
|
|
||||||
<data android:pathPattern="/en/chat" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/profile/.*" />
|
|
||||||
<data android:pathPattern="/en/profile/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/diary/.*" />
|
|
||||||
<data android:pathPattern="/en/diary/.*" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
|||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/env_config.dart';
|
import 'package:eatvio_app/core/env_config.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_report_policy.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart' as di;
|
import 'package:eatvio_app/core/locator_service.dart' as di;
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/firebase_options.dart';
|
import 'package:eatvio_app/firebase_options.dart';
|
||||||
@@ -16,6 +17,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||||||
|
|
||||||
/// Класс для инициализации приложения
|
/// Класс для инициализации приложения
|
||||||
class AppInitializer {
|
class AppInitializer {
|
||||||
|
|
||||||
/// Инициализация всех сервисов приложения
|
/// Инициализация всех сервисов приложения
|
||||||
static Future<void> initialize({required AppBuildType buildType}) async {
|
static Future<void> initialize({required AppBuildType buildType}) async {
|
||||||
await _initializeEnvironment();
|
await _initializeEnvironment();
|
||||||
@@ -79,11 +81,12 @@ class AppInitializer {
|
|||||||
options.dsn = EnvConfig.sentryDsn;
|
options.dsn = EnvConfig.sentryDsn;
|
||||||
options.tracesSampleRate = 1.0;
|
options.tracesSampleRate = 1.0;
|
||||||
options.debug = false;
|
options.debug = false;
|
||||||
|
options.beforeSend = (event, hint) {
|
||||||
|
if (kDebugMode) return null;
|
||||||
|
if (!ErrorReportPolicy.allowSentryEvent(event, hint)) return null;
|
||||||
|
return event;
|
||||||
|
};
|
||||||
}, appRunner: appRunner);
|
}, appRunner: appRunner);
|
||||||
FlutterError.onError = (details) async {
|
|
||||||
if (kDebugMode) return;
|
|
||||||
await Sentry.captureException(details.exception, stackTrace: details.stack);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:developer' as developer;
|
import 'dart:developer' as developer;
|
||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/routing/auth_routes.dart';
|
import 'package:eatvio_app/core/routing/auth_routes.dart';
|
||||||
import 'package:eatvio_app/core/routing/content_routes.dart';
|
import 'package:eatvio_app/core/routing/content_routes.dart';
|
||||||
import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
|
import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
|
||||||
@@ -6,6 +7,7 @@ import 'package:eatvio_app/core/routing/other_routes.dart';
|
|||||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/core/routing/settings_routes.dart';
|
import 'package:eatvio_app/core/routing/settings_routes.dart';
|
||||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||||
|
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -14,8 +16,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
|
static const List<String> deepLinkHosts = ['eatvio.ru', 'eatvio.com', 'dev2.eatvio.com'];
|
||||||
static const List<String> _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru'];
|
static const List<String> metaSegments = ['ru', 'en', 'api', 'v2'];
|
||||||
|
|
||||||
static GoRouter appRouter = GoRouter(
|
static GoRouter appRouter = GoRouter(
|
||||||
initialLocation: RouteConstants.updateScreen,
|
initialLocation: RouteConstants.updateScreen,
|
||||||
@@ -30,129 +32,212 @@ class AppRouter {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Обработка редиректов для deep links
|
static String? _resolveRecipeDeepLink(List<String> effectiveSegments) {
|
||||||
/// Возвращает null для обычной навигации, чтобы не мешать внутренним переходам
|
if (effectiveSegments.length != 2) return null;
|
||||||
static String? _handleRedirect(BuildContext context, GoRouterState state) {
|
final contentIdentifier = effectiveSegments[1];
|
||||||
final uri = state.uri;
|
developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/about-recipes-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveBlogPostDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 2) return null;
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/blog-post-screen?id=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolvePlanDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 2) return null;
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
final planId = int.tryParse(contentIdentifier);
|
||||||
|
if (planId != null) {
|
||||||
|
developer.log('Deep link: diary', name: 'AppRouter');
|
||||||
|
return RouteConstants.diaryScreen;
|
||||||
|
}
|
||||||
|
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/about-plan-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveDietDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length == 2) {
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
final dietId = int.tryParse(contentIdentifier);
|
||||||
|
if (dietId != null) {
|
||||||
|
developer.log('Deep link: diet/id=$dietId', name: 'AppRouter');
|
||||||
|
return '/about-diet-screen?id=$dietId';
|
||||||
|
}
|
||||||
|
developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/about-diet-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
const dietTabIndexWithHiddenPlansTab = 3;
|
||||||
|
const dietTabIndexWithoutHiddenPlansTab = 4;
|
||||||
|
|
||||||
|
final initialTabIndex = hidePlans ? dietTabIndexWithHiddenPlansTab : dietTabIndexWithoutHiddenPlansTab;
|
||||||
|
return '${RouteConstants.feedScreen}?initialTabIndex=$initialTabIndex';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveChatDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length == 2) {
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '${RouteConstants.chatScreen}?uuid=$contentIdentifier';
|
||||||
|
} else if (effectiveSegments.length == 3 && effectiveSegments[1] == 'show') {
|
||||||
|
final contentIdentifier = effectiveSegments[2];
|
||||||
|
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '${RouteConstants.chatScreen}?uuid=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
developer.log('Deep link: chat', name: 'AppRouter');
|
||||||
|
return RouteConstants.chatSelectScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveProfileDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length == 2) {
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/profile-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
return RouteConstants.myProfileScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveBadgeDeepLink(List<String> effectiveSegments) {
|
||||||
|
return RouteConstants.myProfileScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveDiaryDeepLink(List<String> effectiveSegments) {
|
||||||
|
developer.log('Deep link: diary', name: 'AppRouter');
|
||||||
|
return RouteConstants.diaryScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveConsultantDiaryCommentDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 2) return null;
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter');
|
||||||
|
return RouteConstants.diaryScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveConsultationsDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 3 || effectiveSegments[1] != 'status') return RouteConstants.consultationsScreen;
|
||||||
|
final consultationId = effectiveSegments[2];
|
||||||
|
developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter');
|
||||||
|
return '${InfoByOrderScreen.path}?planId=$consultationId';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveDeepLinkUri(Uri uri) {
|
||||||
final host = uri.host.toLowerCase();
|
final host = uri.host.toLowerCase();
|
||||||
final segments = uri.pathSegments;
|
final segments = uri.pathSegments;
|
||||||
|
|
||||||
// Проверяем, является ли это deep link по домену
|
final isDeepLink = deepLinkHosts.any((allowedHost) => host == allowedHost || host.endsWith('.$allowedHost'));
|
||||||
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();
|
|
||||||
return RouteConstants.feedScreen;
|
return RouteConstants.feedScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка языка (первый сегмент должен быть ru или en)
|
final effectiveSegments = segments.where((segment) => !metaSegments.contains(segment)).toList();
|
||||||
if (segments[0] != 'ru' && segments[0] != 'en') {
|
if (effectiveSegments.isEmpty) {
|
||||||
developer.log(
|
developer.log('No effective segments after language check, redirecting to feed', name: 'AppRouter');
|
||||||
'Invalid language segment: ${segments[0]}, redirecting to feed',
|
|
||||||
name: 'AppRouter',
|
|
||||||
);
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return RouteConstants.feedScreen;
|
return RouteConstants.feedScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка deep links с учетом языка
|
final type = effectiveSegments[0];
|
||||||
// Формат: /{lang}/{type}/{identifier?}
|
|
||||||
if (segments.length == 4) {
|
|
||||||
// Формат: /{lang}/diary/{...}
|
|
||||||
final type = segments[1];
|
|
||||||
|
|
||||||
if (type == 'diary') {
|
|
||||||
developer.log('Deep link: diary, redirecting to diary-screen', name: 'AppRouter');
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return RouteConstants.diaryScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
developer.log(
|
|
||||||
'Unknown deep link type for 4 segments: $type, redirecting to feed',
|
|
||||||
name: 'AppRouter',
|
|
||||||
);
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return RouteConstants.feedScreen;
|
|
||||||
} else if (segments.length == 3) {
|
|
||||||
// Формат: /{lang}/{type}/{identifier}
|
|
||||||
final type = segments[1];
|
|
||||||
final contentIdentifier = segments[2];
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'recipe':
|
case 'recipe':
|
||||||
developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter');
|
final target = _resolveRecipeDeepLink(effectiveSegments);
|
||||||
FlutterNativeSplash.remove();
|
if (target != null) return target;
|
||||||
return '/about-recipes-screen?slug=$contentIdentifier';
|
break;
|
||||||
case 'blog_post':
|
case 'blog_post':
|
||||||
developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter');
|
final target = _resolveBlogPostDeepLink(effectiveSegments);
|
||||||
FlutterNativeSplash.remove();
|
if (target != null) return target;
|
||||||
return '/blog-post-screen?id=$contentIdentifier';
|
break;
|
||||||
case 'plan':
|
case 'plan':
|
||||||
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter');
|
final target = _resolvePlanDeepLink(effectiveSegments);
|
||||||
FlutterNativeSplash.remove();
|
if (target != null) return target;
|
||||||
return '/about-plan-screen?slug=$contentIdentifier';
|
break;
|
||||||
case 'diet':
|
case 'diet':
|
||||||
developer.log('Deep link: diet/$contentIdentifier', name: 'AppRouter');
|
final target = _resolveDietDeepLink(effectiveSegments);
|
||||||
FlutterNativeSplash.remove();
|
if (target != null) return target;
|
||||||
return '/about-diet-screen?slug=$contentIdentifier';
|
break;
|
||||||
case 'chat':
|
case 'chat':
|
||||||
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
|
final target = _resolveChatDeepLink(effectiveSegments);
|
||||||
FlutterNativeSplash.remove();
|
if (target != null) return target;
|
||||||
return RouteConstants.chatSelectScreen;
|
break;
|
||||||
case 'profile':
|
case 'profile':
|
||||||
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
|
final target = _resolveProfileDeepLink(effectiveSegments);
|
||||||
FlutterNativeSplash.remove();
|
if (target != null) return target;
|
||||||
return '/profile-screen?slug=$contentIdentifier';
|
break;
|
||||||
case 'diary':
|
case 'badge':
|
||||||
developer.log('Deep link: diary/$contentIdentifier', name: 'AppRouter');
|
final target = _resolveBadgeDeepLink(effectiveSegments);
|
||||||
FlutterNativeSplash.remove();
|
if (target != null) return target;
|
||||||
return RouteConstants.diaryScreen;
|
break;
|
||||||
default:
|
case 'diary':
|
||||||
developer.log(
|
final target = _resolveDiaryDeepLink(effectiveSegments);
|
||||||
'Unknown deep link type: $type, redirecting to feed',
|
if (target != null) return target;
|
||||||
name: 'AppRouter',
|
break;
|
||||||
);
|
case 'consultant_diary_comment':
|
||||||
FlutterNativeSplash.remove();
|
final target = _resolveConsultantDiaryCommentDeepLink(effectiveSegments);
|
||||||
return RouteConstants.feedScreen;
|
if (target != null) return target;
|
||||||
}
|
break;
|
||||||
} else if (segments.length == 2) {
|
case 'consultations':
|
||||||
// Формат: /{lang}/{type} - только для chat
|
final target = _resolveConsultationsDeepLink(effectiveSegments);
|
||||||
final type = segments[1];
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
if (type == 'chat') {
|
|
||||||
developer.log('Deep link: chat', name: 'AppRouter');
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return RouteConstants.chatSelectScreen;
|
|
||||||
} else {
|
|
||||||
developer.log(
|
|
||||||
'Unknown deep link type for 2 segments: $type, redirecting to feed',
|
|
||||||
name: 'AppRouter',
|
|
||||||
);
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return RouteConstants.feedScreen;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Неизвестный формат deep link
|
|
||||||
developer.log(
|
|
||||||
'Unknown deep link format: ${segments.length} segments, redirecting to feed',
|
|
||||||
name: 'AppRouter',
|
|
||||||
);
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return RouteConstants.feedScreen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
developer.log('Unknown deep link format: ${effectiveSegments.length} effective segments, type=$type, redirecting to feed', name: 'AppRouter');
|
||||||
|
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);
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
if (target != null) {
|
||||||
|
appRouter.go(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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,15 +5,13 @@ import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
|
|||||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class CommentsCubit extends Cubit<CommentsState> {
|
class CommentsCubit extends Cubit<CommentsState> {
|
||||||
final FeedRepository feedRepository;
|
final FeedRepository feedRepository;
|
||||||
final CommentsRepository commentsRepository;
|
final CommentsRepository commentsRepository;
|
||||||
|
|
||||||
CommentsCubit({
|
CommentsCubit({required this.feedRepository, required this.commentsRepository}) : super(const CommentsState());
|
||||||
required this.feedRepository,
|
|
||||||
required this.commentsRepository,
|
|
||||||
}) : super(const CommentsState());
|
|
||||||
|
|
||||||
void setReplyId(int? replyId) {
|
void setReplyId(int? replyId) {
|
||||||
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
|
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
|
||||||
@@ -36,51 +34,34 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
|
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.error(errorMessage: e.toString()));
|
emit(CommentsState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendComment({
|
Future<bool> sendComment({required String id, required String commentText, required CommentSourceType commentSourceType, int? parentCommentId}) async {
|
||||||
required String id,
|
|
||||||
required String commentText,
|
|
||||||
required CommentSourceType commentSourceType,
|
|
||||||
int? parentCommentId,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
switch (commentSourceType) {
|
switch (commentSourceType) {
|
||||||
case CommentSourceType.blogPost:
|
case CommentSourceType.blogPost:
|
||||||
await feedRepository.sendBlogPostComment(
|
await feedRepository.sendBlogPostComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case CommentSourceType.diet:
|
case CommentSourceType.diet:
|
||||||
await feedRepository.sendDietsComment(
|
await feedRepository.sendDietsComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case CommentSourceType.recipe:
|
case CommentSourceType.recipe:
|
||||||
await feedRepository.sendRecipesComment(
|
await feedRepository.sendRecipesComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case CommentSourceType.plan:
|
case CommentSourceType.plan:
|
||||||
await feedRepository.sendPlansComment(
|
await feedRepository.sendPlansComment(slug: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
slug: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
getComments(id, commentSourceType);
|
getComments(id, commentSourceType);
|
||||||
} catch (e) {
|
return true;
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.error(errorMessage: e.toString()));
|
emit(CommentsState.error(errorMessage: e.toString()));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +74,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
emit(CommentsState.loaded(comment: updatedCommentModel));
|
emit(CommentsState.loaded(comment: updatedCommentModel));
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.loaded(comment: commentModel));
|
emit(CommentsState.loaded(comment: commentModel));
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
@@ -101,13 +83,15 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
|
|
||||||
Future<bool> updateComment({required int id, required String body}) async {
|
Future<bool> updateComment({required int id, required String body}) async {
|
||||||
final commentModel = (state as CommentsStateLoaded).comment;
|
final commentModel = (state as CommentsStateLoaded).comment;
|
||||||
final updatedCommentModel =
|
final updatedCommentModel = commentModel.copyWith(
|
||||||
commentModel.copyWith(data: commentModel.data.map((comment) => comment.id == id ? comment.copyWith(body: body) : comment).toList());
|
data: commentModel.data.map((comment) => comment.id == id ? comment.copyWith(body: body) : comment).toList(),
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await commentsRepository.updateComment(id: id, body: body);
|
await commentsRepository.updateComment(id: id, body: body);
|
||||||
emit(CommentsState.loaded(comment: updatedCommentModel));
|
emit(CommentsState.loaded(comment: updatedCommentModel));
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.loaded(comment: commentModel));
|
emit(CommentsState.loaded(comment: commentModel));
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
|
import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
|
||||||
import 'package:eatvio_app/core/comments/services/comments_service.dart';
|
import 'package:eatvio_app/core/comments/services/comments_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class CommentsRepository {
|
class CommentsRepository {
|
||||||
final CommentsService service;
|
final CommentsService service;
|
||||||
@@ -8,20 +7,12 @@ class CommentsRepository {
|
|||||||
CommentsRepository({required this.service});
|
CommentsRepository({required this.service});
|
||||||
|
|
||||||
Future<void> deleteComment({required int id}) async {
|
Future<void> deleteComment({required int id}) async {
|
||||||
try {
|
return service.deleteComment(id);
|
||||||
return service.deleteComment(id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateComment({required int id, required String body}) async {
|
Future<void> updateComment({required int id, required String body}) async {
|
||||||
try {
|
return service.updateComment(id, CommentUpdateModel(body: body));
|
||||||
return service.updateComment(id, CommentUpdateModel(body: body));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:eatvio_app/core/cubit/marketing/marketing_cubit_state.dart';
|
|||||||
import 'package:eatvio_app/core/prefs.dart';
|
import 'package:eatvio_app/core/prefs.dart';
|
||||||
import 'package:eatvio_app/core/prefs_keys.dart';
|
import 'package:eatvio_app/core/prefs_keys.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class MarketingCubit extends Cubit<MarketingCubitState> {
|
class MarketingCubit extends Cubit<MarketingCubitState> {
|
||||||
final Prefs prefs;
|
final Prefs prefs;
|
||||||
@@ -22,8 +23,9 @@ class MarketingCubit extends Cubit<MarketingCubitState> {
|
|||||||
await prefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true);
|
await prefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true);
|
||||||
}
|
}
|
||||||
return needToShowMarketingBottomSheet;
|
return needToShowMarketingBottomSheet;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
rethrow;
|
await reportFrom(e, st, this);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/constants/app_constants.dart';
|
import 'package:eatvio_app/core/constants/app_constants.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/sentry_error_reporter.dart';
|
||||||
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
|
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
|
||||||
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
||||||
import 'package:eatvio_app/core/prefs.dart';
|
import 'package:eatvio_app/core/prefs.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:sentry_dio/sentry_dio.dart';
|
import 'package:sentry_dio/sentry_dio.dart';
|
||||||
|
|
||||||
@@ -14,6 +17,7 @@ class ExternalRegistrations {
|
|||||||
|
|
||||||
/// Регистрация всех внешних зависимостей
|
/// Регистрация всех внешних зависимостей
|
||||||
static void registerAll() {
|
static void registerAll() {
|
||||||
|
sl.registerLazySingleton<ErrorReporter>(SentryErrorReporter.new);
|
||||||
_registerDio();
|
_registerDio();
|
||||||
_registerPrefs();
|
_registerPrefs();
|
||||||
}
|
}
|
||||||
@@ -30,7 +34,9 @@ class ExternalRegistrations {
|
|||||||
sendTimeout: AppConstants.pendingDelay,
|
sendTimeout: AppConstants.pendingDelay,
|
||||||
),
|
),
|
||||||
)..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()])
|
)..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()])
|
||||||
..addSentry(),
|
..addSentry(
|
||||||
|
failedRequestStatusCodes: const [SentryStatusCode.defaultRange()],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class ServiceRegistrations {
|
|||||||
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
||||||
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
||||||
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
||||||
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi(errorReporter: sl()));
|
||||||
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
||||||
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
||||||
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
||||||
|
|||||||
80
lib/core/error_reporting/error_report_policy.dart
Normal file
80
lib/core/error_reporting/error_report_policy.dart
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
|
/// Rules for which errors are sent to crash reporting.
|
||||||
|
///
|
||||||
|
/// [shouldReport] is used by [ErrorReporter]. [allowSentryEvent] is used in
|
||||||
|
/// [SentryFlutter.init] as a last-line filter (Dio failed-request capture, etc.).
|
||||||
|
class ErrorReportPolicy {
|
||||||
|
ErrorReportPolicy._();
|
||||||
|
|
||||||
|
/// When `false`, the error is not sent via [ErrorReporter].
|
||||||
|
static bool shouldReport(Object? error) {
|
||||||
|
if (error == null) return false;
|
||||||
|
if (error is String) return false;
|
||||||
|
if (error is SocketException) return false;
|
||||||
|
|
||||||
|
if (error is DioException) {
|
||||||
|
return _shouldReportDio(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used in SDK [SentryOptions.beforeSend]. Return `false` to drop the event.
|
||||||
|
///
|
||||||
|
/// For [DioException], rules differ from [shouldReport]: events from `sentry_dio`
|
||||||
|
/// (e.g. 5xx) must pass through here, while [shouldReport] returns false for 5xx
|
||||||
|
/// so [SentryErrorReporter] does not duplicate them after `catch` in cubits / manual [report] calls.
|
||||||
|
static bool allowSentryEvent(SentryEvent event, Hint hint) {
|
||||||
|
final t = event.throwable;
|
||||||
|
if (t == null) return true;
|
||||||
|
if (t is DioException) {
|
||||||
|
return _allowSentrySdkDio(t);
|
||||||
|
}
|
||||||
|
return shouldReport(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel, timeouts, socket / connection errors — do not send (noisy, often user environment).
|
||||||
|
static bool _isTransientDioFailure(DioException e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case DioExceptionType.cancel:
|
||||||
|
case DioExceptionType.connectionTimeout:
|
||||||
|
case DioExceptionType.sendTimeout:
|
||||||
|
case DioExceptionType.receiveTimeout:
|
||||||
|
case DioExceptionType.connectionError:
|
||||||
|
return true;
|
||||||
|
case DioExceptionType.unknown:
|
||||||
|
case DioExceptionType.badResponse:
|
||||||
|
case DioExceptionType.badCertificate:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return e.error is SocketException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Noise to drop for any Dio-originated SDK event; do not use for manual [report].
|
||||||
|
static bool _allowSentrySdkDio(DioException e) {
|
||||||
|
if (_isTransientDioFailure(e)) return false;
|
||||||
|
final code = e.response?.statusCode;
|
||||||
|
if (code != null && code >= 400 && code < 500) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _shouldReportDio(DioException e) {
|
||||||
|
if (_isTransientDioFailure(e)) return false;
|
||||||
|
|
||||||
|
final code = e.response?.statusCode;
|
||||||
|
if (code != null) {
|
||||||
|
if (code >= 500 && code <= 599) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (code >= 400 && code < 500) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
lib/core/error_reporting/error_reporter.dart
Normal file
9
lib/core/error_reporting/error_reporter.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/// Application-wide error reporting (e.g. Sentry).
|
||||||
|
abstract class ErrorReporter {
|
||||||
|
Future<void> report(
|
||||||
|
Object error,
|
||||||
|
StackTrace? stackTrace, {
|
||||||
|
String? origin,
|
||||||
|
Map<String, Object?>? extras,
|
||||||
|
});
|
||||||
|
}
|
||||||
14
lib/core/error_reporting/no_op_error_reporter.dart
Normal file
14
lib/core/error_reporting/no_op_error_reporter.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
|
||||||
|
/// Reporting sink that does nothing (dev tooling, tests, diagnostics).
|
||||||
|
class NoOpErrorReporter implements ErrorReporter {
|
||||||
|
const NoOpErrorReporter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> report(
|
||||||
|
Object error,
|
||||||
|
StackTrace? stackTrace, {
|
||||||
|
String? origin,
|
||||||
|
Map<String, Object?>? extras,
|
||||||
|
}) async {}
|
||||||
|
}
|
||||||
17
lib/core/error_reporting/report_from.dart
Normal file
17
lib/core/error_reporting/report_from.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/no_op_error_reporter.dart';
|
||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
|
||||||
|
/// Отправка в [ErrorReporter] с тегом origin = [context.runtimeType] (имя кубита/виджета и т.д.).
|
||||||
|
///
|
||||||
|
/// Если [ErrorReporter] не зарегистрирован (например, после [GetIt.reset] в тестах), используется [NoOpErrorReporter].
|
||||||
|
Future<void> reportFrom(Object error, StackTrace? stackTrace, Object context) {
|
||||||
|
final reporter = sl.isRegistered<ErrorReporter>()
|
||||||
|
? sl<ErrorReporter>()
|
||||||
|
: const NoOpErrorReporter();
|
||||||
|
return reporter.report(
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
origin: context.runtimeType.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
33
lib/core/error_reporting/sentry_error_reporter.dart
Normal file
33
lib/core/error_reporting/sentry_error_reporter.dart
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:eatvio_app/core/error_reporting/error_report_policy.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
|
class SentryErrorReporter implements ErrorReporter {
|
||||||
|
SentryErrorReporter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> report(
|
||||||
|
Object error,
|
||||||
|
StackTrace? stackTrace, {
|
||||||
|
String? origin,
|
||||||
|
Map<String, Object?>? extras,
|
||||||
|
}) async {
|
||||||
|
if (!ErrorReportPolicy.shouldReport(error)) return;
|
||||||
|
|
||||||
|
await Sentry.captureException(
|
||||||
|
error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
withScope: (scope) {
|
||||||
|
if (origin != null && origin.isNotEmpty) {
|
||||||
|
scope.setTag('origin', origin);
|
||||||
|
}
|
||||||
|
if (extras != null && extras.isNotEmpty) {
|
||||||
|
scope.setContexts(
|
||||||
|
'report_extras',
|
||||||
|
extras.map((k, v) => MapEntry(k, v?.toString())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,18 +5,20 @@ 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';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/services.dart' show MethodChannel, PlatformException;
|
import 'package:flutter/services.dart' show MethodChannel, PlatformException;
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
|
||||||
class FirebaseApi {
|
class FirebaseApi {
|
||||||
|
final ErrorReporter _errorReporter;
|
||||||
final _firebaseMessaging = FirebaseMessaging.instance;
|
final _firebaseMessaging = FirebaseMessaging.instance;
|
||||||
|
|
||||||
|
FirebaseApi({required ErrorReporter errorReporter}) : _errorReporter = errorReporter;
|
||||||
|
|
||||||
Future<String?> getToken() async {
|
Future<String?> getToken() async {
|
||||||
String? token;
|
String? token;
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ class FirebaseApi {
|
|||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +89,7 @@ class FirebaseApi {
|
|||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,12 +115,12 @@ class FirebaseApi {
|
|||||||
} on PlatformException catch (e, st) {
|
} on PlatformException catch (e, st) {
|
||||||
showErrorToast('$e $st');
|
showErrorToast('$e $st');
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
return null;
|
return null;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
showErrorToast('$e $st');
|
showErrorToast('$e $st');
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:eatvio_app/core/app_initializer.dart';
|
|||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
abstract class ImageCompressUtility {
|
abstract class ImageCompressUtility {
|
||||||
@@ -30,44 +29,21 @@ abstract class ImageCompressUtility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<File> compressFile(File file) async {
|
static Future<File> compressFile(File file) async {
|
||||||
// Skip compression in test environment to avoid hangs
|
|
||||||
try {
|
try {
|
||||||
final appConfig = sl<AppConfig>();
|
final appConfig = sl<AppConfig>();
|
||||||
if (appConfig.buildType == AppBuildType.tests) {
|
if (appConfig.buildType == AppBuildType.tests) {
|
||||||
// In tests, return the original file without compression
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {}
|
||||||
// If AppConfig is not available, proceed with compression
|
|
||||||
|
final compressedPath = '${p.withoutExtension(file.path)}_compressed.jpg';
|
||||||
|
|
||||||
|
final result = await FlutterImageCompress.compressAndGetFile(file.absolute.path, compressedPath, quality: 90, minHeight: 1080, minWidth: 1080);
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
final image = img.decodeImage(file.readAsBytesSync())!;
|
return File(result.path);
|
||||||
int width = image.width;
|
|
||||||
int height = image.height;
|
|
||||||
|
|
||||||
if (width > height) {
|
|
||||||
if (width > 1080) {
|
|
||||||
height = (height * 1080) ~/ width;
|
|
||||||
width = 1080;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (height > 1080) {
|
|
||||||
width = (width * 1080) ~/ height;
|
|
||||||
height = 1080;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String filePathWithoutExtension = p.withoutExtension(file.path);
|
|
||||||
String compressedFilePath = '${filePathWithoutExtension}_compressed.jpg';
|
|
||||||
|
|
||||||
final result = await FlutterImageCompress.compressAndGetFile(
|
|
||||||
file.absolute.path,
|
|
||||||
compressedFilePath,
|
|
||||||
quality: 90,
|
|
||||||
minHeight: height,
|
|
||||||
minWidth: width,
|
|
||||||
);
|
|
||||||
|
|
||||||
return File(result!.path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -12,18 +13,21 @@ class PhoneVerifyErrorInterceptor extends Interceptor {
|
|||||||
final data = err.response?.data;
|
final data = err.response?.data;
|
||||||
|
|
||||||
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
|
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
|
||||||
final String phone = await sl<ProfileRepository>().getMyPhone();
|
final String phone = await sl<ProfileRepository>().getMyPhone();
|
||||||
final String passKey = await sl<ProfileRepository>().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone});
|
final String passKey = await sl<ProfileRepository>().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone});
|
||||||
await showErrorDialog(AppRouter.navigatorKey.currentContext!,
|
await showErrorDialog(
|
||||||
title: 'Привяжите номер телефона', description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона');
|
AppRouter.navigatorKey.currentContext!,
|
||||||
|
title: 'Привяжите номер телефона',
|
||||||
|
description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона',
|
||||||
|
);
|
||||||
|
final currentContext = AppRouter.navigatorKey.currentContext;
|
||||||
|
|
||||||
|
if (currentContext != null && currentContext.mounted && GoRouter.of(currentContext).state.path == RouteConstants.stepperBecomeConsultantScreen) {
|
||||||
|
GoRouter.of(currentContext).pop();
|
||||||
|
}
|
||||||
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
|
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
|
||||||
return handler.reject(
|
return handler.reject(
|
||||||
DioException(
|
DioException(requestOptions: err.requestOptions, response: err.response, error: 'Phone number not verified', type: DioExceptionType.badResponse),
|
||||||
requestOptions: err.requestOptions,
|
|
||||||
response: err.response,
|
|
||||||
error: 'Phone number not verified',
|
|
||||||
type: DioExceptionType.badResponse,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:eatvio_app/core/utils/toasts.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
||||||
@@ -33,7 +34,8 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
result = await _connectivity.checkConnectivity();
|
result = await _connectivity.checkConnectivity();
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log('Couldn\'t check connectivity status', error: e);
|
log('Couldn\'t check connectivity status', error: e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -42,9 +44,13 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateAccessStatus(List<ConnectivityResult> result) async {
|
Future<void> _updateAccessStatus(List<ConnectivityResult> result) async {
|
||||||
final hasInternet = await _checkInternetAccess(result);
|
try {
|
||||||
if (!hasInternet) showInternetConnectionToast();
|
final hasInternet = await _checkInternetAccess(result);
|
||||||
emit(state.copyWith(isInternetAvailable: hasInternet));
|
if (!hasInternet) showInternetConnectionToast();
|
||||||
|
emit(state.copyWith(isInternetAvailable: hasInternet));
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async {
|
Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async {
|
||||||
@@ -57,7 +63,8 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
|||||||
try {
|
try {
|
||||||
final response = await http.get(uri).timeout(const Duration(seconds: 3));
|
final response = await http.get(uri).timeout(const Duration(seconds: 3));
|
||||||
return response.statusCode >= 100 && response.statusCode < 600;
|
return response.statusCode >= 100 && response.statusCode < 600;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,5 +32,5 @@ Future<void> init({required AppBuildType buildType}) async {
|
|||||||
CubitRegistrations.registerAll();
|
CubitRegistrations.registerAll();
|
||||||
|
|
||||||
// Регистрация WebSocket менеджера
|
// Регистрация WebSocket менеджера
|
||||||
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
|
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl(), errorReporter: sl()));
|
||||||
}
|
}
|
||||||
|
|||||||
64
lib/core/notifications/push_navigation_handler.dart
Normal file
64
lib/core/notifications/push_navigation_handler.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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');
|
||||||
|
|
||||||
|
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'));
|
||||||
|
|
||||||
|
_hmsChannel.setMethodCallHandler((call) async {
|
||||||
|
if (call.method == 'openActionUrl') {
|
||||||
|
final url = call.arguments as String? ?? '';
|
||||||
|
handleActionUrlFromNative(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,17 +10,14 @@ import 'package:eatvio_app/features/registration/presentation/pages/verify_email
|
|||||||
import 'package:eatvio_app/features/registration_v2/presentation/pages/registration_v2_page.dart';
|
import 'package:eatvio_app/features/registration_v2/presentation/pages/registration_v2_page.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// Маршруты для аутентификации
|
|
||||||
class AuthRoutes {
|
class AuthRoutes {
|
||||||
/// Создание маршрутов аутентификации
|
|
||||||
static List<RouteBase> createRoutes() {
|
static List<RouteBase> createRoutes() {
|
||||||
return [
|
return [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.authScreen,
|
path: RouteConstants.authScreen,
|
||||||
name: 'authScreen',
|
name: 'authScreen',
|
||||||
builder: (context, state) => const AuthScreen(),
|
builder: (context, state) => const AuthScreen(),
|
||||||
// registerAnonymousPushToken вызывается в AuthCubit.initializeUser()
|
|
||||||
// и не должен блокировать навигацию
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
||||||
GoRoute(path: RouteConstants.registrationScreenV2, name: 'registrationScreenV2', builder: (context, state) => const RegistrationV2Page()),
|
GoRoute(path: RouteConstants.registrationScreenV2, name: 'registrationScreenV2', builder: (context, state) => const RegistrationV2Page()),
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -31,8 +32,8 @@ class MainNavigationRoutes {
|
|||||||
profileAvatarCubit.getMyProfile();
|
profileAvatarCubit.getMyProfile();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return BottomNavigationBarWidget(child: child);
|
return _MainShellWithNavScope(currentLocation: state.matchedLocation, child: child);
|
||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -45,7 +46,14 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.feedScreen,
|
path: RouteConstants.feedScreen,
|
||||||
name: 'feedScreen',
|
name: 'feedScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
|
pageBuilder: (context, state) {
|
||||||
|
final initialTabIndexFromQuery = int.tryParse(state.uri.queryParameters['initialTabIndex'] ?? '');
|
||||||
|
final extra = state.extra;
|
||||||
|
if (extra is int) {
|
||||||
|
return NoTransitionPage(child: FeedScreen(initialTabIndex: extra));
|
||||||
|
}
|
||||||
|
return NoTransitionPage(child: FeedScreen(initialTabIndex: initialTabIndexFromQuery));
|
||||||
|
},
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
try {
|
try {
|
||||||
await sl<WsManager>().initialize();
|
await sl<WsManager>().initialize();
|
||||||
@@ -58,7 +66,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 +76,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,
|
||||||
@@ -78,19 +89,34 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.menuScreen,
|
path: RouteConstants.menuScreen,
|
||||||
name: 'menuScreen',
|
name: 'menuScreen',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
pageBuilder: (context, state) => const NoTransitionPage(child: MenuScreen()),
|
||||||
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
|
||||||
builder: (BuildContext context, ProfileAvatarState state) {
|
|
||||||
if (state is ProfileAvatarStateLoaded) {
|
|
||||||
return MenuScreen(profileModel: state.profile);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,10 +156,7 @@ class OtherRoutes {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.extra is Map<String, dynamic>) {
|
if (state.extra is Map<String, dynamic>) {
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return RecipeCreateScreen(
|
return RecipeCreateScreen(recipe: data['recipe'] as Recipe?, autoValidate: data['autoValidate'] as bool? ?? false);
|
||||||
recipe: data['recipe'] as Recipe?,
|
|
||||||
autoValidate: data['autoValidate'] as bool? ?? false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null);
|
return RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null);
|
||||||
},
|
},
|
||||||
@@ -204,7 +201,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
|
||||||
@@ -212,6 +209,10 @@ class OtherRoutes {
|
|||||||
path: RouteConstants.chatScreen,
|
path: RouteConstants.chatScreen,
|
||||||
name: 'chatScreen',
|
name: 'chatScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final uuidFromQuery = state.uri.queryParameters['uuid'];
|
||||||
|
if (uuidFromQuery != null) {
|
||||||
|
return ChatScreen(uuid: uuidFromQuery, sendInitialMessage: false);
|
||||||
|
}
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final uuid = data['uuid'] as String;
|
final uuid = data['uuid'] as String;
|
||||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||||
@@ -233,7 +234,7 @@ class OtherRoutes {
|
|||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final slug = data['slug'] as String?;
|
final slug = data['slug'] as String?;
|
||||||
final isCanceled = data['isCanceled'] as bool?;
|
final isCanceled = data['isCanceled'] as bool?;
|
||||||
return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled,);
|
return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
||||||
@@ -267,6 +268,11 @@ class OtherRoutes {
|
|||||||
path: InfoByOrderScreen.path,
|
path: InfoByOrderScreen.path,
|
||||||
name: InfoByOrderScreen.name,
|
name: InfoByOrderScreen.name,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final queryParams = state.uri.queryParameters;
|
||||||
|
final planIdFromQuery = int.tryParse(queryParams['planId'] ?? '');
|
||||||
|
if (planIdFromQuery != null) {
|
||||||
|
return InfoByOrderScreen(planId: planIdFromQuery, successfulPayment: null, customerSlug: null, isConsultantView: true);
|
||||||
|
}
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final planId = data['planId'] as int;
|
final planId = data['planId'] as int;
|
||||||
final successfulPayment = data['successfulPayment'] as bool?;
|
final successfulPayment = data['successfulPayment'] as bool?;
|
||||||
@@ -290,8 +296,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(
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:eatvio_app/core/views/cubit/views_state.dart';
|
import 'package:eatvio_app/core/views/cubit/views_state.dart';
|
||||||
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
@@ -86,9 +88,13 @@ class ViewsCubit extends Cubit<ViewsCubitState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async {
|
Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async {
|
||||||
await viewsRepository.viewContent(
|
try {
|
||||||
viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids),
|
await viewsRepository.viewContent(
|
||||||
);
|
viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids),
|
||||||
|
);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _scheduleDebouncedSend() {
|
void _scheduleDebouncedSend() {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:eatvio_app/core/views/service/views_service.dart';
|
import 'package:eatvio_app/core/views/service/views_service.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class ViewsRepository {
|
class ViewsRepository {
|
||||||
final ViewsService viewsService;
|
final ViewsService viewsService;
|
||||||
@@ -8,11 +7,7 @@ class ViewsRepository {
|
|||||||
ViewsRepository({required this.viewsService});
|
ViewsRepository({required this.viewsService});
|
||||||
|
|
||||||
Future<void> viewContent({required ViewModel viewModel}) async {
|
Future<void> viewContent({required ViewModel viewModel}) async {
|
||||||
try {
|
await viewsService.viewContent(viewModel: viewModel);
|
||||||
await viewsService.viewContent(viewModel: viewModel);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ import 'package:eatvio_app/core/const.dart';
|
|||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
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:eatvio_app/core/error_reporting/error_reporter.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;
|
||||||
|
final ErrorReporter _errorReporter;
|
||||||
|
|
||||||
WsManager({required this.wsService});
|
WsManager({required this.wsService, required ErrorReporter errorReporter})
|
||||||
|
: _errorReporter = errorReporter;
|
||||||
|
|
||||||
PusherChannelsClient? client;
|
PusherChannelsClient? client;
|
||||||
int? userId;
|
int? userId;
|
||||||
@@ -111,13 +114,28 @@ class WsManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
_subscribe();
|
_subscribe();
|
||||||
|
await _connectCubitsToStreams();
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await _errorReporter.report(e, st, origin: 'WsManager');
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Подключает 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 _errorReporter.report(e, st, origin: 'WsManager');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _initPrivateNotificationsChannel() async {
|
Future<void> _initPrivateNotificationsChannel() async {
|
||||||
final token = await TokenStorage.getTokenFromStorage();
|
final token = await TokenStorage.getTokenFromStorage();
|
||||||
_privateNotificationChannel = client?.privateChannel(
|
_privateNotificationChannel = client?.privateChannel(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state
|
|||||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
|
|
||||||
class AuthCubit extends Cubit<AuthState> {
|
class AuthCubit extends Cubit<AuthState> {
|
||||||
@@ -26,7 +27,8 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
emit(AuthState.loaded(token: token));
|
emit(AuthState.loaded(token: token));
|
||||||
await TokenStorage.saveTokenInStorage(token);
|
await TokenStorage.saveTokenInStorage(token);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Неверный адрес электронной почты или пароль');
|
showErrorToast('Неверный адрес электронной почты или пароль');
|
||||||
emit(AuthState.error(errorMessage: '$e'));
|
emit(AuthState.error(errorMessage: '$e'));
|
||||||
@@ -37,22 +39,30 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
Future<void> initializeUser() async {
|
Future<void> initializeUser() async {
|
||||||
try {
|
try {
|
||||||
await registerAnonymousPushToken();
|
await registerAnonymousPushToken();
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
log('Error in initializeUser: $e');
|
log('Error in initializeUser: $e', stackTrace: st);
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> registerAnonymousPushToken() async {
|
Future<void> registerAnonymousPushToken() async {
|
||||||
final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false;
|
try {
|
||||||
if (!hasAnonymousTokenBeenSent) {
|
final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false;
|
||||||
await firebaseApi.registerAnonymousPushToken();
|
if (!hasAnonymousTokenBeenSent) {
|
||||||
prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true);
|
await firebaseApi.registerAnonymousPushToken();
|
||||||
|
prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true);
|
||||||
|
}
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> registerPushToken() async {
|
Future<void> registerPushToken() async {
|
||||||
await firebaseApi.registerPushToken();
|
try {
|
||||||
|
await firebaseApi.registerPushToken();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:eatvio_app/features/auth/models/oauth_verification_request_respo
|
|||||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
||||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
||||||
final AuthRepository authRepository;
|
final AuthRepository authRepository;
|
||||||
@@ -29,7 +30,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
log('OAuthCubit: Got token model: ${tokenModel.token}');
|
log('OAuthCubit: Got token model: ${tokenModel.token}');
|
||||||
TokenStorage.saveTokenInStorage(tokenModel.token);
|
TokenStorage.saveTokenInStorage(tokenModel.token);
|
||||||
emit(OAuthState.loaded(token: tokenModel.token));
|
emit(OAuthState.loaded(token: tokenModel.token));
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log('OAuthCubit: Error occurred: $e');
|
log('OAuthCubit: Error occurred: $e');
|
||||||
// Проверяем, является ли ошибка ошибкой валидации email/phone
|
// Проверяем, является ли ошибка ошибкой валидации email/phone
|
||||||
final errors = e.response?.data['errors'] as Map<String, dynamic>?;
|
final errors = e.response?.data['errors'] as Map<String, dynamic>?;
|
||||||
@@ -62,7 +64,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
} else {
|
} else {
|
||||||
emit(OAuthState.error(errorMessage: e.toString()));
|
emit(OAuthState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log('OAuthCubit: Error occurred: $e');
|
log('OAuthCubit: Error occurred: $e');
|
||||||
emit(OAuthState.error(errorMessage: e.toString()));
|
emit(OAuthState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
@@ -103,7 +106,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
TokenStorage.saveTokenInStorage(token);
|
TokenStorage.saveTokenInStorage(token);
|
||||||
cacheRegistrationDate(DateTime.now());
|
cacheRegistrationDate(DateTime.now());
|
||||||
emit(OAuthState.loaded(token: token));
|
emit(OAuthState.loaded(token: token));
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(OAuthState.error(errorMessage: e.toString()));
|
emit(OAuthState.error(errorMessage: e.toString()));
|
||||||
final errors = e.response?.data['errors'] as Map<String, dynamic>?;
|
final errors = e.response?.data['errors'] as Map<String, dynamic>?;
|
||||||
final emailErrors = errors?['email'] as List<dynamic>?;
|
final emailErrors = errors?['email'] as List<dynamic>?;
|
||||||
@@ -153,7 +157,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
// Сбрасываем состояние после успешного запроса кода
|
// Сбрасываем состояние после успешного запроса кода
|
||||||
emit(const OAuthState());
|
emit(const OAuthState());
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log('OAuthCubit: Error requesting verification code: $e');
|
log('OAuthCubit: Error requesting verification code: $e');
|
||||||
emit(OAuthState.error(errorMessage: e.toString()));
|
emit(OAuthState.error(errorMessage: e.toString()));
|
||||||
return null;
|
return null;
|
||||||
@@ -200,10 +205,10 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
log('OAuthCubit: OAuth verification confirmed successfully, token received');
|
log('OAuthCubit: OAuth verification confirmed successfully, token received');
|
||||||
TokenStorage.saveTokenInStorage(response.token);
|
TokenStorage.saveTokenInStorage(response.token);
|
||||||
emit(OAuthState.loaded(token: response.token));
|
emit(OAuthState.loaded(token: response.token));
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log('OAuthCubit: Error confirming OAuth verification: $e');
|
log('OAuthCubit: Error confirming OAuth verification: $e');
|
||||||
emit(OAuthState.error(errorMessage: e.toString()));
|
emit(OAuthState.error(errorMessage: e.toString()));
|
||||||
rethrow; // Пробрасываем ошибку дальше, чтобы UI мог её обработать
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
|
||||||
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
|
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ResetPasswordCubit extends Cubit<ResetPasswordState> {
|
class ResetPasswordCubit extends Cubit<ResetPasswordState> {
|
||||||
final ResetPasswordRepository resetPasswordRepository;
|
final ResetPasswordRepository resetPasswordRepository;
|
||||||
@@ -15,7 +16,8 @@ class ResetPasswordCubit extends Cubit<ResetPasswordState> {
|
|||||||
} else {
|
} else {
|
||||||
emit(const ResetPasswordState.codeSendError());
|
emit(const ResetPasswordState.codeSendError());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(const ResetPasswordState.codeSendError());
|
emit(const ResetPasswordState.codeSendError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +30,8 @@ class ResetPasswordCubit extends Cubit<ResetPasswordState> {
|
|||||||
} else {
|
} else {
|
||||||
emit(const ResetPasswordState.resetPasswordError());
|
emit(const ResetPasswordState.resetPasswordError());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(const ResetPasswordState.resetPasswordError());
|
emit(const ResetPasswordState.resetPasswordError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_respo
|
|||||||
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
|
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
|
||||||
import 'package:eatvio_app/features/auth/models/token_model.dart';
|
import 'package:eatvio_app/features/auth/models/token_model.dart';
|
||||||
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class AuthRepository {
|
class AuthRepository {
|
||||||
final AuthService authService;
|
final AuthService authService;
|
||||||
@@ -15,14 +14,10 @@ class AuthRepository {
|
|||||||
AuthRepository({required this.authService});
|
AuthRepository({required this.authService});
|
||||||
|
|
||||||
Future<TokenModel> getTokenByCredentials(String email, String password) async {
|
Future<TokenModel> getTokenByCredentials(String email, String password) async {
|
||||||
try {
|
return await authService.getTokenByCredentials(
|
||||||
return await authService.getTokenByCredentials(
|
credentials: CredentialsModel(email: email, password: password),
|
||||||
credentials: CredentialsModel(email: email, password: password),
|
);
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TokenModel> signInViaSocialMedia({
|
Future<TokenModel> signInViaSocialMedia({
|
||||||
@@ -31,14 +26,10 @@ class AuthRepository {
|
|||||||
required String refreshToken,
|
required String refreshToken,
|
||||||
required String clientId,
|
required String clientId,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
return await authService.signInViaSocialMedia(
|
||||||
return await authService.signInViaSocialMedia(
|
body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken, clientId: clientId),
|
||||||
body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken, clientId: clientId),
|
);
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> signUpViaSocialMedia({
|
Future<String> signUpViaSocialMedia({
|
||||||
@@ -56,47 +47,39 @@ class AuthRepository {
|
|||||||
required int height,
|
required int height,
|
||||||
required String clientId,
|
required String clientId,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
return (await authService.signUpViaSocialMedia(
|
||||||
return (await authService.signUpViaSocialMedia(
|
body: OAuthModel(
|
||||||
body: OAuthModel(
|
provider: provider,
|
||||||
provider: provider,
|
accessToken: accessToken,
|
||||||
accessToken: accessToken,
|
id: id,
|
||||||
id: id,
|
refreshToken: refreshToken,
|
||||||
refreshToken: refreshToken,
|
firstName: firstName,
|
||||||
firstName: firstName,
|
lastName: lastName,
|
||||||
lastName: lastName,
|
email: email,
|
||||||
email: email,
|
phone: phone,
|
||||||
phone: phone,
|
dateOfBirth: dateOfBirth,
|
||||||
dateOfBirth: dateOfBirth,
|
gender: gender == 'М' ? 'male' : 'female',
|
||||||
gender: gender == 'М' ? 'male' : 'female',
|
weight: weight,
|
||||||
weight: weight,
|
height: height,
|
||||||
height: height,
|
clientId: clientId,
|
||||||
clientId: clientId,
|
),
|
||||||
),
|
))['token']!
|
||||||
))['token']!
|
as String;
|
||||||
as String;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OAuthVerificationRequestResponse?> requestOAuthVerification({String? email, String? phone}) async {
|
Future<OAuthVerificationRequestResponse?> requestOAuthVerification({String? email, String? phone}) async {
|
||||||
try {
|
Map<String, dynamic>? body;
|
||||||
Map<String, dynamic>? body;
|
if (email != null) {
|
||||||
if (email != null) {
|
body = {'email': email};
|
||||||
body = {'email': email};
|
} else if (phone != null) {
|
||||||
} else if (phone != null) {
|
body = {'phone': phone};
|
||||||
body = {'phone': phone};
|
|
||||||
}
|
|
||||||
if (body != null) {
|
|
||||||
return await authService.requestOAuthVerification(body: body);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
if (body != null) {
|
||||||
|
return await authService.requestOAuthVerification(body: body);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OAuthVerificationConfirmResponse> confirmOAuthVerification({
|
Future<OAuthVerificationConfirmResponse> confirmOAuthVerification({
|
||||||
@@ -116,33 +99,28 @@ class AuthRepository {
|
|||||||
required int weight,
|
required int weight,
|
||||||
required int height,
|
required int height,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final request = OAuthVerificationConfirmRequest(
|
||||||
final request = OAuthVerificationConfirmRequest(
|
provider: provider,
|
||||||
provider: provider,
|
accessToken: accessToken,
|
||||||
accessToken: accessToken,
|
refreshToken: refreshToken,
|
||||||
refreshToken: refreshToken,
|
clientId: clientId,
|
||||||
clientId: clientId,
|
id: id,
|
||||||
id: id,
|
email: email,
|
||||||
email: email,
|
phone: phone,
|
||||||
phone: phone,
|
code: code,
|
||||||
code: code,
|
verificationType: verificationType,
|
||||||
verificationType: verificationType,
|
firstName: firstName,
|
||||||
firstName: firstName,
|
lastName: lastName,
|
||||||
lastName: lastName,
|
dateOfBirth: dateOfBirth,
|
||||||
dateOfBirth: dateOfBirth,
|
gender: gender,
|
||||||
gender: gender,
|
weight: weight,
|
||||||
weight: weight,
|
height: height,
|
||||||
height: height,
|
);
|
||||||
);
|
// Логируем запрос перед отправкой
|
||||||
// Логируем запрос перед отправкой
|
log(
|
||||||
log(
|
'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone',
|
||||||
'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone',
|
);
|
||||||
);
|
return await authService.confirmOAuthVerification(body: request);
|
||||||
return await authService.confirmOAuthVerification(body: request);
|
|
||||||
} catch (e, s) {
|
|
||||||
log('AuthRepository: Error in confirmOAuthVerification: $e');
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class ResetPasswordRepository {
|
class ResetPasswordRepository {
|
||||||
final ResetPasswordService resetPasswordService;
|
final ResetPasswordService resetPasswordService;
|
||||||
@@ -9,22 +8,18 @@ class ResetPasswordRepository {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Future<bool> sendEmailCodeForPasswordChange(String email) async {
|
Future<bool> sendEmailCodeForPasswordChange(String email) async {
|
||||||
try {
|
final response = await resetPasswordService.sendEmailCodeForPasswordChange(
|
||||||
final response = await resetPasswordService.sendEmailCodeForPasswordChange(
|
data: {
|
||||||
data: {
|
'email': email,
|
||||||
'email': email,
|
},
|
||||||
},
|
);
|
||||||
);
|
int statusCode = response.response.statusCode ?? 0;
|
||||||
int statusCode = response.response.statusCode ?? 0;
|
if (statusCode >= 200 && statusCode < 300) {
|
||||||
if (statusCode >= 200 && statusCode < 300) {
|
return true;
|
||||||
return true;
|
} else {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> confirmPasswordChange(
|
Future<bool> confirmPasswordChange(
|
||||||
@@ -33,24 +28,20 @@ class ResetPasswordRepository {
|
|||||||
String passwordConfirmation,
|
String passwordConfirmation,
|
||||||
String email,
|
String email,
|
||||||
) async {
|
) async {
|
||||||
try {
|
final response = await resetPasswordService.confirmPasswordChange(
|
||||||
final response = await resetPasswordService.confirmPasswordChange(
|
data: {
|
||||||
data: {
|
'code': code,
|
||||||
'code': code,
|
'password': password,
|
||||||
'password': password,
|
'password_confirmation': passwordConfirmation,
|
||||||
'password_confirmation': passwordConfirmation,
|
'email': email,
|
||||||
'email': email,
|
},
|
||||||
},
|
);
|
||||||
);
|
int statusCode = response.response.statusCode ?? 0;
|
||||||
int statusCode = response.response.statusCode ?? 0;
|
if (statusCode >= 200 && statusCode < 300) {
|
||||||
if (statusCode >= 200 && statusCode < 300) {
|
return true;
|
||||||
return true;
|
} else {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import 'package:eatvio_app/features/auth/models/yandex_profile_model.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/core/env_config.dart';
|
import 'package:eatvio_app/core/env_config.dart';
|
||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:vk_id/vk_id.dart';
|
import 'package:vk_id/vk_id.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ class SocialAuthFacade {
|
|||||||
}
|
}
|
||||||
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
|
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
await sl<ErrorReporter>().report(e, s, origin: 'SocialAuthFacade');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +162,7 @@ class SocialAuthFacade {
|
|||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
await sl<ErrorReporter>().report(e, s, origin: 'SocialAuthFacade');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -209,7 +210,7 @@ class SocialAuthFacade {
|
|||||||
|
|
||||||
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
|
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
await sl<ErrorReporter>().report(e, s, origin: 'SocialAuthFacade');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart';
|
import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart';
|
||||||
import 'package:eatvio_app/features/barcode/services/barcode_service.dart';
|
import 'package:eatvio_app/features/barcode/services/barcode_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class BarcodeRepository {
|
class BarcodeRepository {
|
||||||
final BarcodeService barcodeService;
|
final BarcodeService barcodeService;
|
||||||
@@ -15,49 +14,46 @@ class BarcodeRepository {
|
|||||||
/// Получить информацию о продукте по штрихкоду
|
/// Получить информацию о продукте по штрихкоду
|
||||||
/// Возвращает либо данные продукта, либо статус задачи обработки
|
/// Возвращает либо данные продукта, либо статус задачи обработки
|
||||||
Future<BarcodeResult> getProductByBarcode(String barcode) async {
|
Future<BarcodeResult> getProductByBarcode(String barcode) async {
|
||||||
try {
|
final response = await dio.get<Map<String, dynamic>>(
|
||||||
final response = await dio.get('/api/gsone/barcode/$barcode');
|
'/api/gsone/barcode/$barcode',
|
||||||
|
options: Options(validateStatus: (_) => true),
|
||||||
if (response.statusCode == 200) {
|
);
|
||||||
// Данные найдены в БД
|
|
||||||
final product = BarcodeProductModel.fromJson(response.data as Map<String, dynamic>);
|
final code = response.statusCode ?? 0;
|
||||||
return BarcodeResult.success(product);
|
final data = response.data;
|
||||||
} else if (response.statusCode == 202) {
|
if (data is! Map<String, dynamic>) {
|
||||||
// Запущена задача обработки
|
throw DioException(
|
||||||
final taskStatus = BarcodeTaskStatusModel.fromJson(response.data as Map<String, dynamic>);
|
requestOptions: response.requestOptions,
|
||||||
return BarcodeResult.processing(taskStatus.taskId ?? '');
|
response: response,
|
||||||
}
|
message: 'Invalid barcode response',
|
||||||
|
);
|
||||||
throw Exception('Unexpected status code: ${response.statusCode}');
|
|
||||||
} on DioException catch (e, s) {
|
|
||||||
if (e.response?.statusCode == 202) {
|
|
||||||
// Если получен статус 202, значит запущена задача обработки
|
|
||||||
final taskStatus = BarcodeTaskStatusModel.fromJson(e.response!.data as Map<String, dynamic>);
|
|
||||||
return BarcodeResult.processing(taskStatus.taskId ?? '');
|
|
||||||
}
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
if (code == 200) {
|
||||||
|
final product = BarcodeProductModel.fromJson(data);
|
||||||
|
return BarcodeResult.success(product);
|
||||||
|
}
|
||||||
|
if (code == 202) {
|
||||||
|
final taskStatus = BarcodeTaskStatusModel.fromJson(data);
|
||||||
|
return BarcodeResult.processing(taskStatus.taskId ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw DioException(
|
||||||
|
requestOptions: response.requestOptions,
|
||||||
|
response: response,
|
||||||
|
message: 'Unexpected status code: $code',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Получить статус задачи обработки
|
/// Получить статус задачи обработки
|
||||||
Future<BarcodeTaskStatus> getTaskStatus(String taskId) async {
|
Future<BarcodeTaskStatus> getTaskStatus(String taskId) async {
|
||||||
try {
|
final statusModel = await barcodeService.getTaskStatus(taskId: taskId);
|
||||||
final statusModel = await barcodeService.getTaskStatus(taskId: taskId);
|
|
||||||
|
if (statusModel.status == 'completed' && statusModel.data != null) {
|
||||||
if (statusModel.status == 'completed' && statusModel.data != null) {
|
return BarcodeTaskStatus.completed(statusModel.data!);
|
||||||
return BarcodeTaskStatus.completed(statusModel.data!);
|
} else if (statusModel.status == 'failed') {
|
||||||
} else if (statusModel.status == 'failed') {
|
return BarcodeTaskStatus.failed();
|
||||||
return BarcodeTaskStatus.failed();
|
} else {
|
||||||
} else {
|
return BarcodeTaskStatus.processing();
|
||||||
return BarcodeTaskStatus.processing();
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,4 +105,3 @@ enum BarcodeTaskStatusType {
|
|||||||
processing,
|
processing,
|
||||||
failed,
|
failed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
|||||||
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart';
|
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class BlogPostCubit extends Cubit<BlogPostState> {
|
class BlogPostCubit extends Cubit<BlogPostState> {
|
||||||
final FeedRepository feedRepository;
|
final FeedRepository feedRepository;
|
||||||
@@ -16,6 +17,7 @@ class BlogPostCubit extends Cubit<BlogPostState> {
|
|||||||
|
|
||||||
emit(state.copyWith(blogPostModel: postModel, isLoading: false));
|
emit(state.copyWith(blogPostModel: postModel, isLoading: false));
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(state.copyWith(isLoading: false));
|
emit(state.copyWith(isLoading: false));
|
||||||
}
|
}
|
||||||
@@ -37,6 +39,7 @@ class BlogPostCubit extends Cubit<BlogPostState> {
|
|||||||
await feedRepository.likeBlogPost(id: current.id);
|
await feedRepository.likeBlogPost(id: current.id);
|
||||||
}
|
}
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
// rollback on error
|
// rollback on error
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(state.copyWith(blogPostModel: current));
|
emit(state.copyWith(blogPostModel: current));
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -77,9 +78,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
bottomSheet: Container(
|
bottomSheet: Container(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 8),
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))),
|
border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))),
|
||||||
@@ -146,14 +145,18 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (textEditingController.text.length > 2) {
|
if (textEditingController.text.length > 2) {
|
||||||
commentsCubit.sendComment(
|
final isSent = await commentsCubit.sendComment(
|
||||||
id: widget.id.toString(),
|
id: widget.id.toString(),
|
||||||
commentText: textEditingController.text,
|
commentText: textEditingController.text,
|
||||||
parentCommentId: replyId,
|
parentCommentId: replyId,
|
||||||
commentSourceType: CommentSourceType.blogPost,
|
commentSourceType: CommentSourceType.blogPost,
|
||||||
);
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (isSent) context.read<FeedCubit>().incrementCommentsState(widget.id);
|
||||||
|
|
||||||
context.read<CommentsCubit>().setReplyId(null);
|
context.read<CommentsCubit>().setReplyId(null);
|
||||||
setState(() {
|
setState(() {
|
||||||
textEditingController.clear();
|
textEditingController.clear();
|
||||||
@@ -236,9 +239,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
|||||||
if (blogPostState.blogPostModel?.description.isNotEmpty ?? false)
|
if (blogPostState.blogPostModel?.description.isNotEmpty ?? false)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
child: MarkdownHtmlWidget(
|
child: MarkdownHtmlWidget(markdownText: blogPostState.blogPostModel!.description),
|
||||||
markdownText: blogPostState.blogPostModel!.description,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (blogPostState.blogPostModel?.image.isNotEmpty ?? false)
|
if (blogPostState.blogPostModel?.image.isNotEmpty ?? false)
|
||||||
@@ -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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import 'package:eatvio_app/core/cubit/base_cubit.dart';
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
|
|
||||||
class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
|
class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
|
||||||
final BlogPostRepository blogPostRepository;
|
final BlogPostRepository blogPostRepository;
|
||||||
|
|
||||||
BlogPostCreateCubit({required this.blogPostRepository}) : super(const BlogPostCreateState());
|
BlogPostCreateCubit({required this.blogPostRepository}) : super(const BlogPostCreateState());
|
||||||
@@ -15,7 +18,8 @@ class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
|
|||||||
emit(const BlogPostCreateState.loaded());
|
emit(const BlogPostCreateState.loaded());
|
||||||
return true;
|
return true;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
handleError(error, stackTrace);
|
log('BlogPostCreateCubit: $error', stackTrace: stackTrace);
|
||||||
|
await reportFrom(error, stackTrace, this);
|
||||||
emit(BlogPostCreateState.error(errorMessage: error.toString()));
|
emit(BlogPostCreateState.error(errorMessage: error.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -28,7 +32,8 @@ class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
|
|||||||
emit(const BlogPostCreateState.loaded());
|
emit(const BlogPostCreateState.loaded());
|
||||||
return true;
|
return true;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
handleError(error, stackTrace);
|
log('BlogPostCreateCubit: $error', stackTrace: stackTrace);
|
||||||
|
await reportFrom(error, stackTrace, this);
|
||||||
emit(BlogPostCreateState.error(errorMessage: error.toString()));
|
emit(BlogPostCreateState.error(errorMessage: error.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -39,7 +44,8 @@ class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
|
|||||||
await blogPostRepository.deleteBlogPost(id);
|
await blogPostRepository.deleteBlogPost(id);
|
||||||
return true;
|
return true;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
handleError(error, stackTrace);
|
log('BlogPostCreateCubit: $error', stackTrace: stackTrace);
|
||||||
|
await reportFrom(error, stackTrace, this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
@@ -8,7 +8,6 @@ import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
|||||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class BlogPostRepository {
|
class BlogPostRepository {
|
||||||
final BlogPostService blogPostService;
|
final BlogPostService blogPostService;
|
||||||
@@ -23,48 +22,30 @@ class BlogPostRepository {
|
|||||||
String? description,
|
String? description,
|
||||||
XFile? image,
|
XFile? image,
|
||||||
) async {
|
) async {
|
||||||
FormData formData = FormData.fromMap({
|
final formData = FormData.fromMap({
|
||||||
if (title != null && title.isNotEmpty) 'title': title,
|
if (title != null && title.isNotEmpty) 'title': title,
|
||||||
if (description != null && description.isNotEmpty) 'description_md': description,
|
if (description != null && description.isNotEmpty) 'description_md': description,
|
||||||
if (videoUrl != null && videoUrl.isNotEmpty) 'video_url': videoUrl,
|
if (videoUrl != null && videoUrl.isNotEmpty) 'video_url': videoUrl,
|
||||||
if (image != null) 'image': await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name),
|
if (image != null) 'image': await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name),
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
return blogPostService.createBlogPost(
|
||||||
return await blogPostService.createBlogPost(
|
formData: formData,
|
||||||
formData: formData,
|
);
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<XFile?> downloadImage(String imageUrl) async {
|
Future<XFile?> downloadImage(String imageUrl) async {
|
||||||
try {
|
final response = await http.get(Uri.parse('${sl<AppConfig>().baseUrl}/storage/$imageUrl'));
|
||||||
// Загрузка изображения
|
|
||||||
final response = await http.get(Uri.parse('${sl<AppConfig>().baseUrl}/storage/$imageUrl'));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// Получение временной директории
|
final directory = await getTemporaryDirectory();
|
||||||
final directory = await getTemporaryDirectory();
|
final filePath = '${directory.path}/image.jpg';
|
||||||
// Создание файла в временной директории
|
final file = File(filePath);
|
||||||
final filePath = '${directory.path}/image.jpg'; // Вы можете изменить название файла
|
await file.writeAsBytes(response.bodyBytes);
|
||||||
final file = File(filePath);
|
|
||||||
// Запись данных в файл
|
|
||||||
await file.writeAsBytes(response.bodyBytes);
|
|
||||||
|
|
||||||
// Возвращаем объект XFile
|
return XFile(file.path);
|
||||||
return XFile(file.path);
|
|
||||||
} else {
|
|
||||||
log('Ошибка загрузки изображения: ${response.statusCode}');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
log('Произошла ошибка: $e');
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateBlogPost(
|
Future<void> updateBlogPost(
|
||||||
@@ -75,9 +56,9 @@ class BlogPostRepository {
|
|||||||
XFile? image,
|
XFile? image,
|
||||||
String? oldImageUrl,
|
String? oldImageUrl,
|
||||||
) async {
|
) async {
|
||||||
FormData formData = FormData.fromMap({
|
final formData = FormData.fromMap({
|
||||||
'title': title,
|
'title': title,
|
||||||
'description_md': description,
|
'description_md': description,
|
||||||
'video_url': videoUrl,
|
'video_url': videoUrl,
|
||||||
'image': image != null
|
'image': image != null
|
||||||
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name)
|
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name)
|
||||||
@@ -86,23 +67,13 @@ class BlogPostRepository {
|
|||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
return blogPostService.updateBlogPost(
|
||||||
return await blogPostService.updateBlogPost(
|
id: id,
|
||||||
id: id,
|
formData: formData,
|
||||||
formData: formData,
|
);
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteBlogPost(int id) async {
|
Future<void> deleteBlogPost(int id) async {
|
||||||
try {
|
await blogPostService.deleteBlogPost(id: id);
|
||||||
await blogPostService.deleteBlogPost(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
|
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
import 'package:eatvio_app/core/app_router.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/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
@@ -28,7 +31,8 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
|||||||
|
|
||||||
return age;
|
return age;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
unawaited(reportFrom(e, st, this));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -51,10 +55,12 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
|||||||
]);
|
]);
|
||||||
AppMetrica.setUserProfileID(profile.slug);
|
AppMetrica.setUserProfileID(profile.slug);
|
||||||
AppMetrica.reportUserProfile(userProfile);
|
AppMetrica.reportUserProfile(userProfile);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
// Игнорируем ошибки плагинов, профиль уже загружен
|
// Игнорируем ошибки плагинов, профиль уже загружен
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
||||||
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_state.dart';
|
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_state.dart';
|
||||||
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
|
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class CalendarCubit extends Cubit<CalendarState> {
|
class CalendarCubit extends Cubit<CalendarState> {
|
||||||
final CalendarRepository calendarRepository;
|
final CalendarRepository calendarRepository;
|
||||||
@@ -12,7 +13,8 @@ class CalendarCubit extends Cubit<CalendarState> {
|
|||||||
try {
|
try {
|
||||||
final calendarList = await calendarRepository.getCalendar();
|
final calendarList = await calendarRepository.getCalendar();
|
||||||
emit(CalendarState.loaded(calendarList: calendarList));
|
emit(CalendarState.loaded(calendarList: calendarList));
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CalendarState.error(errorMessage: e.toString()));
|
emit(CalendarState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:eatvio_app/features/calendar/models/calendar_model.dart';
|
import 'package:eatvio_app/features/calendar/models/calendar_model.dart';
|
||||||
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
|
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class CalendarRepository {
|
class CalendarRepository {
|
||||||
final CalendarService calendarService;
|
final CalendarService calendarService;
|
||||||
@@ -8,11 +7,7 @@ class CalendarRepository {
|
|||||||
CalendarRepository({required this.calendarService});
|
CalendarRepository({required this.calendarService});
|
||||||
|
|
||||||
Future<List<CalendarModel>> getCalendar() async {
|
Future<List<CalendarModel>> getCalendar() async {
|
||||||
try {
|
return await calendarService.getCalendar();
|
||||||
return await calendarService.getCalendar();
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_s
|
|||||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
|
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
|
||||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
|
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class CancelPlanCubit extends Cubit<CancelPlanState> {
|
class CancelPlanCubit extends Cubit<CancelPlanState> {
|
||||||
final CancelPlanRepository cancelPlanRepository;
|
final CancelPlanRepository cancelPlanRepository;
|
||||||
@@ -18,6 +19,7 @@ class CancelPlanCubit extends Cubit<CancelPlanState> {
|
|||||||
await cancelPlanRepository.cancelChosenPlan(id: id, body: body);
|
await cancelPlanRepository.cancelChosenPlan(id: id, body: body);
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
|
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
|
||||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
|
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class CancelPlanRepository {
|
class CancelPlanRepository {
|
||||||
final CancelPlanService cancelService;
|
final CancelPlanService cancelService;
|
||||||
@@ -9,11 +8,7 @@ class CancelPlanRepository {
|
|||||||
required int id,
|
required int id,
|
||||||
CancelationModel? body
|
CancelationModel? body
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await cancelService.cancelChosenPlan(id: id, body: body);
|
||||||
await cancelService.cancelChosenPlan(id: id, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
|||||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ChatRoomCubit extends Cubit<ChatRoomState> {
|
class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||||
@@ -64,7 +65,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
currentPage++;
|
currentPage++;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
|
emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +77,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
final cards = await cardsRepository.getCards();
|
final cards = await cardsRepository.getCards();
|
||||||
_setCard(cards: cards);
|
_setCard(cards: cards);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +93,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
|
|
||||||
Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async {
|
Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async {
|
||||||
try {
|
try {
|
||||||
|
emit(const ChatRoomState.loading());
|
||||||
final chatRoom = await chatRepository.getChatRoom(uuid);
|
final chatRoom = await chatRepository.getChatRoom(uuid);
|
||||||
ChosenPlanModel? chosenPlan;
|
ChosenPlanModel? chosenPlan;
|
||||||
if (chatRoom.chosenPlanId != null) {
|
if (chatRoom.chosenPlanId != null) {
|
||||||
@@ -111,7 +115,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
if (chosenPlan?.consultantId == chatRoom.users.sender.id) {
|
if (chosenPlan?.consultantId == chatRoom.users.sender.id) {
|
||||||
await checkCards();
|
await checkCards();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
typingSubscription?.cancel();
|
typingSubscription?.cancel();
|
||||||
messagesSubscription?.cancel();
|
messagesSubscription?.cancel();
|
||||||
@@ -121,13 +126,17 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateChosenPlanState() async {
|
Future<void> updateChosenPlanState() async {
|
||||||
final currentState = state;
|
try {
|
||||||
if (currentState is ChatRoomStateLoaded) {
|
final currentState = state;
|
||||||
final chosenPlanId = currentState.chatRoom.chosenPlanId;
|
if (currentState is ChatRoomStateLoaded) {
|
||||||
if (chosenPlanId != null) {
|
final chosenPlanId = currentState.chatRoom.chosenPlanId;
|
||||||
final chosenPlan = await chatRepository.getChosenPlan(id: chosenPlanId);
|
if (chosenPlanId != null) {
|
||||||
emit(currentState.copyWith.chatRoom(chosenPlan: chosenPlan));
|
final chosenPlan = await chatRepository.getChosenPlan(id: chosenPlanId);
|
||||||
|
emit(currentState.copyWith.chatRoom(chosenPlan: chosenPlan));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +151,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: chosenPlan));
|
emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: chosenPlan));
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -151,7 +161,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
try {
|
try {
|
||||||
await chatRepository.cancelChosenPlan(id: planId);
|
await chatRepository.cancelChosenPlan(id: planId);
|
||||||
emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: null));
|
emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: null));
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
|
emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,12 +176,12 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
|
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
|
||||||
final message = ChatMessageModel.fromJson(jsonDecode(event.data));
|
final message = ChatMessageModel.fromJson(jsonDecode(event.data));
|
||||||
final currentState = state as ChatRoomStateLoaded;
|
final currentState = state as ChatRoomStateLoaded;
|
||||||
|
|
||||||
// Сообщения от текущего пользователя не приходят через WebSocket,
|
// Сообщения от текущего пользователя не приходят через WebSocket,
|
||||||
// они обрабатываются через ответ API в sendMessage
|
// они обрабатываются через ответ API в sendMessage
|
||||||
// Здесь обрабатываем только входящие сообщения от других пользователей
|
// Здесь обрабатываем только входящие сообщения от других пользователей
|
||||||
List<ChatMessageModel> messagesList = [message, ...currentState.chatMessages];
|
List<ChatMessageModel> messagesList = [message, ...currentState.chatMessages];
|
||||||
|
|
||||||
chatRepository.emitNewMessageToChats(message);
|
chatRepository.emitNewMessageToChats(message);
|
||||||
emit(currentState.copyWith(chatMessages: messagesList, isLoading: isLoading));
|
emit(currentState.copyWith(chatMessages: messagesList, isLoading: isLoading));
|
||||||
EasyDebounce.debounce('searchChatRoom', const Duration(milliseconds: 300), () => sl<ChatsCubit>().getChatRooms());
|
EasyDebounce.debounce('searchChatRoom', const Duration(milliseconds: 300), () => sl<ChatsCubit>().getChatRooms());
|
||||||
@@ -191,7 +202,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
final messageUuid = uuid.v4();
|
final messageUuid = uuid.v4();
|
||||||
final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null;
|
final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null;
|
||||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||||
|
|
||||||
// Создаем временное сообщение с состоянием loading
|
// Создаем временное сообщение с состоянием loading
|
||||||
emit(
|
emit(
|
||||||
(state as ChatRoomStateLoaded).copyWith(
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
@@ -216,7 +227,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
try {
|
try {
|
||||||
// Отправляем сообщение и получаем ответ от сервера с правильными данными
|
// Отправляем сообщение и получаем ответ от сервера с правильными данными
|
||||||
final serverMessage = await chatRepository.sendMessage(data: data, files: compressedFiles, slug: userSlug);
|
final serverMessage = await chatRepository.sendMessage(data: data, files: compressedFiles, slug: userSlug);
|
||||||
|
|
||||||
// Заменяем локальное сообщение данными с сервера (с правильными URL файлов)
|
// Заменяем локальное сообщение данными с сервера (с правильными URL файлов)
|
||||||
emit(
|
emit(
|
||||||
(state as ChatRoomStateLoaded).copyWith(
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
@@ -238,7 +249,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(
|
emit(
|
||||||
(state as ChatRoomStateLoaded).copyWith(
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||||
@@ -253,6 +265,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
sl<ChatsCubit>().getChatRooms();
|
sl<ChatsCubit>().getChatRooms();
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -274,6 +287,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -286,6 +300,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
final paymentModel = await chatRepository.getPaymentLink(id: planId);
|
final paymentModel = await chatRepository.getPaymentLink(id: planId);
|
||||||
return paymentModel.paymentUrl;
|
return paymentModel.paymentUrl;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -300,7 +315,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
body: PlanComplainModel(description: description),
|
body: PlanComplainModel(description: description),
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -312,7 +328,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
if (planId == null) return false;
|
if (planId == null) return false;
|
||||||
await chatRepository.finishChosenPlan(id: planId);
|
await chatRepository.finishChosenPlan(id: planId);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -341,7 +358,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
if (message != null) {
|
if (message != null) {
|
||||||
try {
|
try {
|
||||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||||
|
|
||||||
// Определяем, есть ли файлы для повторной отправки
|
// Определяем, есть ли файлы для повторной отправки
|
||||||
List<File>? filesToResend;
|
List<File>? filesToResend;
|
||||||
if (message.mediaData != null && message.mediaData!.isNotEmpty) {
|
if (message.mediaData != null && message.mediaData!.isNotEmpty) {
|
||||||
@@ -352,7 +369,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
filesToResend = message.mediaData!.cast<File>();
|
filesToResend = message.mediaData!.cast<File>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
(state as ChatRoomStateLoaded).copyWith(
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
chatMessages: [
|
chatMessages: [
|
||||||
@@ -375,12 +392,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Отправляем сообщение и получаем ответ от сервера
|
// Отправляем сообщение и получаем ответ от сервера
|
||||||
final serverMessage = await chatRepository.sendMessage(
|
final serverMessage = await chatRepository.sendMessage(data: message.data, files: filesToResend, slug: userSlug);
|
||||||
data: message.data,
|
|
||||||
files: filesToResend,
|
|
||||||
slug: userSlug,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Заменяем локальное сообщение данными с сервера
|
// Заменяем локальное сообщение данными с сервера
|
||||||
emit(
|
emit(
|
||||||
(state as ChatRoomStateLoaded).copyWith(
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
@@ -400,7 +413,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(
|
emit(
|
||||||
(state as ChatRoomStateLoaded).copyWith(
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||||
@@ -414,6 +428,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
}
|
}
|
||||||
sl<ChatsCubit>().getChatRooms();
|
sl<ChatsCubit>().getChatRooms();
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_sta
|
|||||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ChatsCubit extends Cubit<ChatsState> {
|
class ChatsCubit extends Cubit<ChatsState> {
|
||||||
final ChatRepository chatRepository;
|
final ChatRepository chatRepository;
|
||||||
@@ -31,10 +32,14 @@ class ChatsCubit extends Cubit<ChatsState> {
|
|||||||
late int lastPage;
|
late int lastPage;
|
||||||
|
|
||||||
Future<void> connectToWebSocket() async {
|
Future<void> connectToWebSocket() async {
|
||||||
await newIncomingMessagesSubscription?.cancel();
|
try {
|
||||||
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
|
await newIncomingMessagesSubscription?.cancel();
|
||||||
_handleNewMessageViaSocket(event.data);
|
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
|
||||||
});
|
_handleNewMessageViaSocket(event.data);
|
||||||
|
});
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getChatRoomsWithLoading({String? query}) async {
|
Future<void> getChatRoomsWithLoading({String? query}) async {
|
||||||
@@ -53,7 +58,8 @@ class ChatsCubit extends Cubit<ChatsState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
page++;
|
page++;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
||||||
);
|
);
|
||||||
@@ -76,6 +82,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
|||||||
);
|
);
|
||||||
page++;
|
page++;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
||||||
@@ -99,6 +106,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
|||||||
);
|
);
|
||||||
page++;
|
page++;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@@ -132,33 +140,37 @@ class ChatsCubit extends Cubit<ChatsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleNewMessageViaSocket(dynamic data) async {
|
Future<void> _handleNewMessageViaSocket(dynamic data) async {
|
||||||
final json = jsonDecode(data);
|
try {
|
||||||
final message = ChatMessageModel.fromJson(json['message']);
|
final json = jsonDecode(data);
|
||||||
|
final message = ChatMessageModel.fromJson(json['message']);
|
||||||
|
|
||||||
final currentChatRooms = [...state.chatRooms];
|
final currentChatRooms = [...state.chatRooms];
|
||||||
|
|
||||||
bool wasChanged = false;
|
bool wasChanged = false;
|
||||||
bool willIncrementUnreadChats = false;
|
bool willIncrementUnreadChats = false;
|
||||||
final updatedChatRooms = currentChatRooms.map((chatRoom) {
|
final updatedChatRooms = currentChatRooms.map((chatRoom) {
|
||||||
if (chatRoom.id == message.roomId) {
|
if (chatRoom.id == message.roomId) {
|
||||||
wasChanged = true;
|
wasChanged = true;
|
||||||
if (chatRoom.unreadMessagesCount == 0) {
|
if (chatRoom.unreadMessagesCount == 0) {
|
||||||
willIncrementUnreadChats = true;
|
willIncrementUnreadChats = true;
|
||||||
|
}
|
||||||
|
return chatRoom.copyWith(lastMessage: message, unreadMessagesCount: chatRoom.unreadMessagesCount + 1);
|
||||||
}
|
}
|
||||||
return chatRoom.copyWith(lastMessage: message, unreadMessagesCount: chatRoom.unreadMessagesCount + 1);
|
return chatRoom;
|
||||||
}
|
}).toList();
|
||||||
return chatRoom;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
if (!wasChanged && _query == null) {
|
if (!wasChanged && _query == null) {
|
||||||
getChatRooms();
|
getChatRooms();
|
||||||
} else {
|
} else {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
chatRooms: updatedChatRooms,
|
chatRooms: updatedChatRooms,
|
||||||
chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount,
|
chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
import 'package:easy_debounce/easy_debounce.dart';
|
import 'package:easy_debounce/easy_debounce.dart';
|
||||||
|
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
@@ -62,6 +63,14 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
chatRoomCubit.setTimer();
|
chatRoomCubit.setTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(ChatScreen oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.uuid != widget.uuid) {
|
||||||
|
chatRoomCubit.initChat(widget.uuid, sendInitialMessage: widget.sendInitialMessage ?? false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -96,11 +105,34 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
return _buildChat(context, state);
|
return _buildChat(context, state);
|
||||||
} else if (state is ChatRoomStateError) {
|
} else if (state is ChatRoomStateError) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(),
|
appBar: AppBar(
|
||||||
|
leading: BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go(RouteConstants.chatSelectScreen);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)),
|
body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const Scaffold(body: Loader());
|
return Scaffold(
|
||||||
|
body: const Loader(),
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go(RouteConstants.chatSelectScreen);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -148,8 +180,18 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
|
leading: BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go(RouteConstants.chatSelectScreen);
|
||||||
|
},
|
||||||
|
),
|
||||||
backgroundColor: appTheme.gray100,
|
backgroundColor: appTheme.gray100,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
|
|
||||||
title: GestureDetector(
|
title: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed('profileScreen', extra: state.chatRoom.users.receiver.slug);
|
GoRouter.of(context).pushNamed('profileScreen', extra: state.chatRoom.users.receiver.slug);
|
||||||
@@ -224,7 +266,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId;
|
final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId;
|
||||||
final message = state.chatMessages[index];
|
final message = state.chatMessages[index];
|
||||||
final isPlanOrderMessage = message.type == 'plan_order';
|
final isPlanOrderMessage = message.type == 'plan_order';
|
||||||
|
|
||||||
PacketOptions? messageOption;
|
PacketOptions? messageOption;
|
||||||
if (isPlanOrderMessage && chosenPlan != null) {
|
if (isPlanOrderMessage && chosenPlan != null) {
|
||||||
if (chosenPlan.optionType == 'options1') {
|
if (chosenPlan.optionType == 'options1') {
|
||||||
@@ -235,7 +277,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
messageOption = chosenPlan.plan.packet3Options;
|
messageOption = chosenPlan.plan.packet3Options;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
_messageDate(message.updatedAt),
|
_messageDate(message.updatedAt),
|
||||||
@@ -284,7 +326,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId;
|
final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId;
|
||||||
final message = state.chatMessages[index];
|
final message = state.chatMessages[index];
|
||||||
final isPlanOrderMessage = message.type == 'plan_order';
|
final isPlanOrderMessage = message.type == 'plan_order';
|
||||||
|
|
||||||
PacketOptions? messageOption;
|
PacketOptions? messageOption;
|
||||||
if (isPlanOrderMessage && chosenPlan != null) {
|
if (isPlanOrderMessage && chosenPlan != null) {
|
||||||
if (chosenPlan.optionType == 'options1') {
|
if (chosenPlan.optionType == 'options1') {
|
||||||
@@ -295,7 +337,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
messageOption = chosenPlan.plan.packet3Options;
|
messageOption = chosenPlan.plan.packet3Options;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
if (isPlanOrderMessage && chosenPlan != null && messageOption != null)
|
if (isPlanOrderMessage && chosenPlan != null && messageOption != null)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
@@ -12,7 +11,6 @@ import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
|||||||
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
||||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class ChatRepository {
|
class ChatRepository {
|
||||||
final ChatService chatService;
|
final ChatService chatService;
|
||||||
@@ -34,160 +32,104 @@ class ChatRepository {
|
|||||||
String? data,
|
String? data,
|
||||||
List<File>? files,
|
List<File>? files,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final multipartFiles = <MultipartFile>[];
|
||||||
final multipartFiles = <MultipartFile>[];
|
|
||||||
|
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
for (final file in files) {
|
for (final file in files) {
|
||||||
final fileBytes = await file.readAsBytes();
|
final fileBytes = await file.readAsBytes();
|
||||||
final multipartFile = MultipartFile.fromBytes(
|
final multipartFile = MultipartFile.fromBytes(
|
||||||
fileBytes,
|
fileBytes,
|
||||||
filename: file.path.split('/').last,
|
filename: file.path.split('/').last,
|
||||||
contentType: MediaType('application', 'octet-stream'),
|
contentType: MediaType('application', 'octet-stream'),
|
||||||
);
|
);
|
||||||
multipartFiles.add(multipartFile);
|
multipartFiles.add(multipartFile);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если data пустая строка или null, не передаем её вообще
|
|
||||||
// Сервер требует либо непустую строку, либо отсутствие поля
|
|
||||||
final dataToSend = (data == null || data.trim().isEmpty) ? null : data;
|
|
||||||
|
|
||||||
return await chatService.sendMessage(
|
|
||||||
slug: slug,
|
|
||||||
data: dataToSend,
|
|
||||||
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
|
|
||||||
socketId: (await SocketIdStorage.getSocketIdFromStorage())!,
|
|
||||||
);
|
|
||||||
} catch (e, st) {
|
|
||||||
log(e.toString(), stackTrace: st);
|
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если data пустая строка или null, не передаем её вообще
|
||||||
|
// Сервер требует либо непустую строку, либо отсутствие поля
|
||||||
|
final dataToSend = (data == null || data.trim().isEmpty) ? null : data;
|
||||||
|
|
||||||
|
return await chatService.sendMessage(
|
||||||
|
slug: slug,
|
||||||
|
data: dataToSend,
|
||||||
|
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
|
||||||
|
socketId: (await SocketIdStorage.getSocketIdFromStorage())!,
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ChatRoomModel> getChatRoom(String uuid) async {
|
Future<ChatRoomModel> getChatRoom(String uuid) async {
|
||||||
try {
|
return (await chatService.getChatRoom(uuid: uuid));
|
||||||
return (await chatService.getChatRoom(uuid: uuid));
|
|
||||||
} catch (e, st) {
|
|
||||||
log(e.toString(), stackTrace: st);
|
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ChatRoomPaginationModel> getChatRooms({String? query, int page = 1}) async {
|
Future<ChatRoomPaginationModel> getChatRooms({String? query, int page = 1}) async {
|
||||||
try {
|
return (await chatService.getChatRooms(query: query, page: page));
|
||||||
return (await chatService.getChatRooms(query: query, page: page));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ChatMessagesModel> getChatMessages(String id, int page) async {
|
Future<ChatMessagesModel> getChatMessages(String id, int page) async {
|
||||||
try {
|
return (await chatService.getChatMessages(id: id, page: page));
|
||||||
return (await chatService.getChatMessages(id: id, page: page));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async {
|
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async {
|
||||||
try {
|
return (await chatService.getChatRoomIdByUserSlug(user: slug));
|
||||||
return (await chatService.getChatRoomIdByUserSlug(user: slug));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> markMessagesAsRead(String id, List<int> messageIds) async {
|
Future<void> markMessagesAsRead(String id, List<int> messageIds) async {
|
||||||
try {
|
await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds});
|
||||||
await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds});
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> cancelChosenPlan({
|
Future<void> cancelChosenPlan({
|
||||||
required int id,
|
required int id,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await chatService.cancelChosenPlan(id: id);
|
||||||
await chatService.cancelChosenPlan(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> choseStartPlanDate({required int id, required Map<String, String> date}) async {
|
Future<void> choseStartPlanDate({required int id, required Map<String, String> date}) async {
|
||||||
try {
|
await chatService.choseStartPlanDate(id: id, date: date);
|
||||||
await chatService.choseStartPlanDate(id: id, date: date);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PaymentModel> getPaymentLink({
|
Future<PaymentModel> getPaymentLink({
|
||||||
required int id,
|
required int id,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
return await chatService.getPaymentLink(id: id);
|
||||||
return await chatService.getPaymentLink(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ChosenPlanModel> getChosenPlan({
|
Future<ChosenPlanModel> getChosenPlan({
|
||||||
required int id,
|
required int id,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
return await chatService.getChosenPlan(id: id);
|
||||||
return await chatService.getChosenPlan(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> finishChosenPlan({
|
Future<void> finishChosenPlan({
|
||||||
required int id,
|
required int id,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await chatService.finishChosenPlan(id: id);
|
||||||
await chatService.finishChosenPlan(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> complainAboutConsultation({
|
Future<void> complainAboutConsultation({
|
||||||
required int id,
|
required int id,
|
||||||
required PlanComplainModel body,
|
required PlanComplainModel body,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await chatService.complainAboutConsultation(id: id, body: body);
|
||||||
await chatService.complainAboutConsultation(id: id, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getChatByUserSlug(String slug) async {
|
Future<String> getChatByUserSlug(String slug) async {
|
||||||
try {
|
final response = await chatService.getChatByUserSlug(slug: slug);
|
||||||
final response = await chatService.getChatByUserSlug(slug: slug);
|
if (response.response.statusCode == 404) {
|
||||||
if (response.response.statusCode == 404) {
|
return '404';
|
||||||
return '404';
|
|
||||||
} else {
|
|
||||||
return response.data['id'];
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
return 'error';
|
|
||||||
}
|
}
|
||||||
|
return response.data['id'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_
|
|||||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
|
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
|
||||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ComplainingCubit extends Cubit<ComplainingState> {
|
class ComplainingCubit extends Cubit<ComplainingState> {
|
||||||
final ComplainingRepository repository;
|
final ComplainingRepository repository;
|
||||||
@@ -81,6 +82,7 @@ class ComplainingCubit extends Cubit<ComplainingState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
emit(state.copyWith(wasSentSuccessfully: false));
|
emit(state.copyWith(wasSentSuccessfully: false));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
|
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
|
||||||
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class ComplainingRepository {
|
class ComplainingRepository {
|
||||||
final ComplainingService service;
|
final ComplainingService service;
|
||||||
@@ -11,12 +10,8 @@ class ComplainingRepository {
|
|||||||
required String category,
|
required String category,
|
||||||
required ComplainModel body,
|
required ComplainModel body,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await service.sendPlanComplain(slug: slug, category: category, body: body);
|
||||||
await service.sendPlanComplain(slug: slug, category: category, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendRecipeComplain({
|
Future<void> sendRecipeComplain({
|
||||||
@@ -24,12 +19,8 @@ class ComplainingRepository {
|
|||||||
required String category,
|
required String category,
|
||||||
required ComplainModel body,
|
required ComplainModel body,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await service.sendRecipeComplain(slug: slug, category: category, body: body);
|
||||||
await service.sendRecipeComplain(slug: slug, category: category, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendBlogPostsComplain({
|
Future<void> sendBlogPostsComplain({
|
||||||
@@ -37,12 +28,8 @@ class ComplainingRepository {
|
|||||||
required String category,
|
required String category,
|
||||||
required ComplainModel body,
|
required ComplainModel body,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await service.sendBlogPostsComplain(id: id, category: category, body: body);
|
||||||
await service.sendBlogPostsComplain(id: id, category: category, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendProfileComplain({
|
Future<void> sendProfileComplain({
|
||||||
@@ -50,12 +37,8 @@ class ComplainingRepository {
|
|||||||
required String category,
|
required String category,
|
||||||
required ComplainModel body,
|
required ComplainModel body,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await service.sendProfileComplain(slug: slug, category: category, body: body);
|
||||||
await service.sendProfileComplain(slug: slug, category: category, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendDietComplain({
|
Future<void> sendDietComplain({
|
||||||
@@ -63,12 +46,8 @@ class ComplainingRepository {
|
|||||||
required String category,
|
required String category,
|
||||||
required ComplainModel body,
|
required ComplainModel body,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await service.sendDietComplain(id: id, category: category, body: body);
|
||||||
await service.sendDietComplain(id: id, category: category, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendCommentComplain({
|
Future<void> sendCommentComplain({
|
||||||
@@ -76,11 +55,7 @@ class ComplainingRepository {
|
|||||||
required String category,
|
required String category,
|
||||||
required ComplainModel body,
|
required ComplainModel body,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
await service.sendCommentComplain(id: id, category: category, body: body);
|
||||||
await service.sendCommentComplain(id: id, category: category, body: body);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
||||||
final ConsultantRepository consultantRepository;
|
final ConsultantRepository consultantRepository;
|
||||||
@@ -20,70 +22,106 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Future<void> getMyRequestStatus(String slug) async {
|
Future<void> getMyRequestStatus(String slug) async {
|
||||||
_update = true;
|
try {
|
||||||
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
|
_update = true;
|
||||||
final certPhotos = await consultantRepository.getConsultantCertPhotos();
|
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
|
||||||
|
final certPhotos = await consultantRepository.getConsultantCertPhotos();
|
||||||
|
|
||||||
List<CertificatesModel>? certificates = myRequestStatus.educationModel.completedCourses?.map((courseMap) {
|
List<CertificatesModel>? certificates = myRequestStatus.educationModel.completedCourses?.map((courseMap) {
|
||||||
final course = courseMap.values.first;
|
final course = courseMap.values.first;
|
||||||
|
|
||||||
return CertificatesModel(title: course.title, date: course.date);
|
return CertificatesModel(title: course.title, date: course.date);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
String? sanitizeLink(String? link) {
|
String? sanitizeLink(String? link) {
|
||||||
return (link != null && link.isNotEmpty) ? link : null;
|
return (link != null && link.isNotEmpty) ? link : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
RegisterConsultantState(
|
RegisterConsultantState(
|
||||||
consultant: ConsultantProfileModel(
|
consultant: ConsultantProfileModel(
|
||||||
certificates: certificates,
|
certificates: certificates,
|
||||||
socialLinks: SocialLinksModel(
|
socialLinks: SocialLinksModel(
|
||||||
vkLink: sanitizeLink(myRequestStatus.educationModel.vkLink),
|
vkLink: sanitizeLink(myRequestStatus.educationModel.vkLink),
|
||||||
tgLink: sanitizeLink(myRequestStatus.educationModel.tgLink),
|
tgLink: sanitizeLink(myRequestStatus.educationModel.tgLink),
|
||||||
fbLink: sanitizeLink(myRequestStatus.educationModel.fbLink),
|
fbLink: sanitizeLink(myRequestStatus.educationModel.fbLink),
|
||||||
instaLink: sanitizeLink(myRequestStatus.educationModel.instaLink),
|
instaLink: sanitizeLink(myRequestStatus.educationModel.instaLink),
|
||||||
personalSiteLink: sanitizeLink(myRequestStatus.educationModel.personalSiteLink),
|
personalSiteLink: sanitizeLink(myRequestStatus.educationModel.personalSiteLink),
|
||||||
|
),
|
||||||
|
hasMedicalEducation: myRequestStatus.educationModel.hasMedicalEducation == 1,
|
||||||
),
|
),
|
||||||
hasMedicalEducation: myRequestStatus.educationModel.hasMedicalEducation == 1,
|
acceptOffer: false,
|
||||||
|
loading: false,
|
||||||
|
requestModel: myRequestStatus,
|
||||||
|
certPhotos: certPhotos,
|
||||||
|
isUpdating: _update,
|
||||||
|
error: null,
|
||||||
),
|
),
|
||||||
acceptOffer: false,
|
);
|
||||||
loading: false,
|
} catch (e, st) {
|
||||||
requestModel: myRequestStatus,
|
await reportFrom(e, st, this);
|
||||||
certPhotos: certPhotos,
|
log(e.toString());
|
||||||
isUpdating: _update
|
emit(state.copyWith(loading: false, error: e.toString()));
|
||||||
),
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> disableLoading() async {
|
Future<void> disableLoading() async {
|
||||||
final certPhotos = await consultantRepository.getConsultantCertPhotos();
|
try {
|
||||||
emit(state.copyWith(loading: false, certPhotos: certPhotos));
|
final certPhotos = await consultantRepository.getConsultantCertPhotos();
|
||||||
|
emit(state.copyWith(loading: false, certPhotos: certPhotos, error: null));
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
log(e.toString());
|
||||||
|
emit(state.copyWith(loading: false, error: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Повторная загрузка после [DataErrorWidget], аналогично [AboutDietCubit.getDiet].
|
||||||
|
Future<void> reloadData(String? slug) async {
|
||||||
|
emit(state.copyWith(loading: true, error: null));
|
||||||
|
if (slug != null) {
|
||||||
|
await getMyRequestStatus(slug);
|
||||||
|
} else {
|
||||||
|
await disableLoading();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
|
void updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
|
||||||
emit(state.copyWith(consultant: consultant, acceptOffer: acceptOffer, loading: false));
|
emit(state.copyWith(consultant: consultant, acceptOffer: acceptOffer, loading: false, error: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> registerConsultant() async {
|
Future<void> registerConsultant() async {
|
||||||
if (state.newAddedPhotos.isNotEmpty) {
|
emit(state.copyWith(error: null));
|
||||||
await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos);
|
try {
|
||||||
}
|
if (state.newAddedPhotos.isNotEmpty) {
|
||||||
if (state.deletedPhotoIds.isNotEmpty) {
|
await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos);
|
||||||
for (final int id in state.deletedPhotoIds) {
|
|
||||||
await consultantRepository.deletePhoto(id);
|
|
||||||
}
|
}
|
||||||
|
if (state.deletedPhotoIds.isNotEmpty) {
|
||||||
|
for (final int id in state.deletedPhotoIds) {
|
||||||
|
await consultantRepository.deletePhoto(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
log(e.toString());
|
||||||
|
emit(state.copyWith(error: e.toString()));
|
||||||
}
|
}
|
||||||
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addCertPhotos(List<File> files) async {
|
Future<void> addCertPhotos(List<File> files) async {
|
||||||
if (files.isEmpty) {
|
if (files.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final compressedFiles = await ImageCompressUtility.compressFilesList(files);
|
try {
|
||||||
|
final compressedFiles = await ImageCompressUtility.compressFilesList(files);
|
||||||
|
|
||||||
emit(state.copyWith(newAddedPhotos: [...state.newAddedPhotos, ...compressedFiles]));
|
emit(state.copyWith(newAddedPhotos: [...state.newAddedPhotos, ...compressedFiles], error: null));
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
log(e.toString());
|
||||||
|
emit(state.copyWith(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeCertPhoto(int? id, String? path) async {
|
Future<void> removeCertPhoto(int? id, String? path) async {
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ abstract class RegisterConsultantState with _$RegisterConsultantState {
|
|||||||
@Default(<ConsultantCertPhotoModel>[]) List<ConsultantCertPhotoModel> certPhotos,
|
@Default(<ConsultantCertPhotoModel>[]) List<ConsultantCertPhotoModel> certPhotos,
|
||||||
@Default(<File>[]) List<File> newAddedPhotos,
|
@Default(<File>[]) List<File> newAddedPhotos,
|
||||||
@Default(<int>[]) List<int> deletedPhotoIds,
|
@Default(<int>[]) List<int> deletedPhotoIds,
|
||||||
|
String? error,
|
||||||
}) = _RegisterConsultantState;
|
}) = _RegisterConsultantState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,18 +17,13 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
|
|||||||
title: const Text('Стать консультантом'),
|
title: const Text('Стать консультантом'),
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
leading: IconButton(
|
leading: IconButton(onPressed: () => GoRouter.of(context).pop(), icon: const Icon(Icons.close)),
|
||||||
onPressed: () => GoRouter.of(context).pop(),
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
body: const Padding(
|
body: const Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.all(16),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Чтобы стать консультантом необходимо пройти короткую регистрацию. Подготовьте свои дипломы и/или сертификаты в формате jpg, они Вам пригодятся для проверки и подтверждения. Также необходимо будет указать ссылки на Ваши соц. сети и указать тип лица (физ. лицо или юр. лицо). На данный момент доступна регистрация в качестве физ. лица.\n\nВнутри Итвио разница между обычным пользователем и консультантом заключается в дополнительных возможностях последнего создавать публичные диеты, рецепты и планы питания; участвовать в ежемесячных рейтингах.',
|
'Чтобы стать консультантом необходимо пройти короткую регистрацию. Подготовьте свои дипломы и/или сертификаты в формате jpg, они Вам пригодятся для проверки и подтверждения. Также необходимо будет указать ссылки на Ваши соц. сети и указать тип лица (физ. лицо или юр. лицо). На данный момент доступна регистрация в качестве физ. лица.\n\nВнутри Итвио разница между обычным пользователем и консультантом заключается в дополнительных возможностях последнего создавать публичные диеты, рецепты и планы питания; участвовать в ежемесячных рейтингах.',
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 16),
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomSheet: _startRegistrationButton(),
|
bottomSheet: _startRegistrationButton(),
|
||||||
@@ -37,24 +32,16 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
|
|||||||
|
|
||||||
InkWell _startRegistrationButton() {
|
InkWell _startRegistrationButton() {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen'),
|
onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen', extra: {'slug': null}),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
height: 52,
|
height: 52,
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
color: appTheme.blueA400,
|
|
||||||
),
|
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Начать регистрацию',
|
'Начать регистрацию',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.white, height: 1, fontWeight: FontWeight.w600, fontSize: 18),
|
||||||
color: Colors.white,
|
|
||||||
height: 1,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||||
@@ -9,6 +10,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/presentation/pa
|
|||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.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';
|
||||||
@@ -48,8 +50,37 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
|
|||||||
create: (context) => registrationConsultantCubit,
|
create: (context) => registrationConsultantCubit,
|
||||||
child: BlocBuilder<RegisterConsultantCubit, RegisterConsultantState>(
|
child: BlocBuilder<RegisterConsultantCubit, RegisterConsultantState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.loading == false) {
|
if (state.loading) {
|
||||||
|
return const Scaffold(body: Loader());
|
||||||
|
}
|
||||||
|
if (state.error != null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
leading: BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go('/feed-screen');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'',
|
||||||
|
maxLines: 2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: true,
|
||||||
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: DataErrorWidget(
|
||||||
|
onReload: () => context.read<RegisterConsultantCubit>().reloadData(widget.slug),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
floatingActionButton: Row(
|
floatingActionButton: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -136,6 +167,9 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
|
|||||||
try {
|
try {
|
||||||
await registrationConsultantCubit.registerConsultant();
|
await registrationConsultantCubit.registerConsultant();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
if (registrationConsultantCubit.state.error != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await showModalBottomSheet(
|
await showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
@@ -183,7 +217,7 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
showDialog(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 4),
|
actionsPadding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
@@ -191,9 +225,7 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('Ок'),
|
child: const Text('Ок'),
|
||||||
onPressed: () {
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -282,9 +314,6 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return const Scaffold(body: Loader());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:eatvio_app/features/consultant/become_consultant/models/consulta
|
|||||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class ConsultantRepository {
|
class ConsultantRepository {
|
||||||
final ConsultantService consultantService;
|
final ConsultantService consultantService;
|
||||||
@@ -13,90 +12,58 @@ class ConsultantRepository {
|
|||||||
const ConsultantRepository({required this.consultantService});
|
const ConsultantRepository({required this.consultantService});
|
||||||
|
|
||||||
Future<void> registerConsultant({required ConsultantProfileModel consultant, bool update = false}) async {
|
Future<void> registerConsultant({required ConsultantProfileModel consultant, bool update = false}) async {
|
||||||
try {
|
if (update) {
|
||||||
if (update) {
|
return await consultantService.consultantUpdate(consultantModel: consultant);
|
||||||
return await consultantService.consultantUpdate(consultantModel: consultant);
|
} else {
|
||||||
} else {
|
return await consultantService.consultantRegistration(consultantModel: consultant);
|
||||||
return await consultantService.consultantRegistration(consultantModel: consultant);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ConsultationRequestModel> getConsultantRequestStatus(String slug) async {
|
Future<ConsultationRequestModel> getConsultantRequestStatus(String slug) async {
|
||||||
try {
|
return await consultantService.getConsultantRequestStatus(slug: slug);
|
||||||
return await consultantService.getConsultantRequestStatus(slug: slug);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ConsultationRequestModel> getMyRequestStatus(String slug) async {
|
Future<ConsultationRequestModel> getMyRequestStatus(String slug) async {
|
||||||
try {
|
return await consultantService.getMyRequestStatus(slug: slug);
|
||||||
return await consultantService.getMyRequestStatus(slug: slug);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ConsultantCertPhotoModel>> getConsultantCertPhotos() async {
|
Future<List<ConsultantCertPhotoModel>> getConsultantCertPhotos() async {
|
||||||
try {
|
return await consultantService.getConsultantCertPhotos();
|
||||||
return await consultantService.getConsultantCertPhotos();
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ConsultantCertPhotoModel>> storeConsultantCertPhotos({required List<File> files}) async {
|
Future<List<ConsultantCertPhotoModel>> storeConsultantCertPhotos({required List<File> files}) async {
|
||||||
try {
|
final multipartFiles = <MultipartFile>[];
|
||||||
final multipartFiles = <MultipartFile>[];
|
|
||||||
|
|
||||||
for (final file in files) {
|
for (final file in files) {
|
||||||
final fileBytes = await file.readAsBytes();
|
final fileBytes = await file.readAsBytes();
|
||||||
final multipartFile = MultipartFile.fromBytes(fileBytes, filename: file.path.split('/').last, contentType: MediaType('application', 'octet-stream'));
|
final multipartFile = MultipartFile.fromBytes(fileBytes, filename: file.path.split('/').last, contentType: MediaType('application', 'octet-stream'));
|
||||||
multipartFiles.add(multipartFile);
|
multipartFiles.add(multipartFile);
|
||||||
}
|
|
||||||
|
|
||||||
if (multipartFiles.isEmpty) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return await consultantService.storeConsultantCertPhotos(photos: multipartFiles);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (multipartFiles.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return await consultantService.storeConsultantCertPhotos(photos: multipartFiles);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> hideConsultantCertPhoto(int id) async {
|
Future<void> hideConsultantCertPhoto(int id) async {
|
||||||
try {
|
await consultantService.hideConsultantCertPhoto(payload: {'cert_photo_id': id});
|
||||||
await consultantService.hideConsultantCertPhoto(payload: {'cert_photo_id': id});
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> unHideConsultantCertPhoto(int id) async {
|
Future<void> unHideConsultantCertPhoto(int id) async {
|
||||||
try {
|
await consultantService.unHideConsultantCertPhoto(payload: {'cert_photo_id': id});
|
||||||
await consultantService.unHideConsultantCertPhoto(payload: {'cert_photo_id': id});
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePhoto(int id) async {
|
Future<void> deletePhoto(int id) async {
|
||||||
try {
|
await consultantService.deletePhoto(id: id);
|
||||||
await consultantService.deletePhoto(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultant_rating/repositories/consultant_ra
|
|||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
|
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
|
||||||
final ConsultantRatingRepository repository;
|
final ConsultantRatingRepository repository;
|
||||||
@@ -20,6 +21,7 @@ class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
|
|||||||
final absoluteRating = await feedRepository.getConsultants((await profileRepository.getMyProfile()).name);
|
final absoluteRating = await feedRepository.getConsultants((await profileRepository.getMyProfile()).name);
|
||||||
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null, absoluteRating: absoluteRating.first.totalScores));
|
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null, absoluteRating: absoluteRating.first.totalScores));
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
|
emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
@@ -31,6 +33,7 @@ class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
|
|||||||
final scoreLogs = await repository.getScoreLogs(date: date);
|
final scoreLogs = await repository.getScoreLogs(date: date);
|
||||||
emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null));
|
emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null));
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(state.copyWith(isRatingLoading: true, error: e.toString()));
|
emit(state.copyWith(isRatingLoading: true, error: e.toString()));
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class ConsultantRatingRepository {
|
class ConsultantRatingRepository {
|
||||||
final ConsultantRatingService service;
|
final ConsultantRatingService service;
|
||||||
@@ -9,21 +8,13 @@ class ConsultantRatingRepository {
|
|||||||
ConsultantRatingRepository(this.service);
|
ConsultantRatingRepository(this.service);
|
||||||
|
|
||||||
Future<StatisticsModel> getConsultantStatistics({String? date}) async {
|
Future<StatisticsModel> getConsultantStatistics({String? date}) async {
|
||||||
try {
|
return await service.getConsultantStatistics(date: date);
|
||||||
return await service.getConsultantStatistics(date: date);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<RatingModel>> getScoreLogs({String? date}) async {
|
Future<List<RatingModel>> getScoreLogs({String? date}) async {
|
||||||
try {
|
final data = (await service.getScoreLogs(date: date)).data;
|
||||||
final data = (await service.getScoreLogs(date: date)).data;
|
return data;
|
||||||
return data;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:eatvio_app/core/enums/base_state.dart';
|
|||||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
|
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
|
||||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ConsultationsCubit extends Cubit<ConsultationsState> {
|
class ConsultationsCubit extends Cubit<ConsultationsState> {
|
||||||
final ConsultationsRepository consultationsRepository;
|
final ConsultationsRepository consultationsRepository;
|
||||||
@@ -14,6 +15,7 @@ class ConsultationsCubit extends Cubit<ConsultationsState> {
|
|||||||
final consultations = await consultationsRepository.getConsultations();
|
final consultations = await consultationsRepository.getConsultations();
|
||||||
emit(state.copyWith(consultations: consultations, state: BaseState.success));
|
emit(state.copyWith(consultations: consultations, state: BaseState.success));
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(state.copyWith(state: BaseState.error));
|
emit(state.copyWith(state: BaseState.error));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,8 +172,8 @@ 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(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
||||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
|
||||||
@@ -11,13 +10,8 @@ class ConsultationsRepository {
|
|||||||
|
|
||||||
ConsultationsRepository({required this.consultationService});
|
ConsultationsRepository({required this.consultationService});
|
||||||
Future<List<ConsultationModel>> getConsultations() async {
|
Future<List<ConsultationModel>> getConsultations() async {
|
||||||
try {
|
final consultations = await consultationService.getConsultations();
|
||||||
final consultations = await consultationService.getConsultations();
|
return consultations.data;
|
||||||
return consultations.data;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ConsultationModel>> getFilteredConsultations({
|
Future<List<ConsultationModel>> getFilteredConsultations({
|
||||||
@@ -26,44 +20,24 @@ class ConsultationsRepository {
|
|||||||
FiltersComparisonEnum? stateComparison,
|
FiltersComparisonEnum? stateComparison,
|
||||||
FiltersComparisonEnum? dateComparison,
|
FiltersComparisonEnum? dateComparison,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final model = await consultationService.getFilteredConsultations(
|
||||||
final model = await consultationService.getFilteredConsultations(
|
state: state,
|
||||||
state: state,
|
date: date,
|
||||||
date: date,
|
stateComparison: stateComparison,
|
||||||
stateComparison: stateComparison,
|
dateComparison: dateComparison,
|
||||||
dateComparison: dateComparison,
|
);
|
||||||
);
|
return model.data;
|
||||||
return model.data;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<StatisticModel> getStatistic() async {
|
Future<StatisticModel> getStatistic() async {
|
||||||
try {
|
return consultationService.getStatistics();
|
||||||
return await consultationService.getStatistics();
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async {
|
Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async {
|
||||||
try {
|
return consultationService.getConsultantFinances(date, dateComparison);
|
||||||
return await consultationService.getConsultantFinances(date, dateComparison);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async {
|
Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async {
|
||||||
try {
|
return consultationService.getUserPurchases(date, dateComparison);
|
||||||
return await consultationService.getUserPurchases(date, dateComparison);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
|||||||
if (state.published) {
|
if (state.published) {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop();
|
context.pop(true);
|
||||||
showBottomSheetWithBlur(
|
showBottomSheetWithBlur(
|
||||||
userRootNavigator: true,
|
userRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
@@ -113,7 +113,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
|||||||
} else if (state.drafted) {
|
} else if (state.drafted) {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop();
|
context.pop(true);
|
||||||
showBottomSheetWithBlur(
|
showBottomSheetWithBlur(
|
||||||
context: context,
|
context: context,
|
||||||
child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true),
|
child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true),
|
||||||
@@ -231,7 +231,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
|||||||
if (dietState == DietState.draft) {
|
if (dietState == DietState.draft) {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop();
|
context.pop(true);
|
||||||
showBottomSheetWithBlur(
|
showBottomSheetWithBlur(
|
||||||
userRootNavigator: true,
|
userRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
@@ -241,7 +241,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
|||||||
} else {
|
} else {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop();
|
context.pop(true);
|
||||||
showBottomSheetWithBlur(
|
showBottomSheetWithBlur(
|
||||||
context: context,
|
context: context,
|
||||||
child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true),
|
child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true),
|
||||||
@@ -304,7 +304,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
|||||||
} else {
|
} else {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop();
|
context.pop(true);
|
||||||
showBottomSheetWithBlur(
|
showBottomSheetWithBlur(
|
||||||
userRootNavigator: true,
|
userRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
|||||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
class CreateDietCubit extends Cubit<CreateDietState> {
|
class CreateDietCubit extends Cubit<CreateDietState> {
|
||||||
@@ -29,9 +29,10 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
|
|
||||||
Future<void> setInitialData(Diet? diet) async {
|
Future<void> setInitialData(Diet? diet) async {
|
||||||
if (diet == null) return;
|
if (diet == null) return;
|
||||||
emit(state.copyWith(isLoading: true, isDietBeingEdited: true));
|
try {
|
||||||
dietId = diet.id;
|
emit(state.copyWith(isLoading: true, isDietBeingEdited: true));
|
||||||
final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id);
|
dietId = diet.id;
|
||||||
|
final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id);
|
||||||
final countOfDays = diet.duration;
|
final countOfDays = diet.duration;
|
||||||
final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]);
|
final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]);
|
||||||
|
|
||||||
@@ -66,7 +67,11 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
imageUrl: appConfig.buildType == AppBuildType.tests ? null : imageUrl,
|
imageUrl: appConfig.buildType == AppBuildType.tests ? null : imageUrl,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
emit(state.copyWith(isLoading: false, isDietBeingEdited: false, dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration)));
|
emit(state.copyWith(isLoading: false, isDietBeingEdited: false, dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration)));
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
emit(state.copyWith(isLoading: false, isDietBeingEdited: false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDetails(String text) {
|
void setDetails(String text) {
|
||||||
@@ -112,7 +117,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
||||||
return await glossaryRepository.getGlossaryListByQuery(query);
|
try {
|
||||||
|
return await glossaryRepository.getGlossaryListByQuery(query);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setImage(XFile? image, {String? imageUrl}) {
|
void setImage(XFile? image, {String? imageUrl}) {
|
||||||
@@ -204,7 +214,8 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
|
|
||||||
Future<Diet?> getDietForPreview() async {
|
Future<Diet?> getDietForPreview() async {
|
||||||
if (!_isFormValidForDraft()) return null;
|
if (!_isFormValidForDraft()) return null;
|
||||||
final authorProfile = await profileRepository.getMyProfile();
|
try {
|
||||||
|
final authorProfile = await profileRepository.getMyProfile();
|
||||||
final imageBytes = (await state.dietData.image?.readAsBytes()) ?? <int>[];
|
final imageBytes = (await state.dietData.image?.readAsBytes()) ?? <int>[];
|
||||||
String image = '';
|
String image = '';
|
||||||
if (imageBytes.isNotEmpty && imageBytes.length <= _maxImageBytesForBase64) {
|
if (imageBytes.isNotEmpty && imageBytes.length <= _maxImageBytesForBase64) {
|
||||||
@@ -297,19 +308,26 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
dailyNutrition: dailyNutrition,
|
dailyNutrition: dailyNutrition,
|
||||||
);
|
);
|
||||||
return diet;
|
return diet;
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File> downloadImageAsFile(String imageUrl) async {
|
Future<File?> downloadImageAsFile(String imageUrl) async {
|
||||||
final response = await http.get(Uri.parse(imageUrl));
|
try {
|
||||||
|
final response = await http.get(Uri.parse(imageUrl));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Не удалось загрузить изображение');
|
||||||
|
}
|
||||||
final tempDir = await getTemporaryDirectory();
|
final tempDir = await getTemporaryDirectory();
|
||||||
final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
|
final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
return await file.writeAsBytes(response.bodyBytes);
|
return file.writeAsBytes(response.bodyBytes);
|
||||||
} else {
|
} catch (e, st) {
|
||||||
throw Exception('Не удалось загрузить изображение');
|
await reportFrom(e, st, this);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +371,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft));
|
emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft));
|
||||||
return null;
|
return null;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(state.copyWith(isDietPublishing: false));
|
emit(state.copyWith(isDietPublishing: false));
|
||||||
}
|
}
|
||||||
@@ -391,7 +409,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
emit(state.copyWith(isDietBeingEdited: false));
|
emit(state.copyWith(isDietBeingEdited: false));
|
||||||
return null;
|
return null;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(state.copyWith(isDietBeingEdited: false));
|
emit(state.copyWith(isDietBeingEdited: false));
|
||||||
return 'error';
|
return 'error';
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'dart:io';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class CreateDietRepository {
|
class CreateDietRepository {
|
||||||
final CreateDietService createDietService;
|
final CreateDietService createDietService;
|
||||||
@@ -62,48 +61,40 @@ class CreateDietRepository {
|
|||||||
dietMap.addAll({'name': name, 'duration': duration});
|
dietMap.addAll({'name': name, 'duration': duration});
|
||||||
|
|
||||||
final formData = FormData.fromMap(dietMap);
|
final formData = FormData.fromMap(dietMap);
|
||||||
try {
|
switch (state) {
|
||||||
switch (state) {
|
case DietState.active:
|
||||||
case DietState.active:
|
return await createDietService.createPublishedDiet(formData: formData);
|
||||||
return await createDietService.createPublishedDiet(formData: formData);
|
case DietState.draft:
|
||||||
case DietState.draft:
|
return await createDietService.createDraftedDiet(formData: formData);
|
||||||
return await createDietService.createDraftedDiet(formData: formData);
|
case DietState.personal:
|
||||||
case DietState.personal:
|
return await createDietService.createPersonalDiet(formData: formData);
|
||||||
return await createDietService.createPersonalDiet(formData: formData);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<int, Map<int, Map<int, DailySchedule>>>> getDietPortionsNutrition(int dietId) async {
|
Future<Map<int, Map<int, Map<int, DailySchedule>>>> getDietPortionsNutrition(int dietId) async {
|
||||||
try {
|
final response = await createDietService.getDietPortionsNutrition(diet: dietId);
|
||||||
final response = await createDietService.getDietPortionsNutrition(diet: dietId);
|
final data = response.data;
|
||||||
final data = response.data;
|
final dailySchedule = (data as Map<String, dynamic>).map((key, value) {
|
||||||
final dailySchedule = (data as Map<String, dynamic>).map((key, value) {
|
return MapEntry(
|
||||||
return MapEntry(
|
int.parse(key),
|
||||||
int.parse(key),
|
(value as Map<String, dynamic>).map((key, value) {
|
||||||
(value as Map<String, dynamic>).map((key, value) {
|
return MapEntry(
|
||||||
return MapEntry(
|
int.parse(key),
|
||||||
int.parse(key),
|
(value as Map<String, dynamic>).map((key, value) {
|
||||||
(value as Map<String, dynamic>).map((key, value) {
|
final portionMap = Map<String, dynamic>.from(value as Map<String, dynamic>);
|
||||||
final portionMap = Map<String, dynamic>.from(value as Map<String, dynamic>);
|
final pnvid = portionMap['product_nutritional_value_id'];
|
||||||
final pnvid = portionMap['product_nutritional_value_id'];
|
if (pnvid is String) {
|
||||||
if (pnvid is String) {
|
portionMap['product_nutritional_value_id'] = int.tryParse(pnvid);
|
||||||
portionMap['product_nutritional_value_id'] = int.tryParse(pnvid);
|
}
|
||||||
}
|
return MapEntry(int.parse(key), DailySchedule.fromJson(portionMap));
|
||||||
return MapEntry(int.parse(key), DailySchedule.fromJson(portionMap));
|
}),
|
||||||
}),
|
);
|
||||||
);
|
}),
|
||||||
}),
|
);
|
||||||
);
|
});
|
||||||
});
|
return dailySchedule;
|
||||||
return dailySchedule;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> updateDiet({
|
Future<int> updateDiet({
|
||||||
@@ -161,23 +152,16 @@ class CreateDietRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final formData = FormData.fromMap(dietMap);
|
final formData = FormData.fromMap(dietMap);
|
||||||
try {
|
switch (state) {
|
||||||
switch (state) {
|
case DietState.active:
|
||||||
case DietState.active:
|
final response = await createDietService.updatePublished(diet: dietId, formData: formData);
|
||||||
final response = await createDietService.updatePublished(diet: dietId, formData: formData);
|
return response.response.statusCode ?? 0;
|
||||||
return response.response.statusCode ?? 0;
|
case DietState.draft:
|
||||||
case DietState.draft:
|
final response = await createDietService.updateDrafted(diet: dietId, formData: formData);
|
||||||
final response = await createDietService.updateDrafted(diet: dietId, formData: formData);
|
return response.response.statusCode ?? 0;
|
||||||
return response.response.statusCode ?? 0;
|
case DietState.personal:
|
||||||
case DietState.personal:
|
final response = await createDietService.updatePersonal(diet: dietId, formData: formData);
|
||||||
final response = await createDietService.updatePersonal(diet: dietId, formData: formData);
|
return response.response.statusCode ?? 0;
|
||||||
return response.response.statusCode ?? 0;
|
|
||||||
}
|
|
||||||
} on DioException catch (e) {
|
|
||||||
return e.response?.statusCode ?? 0;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
|||||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
|
|
||||||
class CreatePlanCubit extends Cubit<CreatePlanState> {
|
class CreatePlanCubit extends Cubit<CreatePlanState> {
|
||||||
@@ -457,7 +458,8 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
|
|||||||
await createPlanRepository.createPlan(model: planMap);
|
await createPlanRepository.createPlan(model: planMap);
|
||||||
emit(state.copyWith(isLoading: false, planCreated: true));
|
emit(state.copyWith(isLoading: false, planCreated: true));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
emit(state.copyWith(isLoading: false, planCreated: true));
|
emit(state.copyWith(isLoading: false, planCreated: true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class CreatePlanRepository {
|
class CreatePlanRepository {
|
||||||
final CreatePlanService createPlanService;
|
final CreatePlanService createPlanService;
|
||||||
@@ -10,13 +9,9 @@ class CreatePlanRepository {
|
|||||||
Future<void> createPlan({
|
Future<void> createPlan({
|
||||||
required Map<String, dynamic> model,
|
required Map<String, dynamic> model,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final formData = FormData.fromMap(model);
|
||||||
final formData = FormData.fromMap(model);
|
return await createPlanService.createPlan(formData: formData);
|
||||||
return await createPlanService.createPlan(formData: formData);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
|||||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
await recipeRepository.publishRecipe(model, update, slug);
|
await recipeRepository.publishRecipe(model, update, slug);
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
emit(state.copyWith(drafted: true));
|
emit(state.copyWith(drafted: true));
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
await Sentry.captureException(e, stackTrace: st);
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -204,16 +204,19 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File> downloadImageAsFile(String imageUrl) async {
|
Future<File?> downloadImageAsFile(String imageUrl) async {
|
||||||
final response = await http.get(Uri.parse(imageUrl));
|
try {
|
||||||
|
final response = await http.get(Uri.parse(imageUrl));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Не удалось загрузить изображение');
|
||||||
|
}
|
||||||
final tempDir = await getTemporaryDirectory();
|
final tempDir = await getTemporaryDirectory();
|
||||||
final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
|
final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
return await file.writeAsBytes(response.bodyBytes);
|
return file.writeAsBytes(response.bodyBytes);
|
||||||
} else {
|
} catch (e, st) {
|
||||||
throw Exception('Не удалось загрузить изображение');
|
await reportFrom(e, st, this);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,44 +225,53 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> recipeToEntity(Recipe recipe) async {
|
Future<void> recipeToEntity(Recipe recipe) async {
|
||||||
final file = await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${recipe.image}');
|
try {
|
||||||
|
final file = await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${recipe.image}');
|
||||||
|
if (file == null) {
|
||||||
|
disableLoading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final List<StepEntity> steps = await Future.wait(
|
final List<StepEntity> steps = await Future.wait(
|
||||||
recipe.steps!.map(
|
recipe.steps!.map(
|
||||||
(e) async => StepEntity(
|
(e) async => StepEntity(
|
||||||
description: e.description,
|
description: e.description,
|
||||||
image: e.image.isEmpty ? null : await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${e.image}'),
|
image: e.image.isEmpty ? null : await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${e.image}'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
emit(
|
||||||
emit(
|
CreateRecipeState(
|
||||||
CreateRecipeState(
|
entity: RecipeCreateEntity(
|
||||||
entity: RecipeCreateEntity(
|
title: recipe.title,
|
||||||
title: recipe.title,
|
typeOfRecipe: TypeOfRecipe.fromApiType(recipe.type!),
|
||||||
typeOfRecipe: TypeOfRecipe.fromApiType(recipe.type!),
|
countOfPortions: recipe.servings,
|
||||||
countOfPortions: recipe.servings,
|
energyInOnePortion: recipe.calories,
|
||||||
energyInOnePortion: recipe.calories,
|
totalCookHours: recipe.cookingTime ~/ 60,
|
||||||
totalCookHours: recipe.cookingTime ~/ 60,
|
totalCookMinutes: recipe.cookingTime % 60,
|
||||||
totalCookMinutes: recipe.cookingTime % 60,
|
activeCookHours: recipe.activeCookingTime ~/ 60,
|
||||||
activeCookHours: recipe.activeCookingTime ~/ 60,
|
activeCookMinutes: recipe.activeCookingTime % 60,
|
||||||
activeCookMinutes: recipe.activeCookingTime % 60,
|
description: recipe.description,
|
||||||
description: recipe.description,
|
image: file,
|
||||||
image: file,
|
videoUrl: recipe.videoUrl,
|
||||||
videoUrl: recipe.videoUrl,
|
ingredients: recipe.ingredients!
|
||||||
ingredients: recipe.ingredients!
|
.map((e) => i.Ingredient(
|
||||||
.map((e) => i.Ingredient(
|
name: e.name,
|
||||||
name: e.name,
|
unit: e.unit!,
|
||||||
unit: e.unit!,
|
portion: e.portion,
|
||||||
portion: e.portion,
|
productId: e.productId,
|
||||||
productId: e.productId,
|
))
|
||||||
))
|
.toList(),
|
||||||
.toList(),
|
steps: steps,
|
||||||
steps: steps,
|
tags: recipe.tags.map((e) => (e.name)).toList(),
|
||||||
tags: recipe.tags.map((e) => (e.name)).toList(),
|
),
|
||||||
|
loading: false,
|
||||||
),
|
),
|
||||||
loading: false,
|
);
|
||||||
),
|
} catch (e, st) {
|
||||||
);
|
await reportFrom(e, st, this);
|
||||||
|
disableLoading();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool validateAll() {
|
bool validateAll() {
|
||||||
@@ -376,13 +388,18 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
await recipeRepository.deleteRecipe(slug);
|
await recipeRepository.deleteRecipe(slug);
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
await reportFrom(e, s, this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
||||||
return await glossaryRepository.getGlossaryListByQuery(query);
|
try {
|
||||||
|
return await glossaryRepository.getGlossaryListByQuery(query);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool validateSteps() {
|
bool validateSteps() {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
|
|||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop();
|
context.pop('active');
|
||||||
showBottomSheetWithBlur(
|
showBottomSheetWithBlur(
|
||||||
userRootNavigator: true,
|
userRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
@@ -157,7 +157,7 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
|
|||||||
});
|
});
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.pop();
|
context.pop('drafted');
|
||||||
} else {
|
} else {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
showErrorDialog(context, title: 'Не удалось сохранить черновик');
|
showErrorDialog(context, title: 'Не удалось сохранить черновик');
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
|
|||||||
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
|
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:eatvio_app/features/create_recipe/models/product.dart';
|
import 'package:eatvio_app/features/create_recipe/models/product.dart';
|
||||||
|
|
||||||
class RecipeRepository {
|
class RecipeRepository {
|
||||||
@@ -16,12 +15,8 @@ class RecipeRepository {
|
|||||||
RecipeRepository({required this.recipeService});
|
RecipeRepository({required this.recipeService});
|
||||||
|
|
||||||
Future<Map<String, List<Product>>> inputProduct(String name) async {
|
Future<Map<String, List<Product>>> inputProduct(String name) async {
|
||||||
try {
|
return await recipeService.getInputProduct(name);
|
||||||
return await recipeService.getInputProduct(name);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List> _buildStepsMap(List<Step?>? steps) async {
|
Future<List> _buildStepsMap(List<Step?>? steps) async {
|
||||||
@@ -110,67 +105,43 @@ class RecipeRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async {
|
Future<void> publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async {
|
||||||
try {
|
final formData = await _buildPublishFormData(recipe);
|
||||||
final formData = await _buildPublishFormData(recipe);
|
if (update) {
|
||||||
if (update) {
|
await recipeService.updateRecipe(slug: slug!, formData: formData);
|
||||||
await recipeService.updateRecipe(slug: slug!, formData: formData);
|
} else {
|
||||||
} else {
|
await recipeService.publishRecipe(formData: formData);
|
||||||
await recipeService.publishRecipe(formData: formData);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveAsDraft(RecipeModel recipe, [bool update = false, String? slug]) async {
|
Future<void> saveAsDraft(RecipeModel recipe, [bool update = false, String? slug]) async {
|
||||||
try {
|
final formData = await _buildDraftFormData(recipe);
|
||||||
final formData = await _buildDraftFormData(recipe);
|
log(formData.fields.toString());
|
||||||
log(formData.fields.toString());
|
if (update) {
|
||||||
if (update) {
|
await recipeService.updateRecipe(slug: slug!, formData: formData);
|
||||||
await recipeService.updateRecipe(slug: slug!, formData: formData);
|
} else {
|
||||||
} else {
|
await recipeService.createDraftedRecipe(formData: formData);
|
||||||
await recipeService.createDraftedRecipe(formData: formData);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> draftExistingRecipe(int id) async {
|
Future<void> draftExistingRecipe(int id) async {
|
||||||
try {
|
await recipeService.draftRecipe(id: id);
|
||||||
await recipeService.draftRecipe(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RecipeModel> getRecipe(String recipeID) async {
|
Future<RecipeModel> getRecipe(String recipeID) async {
|
||||||
try {
|
return await recipeService.getRecipe(recipeID);
|
||||||
return await recipeService.getRecipe(recipeID);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<RecipeTag>> getRecipesTags() async {
|
Future<List<RecipeTag>> getRecipesTags() async {
|
||||||
try {
|
return (await recipeService.getTags()).data;
|
||||||
return (await recipeService.getTags()).data;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteRecipe(String slug) async {
|
Future<void> deleteRecipe(String slug) async {
|
||||||
try {
|
return await recipeService.deleteRecipe(slug: slug);
|
||||||
return await recipeService.deleteRecipe(slug: slug);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
|||||||
} else {
|
} else {
|
||||||
platform = 'ios';
|
platform = 'ios';
|
||||||
}
|
}
|
||||||
await dio.post('/api/profile/notification/token/register', data: {'token': await FirebaseApi().getToken(), 'platform': platform});
|
await dio.post('/api/profile/notification/token/register', data: {'token': await sl<FirebaseApi>().getToken(), 'platform': platform});
|
||||||
showInfoToast('Токен добавлен');
|
showInfoToast('Токен добавлен');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showInfoToast('Не удалось добавть токен');
|
showInfoToast('Не удалось добавть токен');
|
||||||
@@ -253,7 +253,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
|||||||
buttonStyle: const ButtonStyle(),
|
buttonStyle: const ButtonStyle(),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
final token = await FirebaseApi().getToken();
|
final token = await sl<FirebaseApi>().getToken();
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
Clipboard.setData(ClipboardData(text: token));
|
Clipboard.setData(ClipboardData(text: token));
|
||||||
showInfoToast('Токен скопирован');
|
showInfoToast('Токен скопирован');
|
||||||
@@ -278,7 +278,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
|||||||
} else {
|
} else {
|
||||||
platform = 'ios';
|
platform = 'ios';
|
||||||
}
|
}
|
||||||
await dio.post('/api/profile/notification/token/unregister', data: {'token': await FirebaseApi().getToken(), 'platform': platform});
|
await dio.post('/api/profile/notification/token/unregister', data: {'token': await sl<FirebaseApi>().getToken(), 'platform': platform});
|
||||||
showInfoToast('Токен удалён');
|
showInfoToast('Токен удалён');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showInfoToast('Не удалось удалить токен');
|
showInfoToast('Не удалось удалить токен');
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
|||||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||||
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
||||||
import 'package:eatvio_app/core/env_config.dart';
|
import 'package:eatvio_app/core/env_config.dart';
|
||||||
|
|
||||||
class TestResult {
|
class TestResult {
|
||||||
final bool success;
|
final bool success;
|
||||||
final String? error;
|
final String? error;
|
||||||
@@ -57,7 +56,9 @@ class CreateRecipeTask implements TestCases {
|
|||||||
Future<TestResult> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final recipeRepository = RecipeRepository(recipeService: CreateRecipeService(dio, baseUrl: baseUrl));
|
final recipeRepository = RecipeRepository(
|
||||||
|
recipeService: CreateRecipeService(dio, baseUrl: baseUrl),
|
||||||
|
);
|
||||||
await recipeRepository.publishRecipe(
|
await recipeRepository.publishRecipe(
|
||||||
const RecipeModel(
|
const RecipeModel(
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
|||||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart';
|
||||||
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
|
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ConsultantCommentsCubit extends Cubit<ConsultantCommentsState> {
|
class ConsultantCommentsCubit extends Cubit<ConsultantCommentsState> {
|
||||||
final DiaryRepository diaryRepository;
|
final DiaryRepository diaryRepository;
|
||||||
@@ -74,6 +75,7 @@ class ConsultantCommentsCubit extends Cubit<ConsultantCommentsState> {
|
|||||||
}
|
}
|
||||||
await diaryRepository.commentUserDay(diaryId: diaryId, commentModel: state.commentsModel);
|
await diaryRepository.commentUserDay(diaryId: diaryId, commentModel: state.commentsModel);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,15 @@ import 'package:eatvio_app/features/glossary/repositories/glossary_repository.da
|
|||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class DiaryCubit extends Cubit<DiaryState> {
|
class DiaryCubit extends Cubit<DiaryState> {
|
||||||
|
static const NutritionModel _zeroNutritionModel = NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0);
|
||||||
|
static const NutritionDataModel _fallbackNutrition = NutritionDataModel(
|
||||||
|
dailyNutritionIntake: _zeroNutritionModel,
|
||||||
|
dailyNutritionRecommendation: _zeroNutritionModel,
|
||||||
|
);
|
||||||
|
|
||||||
late final DateTime today;
|
late final DateTime today;
|
||||||
late DateTime selectedDay;
|
late DateTime selectedDay;
|
||||||
int selectedMeal = 1;
|
int selectedMeal = 1;
|
||||||
@@ -47,6 +54,12 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String comment = '';
|
String comment = '';
|
||||||
|
bool commentNeedsToBeFilled = false;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -77,6 +90,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'));
|
||||||
@@ -95,6 +109,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
emit(currentState);
|
emit(currentState);
|
||||||
return false;
|
return false;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(DiaryState.error(errorMessage: e.toString()));
|
emit(DiaryState.error(errorMessage: e.toString()));
|
||||||
return false;
|
return false;
|
||||||
@@ -108,7 +123,8 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
if (diaryEntity != null) {
|
if (diaryEntity != null) {
|
||||||
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
|
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,6 +135,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
|
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
|
||||||
return diaryDay;
|
return diaryDay;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
if (e is DioException) {
|
if (e is DioException) {
|
||||||
if (e.response?.statusCode == 422) {
|
if (e.response?.statusCode == 422) {
|
||||||
@@ -145,8 +162,9 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
emit(const DiaryState.loading());
|
emit(const DiaryState.loading());
|
||||||
if (diaryDay.diet?.id != null && diaryDay.dayOfChosenDiet != null) {
|
try {
|
||||||
final diet = await dietRepository.getDiet((diaryDay.diet?.id)!);
|
if (diaryDay.diet?.id != null && diaryDay.dayOfChosenDiet != null) {
|
||||||
|
final diet = await dietRepository.getDiet((diaryDay.diet?.id)!);
|
||||||
final meals = diet.dailySchedule[diaryDay.dayOfChosenDiet];
|
final meals = diet.dailySchedule[diaryDay.dayOfChosenDiet];
|
||||||
final firstMealPortions = meals?[1]?.values.map((elem) {
|
final firstMealPortions = meals?[1]?.values.map((elem) {
|
||||||
return PortionModel(
|
return PortionModel(
|
||||||
@@ -214,9 +232,14 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
selectedMeal: selectedMeal,
|
selectedMeal: selectedMeal,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
emit(DiaryState.error(errorMessage: e.toString()));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async {
|
Future<void> setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async {
|
||||||
@@ -272,18 +295,31 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(DiaryState.error(errorMessage: e.toString()));
|
emit(DiaryState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async {
|
Future<bool> finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async {
|
||||||
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
return true;
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<NutritionDataModel> getNutrition(int index) async {
|
Future<NutritionDataModel> getNutrition(int index) async {
|
||||||
return (await diaryRepository.getNutritionData(index));
|
try {
|
||||||
|
return (await diaryRepository.getNutritionData(index));
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
return _fallbackNutrition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addProductToMeal({
|
Future<void> addProductToMeal({
|
||||||
@@ -295,15 +331,23 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
int? productNutritionalValueId,
|
int? productNutritionalValueId,
|
||||||
}) async {
|
}) async {
|
||||||
emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true));
|
emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true));
|
||||||
await diaryRepository.addPortionToMeal(
|
try {
|
||||||
diaryId: diaryId,
|
await diaryRepository.addPortionToMeal(
|
||||||
mealId: mealId,
|
diaryId: diaryId,
|
||||||
productName: productName,
|
mealId: mealId,
|
||||||
portion: portion,
|
productName: productName,
|
||||||
quantity: quantity,
|
portion: portion,
|
||||||
productNutritionalValueId: productNutritionalValueId,
|
quantity: quantity,
|
||||||
);
|
productNutritionalValueId: productNutritionalValueId,
|
||||||
await updateCurrentDiary();
|
);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
final s = state;
|
||||||
|
if (s is DiaryStateLoaded) {
|
||||||
|
emit(s.copyWith(isLoadingAddingProductInMeal: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateProductInMeal({
|
Future<void> updateProductInMeal({
|
||||||
@@ -316,62 +360,95 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
int? productNutritionalValueId,
|
int? productNutritionalValueId,
|
||||||
required PortionState portionState,
|
required PortionState portionState,
|
||||||
}) async {
|
}) async {
|
||||||
if (portionState == PortionState.filledDiet || portionState == PortionState.eatenFromDiet) {
|
try {
|
||||||
await diaryRepository.addPortionToMeal(
|
if (portionState == PortionState.filledDiet || portionState == PortionState.eatenFromDiet) {
|
||||||
diaryId: diaryId,
|
await diaryRepository.addPortionToMeal(
|
||||||
mealId: mealId,
|
diaryId: diaryId,
|
||||||
productName: productName,
|
mealId: mealId,
|
||||||
portion: portion,
|
productName: productName,
|
||||||
quantity: quantity,
|
portion: portion,
|
||||||
productNutritionalValueId: productNutritionalValueId,
|
quantity: quantity,
|
||||||
);
|
productNutritionalValueId: productNutritionalValueId,
|
||||||
} else {
|
);
|
||||||
await diaryRepository.updateProductInMeal(
|
} else {
|
||||||
diaryId: diaryId,
|
await diaryRepository.updateProductInMeal(
|
||||||
mealId: mealId,
|
diaryId: diaryId,
|
||||||
portionId: portionId,
|
mealId: mealId,
|
||||||
productName: productName,
|
portionId: portionId,
|
||||||
portion: portion,
|
productName: productName,
|
||||||
quantity: quantity,
|
portion: portion,
|
||||||
productNutritionalValueId: productNutritionalValueId,
|
quantity: quantity,
|
||||||
portionState: portionState,
|
productNutritionalValueId: productNutritionalValueId,
|
||||||
);
|
portionState: portionState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
}
|
}
|
||||||
await updateCurrentDiary();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
||||||
return await glossaryRepository.getGlossaryListByQuery(query);
|
try {
|
||||||
|
return await glossaryRepository.getGlossaryListByQuery(query);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> uploadPhotos({required List<File> files, required int diaryId}) async {
|
Future<void> uploadPhotos({required List<File> files, required int diaryId}) async {
|
||||||
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
|
Future<void> deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
|
||||||
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GlossaryModerationModel?> sendProductToModeration({
|
Future<GlossaryModerationModel?> sendProductToModeration({
|
||||||
@@ -384,38 +461,60 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
try {
|
try {
|
||||||
return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates);
|
return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
await reportFrom(e, stackTrace, this);
|
||||||
log(e.toString(), stackTrace: stackTrace);
|
log(e.toString(), stackTrace: stackTrace);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillTime({required int diaryId, required String time}) async {
|
Future<void> fillTime({required int diaryId, required String time}) async {
|
||||||
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillWakeup({required int diaryId, required String time}) async {
|
Future<void> fillWakeup({required int diaryId, required String time}) async {
|
||||||
await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillRetire({required int diaryId, required String time}) async {
|
Future<void> fillRetire({required int diaryId, required String time}) async {
|
||||||
await diaryRepository.fillRetire(diaryId: diaryId, time: time);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.fillRetire(diaryId: diaryId, time: time);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillSatiety({required int diaryId, required int satiety}) async {
|
Future<void> fillSatiety({required int diaryId, required int satiety}) async {
|
||||||
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillMood({required int diaryId, required int mood}) async {
|
Future<void> fillMood({required int diaryId, required int mood}) async {
|
||||||
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
|
try {
|
||||||
await updateCurrentDiary();
|
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
|
||||||
|
await updateCurrentDiary();
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fillComment(String comment) {
|
void fillComment(String comment) {
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
|
commentNeedsToBeFilled = comment.isNotEmpty && comment.length < 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||||
@@ -24,31 +25,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 +60,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 +94,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(
|
||||||
@@ -132,11 +135,14 @@ class _DiaryScreenContentState extends State<DiaryScreenContent> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isDiaryFinishing = true;
|
_isDiaryFinishing = true;
|
||||||
});
|
});
|
||||||
await context.read<DiaryCubit>().finishDiaryDay(
|
final isFinished = await context.read<DiaryCubit>().finishDiaryDay(
|
||||||
diaryId: state.diary.id,
|
diaryId: state.diary.id,
|
||||||
wakeUp: state.diary.wakeup.toString(),
|
wakeUp: state.diary.wakeup.toString(),
|
||||||
retire: state.diary.retire.toString(),
|
retire: state.diary.retire.toString(),
|
||||||
);
|
);
|
||||||
|
if (!isFinished && context.mounted) {
|
||||||
|
showErrorDialog(context);
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_isDiaryFinishing = false;
|
_isDiaryFinishing = false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:easy_debounce/easy_debounce.dart';
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||||
|
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.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';
|
||||||
@@ -15,10 +16,14 @@ class CommentForConsultantWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _CommentForConsultantWidgetState extends State<CommentForConsultantWidget> {
|
class _CommentForConsultantWidgetState extends State<CommentForConsultantWidget> {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
|
bool _commentNeedsToBeFilled = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (widget.comment != null) controller.text = widget.comment ?? '';
|
if (widget.comment != null) {
|
||||||
|
controller.text = widget.comment ?? '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,13 +46,27 @@ class _CommentForConsultantWidgetState extends State<CommentForConsultantWidget>
|
|||||||
CustomTextFormField(
|
CustomTextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
EasyDebounce.debounce('client-typing', const Duration(milliseconds: 500), () => context.read<DiaryCubit>().fillComment(value));
|
EasyDebounce.debounce(
|
||||||
|
'client-typing',
|
||||||
|
const Duration(milliseconds: 500),
|
||||||
|
() => context.read<DiaryCubit>().fillComment(value),
|
||||||
|
);
|
||||||
|
if (widget.comment == null) {
|
||||||
|
setState(() {
|
||||||
|
_commentNeedsToBeFilled = value.isNotEmpty && value.length < 3;
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
hasError: _commentNeedsToBeFilled,
|
||||||
hintText: 'Укажите дополнительную информацию',
|
hintText: 'Укажите дополнительную информацию',
|
||||||
minLines: 4,
|
minLines: 4,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
readOnly: widget.comment != null,
|
readOnly: widget.comment != null,
|
||||||
),
|
),
|
||||||
|
if (_commentNeedsToBeFilled) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
const ValidationErrorWidget(error: 'Минимум 3 символа'),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class FinishDayButtonWidget extends StatefulWidget {
|
class FinishDayButtonWidget extends StatefulWidget {
|
||||||
final Future<void> Function() onPressed;
|
final Future<void> Function() onPressed;
|
||||||
@@ -18,6 +20,14 @@ class FinishDayButtonWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _FinishDayButtonWidgetState extends State<FinishDayButtonWidget> {
|
class _FinishDayButtonWidgetState extends State<FinishDayButtonWidget> {
|
||||||
void _onTap() {
|
void _onTap() {
|
||||||
|
if (context.read<DiaryCubit>().commentNeedsToBeFilled) {
|
||||||
|
showErrorDialog(
|
||||||
|
context,
|
||||||
|
title: 'Комментарий слишком короткий',
|
||||||
|
description: 'Минимум 3 символа в комментарии для консультанта.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!widget.hasProducts) {
|
if (!widget.hasProducts) {
|
||||||
showErrorDialog(
|
showErrorDialog(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -257,12 +257,17 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
final energy = double.tryParse(energyController.text.replaceAll(',', '.')) ?? 0;
|
||||||
|
final proteins = double.tryParse(proteinsController.text.replaceAll(',', '.')) ?? 0;
|
||||||
|
final fats = double.tryParse(fatsController.text.replaceAll(',', '.')) ?? 0;
|
||||||
|
final carbohydrates = double.tryParse(carbohydratesController.text.replaceAll(',', '.')) ?? 0;
|
||||||
|
|
||||||
glossary = (await widget.diaryCubit.sendProductToModeration(
|
glossary = (await widget.diaryCubit.sendProductToModeration(
|
||||||
name: productNameController.text,
|
name: productNameController.text,
|
||||||
energy: double.parse(energyController.text.replaceAll(',', '.')),
|
energy: energy,
|
||||||
proteins: double.parse(proteinsController.text.replaceAll(',', '.')),
|
proteins: proteins,
|
||||||
fats: double.parse(fatsController.text.replaceAll(',', '.')),
|
fats: fats,
|
||||||
carbohydrates: double.parse(carbohydratesController.text.replaceAll(',', '.')),
|
carbohydrates: carbohydrates,
|
||||||
))?.glossary;
|
))?.glossary;
|
||||||
setState(() {
|
setState(() {
|
||||||
glossaryState = GlossaryState.success;
|
glossaryState = GlossaryState.success;
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ class PortionCommentWidget extends StatefulWidget {
|
|||||||
class _PortionCommentWidgetState extends State<PortionCommentWidget> {
|
class _PortionCommentWidgetState extends State<PortionCommentWidget> {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
@override
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
@override
|
||||||
void didUpdateWidget(PortionCommentWidget oldWidget) {
|
void didUpdateWidget(PortionCommentWidget oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.portionId != oldWidget.portionId) {
|
if (widget.portionId != oldWidget.portionId) {
|
||||||
@@ -57,17 +62,19 @@ class _PortionCommentWidgetState extends State<PortionCommentWidget> {
|
|||||||
hintText: s.typePortionComment,
|
hintText: s.typePortionComment,
|
||||||
hasError: hasError,
|
hasError: hasError,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
|
final cubit = context.read<ConsultantCommentsCubit>();
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce(
|
||||||
'client-typing',
|
'portion-comment-${widget.portionId}',
|
||||||
const Duration(milliseconds: 500),
|
const Duration(milliseconds: 500),
|
||||||
() {
|
() {
|
||||||
final hasPortionComment = state.commentsModel.portions.any((portion) => portion.id == widget.portionId);
|
final currentState = cubit.state;
|
||||||
|
final hasPortionComment = currentState.commentsModel.portions.any((portion) => portion.id == widget.portionId);
|
||||||
if (hasPortionComment) {
|
if (hasPortionComment) {
|
||||||
final portion = state.commentsModel.portions.firstWhere((portion) => portion.id == widget.portionId);
|
final portion = currentState.commentsModel.portions.firstWhere((portion) => portion.id == widget.portionId);
|
||||||
context.read<ConsultantCommentsCubit>().updatePortionComment(portion.copyWith(comment: value));
|
cubit.updatePortionComment(portion.copyWith(comment: value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.read<ConsultantCommentsCubit>().addPortionComment(PortionCommentModel(id: widget.portionId, comment: value));
|
cubit.addPortionComment(PortionCommentModel(id: widget.portionId, comment: value));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
|
||||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||||
import 'package:eatvio_app/features/diary/models/create_day_model.dart';
|
import 'package:eatvio_app/features/diary/models/create_day_model.dart';
|
||||||
@@ -9,7 +11,6 @@ import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
|
|||||||
import 'package:eatvio_app/features/diary/models/product_moderation_model.dart';
|
import 'package:eatvio_app/features/diary/models/product_moderation_model.dart';
|
||||||
import 'package:eatvio_app/features/diary/services/diary_service.dart';
|
import 'package:eatvio_app/features/diary/services/diary_service.dart';
|
||||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class DiaryRepository {
|
class DiaryRepository {
|
||||||
final DiaryService diaryService;
|
final DiaryService diaryService;
|
||||||
@@ -19,134 +20,82 @@ class DiaryRepository {
|
|||||||
|
|
||||||
DiaryRepository({required this.diaryService});
|
DiaryRepository({required this.diaryService});
|
||||||
|
|
||||||
Future<DiaryDayModel> getDiaryByDate(String date) async {
|
PortionModel _portionWithGlossary(PortionModel portion, List<GlossaryModel> glossaries) {
|
||||||
try {
|
final id = portion.productNutritionalValueId;
|
||||||
Set<int> ids = {};
|
if (id == null) return portion;
|
||||||
final diaryDays = await getDiaryIndexByDate(date);
|
final glossaryItem = glossaries.firstWhereOrNull((g) => g.id == id);
|
||||||
if (diaryDays.isEmpty) {
|
if (glossaryItem == null) return portion;
|
||||||
throw Exception('No diary entry found for $date');
|
return portion.copyWith(
|
||||||
}
|
productCaloriesInfo: ProductCaloriesInfoModel(
|
||||||
final DiaryDayModel diaryDay = diaryDays.last;
|
id: glossaryItem.id,
|
||||||
for (var meal in diaryDay.meals) {
|
productName: glossaryItem.name,
|
||||||
for (var portion in meal.portions) {
|
calories: glossaryItem.energyOn100Gr,
|
||||||
if (portion.productNutritionalValueId != null) {
|
proteins: glossaryItem.proteinsOn100Gr,
|
||||||
ids.add(portion.productNutritionalValueId!);
|
fats: glossaryItem.fatsOn100Gr,
|
||||||
}
|
carbohydrates: glossaryItem.carbohydratesOn100Gr,
|
||||||
}
|
energyOnPortion: glossaryItem.energyOnPortion,
|
||||||
}
|
proteinsOnPortion: glossaryItem.proteinsOnPortion,
|
||||||
if (ids.isNotEmpty) {
|
fatsOnPortion: glossaryItem.fatsOnPortion,
|
||||||
final glossary = await getGlossariesIndex(ids: ids.toList());
|
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
|
||||||
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
|
),
|
||||||
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
|
);
|
||||||
final meal = updatedMeals[mealIndex];
|
}
|
||||||
final updatedPortions = meal.portions.map((portion) {
|
|
||||||
if (portion.productNutritionalValueId != null) {
|
|
||||||
try {
|
|
||||||
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
|
|
||||||
return portion.copyWith(
|
|
||||||
productCaloriesInfo: ProductCaloriesInfoModel(
|
|
||||||
id: glossaryItem.id,
|
|
||||||
productName: glossaryItem.name,
|
|
||||||
calories: glossaryItem.energyOn100Gr,
|
|
||||||
proteins: glossaryItem.proteinsOn100Gr,
|
|
||||||
fats: glossaryItem.fatsOn100Gr,
|
|
||||||
carbohydrates: glossaryItem.carbohydratesOn100Gr,
|
|
||||||
energyOnPortion: glossaryItem.energyOnPortion,
|
|
||||||
proteinsOnPortion: glossaryItem.proteinsOnPortion,
|
|
||||||
fatsOnPortion: glossaryItem.fatsOnPortion,
|
|
||||||
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return portion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return portion;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
|
Future<DiaryDayModel> getDiaryByDate(String date) async {
|
||||||
}
|
final ids = <int>{};
|
||||||
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
|
final diaryDays = await getDiaryIndexByDate(date);
|
||||||
return updatedDiaryDay;
|
if (diaryDays.isEmpty) {
|
||||||
}
|
throw Exception('No diary entry found for $date');
|
||||||
return diaryDay;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
final DiaryDayModel diaryDay = diaryDays.last;
|
||||||
|
for (var meal in diaryDay.meals) {
|
||||||
|
for (var portion in meal.portions) {
|
||||||
|
if (portion.productNutritionalValueId != null) {
|
||||||
|
ids.add(portion.productNutritionalValueId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ids.isEmpty) {
|
||||||
|
return diaryDay;
|
||||||
|
}
|
||||||
|
final glossary = await getGlossariesIndex(ids: ids.toList());
|
||||||
|
final glossaries = glossary.glossariesList;
|
||||||
|
final updatedMeals = diaryDay.meals.map((meal) {
|
||||||
|
final updatedPortions = meal.portions.map((p) => _portionWithGlossary(p, glossaries)).toList();
|
||||||
|
return meal.copyWith(portions: updatedPortions);
|
||||||
|
}).toList();
|
||||||
|
return diaryDay.copyWith(meals: updatedMeals);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async {
|
Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async {
|
||||||
try {
|
return diaryService.getDiaryIndexByDate(date: date);
|
||||||
return (await diaryService.getDiaryIndexByDate(date: date));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DiaryDayModel> getDiaryDay(int diaryId) async {
|
Future<DiaryDayModel> getDiaryDay(int diaryId) async {
|
||||||
try {
|
final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId);
|
||||||
// Align with getDiaryByDate: enrich portions with glossary nutritional info
|
final DiaryDayModel diaryDay = diaryWrapper.diary;
|
||||||
final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId);
|
final ids = <int>{};
|
||||||
final DiaryDayModel diaryDay = diaryWrapper.diary;
|
for (var meal in diaryDay.meals) {
|
||||||
Set<int> ids = {};
|
for (var portion in meal.portions) {
|
||||||
for (var meal in diaryDay.meals) {
|
if (portion.productNutritionalValueId != null) {
|
||||||
for (var portion in meal.portions) {
|
ids.add(portion.productNutritionalValueId!);
|
||||||
if (portion.productNutritionalValueId != null) {
|
|
||||||
ids.add(portion.productNutritionalValueId!);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ids.isNotEmpty) {
|
|
||||||
final glossary = await getGlossariesIndex(ids: ids.toList());
|
|
||||||
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
|
|
||||||
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
|
|
||||||
final meal = updatedMeals[mealIndex];
|
|
||||||
final updatedPortions = meal.portions.map((portion) {
|
|
||||||
if (portion.productNutritionalValueId != null) {
|
|
||||||
try {
|
|
||||||
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
|
|
||||||
return portion.copyWith(
|
|
||||||
productCaloriesInfo: ProductCaloriesInfoModel(
|
|
||||||
id: glossaryItem.id,
|
|
||||||
productName: glossaryItem.name,
|
|
||||||
calories: glossaryItem.energyOn100Gr,
|
|
||||||
proteins: glossaryItem.proteinsOn100Gr,
|
|
||||||
fats: glossaryItem.fatsOn100Gr,
|
|
||||||
carbohydrates: glossaryItem.carbohydratesOn100Gr,
|
|
||||||
energyOnPortion: glossaryItem.energyOnPortion,
|
|
||||||
proteinsOnPortion: glossaryItem.proteinsOnPortion,
|
|
||||||
fatsOnPortion: glossaryItem.fatsOnPortion,
|
|
||||||
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return portion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return portion;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
|
|
||||||
}
|
|
||||||
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
|
|
||||||
return updatedDiaryDay;
|
|
||||||
}
|
|
||||||
return diaryDay;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
if (ids.isEmpty) {
|
||||||
|
return diaryDay;
|
||||||
|
}
|
||||||
|
final glossary = await getGlossariesIndex(ids: ids.toList());
|
||||||
|
final glossaries = glossary.glossariesList;
|
||||||
|
final updatedMeals = diaryDay.meals.map((meal) {
|
||||||
|
final updatedPortions = meal.portions.map((p) => _portionWithGlossary(p, glossaries)).toList();
|
||||||
|
return meal.copyWith(portions: updatedPortions);
|
||||||
|
}).toList();
|
||||||
|
return diaryDay.copyWith(meals: updatedMeals);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<NutritionDataModel> getNutritionData(int index) async {
|
Future<NutritionDataModel> getNutritionData(int index) async {
|
||||||
try {
|
return diaryService.getNutrition(diary: index);
|
||||||
return await diaryService.getNutrition(diary: index);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addPortionToMeal({
|
Future<void> addPortionToMeal({
|
||||||
@@ -157,19 +106,14 @@ class DiaryRepository {
|
|||||||
required int quantity,
|
required int quantity,
|
||||||
int? productNutritionalValueId,
|
int? productNutritionalValueId,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
return diaryService.addProductToMeal(
|
||||||
return await diaryService.addProductToMeal(
|
diary: diaryId,
|
||||||
diary: diaryId,
|
typeNo: mealId,
|
||||||
typeNo: mealId,
|
productName: productName,
|
||||||
productName: productName,
|
portion: portion,
|
||||||
portion: portion,
|
quantity: quantity,
|
||||||
quantity: quantity,
|
productNutritionalValueId: productNutritionalValueId,
|
||||||
productNutritionalValueId: productNutritionalValueId,
|
);
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateProductInMeal({
|
Future<void> updateProductInMeal({
|
||||||
@@ -182,83 +126,43 @@ class DiaryRepository {
|
|||||||
int? productNutritionalValueId,
|
int? productNutritionalValueId,
|
||||||
required PortionState portionState,
|
required PortionState portionState,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson();
|
||||||
Map<String, Object?> data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson();
|
if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId;
|
||||||
if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId;
|
data['state'] = portionState.name;
|
||||||
data['state'] = portionState.name;
|
return diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data);
|
||||||
return await diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
try {
|
return diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
return await diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
try {
|
return diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
return await diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
try {
|
return diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
return await diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
try {
|
return diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
return await diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> uploadPhotos({required int diaryId, required int mealId, required List<File> files}) async {
|
Future<void> uploadPhotos({required int diaryId, required int mealId, required List<File> files}) async {
|
||||||
try {
|
for (var i = 0; i < files.length; i++) {
|
||||||
for (int i = 0; i < files.length; i++) {
|
await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]);
|
||||||
await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePhoto({required int diaryId, required int mealId, required DiaryPhotoModel photo}) async {
|
Future<void> deletePhoto({required int diaryId, required int mealId, required DiaryPhotoModel photo}) async {
|
||||||
try {
|
await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id);
|
||||||
await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async {
|
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async {
|
||||||
try {
|
return diaryService.finishDiaryDay(
|
||||||
return await diaryService.finishDiaryDay(
|
diary: diaryId,
|
||||||
diary: diaryId,
|
finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment),
|
||||||
finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment),
|
);
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GlossaryModerationModel> sendProductToModeration({
|
Future<GlossaryModerationModel> sendProductToModeration({
|
||||||
@@ -268,126 +172,66 @@ class DiaryRepository {
|
|||||||
required double fats,
|
required double fats,
|
||||||
required double carbohydrates,
|
required double carbohydrates,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
return diaryService.sendProductToModeration(
|
||||||
return await diaryService.sendProductToModeration(
|
data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(),
|
||||||
data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(),
|
);
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillSatiety({required int diaryId, required int mealId, required int satiety}) async {
|
Future<void> fillSatiety({required int diaryId, required int mealId, required int satiety}) async {
|
||||||
try {
|
return diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety);
|
||||||
return await diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillMood({required int diaryId, required int mealId, required int mood}) async {
|
Future<void> fillMood({required int diaryId, required int mealId, required int mood}) async {
|
||||||
try {
|
return diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood);
|
||||||
return await diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillTime({required int diaryId, required int mealId, required String time}) async {
|
Future<void> fillTime({required int diaryId, required int mealId, required String time}) async {
|
||||||
try {
|
return diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time);
|
||||||
return await diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillWakeup({required int diaryId, required String time}) async {
|
Future<void> fillWakeup({required int diaryId, required String time}) async {
|
||||||
try {
|
return diaryService.fillWakeup(diary: diaryId, wakeUp: time);
|
||||||
return await diaryService.fillWakeup(diary: diaryId, wakeUp: time);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillRetire({required int diaryId, required String time}) async {
|
Future<void> fillRetire({required int diaryId, required String time}) async {
|
||||||
try {
|
return diaryService.fillRetire(diary: diaryId, retire: time);
|
||||||
return await diaryService.fillRetire(diary: diaryId, retire: time);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> commentUserDay({required int diaryId, required ConsultantCommentModel commentModel}) async {
|
Future<void> commentUserDay({required int diaryId, required ConsultantCommentModel commentModel}) async {
|
||||||
try {
|
await diaryService.commentUserDay(id: diaryId, body: commentModel);
|
||||||
await diaryService.commentUserDay(id: diaryId, body: commentModel);
|
_diaryForceFetchController.add('force');
|
||||||
_diaryForceFetchController.add('force');
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ConsultantReviewModel> getDiaryComments({required int planId, required int diaryId}) async {
|
Future<ConsultantReviewModel> getDiaryComments({required int planId, required int diaryId}) async {
|
||||||
try {
|
return diaryService.getDiaryComments(planId: planId, diaryId: diaryId);
|
||||||
return await diaryService.getDiaryComments(planId: planId, diaryId: diaryId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> viewCommentOfDiaryDayByConsultant({required int diaryId}) async {
|
Future<void> viewCommentOfDiaryDayByConsultant({required int diaryId}) async {
|
||||||
try {
|
await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId);
|
||||||
await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GlossaryPaginationModel> getGlossariesIndex({required List<int> ids}) async {
|
Future<GlossaryPaginationModel> getGlossariesIndex({required List<int> ids}) async {
|
||||||
Map<String, dynamic> filters = {};
|
final filters = <String, dynamic>{};
|
||||||
if (ids.isNotEmpty) {
|
if (ids.isNotEmpty) {
|
||||||
for (int i = 0; i < ids.length; i++) {
|
for (var i = 0; i < ids.length; i++) {
|
||||||
filters['filters[id][\$in][$i]'] = ids[i];
|
filters['filters[id][\$in][$i]'] = ids[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
return diaryService.getGlossariesIndex(filters: filters);
|
||||||
return await diaryService.getGlossariesIndex(filters: filters);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DiaryDayModel> getDiaryDayV2({required String day}) async {
|
Future<DiaryDayModel> getDiaryDayV2({required String day}) async {
|
||||||
try {
|
final diaryDay = await diaryService.getDiaryDayV2(day: day);
|
||||||
final diaryDay = await diaryService.getDiaryDayV2(day: day);
|
return setCPFCToDiary(diaryDay);
|
||||||
return setCPFCToDiary(diaryDay);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createDiaryDay({required String day}) async {
|
Future<void> createDiaryDay({required String day}) async {
|
||||||
try {
|
await diaryService.createDay(body: CreateDayModel(day: day));
|
||||||
await diaryService.createDay(body: CreateDayModel(day: day));
|
await getDiaryByDate(day);
|
||||||
await getDiaryByDate(day);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DiaryDayModel> setCPFCToDiary(DiaryDayModel diaryDay) async {
|
Future<DiaryDayModel> setCPFCToDiary(DiaryDayModel diaryDay) async {
|
||||||
Set<int> ids = {};
|
final ids = <int>{};
|
||||||
for (var meal in diaryDay.meals) {
|
for (var meal in diaryDay.meals) {
|
||||||
for (var portion in meal.portions) {
|
for (var portion in meal.portions) {
|
||||||
if (portion.productNutritionalValueId != null) {
|
if (portion.productNutritionalValueId != null) {
|
||||||
@@ -395,41 +239,15 @@ class DiaryRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ids.isNotEmpty) {
|
if (ids.isEmpty) {
|
||||||
final glossary = await getGlossariesIndex(ids: ids.toList());
|
return diaryDay;
|
||||||
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
|
|
||||||
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
|
|
||||||
final meal = updatedMeals[mealIndex];
|
|
||||||
final updatedPortions = meal.portions.map((portion) {
|
|
||||||
if (portion.productNutritionalValueId != null) {
|
|
||||||
try {
|
|
||||||
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
|
|
||||||
return portion.copyWith(
|
|
||||||
productCaloriesInfo: ProductCaloriesInfoModel(
|
|
||||||
id: glossaryItem.id,
|
|
||||||
productName: glossaryItem.name,
|
|
||||||
calories: glossaryItem.energyOn100Gr,
|
|
||||||
proteins: glossaryItem.proteinsOn100Gr,
|
|
||||||
fats: glossaryItem.fatsOn100Gr,
|
|
||||||
carbohydrates: glossaryItem.carbohydratesOn100Gr,
|
|
||||||
energyOnPortion: glossaryItem.energyOnPortion,
|
|
||||||
proteinsOnPortion: glossaryItem.proteinsOnPortion,
|
|
||||||
fatsOnPortion: glossaryItem.fatsOnPortion,
|
|
||||||
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return portion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return portion;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
|
|
||||||
}
|
|
||||||
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
|
|
||||||
return updatedDiaryDay;
|
|
||||||
}
|
}
|
||||||
return diaryDay;
|
final glossary = await getGlossariesIndex(ids: ids.toList());
|
||||||
|
final glossaries = glossary.glossariesList;
|
||||||
|
final updatedMeals = diaryDay.meals.map((meal) {
|
||||||
|
final updatedPortions = meal.portions.map((p) => _portionWithGlossary(p, glossaries)).toList();
|
||||||
|
return meal.copyWith(portions: updatedPortions);
|
||||||
|
}).toList();
|
||||||
|
return diaryDay.copyWith(meals: updatedMeals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.da
|
|||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class AboutDietCubit extends Cubit<AboutDietState> {
|
class AboutDietCubit extends Cubit<AboutDietState> {
|
||||||
AboutDietCubit({required this.dietsRepository, required this.profileRepository}) : super(const AboutDietState.initial());
|
AboutDietCubit({required this.dietsRepository, required this.profileRepository}) : super(const AboutDietState.initial());
|
||||||
@@ -31,7 +32,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments));
|
dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
emit(AboutDietState.error(message: e.toString()));
|
emit(AboutDietState.error(message: e.toString()));
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
}
|
}
|
||||||
_selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0);
|
_selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0);
|
||||||
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
|
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
emit(AboutDietState.error(message: e.toString()));
|
emit(AboutDietState.error(message: e.toString()));
|
||||||
}
|
}
|
||||||
@@ -90,7 +93,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
EasyDebounce.debounce('like_diet', const Duration(seconds: 1), () async {
|
EasyDebounce.debounce('like_diet', const Duration(seconds: 1), () async {
|
||||||
try {
|
try {
|
||||||
await dietsRepository.likeDiet(id);
|
await dietsRepository.likeDiet(id);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
if (dietIndexInFeed != null) {
|
if (dietIndexInFeed != null) {
|
||||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
||||||
@@ -114,7 +118,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
|
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
|
||||||
try {
|
try {
|
||||||
await dietsRepository.addToFavorite(id);
|
await dietsRepository.addToFavorite(id);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
if (dietIndex != null) {
|
if (dietIndex != null) {
|
||||||
@@ -139,7 +144,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
|
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
|
||||||
try {
|
try {
|
||||||
await dietsRepository.removeFromFavorite(id);
|
await dietsRepository.removeFromFavorite(id);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
if (dietIndex != null) {
|
if (dietIndex != null) {
|
||||||
@@ -159,6 +165,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
await dietsRepository.startDiet(id, stringDate);
|
await dietsRepository.startDiet(id, stringDate);
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -170,10 +177,12 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
final id = (state as DietStateLoaded).diet.id;
|
final id = (state as DietStateLoaded).diet.id;
|
||||||
await dietsRepository.cancelDiet(id);
|
await dietsRepository.cancelDiet(id);
|
||||||
return true;
|
return true;
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
return false;
|
return false;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -194,7 +203,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
Future<Diet?> getDietBySlug(String slug) async {
|
Future<Diet?> getDietBySlug(String slug) async {
|
||||||
try {
|
try {
|
||||||
return await dietsRepository.getDietBySlug(slug);
|
return await dietsRepository.getDietBySlug(slug);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:eatvio_app/features/diets/models/diet_likes_model.dart';
|
|||||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class DietRepository {
|
class DietRepository {
|
||||||
DietRepository({required this.dietsService});
|
DietRepository({required this.dietsService});
|
||||||
@@ -18,76 +17,44 @@ class DietRepository {
|
|||||||
Stream get dietCommentsState => _dietCommentsStateStreamController.stream;
|
Stream get dietCommentsState => _dietCommentsStateStreamController.stream;
|
||||||
|
|
||||||
Future<DietLikesModel> likeDiet(int id) async {
|
Future<DietLikesModel> likeDiet(int id) async {
|
||||||
try {
|
return await dietsService.likeDiet(id: id);
|
||||||
return await dietsService.likeDiet(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Diet> getDiet(int id) async {
|
Future<Diet> getDiet(int id) async {
|
||||||
try {
|
final diet = await dietsService.getDiet(id: id);
|
||||||
final diet = await dietsService.getDiet(id: id);
|
return diet;
|
||||||
return diet;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Diet> getDietBySlug(String slug) async {
|
Future<Diet> getDietBySlug(String slug) async {
|
||||||
try {
|
final diet = await dietsService.getDietBySlug(slug: slug);
|
||||||
final diet = await dietsService.getDietBySlug(slug: slug);
|
return diet;
|
||||||
return diet;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Diet> addToFavorite(int id) async {
|
Future<Diet> addToFavorite(int id) async {
|
||||||
try {
|
return await dietsService.addToFavorite(id: id);
|
||||||
return await dietsService.addToFavorite(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Diet> removeFromFavorite(int id) async {
|
Future<Diet> removeFromFavorite(int id) async {
|
||||||
try {
|
return await dietsService.removeFromFavorite(id: id);
|
||||||
return await dietsService.removeFromFavorite(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startDiet(int id, String date) async {
|
Future<void> startDiet(int id, String date) async {
|
||||||
try {
|
return await dietsService.startDiet(id: id, body: {'date': date});
|
||||||
return await dietsService.startDiet(id: id, body: {'date': date});
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> cancelDiet(int id) async{
|
Future<void> cancelDiet(int id) async{
|
||||||
try {
|
return await dietsService.cancelDiet(id: id);
|
||||||
return await dietsService.cancelDiet(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteDiet(int id) async {
|
Future<void> deleteDiet(int id) async {
|
||||||
try {
|
return dietsService.deleteDiet(id: id);
|
||||||
return await dietsService.deleteDiet(id: id);
|
|
||||||
} catch (e) {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitLikesStateToFeed(LikesState state) => _dietLikeStateStreamController.add(state);
|
void emitLikesStateToFeed(LikesState state) => _dietLikeStateStreamController.add(state);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
|||||||
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ConsultantsCubit extends Cubit<ConsultantsState> {
|
class ConsultantsCubit extends Cubit<ConsultantsState> {
|
||||||
final FeedRepository feedRepository;
|
final FeedRepository feedRepository;
|
||||||
@@ -21,6 +22,7 @@ class ConsultantsCubit extends Cubit<ConsultantsState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(ConsultantsState.error(errorMessage: e.toString()));
|
emit(ConsultantsState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
|||||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class DietsCubit extends Cubit<DietsState> {
|
class DietsCubit extends Cubit<DietsState> {
|
||||||
final FeedRepository feedRepository;
|
final FeedRepository feedRepository;
|
||||||
@@ -109,6 +110,7 @@ class DietsCubit extends Cubit<DietsState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(isLoading: false, errorMessage: e.toString()),
|
state.copyWith(isLoading: false, errorMessage: e.toString()),
|
||||||
@@ -144,7 +146,8 @@ class DietsCubit extends Cubit<DietsState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(isLoading: false, errorMessage: e.toString()),
|
state.copyWith(isLoading: false, errorMessage: e.toString()),
|
||||||
);
|
);
|
||||||
@@ -191,7 +194,8 @@ class DietsCubit extends Cubit<DietsState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await dietsRepository.likeDiet(id);
|
await dietsRepository.likeDiet(id);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@@ -287,7 +291,8 @@ class DietsCubit extends Cubit<DietsState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await dietsRepository.addToFavorite(id);
|
await dietsRepository.addToFavorite(id);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
emit(
|
emit(
|
||||||
@@ -327,7 +332,8 @@ class DietsCubit extends Cubit<DietsState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await dietsRepository.removeFromFavorite(id);
|
await dietsRepository.removeFromFavorite(id);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
emit(
|
emit(
|
||||||
@@ -348,7 +354,8 @@ class DietsCubit extends Cubit<DietsState> {
|
|||||||
try {
|
try {
|
||||||
await dietsRepository.deleteDiet(dietId);
|
await dietsRepository.deleteDiet(dietId);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
|||||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class FeedCubit extends Cubit<FeedState> {
|
class FeedCubit extends Cubit<FeedState> {
|
||||||
final FeedRepository feedRepository;
|
final FeedRepository feedRepository;
|
||||||
@@ -26,6 +27,17 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void incrementCommentsState(int id) {
|
||||||
|
final List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
|
||||||
|
final int index = feedBlogPostList.indexWhere((e) => e.id == id);
|
||||||
|
if (index == -1) return;
|
||||||
|
final current = feedBlogPostList[index];
|
||||||
|
feedBlogPostList[index] = current.copyWith(totalComments: current.totalComments + 1);
|
||||||
|
if (!isClosed) {
|
||||||
|
emit(FeedState.loaded(feedBlogPostList: feedBlogPostList, isLoading: isLoading));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadFeedBlogPostList({String? query}) async {
|
Future<void> loadFeedBlogPostList({String? query}) async {
|
||||||
try {
|
try {
|
||||||
_query = query;
|
_query = query;
|
||||||
@@ -34,7 +46,8 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
emit(FeedState.loaded(feedBlogPostList: feedBlogPosts.feedBlogPostList, isLoading: false));
|
emit(FeedState.loaded(feedBlogPostList: feedBlogPosts.feedBlogPostList, isLoading: false));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
emit(FeedState.error(errorMessage: e.toString()));
|
emit(FeedState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
@@ -58,7 +71,8 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
emit(FeedState.loaded(feedBlogPostList: [...(state as FeedStateLoaded).feedBlogPostList, ...feedBlogPosts.feedBlogPostList], isLoading: isLoading));
|
emit(FeedState.loaded(feedBlogPostList: [...(state as FeedStateLoaded).feedBlogPostList, ...feedBlogPosts.feedBlogPostList], isLoading: isLoading));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
emit(FeedState.error(errorMessage: e.toString()));
|
emit(FeedState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
@@ -70,14 +84,48 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
try {
|
try {
|
||||||
await feedRepository.likeBlogPost(id: id);
|
await feedRepository.likeBlogPost(id: id);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id);
|
Future<void> unlikedPost(int id) async {
|
||||||
|
try {
|
||||||
|
await feedRepository.unlikedBlogPost(id: id);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> viewPost(int id) async => await feedRepository.viewPost(id);
|
Future<void> viewPost(int id) async {
|
||||||
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
|
try {
|
||||||
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id);
|
await feedRepository.viewPost(id);
|
||||||
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug);
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> viewRecipe(String slug) async {
|
||||||
|
try {
|
||||||
|
await feedRepository.viewRecipe(slug);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> viewDiet(int id) async {
|
||||||
|
try {
|
||||||
|
await feedRepository.viewDiet(id);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> viewPlan(String slug) async {
|
||||||
|
try {
|
||||||
|
await feedRepository.viewPlan(slug);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
|||||||
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class PlansCubit extends Cubit<PlansState> {
|
class PlansCubit extends Cubit<PlansState> {
|
||||||
final FeedRepository feedRepository;
|
final FeedRepository feedRepository;
|
||||||
@@ -41,6 +42,17 @@ class PlansCubit extends Cubit<PlansState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
EasyDebounce.cancel('loadPlans');
|
||||||
|
EasyDebounce.cancel('add_or_remove_plan_from_favorite');
|
||||||
|
EasyDebounce.cancel('like_plan');
|
||||||
|
likesStateSubscription.cancel();
|
||||||
|
favoriteStateSubscription.cancel();
|
||||||
|
commentsStateSubscription.cancel();
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
|
|
||||||
void changeDurationFilter(int index) {
|
void changeDurationFilter(int index) {
|
||||||
emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null));
|
emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null));
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce(
|
||||||
@@ -168,6 +180,7 @@ class PlansCubit extends Cubit<PlansState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
await reportFrom(e, stackTrace, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
log(stackTrace.toString());
|
log(stackTrace.toString());
|
||||||
emit(state.copyWith(isLoading: isLoading, errorMessage: e.toString()));
|
emit(state.copyWith(isLoading: isLoading, errorMessage: e.toString()));
|
||||||
@@ -199,7 +212,8 @@ class PlansCubit extends Cubit<PlansState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(state.copyWith(errorMessage: e.toString()));
|
emit(state.copyWith(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +242,8 @@ class PlansCubit extends Cubit<PlansState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await planRepository.addToFavorite(slug);
|
await planRepository.addToFavorite(slug);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
emit(
|
emit(
|
||||||
@@ -268,7 +283,8 @@ class PlansCubit extends Cubit<PlansState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await planRepository.removeFromFavorite(slug);
|
await planRepository.removeFromFavorite(slug);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
emit(
|
emit(
|
||||||
@@ -324,7 +340,8 @@ class PlansCubit extends Cubit<PlansState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await planRepository.likePlan(slug);
|
await planRepository.likePlan(slug);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
|||||||
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class FeedProfileCubit extends Cubit<ProfileState> {
|
class FeedProfileCubit extends Cubit<ProfileState> {
|
||||||
final ProfileRepository profileRepository;
|
final ProfileRepository profileRepository;
|
||||||
@@ -13,6 +14,7 @@ class FeedProfileCubit extends Cubit<ProfileState> {
|
|||||||
final profile = await profileRepository.getMyProfile();
|
final profile = await profileRepository.getMyProfile();
|
||||||
emit(state.copyWith(profile: profile));
|
emit(state.copyWith(profile: profile));
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
await reportFrom(e, stackTrace, this);
|
||||||
log(e.toString(), stackTrace: stackTrace);
|
log(e.toString(), stackTrace: stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
|
|||||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||||
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
class RecipesCubit extends Cubit<RecipesState> {
|
class RecipesCubit extends Cubit<RecipesState> {
|
||||||
@@ -62,6 +63,7 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
);
|
);
|
||||||
lastPage = currentPage;
|
lastPage = currentPage;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
|
emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
|
||||||
}
|
}
|
||||||
@@ -93,7 +95,8 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
lastPage = currentPage;
|
lastPage = currentPage;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
|
emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +135,8 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await aboutRecipeRepository.addToFavorite(slug);
|
await aboutRecipeRepository.addToFavorite(slug);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
emit(
|
emit(
|
||||||
@@ -172,7 +176,8 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await aboutRecipeRepository.removeFromFavorite(slug);
|
await aboutRecipeRepository.removeFromFavorite(slug);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||||
emit(
|
emit(
|
||||||
@@ -228,7 +233,8 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await aboutRecipeRepository.likeRecipe(slug);
|
await aboutRecipeRepository.likeRecipe(slug);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@@ -305,7 +311,8 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
try {
|
try {
|
||||||
await aboutRecipeRepository.deleteRecipe(slug);
|
await aboutRecipeRepository.deleteRecipe(slug);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ import 'package:go_router/go_router.dart';
|
|||||||
import '../widgets/recipes.dart';
|
import '../widgets/recipes.dart';
|
||||||
|
|
||||||
class FeedScreen extends StatelessWidget {
|
class FeedScreen extends StatelessWidget {
|
||||||
const FeedScreen({super.key});
|
const FeedScreen({super.key, this.initialTabIndex = 0});
|
||||||
|
final int? initialTabIndex;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
@@ -44,14 +44,14 @@ class FeedScreen extends StatelessWidget {
|
|||||||
if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()),
|
if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()),
|
||||||
BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()),
|
BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()),
|
||||||
],
|
],
|
||||||
child: const FeedScreenContent(),
|
child: FeedScreenContent(initialTabIndex: initialTabIndex),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FeedScreenContent extends StatefulWidget {
|
class FeedScreenContent extends StatefulWidget {
|
||||||
const FeedScreenContent({super.key});
|
const FeedScreenContent({super.key, required this.initialTabIndex});
|
||||||
|
final int? initialTabIndex;
|
||||||
@override
|
@override
|
||||||
State<FeedScreenContent> createState() => _FeedScreenContentState();
|
State<FeedScreenContent> createState() => _FeedScreenContentState();
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
final consultantsController = TextEditingController();
|
final consultantsController = TextEditingController();
|
||||||
|
|
||||||
late final List<String> chipsLabelList;
|
late final List<String> chipsLabelList;
|
||||||
int _selectedChipIndex = 0;
|
late int _selectedChipIndex;
|
||||||
final blogPostsScrollController = ScrollController();
|
final blogPostsScrollController = ScrollController();
|
||||||
final recipesScrollController = ScrollController();
|
final recipesScrollController = ScrollController();
|
||||||
final dietsScrollController = ScrollController();
|
final dietsScrollController = ScrollController();
|
||||||
@@ -72,6 +72,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
final consultantsScrollController = ScrollController();
|
final consultantsScrollController = ScrollController();
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_selectedChipIndex = widget.initialTabIndex ?? 0;
|
||||||
chipsLabelList = hidePlans ? ['Все', 'Консультанты', 'Рецепты', 'Диеты'] : ['Все', 'Планы питания', 'Консультанты', 'Рецепты', 'Диеты'];
|
chipsLabelList = hidePlans ? ['Все', 'Консультанты', 'Рецепты', 'Диеты'] : ['Все', 'Планы питания', 'Консультанты', 'Рецепты', 'Диеты'];
|
||||||
consultantsScrollController.addListener(_onScrollConsultants);
|
consultantsScrollController.addListener(_onScrollConsultants);
|
||||||
_checkRegistrationDate(context);
|
_checkRegistrationDate(context);
|
||||||
@@ -88,6 +89,17 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(FeedScreenContent oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
final initialTabIndex = widget.initialTabIndex;
|
||||||
|
setState(() {
|
||||||
|
if (initialTabIndex != null) {
|
||||||
|
_selectedChipIndex = initialTabIndex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _onScrollConsultants() {
|
void _onScrollConsultants() {
|
||||||
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
|||||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class FeedRepository {
|
class FeedRepository {
|
||||||
final FeedService feedService;
|
final FeedService feedService;
|
||||||
@@ -15,191 +14,107 @@ class FeedRepository {
|
|||||||
FeedRepository({required this.feedService});
|
FeedRepository({required this.feedService});
|
||||||
|
|
||||||
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({String? query, int page = 1}) async {
|
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({String? query, int page = 1}) async {
|
||||||
try {
|
return (await feedService.getBlogPostList(page: page, query: query));
|
||||||
return (await feedService.getBlogPostList(page: page, query: query));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FeedBlogPostModel> getBlogPost({required int id}) async {
|
Future<FeedBlogPostModel> getBlogPost({required int id}) async {
|
||||||
try {
|
return await feedService.getBlogPost(postId: id);
|
||||||
return await feedService.getBlogPost(postId: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Recipe>> getRecipes({String? query, List<int>? tags, int page = 1}) async {
|
Future<List<Recipe>> getRecipes({String? query, List<int>? tags, int page = 1}) async {
|
||||||
try {
|
return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList;
|
||||||
return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DietsPagination> getDiets({int page = 1, String? query, String? types, int? duration, String? sorting}) async {
|
Future<DietsPagination> getDiets({int page = 1, String? query, String? types, int? duration, String? sorting}) async {
|
||||||
try {
|
return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting));
|
||||||
return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
|
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
|
||||||
try {
|
return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
|
||||||
return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getBlogPostComments({required String id}) async {
|
Future<CommentModel> getBlogPostComments({required String id}) async {
|
||||||
try {
|
return (await feedService.getBlogPostComments(id: id));
|
||||||
return (await feedService.getBlogPostComments(id: id));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async {
|
Future<void> sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async {
|
||||||
try {
|
await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
||||||
await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getDietsComments({required String id}) async {
|
Future<CommentModel> getDietsComments({required String id}) async {
|
||||||
try {
|
return (await feedService.getDietsComments(id: id));
|
||||||
return (await feedService.getDietsComments(id: id));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendDietsComment({required String id, required String commentText, int? parentCommentId}) async {
|
Future<void> sendDietsComment({required String id, required String commentText, int? parentCommentId}) async {
|
||||||
try {
|
await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
||||||
await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getRecipesComments({required String id}) async {
|
Future<CommentModel> getRecipesComments({required String id}) async {
|
||||||
try {
|
return (await feedService.getRecipesComments(id: id));
|
||||||
return (await feedService.getRecipesComments(id: id));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async {
|
Future<void> sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async {
|
||||||
try {
|
await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
||||||
await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getPlansComments({required String slug}) async {
|
Future<CommentModel> getPlansComments({required String slug}) async {
|
||||||
try {
|
return (await feedService.getPlansComments(slug: slug));
|
||||||
return (await feedService.getPlansComments(slug: slug));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async {
|
Future<void> sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async {
|
||||||
try {
|
await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId);
|
||||||
await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> likeBlogPost({required int id}) async {
|
Future<void> likeBlogPost({required int id}) async {
|
||||||
try {
|
await feedService.likeBlogPost(id: id);
|
||||||
await feedService.likeBlogPost(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> unlikedBlogPost({required int id}) async {
|
Future<void> unlikedBlogPost({required int id}) async {
|
||||||
try {
|
await feedService.unlikedBlogPost(id: id);
|
||||||
await feedService.unlikedBlogPost(id: id);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<FeedConsultantModel>> getConsultants([String? name]) async {
|
Future<List<FeedConsultantModel>> getConsultants([String? name]) async {
|
||||||
try {
|
return (await feedService.getConsultants(name: name)).data;
|
||||||
return (await feedService.getConsultants(name: name)).data;
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> viewPost(int id) async {
|
Future<void> viewPost(int id) async {
|
||||||
try {
|
return (await feedService.viewPost(id: id));
|
||||||
return (await feedService.viewPost(id: id));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> viewRecipe(String slug) async {
|
Future<void> viewRecipe(String slug) async {
|
||||||
try {
|
return (await feedService.viewRecipe(slug: slug));
|
||||||
return (await feedService.viewRecipe(slug: slug));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> viewDiet(int id) async {
|
Future<void> viewDiet(int id) async {
|
||||||
try {
|
return (await feedService.viewDiet(id: id));
|
||||||
return (await feedService.viewDiet(id: id));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> viewPlan(String slug) async {
|
Future<void> viewPlan(String slug) async {
|
||||||
try {
|
return (await feedService.viewPlan(slug: slug));
|
||||||
return (await feedService.viewPlan(slug: slug));
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> viewContent({required ViewModel viewModel}) async {
|
Future<void> viewContent({required ViewModel viewModel}) async {
|
||||||
try {
|
await feedService.viewContent(viewModel: viewModel);
|
||||||
await feedService.viewContent(viewModel: viewModel);
|
|
||||||
} catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/arc
|
|||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
|
class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
|
||||||
final ConsultationsRepository consultationsRepository;
|
final ConsultationsRepository consultationsRepository;
|
||||||
@@ -34,7 +35,8 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
|
|||||||
: FiltersComparisonEnum.equal,
|
: FiltersComparisonEnum.equal,
|
||||||
);
|
);
|
||||||
emit(state.copyWith(consultations: consultations, isLoading: false));
|
emit(state.copyWith(consultations: consultations, isLoading: false));
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(state.copyWith(isLoading: false));
|
emit(state.copyWith(isLoading: false));
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
}
|
}
|
||||||
@@ -55,7 +57,8 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
|
|||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
final statistic = await consultationsRepository.getStatistic();
|
final statistic = await consultationsRepository.getStatistic();
|
||||||
emit(state.copyWith(statisticModel: statistic, isLoading: false));
|
emit(state.copyWith(statisticModel: statistic, isLoading: false));
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(state.copyWith(isLoading: false));
|
emit(state.copyWith(isLoading: false));
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user