Compare commits

...

40 Commits

Author SHA1 Message Date
misha
c8284338aa feat(image_compress_utility): adde test for corupted images 2026-04-15 16:27:27 +03:00
misha
03ec91a595 feat(image_compress_utility): improved compressing logic 2026-04-15 15:50:09 +03:00
misha
e814683984 version changed 2026-04-14 09:44:12 +03:00
misha
684415ef5b feat(setting): added profile data apdating for sttings screen, added validation for general info form 2026-04-14 09:28:02 +03:00
misha
631ceb2b7a version changed 2026-04-08 12:47:07 +03:00
misha
71a405c63f feat(notifications): changed navigation flow from Cancelled diet and started diet notifications 2026-04-08 12:43:15 +03:00
misha
4ca90eb2df feat(push_navigation): fixed navigation to chat screen 2026-04-08 12:14:43 +03:00
misha
f17f1ecb57 version changed 2026-04-07 10:32:10 +03:00
misha
a71bcc752d feat(fixes): fixed analyzer issue 2026-04-07 10:06:04 +03:00
misha
aac085def4 feat(app): refactoring of sentry using, feat(become_consultant): fixed possible infinite loading, feat(push_notifications): fixed push navigation from paid plan push 2026-04-07 10:05:11 +03:00
misha
b6b13b1db5 version update 2026-04-01 13:21:19 +03:00
misha
6629286bf1 feat(CI/CD): changed gitea user token name 2026-04-01 10:59:54 +03:00
misha
966bc23f0b feat(CI/CD): removed proxy server 2026-04-01 10:49:50 +03:00
misha
a13b623ca2 feat(feed): fixed comments state updating in feed, feat(consultantRegistration): fixed registration flow for users with unverified phone, feat(tests): fixed tests coverage 2026-04-01 10:37:59 +03:00
misha
ee2dd75a94 feat(ci/cd): added test coverage verification 2026-03-31 10:20:43 +03:00
misha
c450a41715 feat(ci/cd): added token for repo 2026-03-30 10:33:46 +03:00
misha
55b6b75134 feat(ci/cd): env repo changed 2026-03-30 10:16:13 +03:00
misha
335e36dd18 feat(pushNotifications): fixed push notifications navigation 2026-03-26 18:06:20 +03:00
7106bde4ae Merge pull request 'fix 939, 927' (#940) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#940
Reviewed-by: misha <misha@noreply.localhost>
2026-03-26 08:58:17 +00:00
mso7nueh
4f241f7db6 feat: try push handling 2026-03-18 15:39:13 +03:00
mso7nueh
3d7755a9a2 feat: Introduce AppRouter for centralized application routing and deep link handling, and create push_navigation_handler for push notification navigation. 2026-03-17 16:33:54 +03:00
mso7nueh
decc8c63e6 Merge branch 'main' of http://gitea.office.local:3000/mso7nueh/eatvio_app 2026-03-17 12:32:31 +03:00
mso7nueh
ad5b48ef1b Fix merge conflicts with upstream main by retaining list refresh logic 2026-03-17 12:32:02 +03:00
mso7nueh
e8dc0138e2 Merge pull request 'feat(recipes, diets)' (#938) from misha/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#938
Reviewed-by: mso7nueh <mso7nueh@mail.ru>
2026-03-17 09:19:25 +00:00
mso7nueh
96896671b6 merge upstream 2026-03-17 09:18:48 +00:00
mso7nueh
5e2f0430d9 Merge branch 'main' of http://gitea.office.local:3000/mso7nueh/eatvio_app 2026-03-17 12:18:25 +03:00
mso7nueh
2da3b10e63 feat: Implement 'My Recipes' feature, add diary moderation request modal, and introduce diary repository. 2026-03-17 12:18:17 +03:00
misha
5cb121d733 fat(recipes, diets): added list refreshing 2026-03-16 10:32:12 +03:00
mso7nueh
109013c25c Merge pull request 'feat(diary)' (#931) from misha/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#931
Reviewed-by: mso7nueh <mso7nueh@mail.ru>
2026-03-12 13:49:32 +00:00
misha
f5d14d213f Merge remote-tracking branch 'upstream/main' 2026-03-12 16:04:58 +03:00
misha
26ae28a705 feat(tests): fixed test errors 2026-03-12 14:41:16 +03:00
mso7nueh
7672477c03 Merge pull request 'Обновить pubspec.yaml' (#932) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#932
2026-03-11 12:15:49 +00:00
mso7nueh
07277d6e48 Обновить pubspec.yaml 2026-03-11 12:15:33 +00:00
misha
9d1bedff26 feat(diary): fixed consultant portion comments and day finishing errors handling 2026-03-11 11:55:10 +03:00
misha
e39638be7f feat(notifications): removed unknown notification from layoute 2026-03-05 13:44:33 +03:00
442fc0ec14 Merge pull request 'feat(consultant_registration)' (#924) from misha/eatvio_app:issue-914 into main
Reviewed-on: eatvio/eatvio_app#924
2026-03-04 07:56:21 +00:00
a8f8cc8eb0 Merge pull request 'feat(notifications)' (#921) from misha/eatvio_app:issue-911 into main
Reviewed-on: eatvio/eatvio_app#921
2026-03-04 07:55:58 +00:00
misha
a492c2d964 Merge remote-tracking branch 'upstream/main' into issue-914 2026-03-04 09:21:45 +03:00
misha
be0a99b164 fix: ci/cd 2026-02-25 13:02:28 +03:00
misha
6dab0d159b feat(consultant_registration): added new consultant registration flow 2026-02-25 07:08:05 +03:00
193 changed files with 4454 additions and 3447 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
<svg width="10" height="9" viewBox="0 0 10 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.53669 8.89177L6.8122 7.15382C6.52317 7.26749 6.22614 7.34957 5.92111 7.40005C5.61615 7.45053 5.3063 7.47578 4.99155 7.47578C3.95998 7.47578 3.01527 7.19683 2.15742 6.63893C1.29964 6.08096 0.619788 5.34562 0.117849 4.43293C0.0770142 4.36144 0.047164 4.28862 0.0282984 4.21447C0.00943278 4.14041 0 4.06506 0 3.98842C0 3.91178 0.0102087 3.8356 0.030626 3.75987C0.0510433 3.68423 0.0816693 3.61224 0.122504 3.5439C0.317857 3.21368 0.525501 2.89417 0.745437 2.58538C0.96529 2.2765 1.22096 2.00328 1.51243 1.76572L0.406101 0.633204C0.338315 0.564287 0.303606 0.478889 0.301972 0.377008C0.300421 0.275127 0.337662 0.185536 0.413696 0.108233C0.484667 0.0360776 0.570746 0 0.671934 0C0.773041 0 0.85908 0.0360776 0.93005 0.108233L9.05304 8.3668C9.12091 8.43579 9.15689 8.52119 9.16097 8.62299C9.16505 8.72487 9.12908 8.81446 9.05304 8.89177C8.98207 8.96392 8.89599 9 8.79481 9C8.6937 9 8.60766 8.96392 8.53669 8.89177ZM4.99155 6.01955C5.10025 6.01955 5.20405 6.01299 5.30295 5.99988C5.40194 5.98676 5.49855 5.95815 5.5928 5.91406L3.09751 3.37713C3.05725 3.47295 3.02989 3.57118 3.01544 3.67181C3.00098 3.77237 2.99375 3.8779 2.99375 3.98842C2.99375 4.5537 3.18772 5.03355 3.57565 5.42795C3.96358 5.82235 4.43554 6.01955 4.99155 6.01955ZM4.99155 0.501058C6.02622 0.501058 6.97529 0.781293 7.83878 1.34176C8.70227 1.90223 9.38344 2.64201 9.88227 3.56109C9.91992 3.62436 9.94896 3.69224 9.96937 3.76473C9.98979 3.83721 10 3.91178 10 3.98842C10 4.06506 9.99106 4.13962 9.97317 4.21211C9.95529 4.28459 9.92748 4.35247 9.88975 4.41574C9.74086 4.70378 9.57442 4.97521 9.39042 5.23004C9.20634 5.48487 9.00057 5.72471 8.77312 5.94956C8.67136 6.05302 8.55074 6.10059 8.41125 6.09229C8.27184 6.08399 8.15121 6.02811 8.04937 5.92465L7.0223 4.88517C6.97142 4.83344 6.93781 4.77369 6.92147 4.70594C6.90514 4.63827 6.90608 4.56769 6.92429 4.49421C6.94749 4.40868 6.96411 4.32565 6.97415 4.24511C6.98428 4.16465 6.98934 4.07909 6.98934 3.98842C6.98934 3.42313 6.79538 2.94329 6.40745 2.54888C6.01952 2.15448 5.54755 1.95728 4.99155 1.95728C4.90236 1.95728 4.81898 1.96322 4.74139 1.97509C4.66381 1.98688 4.58447 2.00461 4.50337 2.02827C4.43117 2.04994 4.35894 2.05248 4.28666 2.03587C4.21446 2.01926 4.15293 1.9851 4.10205 1.93337L3.64584 1.46967C3.50643 1.32785 3.46012 1.16274 3.50692 0.974343C3.55372 0.785942 3.67136 0.665878 3.85985 0.614149C4.04516 0.575788 4.23174 0.547349 4.41958 0.528832C4.60742 0.510316 4.79807 0.501058 4.99155 0.501058ZM5.94046 3.04758C6.03977 3.15876 6.1216 3.28347 6.18596 3.42172C6.25032 3.55997 6.29066 3.70449 6.30699 3.85527C6.31516 3.91531 6.29303 3.95699 6.2406 3.98032C6.18817 4.00357 6.139 3.9919 6.0931 3.94532L5.04153 2.88093C4.99563 2.83427 4.98542 2.78428 5.0109 2.73097C5.0363 2.67767 5.07852 2.65101 5.13757 2.65101C5.29593 2.66438 5.44199 2.70478 5.57577 2.7722C5.70962 2.83954 5.83119 2.93133 5.94046 3.04758Z" fill="#2F80ED"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -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);
};
} }
} }

View File

@@ -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 (в т.ч. ссылки из пушей dev2.eatvio.com) static const List<String> deepLinkHosts = ['eatvio.ru', 'eatvio.com', 'dev2.eatvio.com'];
static const List<String> _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru', 'dev2.eatvio.com', 'www.dev2.eatvio.com']; static const List<String> metaSegments = ['ru', 'en', 'api', 'v2'];
static GoRouter appRouter = GoRouter( static GoRouter appRouter = GoRouter(
initialLocation: RouteConstants.updateScreen, initialLocation: RouteConstants.updateScreen,
@@ -30,11 +32,102 @@ class AppRouter {
], ],
); );
static String? _resolveRecipeDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length != 2) return null;
final contentIdentifier = effectiveSegments[1];
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) { static String? _resolveDeepLinkUri(Uri uri) {
final host = uri.host.toLowerCase(); final host = uri.host.toLowerCase();
final segments = uri.pathSegments; final segments = uri.pathSegments;
final isDeepLink = _deepLinkHosts.contains(host); final isDeepLink = deepLinkHosts.any((allowedHost) => host == allowedHost || host.endsWith('.$allowedHost'));
if (!isDeepLink) { if (!isDeepLink) {
return null; return null;
@@ -44,120 +137,61 @@ class 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;
} }
if (segments.length >= 4 && final effectiveSegments = segments.where((segment) => !metaSegments.contains(segment)).toList();
segments[0] == 'api' && if (effectiveSegments.isEmpty) {
segments[1] == 'v2' && developer.log('No effective segments after language check, redirecting to feed', name: 'AppRouter');
segments[2] == 'chat' &&
segments[3] == 'show') {
developer.log('Deep link: api/v2/chat/show → chat-select-screen', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.chatSelectScreen;
}
if (segments[0] != 'ru' && segments[0] != 'en') {
developer.log('Invalid language segment: ${segments[0]}, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen; return RouteConstants.feedScreen;
} }
if (segments.length == 4) { final type = effectiveSegments[0];
final type = segments[1];
if (type == 'diary') { switch (type) {
developer.log('Deep link: diary, redirecting to diary-screen', name: 'AppRouter'); case 'recipe':
FlutterNativeSplash.remove(); final target = _resolveRecipeDeepLink(effectiveSegments);
return RouteConstants.diaryScreen; if (target != null) return target;
} break;
if (type == 'consultations' && segments[2] == 'status') { case 'blog_post':
final consultationId = segments[3]; final target = _resolveBlogPostDeepLink(effectiveSegments);
developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter'); if (target != null) return target;
FlutterNativeSplash.remove(); break;
return '${RouteConstants.consultationsScreen}?id=$consultationId'; case 'plan':
} final target = _resolvePlanDeepLink(effectiveSegments);
if (target != null) return target;
developer.log('Unknown deep link type for 4 segments: $type, redirecting to feed', name: 'AppRouter'); break;
FlutterNativeSplash.remove(); case 'diet':
return RouteConstants.feedScreen; final target = _resolveDietDeepLink(effectiveSegments);
} else if (segments.length == 3) { if (target != null) return target;
final type = segments[1]; break;
final contentIdentifier = segments[2]; case 'chat':
final target = _resolveChatDeepLink(effectiveSegments);
switch (type) { if (target != null) return target;
case 'recipe': break;
developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter'); case 'profile':
FlutterNativeSplash.remove(); final target = _resolveProfileDeepLink(effectiveSegments);
return '/about-recipes-screen?slug=$contentIdentifier'; if (target != null) return target;
case 'blog_post': break;
developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter'); case 'badge':
FlutterNativeSplash.remove(); final target = _resolveBadgeDeepLink(effectiveSegments);
return '/blog-post-screen?id=$contentIdentifier'; if (target != null) return target;
case 'plan': break;
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter'); case 'diary':
FlutterNativeSplash.remove(); final target = _resolveDiaryDeepLink(effectiveSegments);
return '/about-plan-screen?slug=$contentIdentifier'; if (target != null) return target;
case 'diet': { break;
final dietId = int.tryParse(contentIdentifier); case 'consultant_diary_comment':
if (dietId != null) { final target = _resolveConsultantDiaryCommentDeepLink(effectiveSegments);
developer.log('Deep link: diet/id=$dietId', name: 'AppRouter'); if (target != null) return target;
FlutterNativeSplash.remove(); break;
return '/about-diet-screen?id=$dietId'; case 'consultations':
} final target = _resolveConsultationsDeepLink(effectiveSegments);
developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter'); if (target != null) return target;
FlutterNativeSplash.remove(); break;
return '/about-diet-screen?slug=$contentIdentifier';
}
case 'chat':
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.chatSelectScreen;
case 'profile':
// В диплинке передан slug — открываем профиль пользователя по slug.
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return '/profile-screen?slug=$contentIdentifier';
case 'badge':
// В badge нет slug, открываем вкладку «Мой профиль».
developer.log('Deep link: badge → my-profile tab', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.myProfileScreen;
case 'diary':
developer.log('Deep link: diary/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
case 'consultant_diary_comment':
developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
default:
developer.log('Unknown deep link type: $type, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen;
}
} else if (segments.length == 2) {
final type = segments[1];
if (type == 'chat') {
developer.log('Deep link: chat', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.chatSelectScreen;
}
if (type == 'diary') {
developer.log('Deep link: diary', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
}
developer.log('Unknown deep link type for 2 segments: $type, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen;
} }
developer.log('Unknown deep link format: ${segments.length} segments, redirecting to feed', name: 'AppRouter'); developer.log('Unknown deep link format: ${effectiveSegments.length} effective segments, type=$type, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen; return RouteConstants.feedScreen;
} }
@@ -190,20 +224,20 @@ class AppRouter {
if (uri.host.isEmpty && uri.pathSegments.isNotEmpty) { if (uri.host.isEmpty && uri.pathSegments.isNotEmpty) {
uri = Uri( uri = Uri(
scheme: 'https', scheme: 'https',
host: _deepLinkHosts.first, host: deepLinkHosts.first,
pathSegments: uri.pathSegments, pathSegments: uri.pathSegments,
queryParameters: uri.queryParameters.isEmpty ? null : uri.queryParameters, queryParameters: uri.queryParameters.isEmpty ? null : uri.queryParameters,
); );
} }
final target = _resolveDeepLinkUri(uri); final target = _resolveDeepLinkUri(uri);
FlutterNativeSplash.remove();
if (target != null) { if (target != null) {
appRouter.go(target); appRouter.go(target);
} else { return;
developer.log('navigateByDeepLinkUrl: unresolved url=$url (normalized=${uri.toString()}), redirecting to feed', name: 'AppRouter');
appRouter.go(RouteConstants.feedScreen);
} }
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}) {

View File

@@ -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);
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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()],
),
); );
} }

View File

@@ -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()));

View 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;
}
}

View 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,
});
}

View 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 {}
}

View 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(),
);
}

View 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())),
);
}
},
);
}
}

View File

@@ -11,11 +11,14 @@ 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;
@@ -61,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');
// Не пробрасываем ошибку дальше, так как это не критичная операция // Не пробрасываем ошибку дальше, так как это не критичная операция
} }
} }
@@ -86,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');
// Не пробрасываем ошибку дальше, так как это не критичная операция // Не пробрасываем ошибку дальше, так как это не критичная операция
} }
} }
@@ -112,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;
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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,
),
); );
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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()));
} }

View File

@@ -5,18 +5,14 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
/// Отвечает за навигацию по диплинкам, пришедшим в пуш-уведомлениях.
class PushNavigationHandler { class PushNavigationHandler {
static bool _initialized = false; static bool _initialized = false;
static const MethodChannel _hmsChannel = MethodChannel('com.eatvioapp/hms_push'); static const MethodChannel _hmsChannel = MethodChannel('com.eatvioapp/hms_push');
/// Инициализация обработчиков Firebase Messaging.
/// Должна вызываться один раз после старта приложения.
static Future<void> initialize() async { static Future<void> initialize() async {
if (_initialized) return; if (_initialized) return;
_initialized = true; _initialized = true;
// Сообщение, по которому запустили приложение из "убитого" состояния.
try { try {
final initialMessage = await FirebaseMessaging.instance.getInitialMessage(); final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) { if (initialMessage != null) {
@@ -25,18 +21,11 @@ class PushNavigationHandler {
}); });
} }
} catch (e) { } catch (e) {
developer.log( developer.log('PushNavigationHandler: error while getting initialMessage: $e', name: 'PushNavigationHandler');
'PushNavigationHandler: error while getting initialMessage: $e',
name: 'PushNavigationHandler',
);
} }
// Тап по уведомлению, когда приложение было в фоне. FirebaseMessaging.onMessageOpenedApp.listen((message) => _handleMessageInternal(message, source: 'onMessageOpenedApp'));
FirebaseMessaging.onMessageOpenedApp.listen(
(message) => _handleMessageInternal(message, source: 'onMessageOpenedApp'),
);
// Подписка на события от native-кода (HMS через MethodChannel).
_hmsChannel.setMethodCallHandler((call) async { _hmsChannel.setMethodCallHandler((call) async {
if (call.method == 'openActionUrl') { if (call.method == 'openActionUrl') {
final url = call.arguments as String? ?? ''; final url = call.arguments as String? ?? '';
@@ -45,25 +34,17 @@ class PushNavigationHandler {
}); });
} }
/// Входная точка для навигации из native-кода (например, HMS через MethodChannel).
static void handleActionUrlFromNative(String url) { static void handleActionUrlFromNative(String url) {
if (url.isEmpty) { if (url.isEmpty) {
developer.log( developer.log('PushNavigationHandler.handleActionUrlFromNative: empty url', name: 'PushNavigationHandler');
'PushNavigationHandler.handleActionUrlFromNative: empty url',
name: 'PushNavigationHandler',
);
return; return;
} }
developer.log( developer.log('PushNavigationHandler.handleActionUrlFromNative: navigating by url=$url', name: 'PushNavigationHandler');
'PushNavigationHandler.handleActionUrlFromNative: navigating by url=$url',
name: 'PushNavigationHandler',
);
AppRouter.navigateByDeepLinkUrl(url); AppRouter.navigateByDeepLinkUrl(url);
} }
/// Публичный метод для единичной обработки сообщения (если понадобится где-то ещё).
static void handleMessage(RemoteMessage message) { static void handleMessage(RemoteMessage message) {
_handleMessageInternal(message, source: 'manualHandle'); _handleMessageInternal(message, source: 'manualHandle');
} }
@@ -71,21 +52,13 @@ class PushNavigationHandler {
static void _handleMessageInternal(RemoteMessage message, {required String source}) { static void _handleMessageInternal(RemoteMessage message, {required String source}) {
final data = message.data; final data = message.data;
final actionUrl = data['action_url'] as String?; final actionUrl = data['action_url'] as String?;
if (actionUrl == null || actionUrl.isEmpty) { if (actionUrl == null || actionUrl.isEmpty) {
developer.log( developer.log('PushNavigationHandler: no action_url in message data, source=$source, data=$data', name: 'PushNavigationHandler');
'PushNavigationHandler: no action_url in message data, source=$source, data=$data',
name: 'PushNavigationHandler',
);
return; return;
} }
developer.log( developer.log('PushNavigationHandler: navigating by action_url=$actionUrl, source=$source', name: 'PushNavigationHandler');
'PushNavigationHandler: navigating by action_url=$actionUrl, source=$source',
name: 'PushNavigationHandler',
);
AppRouter.navigateByDeepLinkUrl(actionUrl); AppRouter.navigateByDeepLinkUrl(actionUrl);
} }
} }

View File

@@ -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()),

View File

@@ -33,10 +33,7 @@ class MainNavigationRoutes {
}); });
} }
return _MainShellWithNavScope( return _MainShellWithNavScope(currentLocation: state.matchedLocation, child: child);
currentLocation: state.matchedLocation,
child: child,
);
}, },
routes: [ routes: [
GoRoute( GoRoute(
@@ -49,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();
@@ -85,16 +89,7 @@ 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();
},
),
),
), ),
], ],
), ),
@@ -105,10 +100,7 @@ class MainNavigationRoutes {
/// Wraps the shell with [BottomNavScope] so descendants (e.g. notifications) /// Wraps the shell with [BottomNavScope] so descendants (e.g. notifications)
/// can switch tabs via [BottomNavController.switchToRoute] instead of pushing. /// can switch tabs via [BottomNavController.switchToRoute] instead of pushing.
class _MainShellWithNavScope extends StatefulWidget { class _MainShellWithNavScope extends StatefulWidget {
const _MainShellWithNavScope({ const _MainShellWithNavScope({required this.child, required this.currentLocation});
required this.child,
required this.currentLocation,
});
final Widget child; final Widget child;
final String currentLocation; final String currentLocation;
@@ -124,10 +116,7 @@ class _MainShellWithNavScopeState extends State<_MainShellWithNavScope> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BottomNavScope( return BottomNavScope(
controller: _navController, controller: _navController,
child: BottomNavigationBarWidget( child: BottomNavigationBarWidget(currentLocation: widget.currentLocation, child: widget.child),
currentLocation: widget.currentLocation,
child: widget.child,
),
); );
} }
} }

View File

@@ -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);
}, },
@@ -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?;
@@ -229,7 +230,12 @@ class OtherRoutes {
GoRoute( GoRoute(
path: RouteConstants.stepperBecomeConsultantScreen, path: RouteConstants.stepperBecomeConsultantScreen,
name: 'stepperBecomeConsultantScreen', name: 'stepperBecomeConsultantScreen',
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String), builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final slug = data['slug'] as String?;
final isCanceled = data['isCanceled'] as bool?;
return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled);
},
), ),
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()), GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()), GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()),
@@ -262,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?;

View File

@@ -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() {

View File

@@ -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;
}
} }
} }

View File

@@ -9,14 +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'; 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;
@@ -114,7 +116,7 @@ class WsManager {
_subscribe(); _subscribe();
await _connectCubitsToStreams(); 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);
} }
} }
@@ -130,7 +132,7 @@ class WsManager {
await sl<NotificationsCubit>().connectToWebSocket(); await sl<NotificationsCubit>().connectToWebSocket();
} catch (e, st) { } catch (e, st) {
log('WsManager._connectCubitsToStreams: $e', stackTrace: st); log('WsManager._connectCubitsToStreams: $e', stackTrace: st);
await Sentry.captureException(e, stackTrace: st); await _errorReporter.report(e, st, origin: 'WsManager');
} }
} }

View File

@@ -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);
}
} }
} }

View File

@@ -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 мог её обработать
} }
} }
} }

View File

@@ -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());
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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,
} }

View File

@@ -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));

View File

@@ -78,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))),
@@ -147,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();
@@ -237,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)

View File

@@ -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;
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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()));
} }

View File

@@ -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()));
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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;

View File

@@ -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;
}
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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)

View File

@@ -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'];
} }
} }

View File

@@ -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));

View File

@@ -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;
}
} }
} }

View File

@@ -22,6 +22,9 @@ abstract class ConsultantCertPhotoModel with _$ConsultantCertPhotoModel {
static const String visibleGroup = 'user_certs'; static const String visibleGroup = 'user_certs';
static const String hiddenGroup = 'hidden_user_certs'; static const String hiddenGroup = 'hidden_user_certs';
static const String pendingGroup = 'pending_user_certs';
bool get isHidden => group == hiddenGroup; bool get isHidden => group == hiddenGroup;
bool get isActive => group == visibleGroup || group == hiddenGroup;
} }

View File

@@ -1,10 +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_cert_photo_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/models/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;
@@ -16,82 +17,120 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
consultant: ConsultantProfileModel(socialLinks: SocialLinksModel(), hasMedicalEducation: true), consultant: ConsultantProfileModel(socialLinks: SocialLinksModel(), hasMedicalEducation: true),
loading: true, loading: true,
acceptOffer: false, acceptOffer: false,
isUpdating: false,
), ),
); );
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 _fetchVisibleCertPhotos(); 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());
), emit(state.copyWith(loading: false, error: e.toString()));
); }
} }
Future<void> disableLoading() async { Future<void> disableLoading() async {
final certPhotos = await _fetchVisibleCertPhotos(); 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 {
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update); emit(state.copyWith(error: null));
try {
if (state.newAddedPhotos.isNotEmpty) {
await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos);
}
if (state.deletedPhotoIds.isNotEmpty) {
for (final int id in state.deletedPhotoIds) {
await consultantRepository.deletePhoto(id);
}
}
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update);
} catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
emit(state.copyWith(error: e.toString()));
}
} }
Future<void> uploadCertPhotos(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);
final uploaded = await consultantRepository.storeConsultantCertPhotos(files: compressedFiles);
emit(state.copyWith(certPhotos: _onlyVisible(uploaded)));
}
Future<void> removeCertPhoto(int id) async {
await consultantRepository.hideConsultantCertPhoto(id);
emit(state.copyWith(certPhotos: state.certPhotos.where((photo) => photo.id != id).toList()));
}
Future<List<ConsultantCertPhotoModel>> _fetchVisibleCertPhotos() async {
try { try {
final photos = await consultantRepository.getConsultantCertPhotos(); final compressedFiles = await ImageCompressUtility.compressFilesList(files);
return _onlyVisible(photos);
} catch (e) { emit(state.copyWith(newAddedPhotos: [...state.newAddedPhotos, ...compressedFiles], error: null));
return []; } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
emit(state.copyWith(error: e.toString()));
} }
} }
List<ConsultantCertPhotoModel> _onlyVisible(List<ConsultantCertPhotoModel> photos) { Future<void> removeCertPhoto(int? id, String? path) async {
return photos.where((photo) => !photo.isHidden).toList(); if (id != null) {
emit(state.copyWith(deletedPhotoIds: [...state.deletedPhotoIds, id]));
return;
}
if (path != null) {
emit(state.copyWith(newAddedPhotos: state.newAddedPhotos.where((file) => file.uri.path != path).toList()));
}
} }
} }

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_cert_photo_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
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';
@@ -11,7 +13,11 @@ abstract class RegisterConsultantState with _$RegisterConsultantState {
required ConsultantProfileModel consultant, required ConsultantProfileModel consultant,
required bool acceptOffer, required bool acceptOffer,
required bool loading, required bool loading,
required bool isUpdating,
ConsultationRequestModel? requestModel, ConsultationRequestModel? requestModel,
@Default(<ConsultantCertPhotoModel>[]) List<ConsultantCertPhotoModel> certPhotos, @Default(<ConsultantCertPhotoModel>[]) List<ConsultantCertPhotoModel> certPhotos,
@Default(<File>[]) List<File> newAddedPhotos,
@Default(<int>[]) List<int> deletedPhotoIds,
String? error,
}) = _RegisterConsultantState; }) = _RegisterConsultantState;
} }

View File

@@ -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,
),
), ),
), ),
), ),

View File

@@ -33,7 +33,9 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
const SizedBox(height: 16), const SizedBox(height: 16),
BlocBuilder<RegisterConsultantCubit, RegisterConsultantState>( BlocBuilder<RegisterConsultantCubit, RegisterConsultantState>(
builder: (context, state) { builder: (context, state) {
final photos = state.certPhotos; final photos = state.certPhotos.where((photo) => !state.deletedPhotoIds.contains(photo.id)).toList();
final addedPhotos = state.newAddedPhotos;
final totalPhotosCount = photos.length + addedPhotos.length;
return Column( return Column(
children: [ children: [
if (isUploading) ...[const LinearProgressIndicator(), const SizedBox(height: 16)], if (isUploading) ...[const LinearProgressIndicator(), const SizedBox(height: 16)],
@@ -47,7 +49,7 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
mainAxisSpacing: 8, mainAxisSpacing: 8,
), ),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
if (index == photos.length) { if (index == totalPhotosCount) {
return InkWell( return InkWell(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
onTap: () => _pickAndUploadPhotos(), onTap: () => _pickAndUploadPhotos(),
@@ -57,10 +59,25 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
), ),
); );
} else { } else {
return _CertPhotoCard(photo: photos[index], allPhotos: photos, onRemove: () => _removePhoto(photos[index].id)); if (index < photos.length) {
final photo = photos[index];
return _CertPhotoCard(
photo: photo,
allPhotos: photos,
onRemove: () => _removePhoto(id: photo.id),
);
} else {
final localIndex = index - photos.length;
final localPhoto = addedPhotos[localIndex];
return _LocalCertPhotoCard(
photo: localPhoto,
allPhotos: addedPhotos,
onRemove: () => _removePhoto(path: localPhoto.uri.path),
);
}
} }
}, },
itemCount: photos.length + 1, itemCount: totalPhotosCount + 1,
), ),
], ],
); );
@@ -82,7 +99,7 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
}); });
try { try {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
await context.read<RegisterConsultantCubit>().uploadCertPhotos(newImages.map((file) => File(file.path)).toList()); await context.read<RegisterConsultantCubit>().addCertPhotos(newImages.map((file) => File(file.path)).toList());
} finally { } finally {
if (mounted) { if (mounted) {
setState(() { setState(() {
@@ -92,8 +109,8 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
} }
} }
Future<void> _removePhoto(int id) async { Future<void> _removePhoto({int? id, String? path}) async {
await context.read<RegisterConsultantCubit>().removeCertPhoto(id); await context.read<RegisterConsultantCubit>().removeCertPhoto(id, path);
} }
} }
@@ -147,3 +164,61 @@ class _CertPhotoCard extends StatelessWidget {
); );
} }
} }
class _LocalCertPhotoCard extends StatelessWidget {
final File photo;
final List<File> allPhotos;
final VoidCallback onRemove;
const _LocalCertPhotoCard({required this.photo, required this.allPhotos, required this.onRemove});
@override
Widget build(BuildContext context) {
return Hero(
tag: photo.path,
child: Material(
borderRadius: BorderRadius.circular(20),
child: InkWell(
onTap: () {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(
imageUrls: allPhotos,
isLocal: true,
index: allPhotos.indexOf(photo),
),
);
},
borderRadius: BorderRadius.circular(20),
child: Stack(
clipBehavior: Clip.none,
children: [
Ink(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
image: DecorationImage(
fit: BoxFit.cover,
image: FileImage(photo),
),
),
),
Positioned(
right: -12,
top: -12,
child: InkWell(
onTap: onRemove,
customBorder: const CircleBorder(),
child: Ink(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(color: Color(0xFFF1F2F6), shape: BoxShape.circle),
child: const Icon(size: 20, Icons.close, color: Colors.black),
),
),
),
],
),
),
),
);
}
}

View File

@@ -1,6 +1,6 @@
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/models/consultation_request_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_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';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart';
@@ -10,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';
@@ -19,8 +20,8 @@ import 'package:lottie/lottie.dart';
class StepperBecomeConsultantScreen extends StatefulWidget { class StepperBecomeConsultantScreen extends StatefulWidget {
final String? slug; final String? slug;
final bool? isCanceled;
const StepperBecomeConsultantScreen({super.key, this.slug}); const StepperBecomeConsultantScreen({super.key, this.slug, this.isCanceled});
@override @override
State<StepperBecomeConsultantScreen> createState() => _StepperBecomeConsultantScreenState(); State<StepperBecomeConsultantScreen> createState() => _StepperBecomeConsultantScreenState();
@@ -49,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: [
@@ -63,25 +93,17 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
highlightElevation: 0, highlightElevation: 0,
focusElevation: 0, focusElevation: 0,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
borderRadius: BorderRadius.circular(20),
),
backgroundColor: const Color(0xFFEAF2FD), backgroundColor: const Color(0xFFEAF2FD),
onPressed: () async { onPressed: () async {
setState(() { setState(() {
pageController.previousPage( pageController.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
}); });
}, },
label: Icon( label: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400),
Icons.arrow_back_ios_new_rounded,
color: appTheme.blueA400,
),
), ),
), ),
if (state.requestModel?.educationModel.requestState == ModerationStatus.canceled) if (widget.isCanceled == true)
InkWell( InkWell(
onTap: () { onTap: () {
showDialog( showDialog(
@@ -94,15 +116,10 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
children: [ children: [
for (int i = 0; i < state.requestModel!.canceledModeration!.additional!.length; i++) ...[ for (int i = 0; i < state.requestModel!.canceledModeration!.additional!.length; i++) ...[
Text('• ${state.requestModel!.canceledModeration!.additional![i]}'), Text('• ${state.requestModel!.canceledModeration!.additional![i]}'),
] ],
], ],
), ),
actions: [ actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
), ),
); );
}, },
@@ -110,26 +127,18 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
child: Ink( child: Ink(
height: 48, height: 48,
width: MediaQuery.of(context).size.width * 0.6, width: MediaQuery.of(context).size.width * 0.6,
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0x33FFCC00)),
borderRadius: BorderRadius.circular(20),
color: const Color(0x33FFCC00),
),
child: const Center( child: const Center(
child: Text( child: Text(
'Комментарий', 'Комментарий',
style: TextStyle( style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF323F4B), height: 1),
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF323F4B),
height: 1,
),
), ),
), ),
), ),
), ),
Container( Container(
height: 48, height: 48,
width: state.requestModel?.educationModel.requestState == ModerationStatus.canceled width: widget.isCanceled == true
? MediaQuery.of(context).size.width * 0.2 ? MediaQuery.of(context).size.width * 0.2
: MediaQuery.of(context).size.width * 0.8, : MediaQuery.of(context).size.width * 0.8,
padding: const EdgeInsets.only(left: 4, right: 16), padding: const EdgeInsets.only(left: 4, right: 16),
@@ -138,18 +147,19 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
highlightElevation: 0, highlightElevation: 0,
elevation: 0, elevation: 0,
focusElevation: 0, focusElevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
borderRadius: BorderRadius.circular(20),
),
backgroundColor: isLoading backgroundColor: isLoading
? Colors.grey ? Colors.grey
: state.requestModel?.educationModel.requestState == ModerationStatus.canceled : widget.isCanceled == true
? const Color(0xFFEAF2FD) ? const Color(0xFFEAF2FD)
: appTheme.blueA400, : appTheme.blueA400,
onPressed: isLoading onPressed: isLoading
? null ? null
: () async { : () async {
if (step == 6) { if (step == 6) {
if (widget.isCanceled == true) {
return;
}
if (registrationConsultantCubit.state.acceptOffer) { if (registrationConsultantCubit.state.acceptOffer) {
setState(() { setState(() {
isLoading = true; isLoading = true;
@@ -157,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,
@@ -170,27 +183,15 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
const Text( const Text(
'Спасибо!\nВаши данные отправлены на модерацию', 'Спасибо!\nВаши данные отправлены на модерацию',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(fontSize: 20, height: 1, fontWeight: FontWeight.w600),
fontSize: 20,
height: 1,
fontWeight: FontWeight.w600,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Lottie.asset( Lottie.asset('assets/lottie_animations/success.json', width: 160, height: 160),
'assets/lottie_animations/success.json',
width: 160,
height: 160,
),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( const Text(
'В течении трех рабочих дней Вам придет уведомление о смене статуса', 'В течении трех рабочих дней Вам придет уведомление о смене статуса',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(fontSize: 16, height: 1, fontWeight: FontWeight.w500),
fontSize: 16,
height: 1,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
InkWell( InkWell(
@@ -201,18 +202,11 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
child: Ink( child: Ink(
width: MediaQuery.of(context).size.width - 32, width: MediaQuery.of(context).size.width - 32,
height: 52, height: 52,
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, fontSize: 18, fontWeight: FontWeight.w600),
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
), ),
), ),
), ),
@@ -223,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),
@@ -231,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();
},
), ),
], ],
), ),
@@ -263,24 +255,15 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
} else { } else {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
setState(() { setState(() {
pageController.nextPage( pageController.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
);
}); });
} }
}, },
label: isLoading label: isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: state.requestModel?.educationModel.requestState == ModerationStatus.canceled : widget.isCanceled == true
? Icon( ? Icon(Icons.arrow_forward_ios_outlined, color: appTheme.blueA400)
Icons.arrow_forward_ios_outlined, : Text('Продолжить', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
color: appTheme.blueA400,
)
: Text(
'Продолжить',
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
),
), ),
), ),
], ],
@@ -288,23 +271,11 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
appBar: AppBar( appBar: AppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
leading: Builder( leading: Builder(
builder: (context) => IconButton( builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()),
icon: const Icon(Icons.close_rounded),
onPressed: () => GoRouter.of(context).pop(),
),
), ),
centerTitle: true, centerTitle: true,
title: SvgPicture.asset( title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain),
ImageConstant.imgLogoEatvio, actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 6'))],
height: 48,
fit: BoxFit.contain,
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 16),
child: Text('шаг $step из 6'),
),
],
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 32), preferredSize: Size(MediaQuery.of(context).size.width, 32),
child: Padding( child: Padding(
@@ -312,10 +283,7 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
child: TweenAnimationBuilder<double>( child: TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 350), duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut, curve: Curves.easeInOut,
tween: Tween<double>( tween: Tween<double>(begin: 1 / 6, end: progressValue),
begin: 1 / 6,
end: progressValue,
),
builder: (context, value, _) => LinearProgressIndicator( builder: (context, value, _) => LinearProgressIndicator(
value: value, value: value,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@@ -346,11 +314,6 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
], ],
), ),
); );
} else {
return const Scaffold(
body: Loader(),
);
}
}, },
), ),
); );

View File

@@ -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,81 +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 {
} await consultantService.deletePhoto(id: id);
} }
} }

View File

@@ -40,4 +40,7 @@ abstract class ConsultantService {
@POST('/api/v2/consultant_request/cert_photos/un_hide') @POST('/api/v2/consultant_request/cert_photos/un_hide')
Future<void> unHideConsultantCertPhoto({@Body() required Map<String, dynamic> payload}); Future<void> unHideConsultantCertPhoto({@Body() required Map<String, dynamic> payload});
@POST('/api/v2/consultant_request/cert_photos/{id}/queue-for-deletion')
Future<void> deletePhoto({@Path() required int id});
} }

View File

@@ -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);
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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));
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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,

View File

@@ -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';

View File

@@ -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;
} }
} }
} }

View File

@@ -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));
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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() {

View File

@@ -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: 'Не удалось сохранить черновик');

View File

@@ -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;
}
} }
} }

View File

@@ -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('Не удалось удалить токен');

View File

@@ -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',

View File

@@ -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);
} }
} }

View File

@@ -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,7 @@ class DiaryCubit extends Cubit<DiaryState> {
} }
String comment = ''; String comment = '';
bool commentNeedsToBeFilled = false;
void setInitialDate(DateTime? date) { void setInitialDate(DateTime? date) {
if (date == null) return; if (date == null) return;
@@ -68,7 +76,7 @@ class DiaryCubit extends Cubit<DiaryState> {
return canSelect; return canSelect;
} }
Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId, DateTime? date}) async { Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId}) async {
final currentState = state; final currentState = state;
try { try {
if (needLoading) { if (needLoading) {
@@ -101,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;
@@ -114,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());
} }
} }
@@ -125,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) {
@@ -151,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(
@@ -220,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 {
@@ -278,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({
@@ -301,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({
@@ -322,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({
@@ -390,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;
} }
} }

View File

@@ -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';
@@ -39,12 +40,12 @@ class UserDiaryScreen extends StatelessWidget {
final int diaryId; final int diaryId;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DiaryScreenContent(diaryId: diaryId,); return DiaryScreenContent(diaryId: diaryId);
} }
} }
class DiaryScreenContent extends StatefulWidget { class DiaryScreenContent extends StatefulWidget {
const DiaryScreenContent({super.key, this.diaryId,this.date}); const DiaryScreenContent({super.key, this.diaryId, this.date});
final int? diaryId; final int? diaryId;
final DateTime? date; final DateTime? date;
@override @override
@@ -134,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;
}); });

View File

@@ -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 символа'),
],
], ],
), ),
), ),

View File

@@ -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,

View File

@@ -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;

View File

@@ -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));
}, },
); );
}, },

View File

@@ -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);
} }
} }

View File

@@ -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;

View File

@@ -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);

View File

@@ -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()));
} }

View File

@@ -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;
} }
} }

View File

@@ -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);
}
}
} }

View File

@@ -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(

View File

@@ -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);
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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) {}
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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());
} }

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultations/repositories/consultations_rep
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.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 ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> { class ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> {
final ConsultationsRepository consultationsRepository; final ConsultationsRepository consultationsRepository;
@@ -22,7 +23,8 @@ class ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> {
state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual, state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
); );
emit(state.copyWith(financesModels: consultantFinances, isLoading: false)); emit(state.copyWith(financesModels: consultantFinances, isLoading: false));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(financesModels: [], isLoading: false)); emit(state.copyWith(financesModels: [], isLoading: false));
log(e.toString()); log(e.toString());
} }

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultations/repositories/consultations_rep
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.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 UserPurchasesCubit extends Cubit<UserPurchasesState> { class UserPurchasesCubit extends Cubit<UserPurchasesState> {
final ConsultationsRepository consultationsRepository; final ConsultationsRepository consultationsRepository;
@@ -23,7 +24,8 @@ class UserPurchasesCubit extends Cubit<UserPurchasesState> {
); );
emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false)); emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(purchasesModels: [], isLoading: false)); emit(state.copyWith(purchasesModels: [], isLoading: false));
log(e.toString()); log(e.toString());
} }

View File

@@ -1,6 +1,5 @@
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/services/glossary_service.dart'; import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class GlossaryRepository { class GlossaryRepository {
final GlossaryService glossaryService; final GlossaryService glossaryService;
@@ -10,11 +9,7 @@ class GlossaryRepository {
}); });
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async { Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
try { return (await glossaryService.getGlossaryByQuery(query: query)).glossariesList;
return (await glossaryService.getGlossaryByQuery(query: query)).glossariesList;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -3,16 +3,30 @@ import 'dart:developer';
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart'; import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_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 MenuCubit extends Cubit<MenuState> { class MenuCubit extends Cubit<MenuState> {
final ProfileRepository profileRepository; final ProfileRepository profileRepository;
MenuCubit(this.profileRepository) : super(MenuState()); MenuCubit(this.profileRepository) : super(const MenuState.initial());
Future<void> getMyProfile() async {
emit(const MenuState.loading());
try {
final profile = await profileRepository.getMyProfile();
emit(MenuState.loaded(profileModel: profile));
} catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
emit(const MenuState.error());
}
}
Future<String?> makePassKey() async { Future<String?> makePassKey() async {
try { try {
final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {}); final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {});
return passKey; return passKey;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
} }
return null; return null;

View File

@@ -1 +1,17 @@
class MenuState {} import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'menu_state.freezed.dart';
@freezed
abstract class MenuState with _$MenuState {
const factory MenuState.initial() = MenuStateInitial;
const factory MenuState.loading() = MenuStateLoading;
const factory MenuState.loaded({
required ProfileModel profileModel,
}) = MenuStateLoaded;
const factory MenuState.error() = MenuStateError;
}

View File

@@ -1,5 +1,6 @@
import 'dart:developer'; import 'dart:developer';
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:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart'; import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart';
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart'; import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
@@ -16,6 +17,7 @@ class NotificationSettingsCubit extends Cubit<NotificationSettingsState> {
final notificationSettings = await _notificationSettingsRepository.getNotificationSettings(); final notificationSettings = await _notificationSettingsRepository.getNotificationSettings();
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings)); emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
} catch (e, stackTrace) { } catch (e, stackTrace) {
await reportFrom(e, stackTrace, this);
log('Error getting notification settings: $e', stackTrace: stackTrace); log('Error getting notification settings: $e', stackTrace: stackTrace);
emit(NotificationSettingsStateError(errorMessage: e.toString())); emit(NotificationSettingsStateError(errorMessage: e.toString()));
} }
@@ -26,6 +28,7 @@ class NotificationSettingsCubit extends Cubit<NotificationSettingsState> {
final notificationSettings = await _notificationSettingsRepository.updateNotificationSettings(data); final notificationSettings = await _notificationSettingsRepository.updateNotificationSettings(data);
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings)); emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
} catch (e, stackTrace) { } catch (e, stackTrace) {
await reportFrom(e, stackTrace, this);
log('Error updating notification settings: $e', stackTrace: stackTrace); log('Error updating notification settings: $e', stackTrace: stackTrace);
emit(NotificationSettingsStateError(errorMessage: e.toString())); emit(NotificationSettingsStateError(errorMessage: e.toString()));
} }

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart'; import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart'; import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_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 BlockedUsersCubit extends Cubit<BlockedUsersState> { class BlockedUsersCubit extends Cubit<BlockedUsersState> {
final BlockedUsersRepository blockedUsersRepository; final BlockedUsersRepository blockedUsersRepository;
@@ -13,17 +14,26 @@ class BlockedUsersCubit extends Cubit<BlockedUsersState> {
try { try {
emit(BlockedUsersState.loaded(profilesList: await blockedUsersRepository.getBlockedUsers())); emit(BlockedUsersState.loaded(profilesList: await blockedUsersRepository.getBlockedUsers()));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(BlockedUsersState.error(errorMessage: '$e $st')); emit(BlockedUsersState.error(errorMessage: '$e $st'));
} }
} }
Future<void> blockUser(String slug) async { Future<void> blockUser(String slug) async {
await blockedUsersRepository.blockUserBySlug(slug); try {
await getBlockedUsers(); await blockedUsersRepository.blockUserBySlug(slug);
await getBlockedUsers();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> unblockUser(String slug) async { Future<void> unblockUser(String slug) async {
await blockedUsersRepository.unblockUserBySlug(slug); try {
await getBlockedUsers(); await blockedUsersRepository.unblockUserBySlug(slug);
await getBlockedUsers();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
} }

View File

@@ -1,11 +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:sentry_flutter/sentry_flutter.dart'; import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/const.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/token_storage.dart'; import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart'; import 'package:eatvio_app/core/websockets/ws_manager.dart';
@@ -13,9 +15,9 @@ import 'package:eatvio_app/features/consultant_rating/presentation/consultant_ra
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart'; import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart';
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart';
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart'; import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart'; import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart'; import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -25,90 +27,107 @@ import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
class MenuScreen extends StatelessWidget { class MenuScreen extends StatelessWidget {
final ProfileModel profileModel; const MenuScreen({super.key});
const MenuScreen({super.key, required this.profileModel});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => sl<MenuCubit>(), create: (context) => sl<MenuCubit>()..getMyProfile(),
child: Builder( child: Builder(
builder: (context) { builder: (context) {
return Scaffold( return BlocBuilder<MenuCubit, MenuState>(
appBar: AppBar(centerTitle: false, title: const Text('Меню')), builder: (BuildContext context, MenuState state) {
body: SingleChildScrollView( if (state is MenuStateLoaded) {
child: Column( final profileModel = state.profileModel;
children: [ return Scaffold(
_tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')), appBar: AppBar(centerTitle: false, title: const Text('Меню')),
if (profileModel.roles.contains('consultant')) _divider(context), body: SingleChildScrollView(
if (profileModel.roles.contains('consultant')) child: Column(
_tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)), children: [
if (profileModel.roles.contains('consultant')) _divider(context), _tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
if (profileModel.roles.contains('consultant')) if (profileModel.roles.contains('consultant')) _divider(context),
_tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)), if (profileModel.roles.contains('consultant'))
if (profileModel.roles.contains('consultant')) _divider(context), _tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)),
if (profileModel.roles.contains('consultant')) if (profileModel.roles.contains('consultant')) _divider(context),
_tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)), if (profileModel.roles.contains('consultant'))
if (!hidePlans) ...[ _tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
_divider(context), if (profileModel.roles.contains('consultant')) _divider(context),
_tile( if (profileModel.roles.contains('consultant'))
leading: 'assets/icons/finance.svg', _tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)),
title: 'Мои финансы', if (!hidePlans) ...[
onTap: () { _divider(context),
if (profileModel.roles.contains('consultant')) { _tile(
GoRouter.of(context).push(ConsultantFinancesScreen.path); leading: 'assets/icons/finance.svg',
return; title: 'Мои финансы',
} onTap: () {
GoRouter.of(context).push(UserPurchasesScreen.path); if (profileModel.roles.contains('consultant')) {
}, GoRouter.of(context).push(ConsultantFinancesScreen.path);
return;
}
GoRouter.of(context).push(UserPurchasesScreen.path);
},
),
],
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
_tile(
leading: 'assets/icons/consultant_rating_icon.svg',
title: 'Мой рейтинг',
onTap: () {
GoRouter.of(context).push(ConsultantRatingScreen.path);
},
),
_divider(context),
_tile(
leading: ImageConstant.icnGlossary,
title: 'Глоссарий',
onTap: () async {
final passKey = await context.read<MenuCubit>().makePassKey();
if (passKey != null && context.mounted) {
GoRouter.of(context).push('/glossary-screen', extra: passKey);
}
},
),
_divider(context),
_tile(
leading: 'assets/icons/notifications.svg',
title: 'Уведомления',
onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen'),
),
_divider(context),
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
_divider(context),
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
// _divider(context),
// _tile(
// leading: ImageConstant.icnDevTools,
// title: 'Для разработчиков',
// onTap: () {
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
// },
// ),
_divider(context),
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
const SizedBox(height: 10),
const AppInfo(),
],
), ),
],
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
_tile(
leading: 'assets/icons/consultant_rating_icon.svg',
title: 'Мой рейтинг',
onTap: () {
GoRouter.of(context).push(ConsultantRatingScreen.path);
},
),
_divider(context),
_tile(
leading: ImageConstant.icnGlossary,
title: 'Глоссарий',
onTap: () async {
final passKey = await context.read<MenuCubit>().makePassKey();
if (passKey != null && context.mounted) {
GoRouter.of(context).push('/glossary-screen', extra: passKey);
}
},
), ),
_divider(context), );
_tile( }
leading: 'assets/icons/notifications.svg',
title: 'Уведомления', if (state is MenuStateError) {
onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen'), return Scaffold(
), appBar: AppBar(centerTitle: false, title: const Text('Меню')),
_divider(context), body: DataErrorWidget(onReload: () async => context.read<MenuCubit>().getMyProfile()),
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')), );
_divider(context), }
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
// _divider(context), return Scaffold(
// _tile( appBar: AppBar(centerTitle: false, title: const Text('Меню')),
// leading: ImageConstant.icnDevTools, body: const Loader(),
// title: 'Для разработчиков', );
// onTap: () { },
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
// },
// ),
_divider(context),
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
const SizedBox(height: 10),
const AppInfo(),
],
),
),
); );
}, },
), ),
@@ -144,7 +163,7 @@ class MenuScreen extends StatelessWidget {
try { try {
await sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout'); await sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout');
} catch (e, s) { } catch (e, s) {
await Sentry.captureException(e, stackTrace: s); await sl<ErrorReporter>().report(e, s, origin: 'MenuScreen');
} finally { } finally {
await sl<WsManager>().clear(); await sl<WsManager>().clear();
await TokenStorage.removeTokenInStorage(); await TokenStorage.removeTokenInStorage();

View File

@@ -1,7 +1,5 @@
import 'dart:developer';
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart'; import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class BlockedUsersRepository { class BlockedUsersRepository {
final BlockedUserService blockedUserService; final BlockedUserService blockedUserService;
@@ -11,40 +9,25 @@ class BlockedUsersRepository {
}); });
Future<List<ProfileModel>> getBlockedUsers() async { Future<List<ProfileModel>> getBlockedUsers() async {
try { return (await blockedUserService.getBlockedUsers()).profilesList;
return (await blockedUserService.getBlockedUsers()).profilesList;
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
Future<void> blockUserBySlug(String slug) async { Future<void> blockUserBySlug(String slug) async {
try { return await blockedUserService.blockUser(
return await blockedUserService.blockUser( data: {
data: { 'slug': slug,
'slug': slug, },
}, );
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
Future<void> unblockUserBySlug(String slug) async { Future<void> unblockUserBySlug(String slug) async {
try { return await blockedUserService.unblockUser(
return await blockedUserService.unblockUser( data: {
data: { 'slug': slug,
'slug': slug, },
}, );
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
} }

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart'; import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart'; import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class NotificationSettingsRepository { class NotificationSettingsRepository {
final NotificationSettingsService _notificationSettingsService; final NotificationSettingsService _notificationSettingsService;
@@ -8,20 +7,12 @@ class NotificationSettingsRepository {
NotificationSettingsRepository(this._notificationSettingsService); NotificationSettingsRepository(this._notificationSettingsService);
Future<NotificationSettingsModel> getNotificationSettings() async { Future<NotificationSettingsModel> getNotificationSettings() async {
try { return await _notificationSettingsService.getNotificationSettings();
return await _notificationSettingsService.getNotificationSettings();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<NotificationSettingsModel> updateNotificationSettings(Map<String, dynamic> data) async { Future<NotificationSettingsModel> updateNotificationSettings(Map<String, dynamic> data) async {
try { return await _notificationSettingsService.updateNotificationSettings(data: data);
return await _notificationSettingsService.updateNotificationSettings(data: data);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More