forked from eatvio/eatvio_app
Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48f433288f | ||
|
|
6211332a07 | ||
|
|
4b1d21b4af | ||
|
|
3b89a69d3b | ||
|
|
3e8c8c7e9f | ||
|
|
e73463dcf7 | ||
|
|
60d30163e7 | ||
|
|
04c1b889a0 | ||
|
|
06117ddb6a | ||
|
|
15f0538f04 | ||
|
|
3c56c59c9f | ||
|
|
038e30b198 | ||
|
|
cdb6fcb462 | ||
|
|
e93a573a72 | ||
|
|
d8378e3686 | ||
|
|
f464da6f9d | ||
|
|
73f27efadd | ||
|
|
90cac4940f | ||
|
|
2fac007675 | ||
|
|
1cafff65dc | ||
|
|
fe99890c0e | ||
|
|
15248a65fe | ||
| ee0d4d17af | |||
|
|
6fa7beb070 | ||
| 24d0d6eedc | |||
|
|
905559b1f0 | ||
| 6cb330752f | |||
|
|
69995289c5 | ||
|
|
f9b63f3401 | ||
| 3068d82b3a | |||
|
|
1305e67817 | ||
|
|
9be8ec7ac3 | ||
|
|
7ad01d3d4e | ||
|
|
832ba421e9 | ||
|
|
7e7f8bfb03 | ||
|
|
1f714b3bc2 | ||
|
|
187d60ffb9 | ||
|
|
64b158d1ff | ||
|
|
62f5cab746 | ||
|
|
0d27da3ec9 | ||
|
|
1387e20553 | ||
|
|
13fc59ff62 | ||
|
|
d3ed027c38 | ||
|
|
108e067b11 | ||
|
|
c58cc54e43 | ||
|
|
6df116ef74 | ||
|
|
b051a588e0 | ||
|
|
721430301a | ||
|
|
98d7b727a3 | ||
|
|
236abdd1d2 | ||
|
|
138a81d9d1 | ||
|
|
f43059c820 | ||
|
|
c4ff14c7b2 | ||
|
|
33151b7dae | ||
|
|
b302be66d7 | ||
|
|
4f0f146fd2 | ||
|
|
7986119054 | ||
|
|
76582fd487 | ||
|
|
cd9357c050 | ||
|
|
d53fdd1ccb | ||
|
|
e75539338b | ||
|
|
7267fc454b | ||
|
|
9e0c89c04e | ||
|
|
a9a479b2d3 | ||
|
|
12af348000 | ||
|
|
039c09962a | ||
|
|
681c11fed9 | ||
|
|
5b616a9dc1 | ||
|
|
3e5cc4f8d7 | ||
|
|
3cc7809210 | ||
|
|
be1d8c295a | ||
|
|
7a177f7517 | ||
|
|
c718875b49 | ||
|
|
c618a6a80d | ||
|
|
3e7202d65e | ||
|
|
f78ccd01b6 | ||
|
|
a4d0fd0108 | ||
|
|
6f1368f3c6 | ||
|
|
83a39835fa | ||
|
|
49b597d5ae | ||
|
|
f5d27ed056 | ||
|
|
8a26fa0990 | ||
|
|
15a9e2b1bd | ||
|
|
7192db31e4 | ||
|
|
190e1781ee | ||
|
|
a134c2043b | ||
|
|
09a1ec0947 | ||
|
|
1db9c57140 | ||
|
|
aacbc0ce5a | ||
|
|
1bdc1c53f6 | ||
|
|
20e71a77d8 | ||
|
|
d037b6aaa7 | ||
|
|
43495f407e | ||
|
|
f0c94c4f82 | ||
| 60b0b20844 | |||
|
|
969f8d37af | ||
| d1c9c1f292 | |||
|
|
ec05b0c8fb | ||
| 5eaab6b213 | |||
|
|
5db29ae681 | ||
| b55a0d0590 | |||
|
|
44a938ea29 | ||
| be78fca7d0 | |||
|
|
955daf4933 | ||
| 8bdbb9059f | |||
|
|
27a0bcd6ce | ||
| d5e2af2de5 | |||
|
|
5a8dcafa8e | ||
| be5796fbfd | |||
|
|
02b621d85a | ||
| 683a7e1066 | |||
|
|
773359e900 | ||
|
|
74fa65a1ba | ||
|
|
46f11a8e78 | ||
|
|
3e2aff3ad4 | ||
|
|
09c53a6492 | ||
|
|
452c603b95 | ||
|
|
6bcd540d54 | ||
|
|
2c38be9fa4 | ||
|
|
74df7d82db | ||
|
|
c8d5fc756e | ||
|
|
1b9534b690 | ||
|
|
5ac5deec48 | ||
|
|
08cf1ad370 | ||
|
|
2c59fd0923 | ||
|
|
958d09906b | ||
|
|
631efe6f9e | ||
|
|
0749a53e24 | ||
| 0982c97d66 | |||
|
|
e4168862ad | ||
| 59f26ee977 | |||
| 187b056413 | |||
|
|
e546cacd0c | ||
| e1266a18f4 | |||
|
|
27f97d0e11 | ||
|
|
ea2ec05ed6 | ||
|
|
7b1574392b | ||
|
|
4426eef2dc | ||
|
|
e04c5c97f9 | ||
|
|
987d80e4ce | ||
|
|
69412ee7ea | ||
|
|
478ac139dd | ||
|
|
058b0267be | ||
|
|
e1ca8c3d77 | ||
|
|
0f9677d6ab | ||
|
|
7f90430000 | ||
|
|
a3f0fe03af | ||
|
|
dd40689fd7 | ||
|
|
a30d10c179 | ||
|
|
460045108e | ||
|
|
9e22a4163a | ||
|
|
f20d645420 | ||
|
|
b1111a461d | ||
|
|
f5e2634c07 | ||
|
|
9e49b3dbca | ||
|
|
e242b27e44 | ||
|
|
1b7a5219d3 | ||
|
|
1740823e48 | ||
|
|
265a9b60c3 | ||
|
|
f145ccb8e0 | ||
|
|
44e3b89875 | ||
|
|
c02585b149 | ||
|
|
69c53321d2 | ||
|
|
87393726fd | ||
|
|
a179e5fa83 |
@@ -11,6 +11,30 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Checkout env repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: mso7nueh/eatvio_app_env
|
||||
path: env_repo
|
||||
token: ${{ vars.MSO7NUEH_TOKEN }}
|
||||
ref: main
|
||||
|
||||
- name: Copy .env file
|
||||
run: |
|
||||
mkdir -p env
|
||||
cp env_repo/.env env/.env
|
||||
|
||||
- name: Load environment variables from .env
|
||||
run: |
|
||||
set -a
|
||||
source env/.env
|
||||
set +a
|
||||
# Export all variables to GITHUB_ENV
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
||||
echo "$line" >> $GITHUB_ENV
|
||||
fi
|
||||
done < env/.env
|
||||
|
||||
- name: Unlock login keychain
|
||||
run: |
|
||||
@@ -18,6 +42,17 @@ jobs:
|
||||
env:
|
||||
KEYCHAIN_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
|
||||
|
||||
- name: Setup API key
|
||||
run: |
|
||||
echo "${API_APPSTORE_KEY}" | tr -d '\n' | base64 --decode > ios/AuthKey.p8
|
||||
chmod 600 ios/AuthKey.p8
|
||||
ls -la ios/AuthKey.p8
|
||||
echo "File size: $(wc -c < ios/AuthKey.p8) bytes"
|
||||
echo "Contents of ios directory:"
|
||||
ls -la ios/
|
||||
env:
|
||||
API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
|
||||
|
||||
- name: Flutter build
|
||||
run: |
|
||||
flutter clean
|
||||
@@ -31,6 +66,8 @@ jobs:
|
||||
LANG: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
MATCH_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
|
||||
FASTLANE_PASSWORD: ${{ vars.FASTLANE_PASSWORD }}
|
||||
MATCH_GIT_URL: http://gitea.office.local:3000/mso7nueh/fastlane
|
||||
FASTLANE_USER: eatvio2023@icloud.com
|
||||
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ vars.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
|
||||
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ vars.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
|
||||
API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
|
||||
on: [ pull_request ]
|
||||
|
||||
env:
|
||||
flutter_version: "3.32.5"
|
||||
flutter_version: "3.32.8"
|
||||
java_version: "21.x"
|
||||
flutter_channel: "stable"
|
||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
||||
@@ -16,6 +16,31 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout env repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: mso7nueh/eatvio_app_env
|
||||
path: env_repo
|
||||
token: ${{ vars.MSO7NUEH_TOKEN }}
|
||||
ref: main
|
||||
|
||||
- name: Copy .env file
|
||||
run: |
|
||||
mkdir -p env
|
||||
cp env_repo/.env env/.env
|
||||
|
||||
- name: Load environment variables from .env
|
||||
run: |
|
||||
set -a
|
||||
source env/.env
|
||||
set +a
|
||||
# Export all variables to GITHUB_ENV
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
||||
echo "$line" >> $GITHUB_ENV
|
||||
fi
|
||||
done < env/.env
|
||||
|
||||
- name: Create key.properties file
|
||||
run: |
|
||||
cat > android/key.properties <<EOF
|
||||
@@ -119,7 +144,7 @@ jobs:
|
||||
run: flutter test
|
||||
|
||||
- name: Build apk
|
||||
run: flutter build apk --release
|
||||
run: flutter build apk --release --dart-define=HIDE_PLANS=false
|
||||
|
||||
- name: Rename and move apk
|
||||
run: |
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -12,5 +12,10 @@
|
||||
/android/app/.cxx
|
||||
*.g.dart
|
||||
*.freezed.dart
|
||||
*.gr.dart
|
||||
*.mocks.dart
|
||||
android/build/
|
||||
ios/XCBuildData/
|
||||
.env
|
||||
env.example
|
||||
env/.env
|
||||
|
||||
84
ENV_SETUP.md
Normal file
84
ENV_SETUP.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Настройка переменных окружения
|
||||
|
||||
Этот проект использует переменные окружения для хранения секретных данных и конфигурации.
|
||||
|
||||
## Установка
|
||||
|
||||
1. Скопируйте файл `env.example` в `.env`:
|
||||
```bash
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
2. Отредактируйте файл `.env` и замените значения на ваши собственные секреты.
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
### API URLs
|
||||
- `BASE_URL` - основной URL API (по умолчанию: https://eatvio.ru)
|
||||
- `DEV_BASE_URL` - URL API для разработки (по умолчанию: https://dev2.eatvio.com)
|
||||
|
||||
### Sentry Configuration
|
||||
- `SENTRY_DSN` - DSN для Sentry мониторинга
|
||||
|
||||
### Firebase Configuration
|
||||
- `FIREBASE_PROJECT_ID` - ID проекта Firebase
|
||||
- `FIREBASE_DATABASE_URL` - URL базы данных Firebase
|
||||
- `FIREBASE_STORAGE_BUCKET` - bucket для хранения файлов
|
||||
- `FIREBASE_MESSAGING_SENDER_ID` - ID отправителя сообщений
|
||||
|
||||
### Firebase Android Configuration
|
||||
- `FIREBASE_ANDROID_API_KEY` - API ключ для Android
|
||||
- `FIREBASE_ANDROID_APP_ID` - ID приложения для Android
|
||||
|
||||
### Firebase iOS Configuration
|
||||
- `FIREBASE_IOS_API_KEY` - API ключ для iOS
|
||||
- `FIREBASE_IOS_APP_ID` - ID приложения для iOS
|
||||
- `FIREBASE_IOS_BUNDLE_ID` - Bundle ID для iOS
|
||||
|
||||
### App Store URLs
|
||||
- `APP_STORE_URL` - URL App Store
|
||||
- `RUSTORE_URL` - URL RuStore
|
||||
|
||||
### Support URLs
|
||||
- `TELEGRAM_SUPPORT_URL` - URL поддержки в Telegram
|
||||
- `SUPPORT_EMAIL` - email для поддержки
|
||||
|
||||
### Test Credentials (только для разработки)
|
||||
- `TEST_EMAIL` - тестовый email
|
||||
- `TEST_PASSWORD` - тестовый пароль
|
||||
|
||||
### Email Validation API
|
||||
- `EMAIL_VALIDATION_API_URL` - URL API для валидации email
|
||||
|
||||
### Connection Check URLs
|
||||
- `GOOGLE_CHECK_URL` - URL для проверки соединения (Google)
|
||||
- `YANDEX_CHECK_URL` - URL для проверки соединения (Yandex)
|
||||
|
||||
## Безопасность
|
||||
|
||||
⚠️ **ВАЖНО**: Файл `.env` добавлен в `.gitignore` и не должен попадать в репозиторий. Все секретные данные должны храниться только в этом файле.
|
||||
|
||||
## Использование в коде
|
||||
|
||||
Для доступа к переменным окружения используйте класс `EnvConfig`:
|
||||
|
||||
```dart
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
String baseUrl = EnvConfig.baseUrl;
|
||||
String sentryDsn = EnvConfig.sentryDsn;
|
||||
```
|
||||
|
||||
## Миграция
|
||||
|
||||
Все секреты были перенесены из кода в переменные окружения:
|
||||
|
||||
1. **Sentry DSN** - из `main.dart`
|
||||
2. **Firebase конфигурация** - из `firebase_options.dart`
|
||||
3. **API URLs** - из `const.dart`
|
||||
4. **URL для проверки соединения** - из `internet_connection_checker_screen.dart`
|
||||
5. **URL для обновления приложения** - из `update_screen.dart`
|
||||
6. **URL поддержки Telegram** - из `support_screen.dart`
|
||||
7. **API для валидации email** - из `registration_repository.dart`
|
||||
8. **Тестовые учетные данные** - из `test_cases.dart`
|
||||
9. **Email поддержки** - из `support_screen.dart`
|
||||
@@ -19,17 +19,17 @@ android {
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = 21
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.eatvioapp.app"
|
||||
minSdk = flutter.minSdkVersion
|
||||
minSdk = 23
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||
|
||||
<application
|
||||
android:label="Eatvio"
|
||||
android:label="Итвио"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
||||
@@ -22,7 +22,7 @@ plugins {
|
||||
// START: FlutterFire Configuration
|
||||
id "com.google.gms.google-services" version "4.4.2" apply false
|
||||
// END: FlutterFire Configuration
|
||||
id "org.jetbrains.kotlin.android" version "2.0.21" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||
3
assets/icons/resend.svg
Normal file
3
assets/icons/resend.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.377 11L15.3193 6.94225C15.1641 6.78709 15.0865 6.60892 15.0865 6.40775C15.0865 6.20642 15.1641 6.02817 15.3193 5.873C15.4744 5.72817 15.6542 5.65734 15.8587 5.6605C16.0631 5.66384 16.2345 5.73467 16.373 5.873L20.8673 10.3673C20.9609 10.4609 21.0269 10.5597 21.0653 10.6635C21.1038 10.7673 21.123 10.8795 21.123 11C21.123 11.1205 21.1038 11.2327 21.0653 11.3365C21.0269 11.4403 20.9609 11.5391 20.8673 11.6328L16.373 16.127C16.2178 16.282 16.0422 16.357 15.8462 16.352C15.6501 16.3468 15.4744 16.2718 15.3193 16.127C15.1641 15.9718 15.0856 15.7946 15.0837 15.5953C15.0817 15.3959 15.1551 15.2218 15.3038 15.073L19.377 11ZM14.0693 11.75H7.25C6.3525 11.75 5.5865 12.0673 4.952 12.702C4.31733 13.3365 4 14.1025 4 15V17.75C4 17.9628 3.92817 18.141 3.7845 18.2845C3.641 18.4282 3.46283 18.5 3.25 18.5C3.03717 18.5 2.859 18.4282 2.7155 18.2845C2.57183 18.141 2.5 17.9628 2.5 17.75V15C2.5 13.6872 2.9635 12.5673 3.8905 11.6405C4.81733 10.7135 5.93717 10.25 7.25 10.25H14.0693L10.7615 6.94225C10.6065 6.78709 10.529 6.60892 10.529 6.40775C10.529 6.20642 10.6065 6.02817 10.7615 5.873C10.9167 5.72817 11.0965 5.65734 11.301 5.6605C11.5055 5.66384 11.677 5.73467 11.8155 5.873L16.3095 10.3673C16.4032 10.4609 16.4692 10.5597 16.5078 10.6635C16.5463 10.7673 16.5655 10.8795 16.5655 11C16.5655 11.1205 16.5463 11.2327 16.5078 11.3365C16.4692 11.4403 16.4032 11.5391 16.3095 11.6328L11.8155 16.127C11.6603 16.282 11.4847 16.357 11.2885 16.352C11.0923 16.3468 10.9167 16.2718 10.7615 16.127C10.6065 15.9718 10.528 15.7946 10.526 15.5953C10.524 15.3959 10.5974 15.2218 10.7463 15.073L14.0693 11.75Z" fill="#2F80ED"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1,3 +1,3 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
gem "fastlane", "~> 2.217"
|
||||
|
||||
@@ -213,10 +213,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
@@ -283,10 +287,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
@@ -388,7 +396,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 39;
|
||||
CURRENT_PROJECT_VERSION = 67;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -406,7 +414,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.50;
|
||||
MARKETING_VERSION = 1.2.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
||||
@@ -538,7 +546,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 39;
|
||||
CURRENT_PROJECT_VERSION = 67;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -556,7 +564,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.50;
|
||||
MARKETING_VERSION = 1.2.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
||||
@@ -576,7 +584,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 39;
|
||||
CURRENT_PROJECT_VERSION = 67;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -594,7 +602,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.50;
|
||||
MARKETING_VERSION = 1.2.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>eatvio_app</string>
|
||||
<string>Итвио</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>39</string>
|
||||
<string>67</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -18,9 +18,66 @@ default_platform(:ios)
|
||||
platform :ios do
|
||||
desc "Push a new beta build to TestFlight"
|
||||
lane :beta do
|
||||
match(type: "appstore")
|
||||
UI.message "Current working directory: #{Dir.pwd}"
|
||||
UI.message "Checking API key file..."
|
||||
|
||||
possible_paths = [
|
||||
"../AuthKey.p8",
|
||||
"./AuthKey.p8",
|
||||
"../../AuthKey.p8",
|
||||
File.expand_path("../AuthKey.p8"),
|
||||
File.expand_path("./AuthKey.p8")
|
||||
]
|
||||
|
||||
auth_key_path = nil
|
||||
possible_paths.each do |path|
|
||||
if File.exist?(path)
|
||||
UI.message "Found AuthKey.p8 at: #{path}"
|
||||
auth_key_path = File.expand_path(path)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if auth_key_path.nil?
|
||||
UI.user_error!("API key file not found. Tried paths: #{possible_paths.join(', ')}")
|
||||
end
|
||||
|
||||
UI.message "Using auth_key_path: #{auth_key_path}"
|
||||
|
||||
match(type: "appstore", readonly: true)
|
||||
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
||||
build_app(workspace: "Runner.xcworkspace", scheme: "Runner", export_method: "app-store", codesigning_identity: "Apple Distribution")
|
||||
upload_to_testflight
|
||||
|
||||
build_app(
|
||||
workspace: "Runner.xcworkspace",
|
||||
scheme: "Runner",
|
||||
export_method: "app-store",
|
||||
codesigning_identity: "Apple Distribution",
|
||||
xcargs: "--dart-define=HIDE_PLANS=true"
|
||||
)
|
||||
|
||||
|
||||
|
||||
app_store_connect_api_key(
|
||||
key_id: "325JS9JCZH",
|
||||
issuer_id: "dfa27d1d-2bb8-4d4d-847e-0b32f9b880b3",
|
||||
key_filepath: auth_key_path,
|
||||
duration: 1200,
|
||||
in_house: false
|
||||
)
|
||||
|
||||
begin
|
||||
upload_to_testflight(
|
||||
skip_waiting_for_build_processing: true,
|
||||
skip_submission: true,
|
||||
distribute_external: false,
|
||||
notify_external_testers: false,
|
||||
changelog: "TestFlight build #{get_build_number(xcodeproj: 'Runner.xcodeproj')}",
|
||||
verbose: true
|
||||
)
|
||||
rescue => ex
|
||||
UI.error "Upload to TestFlight failed: #{ex.message}"
|
||||
UI.error "Full error: #{ex.backtrace.join("\n")}"
|
||||
raise ex
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,22 +5,24 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000206">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000227">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.195115">
|
||||
<testcase classname="fastlane.lanes" name="1: match" time="8.292217">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="116.686863">
|
||||
<testcase classname="fastlane.lanes" name="2: increment_build_number" time="0.201631">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_testflight" time="338.642203">
|
||||
<testcase classname="fastlane.lanes" name="3: build_app" time="9.870647">
|
||||
|
||||
<failure message="/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in 'Fastlane::Actions.execute_action' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:255:in 'block in Fastlane::Runner#execute_action' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in 'Dir.chdir' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in 'Fastlane::Runner#execute_action' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:157:in 'Fastlane::Runner#trigger_action_by_name' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/fast_file.rb:159:in 'Fastlane::FastFile#method_missing' Fastfile:23:in 'block (2 levels) in Fastlane::FastFile#parsing_binding' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane.rb:41:in 'Fastlane::Lane#call' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:49:in 'block in Fastlane::Runner#execute' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in 'Dir.chdir' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in 'Fastlane::Runner#execute' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane_manager.rb:46:in 'Fastlane::LaneManager.cruise_lane' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/command_line_handler.rb:34:in 'Fastlane::CommandLineHandler.handle' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:110:in 'block (2 levels) in Fastlane::CommandsGenerator#run' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in 'Commander::Command#call' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in 'Commander::Command#run' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in 'Commander::Runner#run_active_command' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in 'Commander::Runner#run!' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in 'Commander::Delegates#run!' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:363:in 'Fastlane::CommandsGenerator#run' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:43:in 'Fastlane::CommandsGenerator.start' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in 'Fastlane::CLIToolsDistributor.take_off' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/bin/fastlane:23:in '<top (required)>' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in 'Kernel#load' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in '<main>' Error building the application - see the log above" />
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
@@ -121,7 +119,9 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: 'forget-password-screen',
|
||||
name: 'forgetPasswordScreen',
|
||||
builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool,),
|
||||
builder: (context, state) => ForgetPasswordScreen(
|
||||
isAuth: state.extra as bool,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'scan-qr-code-screen',
|
||||
@@ -205,21 +205,9 @@ class AppRouter {
|
||||
builder: (context, state) => const WelcomeScreen(),
|
||||
redirect: (context, state) async {
|
||||
try {
|
||||
if (await TokenStorage.haveTokenInStorage()) {
|
||||
final firebaseApi = sl<FirebaseApi>();
|
||||
final fbToken = await firebaseApi.getToken();
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
await sl<Dio>().post(
|
||||
'/api/profile/notification/token/register',
|
||||
data: {
|
||||
'platform': Platform.isIOS ? 'ios' : 'android',
|
||||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||||
'token': fbToken,
|
||||
},
|
||||
);
|
||||
}
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false;
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
if (welcomeScreenWasShowed == false) {
|
||||
return null;
|
||||
} else if (await TokenStorage.haveTokenInStorage()) {
|
||||
@@ -228,7 +216,7 @@ class AppRouter {
|
||||
return '/auth-screen';
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
return '/feed-screen';
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -373,10 +361,10 @@ class AppRouter {
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final chatId = data['chatId'] as String;
|
||||
final uuid = data['uuid'] as String;
|
||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||
return ChatScreen(
|
||||
chatId: chatId,
|
||||
uuid: uuid,
|
||||
sendInitialMessage: sendInitialMessage,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_action_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void showCommentsActionsBottomSheet(
|
||||
@@ -23,7 +22,7 @@ void showCommentsActionsBottomSheet(
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).padding.bottom),
|
||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).padding.bottom + 20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: appTheme.whiteA700,
|
||||
@@ -123,7 +122,7 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
if (widget.isMyComment)
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.edit,
|
||||
iconPath: 'assets/icons/edit_paint.svg',
|
||||
needToPop: false,
|
||||
@@ -132,13 +131,13 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
}),
|
||||
)
|
||||
else if (widget.reply != null)
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.reply,
|
||||
iconPath: 'assets/icons/reply.svg',
|
||||
onTap: widget.reply!,
|
||||
),
|
||||
if (widget.reply != null) const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.copy,
|
||||
iconPath: 'assets/icons/copy2.svg',
|
||||
onTap: widget.copy,
|
||||
@@ -146,21 +145,21 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
if (widget.isMyComment) ...[
|
||||
if (widget.reply != null) ...[
|
||||
const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.reply,
|
||||
iconPath: 'assets/icons/reply.svg',
|
||||
onTap: widget.reply!,
|
||||
),
|
||||
],
|
||||
const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.deleteComment,
|
||||
iconPath: 'assets/icons/red_trash.svg',
|
||||
onTap: widget.delete,
|
||||
),
|
||||
] else ...[
|
||||
const CustomDivider(),
|
||||
_ActionWidget(
|
||||
BottomSheetActionWidget(
|
||||
text: s.complain,
|
||||
iconPath: 'assets/icons/complain.svg',
|
||||
onTap: widget.complain,
|
||||
@@ -172,32 +171,3 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionWidget extends StatelessWidget {
|
||||
const _ActionWidget({required this.text, required this.iconPath, required this.onTap, this.needToPop = true});
|
||||
final String text;
|
||||
final String iconPath;
|
||||
final void Function() onTap;
|
||||
final bool needToPop;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (needToPop) Navigator.pop(context);
|
||||
onTap();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
style: TextStyles.textLMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const Spacer(),
|
||||
SvgPicture.asset(iconPath)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
String baseUrl = 'https://eatvio.ru';
|
||||
// String baseUrl = 'https://dev2.eatvio.com';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
String baseUrl = EnvConfig.baseUrl;
|
||||
bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false);
|
||||
|
||||
const List<int> durationValues = [7, 14, 21];
|
||||
|
||||
|
||||
43
lib/core/env_config.dart
Normal file
43
lib/core/env_config.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
class EnvConfig {
|
||||
static String get baseUrl => dotenv.env['BASE_URL'] ?? '';
|
||||
static String get devBaseUrl => dotenv.env['DEV_BASE_URL'] ?? '';
|
||||
|
||||
// Sentry Configuration
|
||||
static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? '';
|
||||
|
||||
// Firebase Configuration
|
||||
static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? '';
|
||||
static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? '';
|
||||
static String get firebaseStorageBucket => dotenv.env['FIREBASE_STORAGE_BUCKET'] ?? '';
|
||||
static String get firebaseMessagingSenderId => dotenv.env['FIREBASE_MESSAGING_SENDER_ID'] ?? '';
|
||||
|
||||
// Firebase Android Configuration
|
||||
static String get firebaseAndroidApiKey => dotenv.env['FIREBASE_ANDROID_API_KEY'] ?? '';
|
||||
static String get firebaseAndroidAppId => dotenv.env['FIREBASE_ANDROID_APP_ID'] ?? '';
|
||||
|
||||
// Firebase iOS Configuration
|
||||
static String get firebaseIosApiKey => dotenv.env['FIREBASE_IOS_API_KEY'] ?? '';
|
||||
static String get firebaseIosAppId => dotenv.env['FIREBASE_IOS_APP_ID'] ?? '';
|
||||
static String get firebaseIosBundleId => dotenv.env['FIREBASE_IOS_BUNDLE_ID'] ?? '';
|
||||
|
||||
// App Store URLs
|
||||
static String get appStoreUrl => dotenv.env['APP_STORE_URL'] ?? '';
|
||||
static String get rustoreUrl => dotenv.env['RUSTORE_URL'] ?? '';
|
||||
|
||||
// Telegram Support
|
||||
static String get telegramSupportUrl => dotenv.env['TELEGRAM_SUPPORT_URL'] ?? '';
|
||||
static String get supportEmail => dotenv.env['SUPPORT_EMAIL'] ?? '';
|
||||
|
||||
// Test Credentials (for development only)
|
||||
static String get testEmail => dotenv.env['TEST_EMAIL'] ?? '';
|
||||
static String get testPassword => dotenv.env['TEST_PASSWORD'] ?? '';
|
||||
|
||||
// Email Validation API
|
||||
static String get emailValidationApiUrl => dotenv.env['EMAIL_VALIDATION_API_URL'] ?? '';
|
||||
|
||||
// Connection Check URLs
|
||||
static String get googleCheckUrl => dotenv.env['GOOGLE_CHECK_URL'] ?? '';
|
||||
static String get yandexCheckUrl => dotenv.env['YANDEX_CHECK_URL'] ?? '';
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
|
||||
class FirebaseApi {
|
||||
@@ -7,4 +14,22 @@ class FirebaseApi {
|
||||
await _firebaseMessaging.requestPermission();
|
||||
return await _firebaseMessaging.getToken();
|
||||
}
|
||||
|
||||
Future<void> registerPushToken() async {
|
||||
if (await TokenStorage.haveTokenInStorage()) {
|
||||
try {
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
await sl<Dio>().post(
|
||||
'/api/profile/notification/token/register',
|
||||
data: {
|
||||
'platform': Platform.isIOS ? 'ios' : 'android',
|
||||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||||
'token': await getToken(),
|
||||
},
|
||||
);
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,6 @@ Future<void> init() async {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
validateStatus: (status) => status != null && status < 500,
|
||||
),
|
||||
)..interceptors.addAll(
|
||||
[
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -11,6 +13,10 @@ class PermissionHandler {
|
||||
required Future<void> Function() onGranted,
|
||||
required BuildContext context,
|
||||
}) async {
|
||||
if (Platform.isAndroid) {
|
||||
await onGranted();
|
||||
return;
|
||||
}
|
||||
final permissionState = await permission.request();
|
||||
if (permissionState.isGranted) {
|
||||
await onGranted();
|
||||
|
||||
@@ -21,3 +21,25 @@ abstract class TokenStorage {
|
||||
|
||||
static Future<void> removeTokenInStorage() async => (await const FlutterSecureStorage().delete(key: 'token'));
|
||||
}
|
||||
|
||||
abstract class SocketIdStorage {
|
||||
static Future<bool> haveSocketIdInStorage() async {
|
||||
try {
|
||||
if (await const FlutterSecureStorage().read(key: 'socketId') == null) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> getSocketIdFromStorage() async => await const FlutterSecureStorage().read(key: 'socketId');
|
||||
|
||||
static Future<void> saveSocketIdInStorage(String socketId) async {
|
||||
await const FlutterSecureStorage().write(key: 'socketId', value: socketId);
|
||||
}
|
||||
|
||||
static Future<void> removeSocketIdInStorage() async => (await const FlutterSecureStorage().delete(key: 'socketId'));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
@@ -59,6 +60,24 @@ class WsManager {
|
||||
),
|
||||
);
|
||||
|
||||
String? extractSocketId(String? data) {
|
||||
if (data == null) return null;
|
||||
try {
|
||||
final decoded = jsonDecode(data);
|
||||
return decoded['socket_id'] as String?;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
client?.eventStream.listen((event) async {
|
||||
if (event.name == 'pusher:connection_established') {
|
||||
final data = event.data;
|
||||
final socketId = extractSocketId(data);
|
||||
await SocketIdStorage.saveSocketIdInStorage(socketId!);
|
||||
}
|
||||
});
|
||||
|
||||
await client?.connect();
|
||||
|
||||
client = client;
|
||||
@@ -77,9 +96,8 @@ class WsManager {
|
||||
EasyDebounce.debounce(
|
||||
'searchChatRoom',
|
||||
const Duration(milliseconds: 300),
|
||||
() => sl<ChatsCubit>().getChatRooms(),
|
||||
() => sl<ChatsCubit>().getChatRooms(),
|
||||
);
|
||||
log(event.rootObject.toString());
|
||||
});
|
||||
_subscribe();
|
||||
} catch (e, st) {
|
||||
|
||||
33
lib/core/widgets/bottom_sheet_action_widget.dart
Normal file
33
lib/core/widgets/bottom_sheet_action_widget.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class BottomSheetActionWidget extends StatelessWidget {
|
||||
const BottomSheetActionWidget({super.key, required this.text, required this.iconPath, required this.onTap, this.needToPop = true, this.color});
|
||||
final String text;
|
||||
final String iconPath;
|
||||
final void Function() onTap;
|
||||
final bool needToPop;
|
||||
final Color? color;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (needToPop) Navigator.pop(context);
|
||||
onTap();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(text, style: TextStyles.textLMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
SvgPicture.asset(iconPath, colorFilter: color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
@@ -66,6 +67,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
await sl<ChatsCubit>().connectToWebSocket();
|
||||
await sl<NotificationsCubit>().getNotifications();
|
||||
await sl<NotificationsCubit>().connectToWebSocket();
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
showSuccessToast('Успешный вход');
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
@@ -79,6 +81,9 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var shortestSide = MediaQuery.sizeOf(context).shortestSide;
|
||||
final bool useMobileLayout = shortestSide < 600;
|
||||
if (!useMobileLayout) isFocused = true;
|
||||
final s = S.of(context);
|
||||
FlutterNativeSplash.remove();
|
||||
return BlocProvider<AuthCubit>(
|
||||
|
||||
@@ -15,7 +15,11 @@ abstract class ChatMessageModel with _$ChatMessageModel {
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false) required bool isRead,
|
||||
@JsonKey(name: 'is_author_message_auth_user') required bool? isCurrentUserAuthor,
|
||||
@JsonKey(name: 'room_id') required String? roomId,
|
||||
@Default(MessageState.sent) @JsonKey(includeToJson: false, includeFromJson: false) MessageState messageState,
|
||||
@Default(null) @JsonKey(includeToJson: false, includeFromJson: false) String? uuid,
|
||||
}) = _ChatMessageModel;
|
||||
|
||||
factory ChatMessageModel.fromJson(Map<String, Object?> json) => _$ChatMessageModelFromJson(json);
|
||||
}
|
||||
|
||||
enum MessageState { loading, sent, error }
|
||||
|
||||
@@ -22,13 +22,9 @@ abstract class ChatRoomModel with _$ChatRoomModel {
|
||||
@freezed
|
||||
abstract class Users with _$Users {
|
||||
const factory Users({
|
||||
|
||||
@JsonKey(name: 'from_chat_user') required ChatUserModel sender,
|
||||
@JsonKey(name: 'to_chat_user') required ChatUserModel receiver,
|
||||
|
||||
}) = _Users;
|
||||
|
||||
factory Users.fromJson(Map<String, Object?> json) => _$UsersFromJson(json);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eatvio_app/core/models/pagination_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -8,29 +9,18 @@ part 'chat_room_pagination_model.g.dart';
|
||||
@freezed
|
||||
abstract class ChatRoomPaginationModel with _$ChatRoomPaginationModel {
|
||||
const factory ChatRoomPaginationModel({
|
||||
@JsonKey(name: "chatRooms") required ChatRooms chatRooms,
|
||||
@JsonKey(name: "data") required List<ChatRoom> data,
|
||||
@JsonKey(name: "number_of_unread_chat_rooms") required int unreadChatsCount,
|
||||
required Meta meta,
|
||||
}) = _ChatRoomPreviewModel;
|
||||
|
||||
factory ChatRoomPaginationModel.fromJson(Map<String, dynamic> json) => _$ChatRoomPaginationModelFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ChatRooms with _$ChatRooms {
|
||||
const factory ChatRooms({
|
||||
@JsonKey(name: "data") required List<ChatRoom> data,
|
||||
@JsonKey(name: "last_page") required int lastPage,
|
||||
@JsonKey(name: "current_page") required int currentPage,
|
||||
}) = _ChatRooms;
|
||||
|
||||
factory ChatRooms.fromJson(Map<String, dynamic> json) => _$ChatRoomsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ChatRoom with _$ChatRoom {
|
||||
const factory ChatRoom({
|
||||
@JsonKey(name: "id") required String id,
|
||||
@JsonKey(name: "room_name") required String roomName,
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage) required ChatMessageModel? lastMessage,
|
||||
@JsonKey(name: "users") required Users users,
|
||||
@JsonKey(name: "unread_messages_count") required int unreadMessagesCount,
|
||||
|
||||
@@ -6,11 +6,8 @@ part 'chat_room_settings_model.g.dart';
|
||||
@freezed
|
||||
abstract class ChatRoomSettingsModel with _$ChatRoomSettingsModel {
|
||||
const factory ChatRoomSettingsModel({
|
||||
required int id,
|
||||
@JsonKey(name: 'room_name') required String roomName,
|
||||
@JsonKey(name: 'room_name') required String name,
|
||||
required String state,
|
||||
@JsonKey(name: 'access_level') required String accessLevel,
|
||||
@JsonKey(name: 'updated_at') required String updatedAt,
|
||||
}) = _ChatRoomSettingsModel;
|
||||
|
||||
factory ChatRoomSettingsModel.fromJson(Map<String, Object?> json) => _$ChatRoomSettingsModelFromJson(json);
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
@@ -19,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/plans/models/chosen_plan_model.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
final ChatRepository chatRepository;
|
||||
@@ -31,12 +33,9 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
late int lastPage;
|
||||
bool isLoading = false;
|
||||
Timer? _timer;
|
||||
var uuid = const Uuid();
|
||||
|
||||
ChatRoomCubit({
|
||||
required this.chatRepository,
|
||||
required this.wsManager,
|
||||
required this.cardsRepository,
|
||||
}) : super(const ChatRoomState.loading());
|
||||
ChatRoomCubit({required this.chatRepository, required this.wsManager, required this.cardsRepository}) : super(const ChatRoomState.loading());
|
||||
|
||||
void setTimer() {
|
||||
_timer?.cancel();
|
||||
@@ -47,14 +46,14 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
_timer?.cancel();
|
||||
}
|
||||
|
||||
Future<void> loadNextPage(String chatId) async {
|
||||
Future<void> loadNextPage(String uuid) async {
|
||||
if (currentPage < lastPage && !isLoading) {
|
||||
isLoading = true;
|
||||
|
||||
emit((state as ChatRoomStateLoaded).copyWith(isLoading: isLoading));
|
||||
|
||||
try {
|
||||
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage + 1);
|
||||
final chatMessages = await chatRepository.getChatMessages(uuid, currentPage + 1);
|
||||
|
||||
isLoading = false;
|
||||
emit(
|
||||
@@ -89,17 +88,16 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initChat(String chatId, {bool sendInitialMessage = false}) async {
|
||||
Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async {
|
||||
try {
|
||||
final chatRoom = await chatRepository.getChatRoom(chatId);
|
||||
final chatRoom = await chatRepository.getChatRoom(uuid);
|
||||
ChosenPlanModel? chosenPlan;
|
||||
if (chatRoom.chosenPlanId != null) {
|
||||
chosenPlan = await chatRepository.getChosenPlan(id: chatRoom.chosenPlanId!);
|
||||
}
|
||||
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage);
|
||||
final chatMessages = await chatRepository.getChatMessages(uuid, currentPage);
|
||||
lastPage = chatMessages.meta.lastPage;
|
||||
|
||||
await _subscribeToChatChannel(chatId: chatId);
|
||||
await _subscribeToChatChannel(uuid: uuid, chatId: chatRoom.id);
|
||||
emit(
|
||||
ChatRoomState.loaded(
|
||||
chatRoom: chatRoom.copyWith(chosenPlan: chosenPlan),
|
||||
@@ -158,43 +156,80 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _subscribeToChatChannel({required String chatId}) async {
|
||||
Future<void> _subscribeToChatChannel({required String uuid, required String chatId}) async {
|
||||
final client = wsManager.client;
|
||||
if (client == null) return;
|
||||
await wsManager.initPrivateDirectChatChannel(chatId);
|
||||
wsManager.subscribeDirectChatChannel();
|
||||
messagesSubscription = wsManager.directChatStream?.listen((event) {
|
||||
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
|
||||
final message = ChatMessageModel.fromJson(jsonDecode(event.data)['message']);
|
||||
final message = ChatMessageModel.fromJson(jsonDecode(event.data));
|
||||
List<ChatMessageModel> messagesList = [message, ...(state as ChatRoomStateLoaded).chatMessages];
|
||||
chatRepository.emitNewMessageToChats(message);
|
||||
emit((state as ChatRoomStateLoaded).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());
|
||||
});
|
||||
|
||||
typingSubscription = wsManager.clientTypingStream?.listen((event) {
|
||||
emit((state as ChatRoomStateLoaded).copyWith(isTyping: true, isLoading: isLoading));
|
||||
EasyDebounce.debounce(
|
||||
'client-typing', const Duration(seconds: 3), () => emit((state as ChatRoomStateLoaded).copyWith(isTyping: false, isLoading: isLoading)));
|
||||
'client-typing',
|
||||
const Duration(seconds: 3),
|
||||
() => emit((state as ChatRoomStateLoaded).copyWith(isTyping: false, isLoading: isLoading)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> sendMessage({
|
||||
String? data,
|
||||
List<File>? files,
|
||||
required String userSlug,
|
||||
}) async {
|
||||
Future<bool> sendMessage({String? data, List<File>? files, required String userSlug}) async {
|
||||
try {
|
||||
await chatRepository.sendMessage(
|
||||
data: data,
|
||||
files: files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null,
|
||||
slug: userSlug,
|
||||
);
|
||||
final messageUuid = uuid.v4();
|
||||
final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null;
|
||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: [
|
||||
ChatMessageModel(
|
||||
id: 1,
|
||||
data: data ?? '',
|
||||
mediaData: compressedFiles,
|
||||
authorId: 1,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '',
|
||||
messageState: MessageState.loading,
|
||||
uuid: messageUuid,
|
||||
),
|
||||
...(state as ChatRoomStateLoaded).chatMessages,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await chatRepository.sendMessage(data: data, files: compressedFiles, slug: userSlug);
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == messageUuid) {
|
||||
return elem.copyWith(messageState: MessageState.sent);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == messageUuid) {
|
||||
return elem.copyWith(messageState: MessageState.error);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
sl<ChatsCubit>().getChatRooms();
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
@@ -239,7 +274,10 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
try {
|
||||
final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
||||
if (planId == null) return false;
|
||||
await chatRepository.complainAboutConsultation(id: planId, body: PlanComplainModel(description: description));
|
||||
await chatRepository.complainAboutConsultation(
|
||||
id: planId,
|
||||
body: PlanComplainModel(description: description),
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
@@ -267,4 +305,70 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
typingSubscription?.cancel();
|
||||
super.close();
|
||||
}
|
||||
|
||||
void deleteMessage(String uuid) {
|
||||
final currentState = state;
|
||||
if (currentState is ChatRoomStateLoaded) {
|
||||
emit(currentState.copyWith(chatMessages: currentState.chatMessages.where((message) => message.uuid != uuid).toList()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resendMessage({required String uuid, required String userSlug}) async {
|
||||
final currentState = state;
|
||||
if (currentState is ChatRoomStateLoaded) {
|
||||
final message = currentState.chatMessages.firstWhereOrNull((message) => message.uuid == uuid);
|
||||
if (message != null) {
|
||||
try {
|
||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: [
|
||||
ChatMessageModel(
|
||||
id: 1,
|
||||
data: message.data,
|
||||
mediaData: message.mediaData,
|
||||
authorId: 1,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: '',
|
||||
messageState: MessageState.loading,
|
||||
uuid: uuid,
|
||||
),
|
||||
...(state as ChatRoomStateLoaded).chatMessages.where((message) => message.uuid != uuid),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await chatRepository.sendMessage(data: message.data, files: message.mediaData as List<File>?, slug: userSlug);
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == uuid) {
|
||||
return elem.copyWith(messageState: MessageState.sent);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||
if (elem.uuid == uuid) {
|
||||
return elem.copyWith(messageState: MessageState.error);
|
||||
}
|
||||
return elem;
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
sl<ChatsCubit>().getChatRooms();
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
required this.chatRepository,
|
||||
required this.wsManager,
|
||||
required this.profileRepository,
|
||||
}) : super(ChatsState()) {
|
||||
}) : super(ChatsState()) {
|
||||
newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((message) {
|
||||
_setNewLastMessage(message);
|
||||
});
|
||||
@@ -29,6 +29,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
String? _query;
|
||||
int page = 1;
|
||||
late int lastPage;
|
||||
|
||||
Future<void> connectToWebSocket() async {
|
||||
await newIncomingMessagesSubscription?.cancel();
|
||||
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
|
||||
@@ -43,10 +44,10 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
try {
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(query: query);
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
lastPage = chatRoomsPaginationModel.chatRooms.lastPage + 1;
|
||||
lastPage = chatRoomsPaginationModel.meta.lastPage + 1;
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: chatRoomsPaginationModel.chatRooms.data,
|
||||
chatRooms: chatRoomsPaginationModel.data,
|
||||
chatsWithUnreadMessagesCount: unreadChats,
|
||||
state: BaseState.success,
|
||||
),
|
||||
@@ -65,10 +66,10 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
try {
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(query: query);
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
lastPage = chatRoomsPaginationModel.chatRooms.lastPage + 1;
|
||||
lastPage = chatRoomsPaginationModel.meta.lastPage + 1;
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: chatRoomsPaginationModel.chatRooms.data,
|
||||
chatRooms: chatRoomsPaginationModel.data,
|
||||
chatsWithUnreadMessagesCount: unreadChats,
|
||||
state: BaseState.success,
|
||||
),
|
||||
@@ -90,7 +91,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: [...state.chatRooms, ...chatRoomsPaginationModel.chatRooms.data],
|
||||
chatRooms: [...state.chatRooms, ...chatRoomsPaginationModel.data],
|
||||
chatsWithUnreadMessagesCount: unreadChats,
|
||||
state: BaseState.success,
|
||||
isLoadingMoreChats: false,
|
||||
|
||||
@@ -32,10 +32,10 @@ import 'package:intl/intl.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
final String chatId;
|
||||
final String uuid;
|
||||
final bool? sendInitialMessage;
|
||||
|
||||
const ChatScreen({required this.chatId, super.key, required this.sendInitialMessage});
|
||||
const ChatScreen({required this.uuid, super.key, required this.sendInitialMessage});
|
||||
|
||||
@override
|
||||
State<ChatScreen> createState() => _ChatScreenState();
|
||||
@@ -64,14 +64,14 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController.addListener(_onScroll);
|
||||
chatRoomCubit.initChat(widget.chatId, sendInitialMessage: widget.sendInitialMessage ?? false);
|
||||
chatRoomCubit.initChat(widget.uuid, sendInitialMessage: widget.sendInitialMessage ?? false);
|
||||
chatRoomCubit.setTimer();
|
||||
_listener = AppLifecycleListener(onHide: _onHide, onResume: _onResume);
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (scrollController.offset >= scrollController.position.maxScrollExtent && !scrollController.position.outOfRange) {
|
||||
chatRoomCubit.loadNextPage(widget.chatId);
|
||||
chatRoomCubit.loadNextPage(widget.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (state is ChatRoomStateLoaded) {
|
||||
return _buildChat(context, state);
|
||||
} else if (state is ChatRoomStateError) {
|
||||
return Scaffold(appBar: AppBar(), body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.chatId)));
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)),
|
||||
);
|
||||
} else {
|
||||
return const Scaffold(body: Loader());
|
||||
}
|
||||
@@ -168,34 +171,19 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFEBF4FF),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
decoration: const BoxDecoration(color: Color(0xFFEBF4FF), shape: BoxShape.circle),
|
||||
child: Text(
|
||||
state.chatRoom.users.receiver.name.split(' ').first[0] + state.chatRoom.users.receiver.name.split(' ')[1][0],
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF7F9CF5),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 20,
|
||||
),
|
||||
style: const TextStyle(color: Color(0xFF7F9CF5), fontWeight: FontWeight.w400, fontSize: 20),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
state.chatRoom.users.receiver.name,
|
||||
style: CustomTextStyles.titleSmallBold.copyWith(
|
||||
color: PrimaryColors().black,
|
||||
),
|
||||
),
|
||||
Text(state.chatRoom.users.receiver.name, style: CustomTextStyles.titleSmallBold.copyWith(color: PrimaryColors().black)),
|
||||
AnimatedCrossFade(
|
||||
firstChild: Text(
|
||||
'Печатает...',
|
||||
style: CustomTextStyles.bodySmallMulishPrimary,
|
||||
),
|
||||
firstChild: Text('Печатает...', style: CustomTextStyles.bodySmallMulishPrimary),
|
||||
firstCurve: Curves.easeIn,
|
||||
secondCurve: Curves.easeOut,
|
||||
secondChild: const SizedBox.shrink(),
|
||||
@@ -251,9 +239,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (!result) return;
|
||||
if (!context.mounted) return;
|
||||
context.read<ChatsCubit>().reduceUnreadMessagesCount(
|
||||
chatId: state.chatRoom.id,
|
||||
count: unreadMessagesIdsCopy.length,
|
||||
);
|
||||
chatId: state.chatRoom.id,
|
||||
count: unreadMessagesIdsCopy.length,
|
||||
);
|
||||
unreadMessagesIdsCopy.clear();
|
||||
});
|
||||
}
|
||||
@@ -264,6 +252,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: isCurrentUserAuthor,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
messageState: state.chatMessages[index].messageState,
|
||||
messageUuid: state.chatMessages[index].uuid ?? '',
|
||||
userSlug: state.chatRoom.users.receiver.slug,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -290,6 +281,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: isCurrentUserAuthor,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
messageState: state.chatMessages[index].messageState,
|
||||
messageUuid: state.chatMessages[index].uuid ?? '',
|
||||
userSlug: state.chatRoom.users.receiver.slug,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -361,11 +355,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final imageIndex = _imageIndexAt(pickedFiles, i);
|
||||
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: imageFiles.map((e) => e.path).toList(),
|
||||
isLocal: true,
|
||||
index: imageIndex,
|
||||
),
|
||||
PhotoViewScreen(imageUrls: imageFiles.map((e) => e.path).toList(), isLocal: true, index: imageIndex),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
@@ -373,14 +363,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: SizedBox(
|
||||
height: 104,
|
||||
width: 104,
|
||||
child: Image.file(
|
||||
pickedFiles[i],
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: SizedBox(height: 104, width: 104, child: Image.file(pickedFiles[i], fit: BoxFit.cover)),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -397,15 +380,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.gray50,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
size: 20,
|
||||
Icons.close,
|
||||
color: Colors.black,
|
||||
),
|
||||
decoration: BoxDecoration(color: appTheme.gray50, shape: BoxShape.circle),
|
||||
child: const Icon(size: 20, Icons.close, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -427,7 +403,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
child: Ink(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Transform.rotate(angle: pi / 4, child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99))),
|
||||
child: Transform.rotate(
|
||||
angle: pi / 4,
|
||||
child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -449,7 +428,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
@@ -525,13 +506,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
const Icon(Icons.attach_file, size: 32, color: Colors.black54),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
fileName,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
child: Text(fileName, textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -579,13 +554,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
// await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug);
|
||||
// }
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.photo_camera),
|
||||
SizedBox(width: 8),
|
||||
Text('Фото'),
|
||||
],
|
||||
),
|
||||
child: const Row(children: [Icon(Icons.photo_camera), SizedBox(width: 8), Text('Фото')]),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
@@ -600,13 +569,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.photo_library_rounded),
|
||||
SizedBox(width: 8),
|
||||
Text('Галерея'),
|
||||
],
|
||||
),
|
||||
child: const Row(children: [Icon(Icons.photo_library_rounded), SizedBox(width: 8), Text('Галерея')]),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
@@ -643,20 +606,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.file_present_rounded),
|
||||
SizedBox(width: 8),
|
||||
Text('Файлы'),
|
||||
],
|
||||
),
|
||||
child: const Row(children: [Icon(Icons.file_present_rounded), SizedBox(width: 8), Text('Файлы')]),
|
||||
),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
isDestructiveAction: true,
|
||||
child: const Text('Отмена'),
|
||||
),
|
||||
cancelButton: CupertinoActionSheetAction(onPressed: () => Navigator.of(context).pop(), isDestructiveAction: true, child: const Text('Отмена')),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -665,7 +618,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 32),
|
||||
child: Text(DateFormat.yMMMMd('RU').format(date), style: const TextStyle(height: 1, color: Color(0xFF8393A1), fontWeight: FontWeight.w500)),
|
||||
child: Text(
|
||||
DateFormat.yMMMMd('RU').format(date),
|
||||
style: const TextStyle(height: 1, color: Color(0xFF8393A1), fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -214,7 +214,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -264,7 +264,7 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
@@ -11,7 +9,7 @@ import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class PhotoViewScreen extends StatefulWidget {
|
||||
final List<String> imageUrls;
|
||||
final List<dynamic> imageUrls;
|
||||
final int index;
|
||||
final bool isLocal;
|
||||
|
||||
@@ -50,14 +48,12 @@ class _PhotoViewScreenState extends State<PhotoViewScreen> {
|
||||
),
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
builder: (BuildContext context, int index) {
|
||||
final imageProvider = widget.isLocal
|
||||
? FileImage(widget.imageUrls[index]) as ImageProvider<Object>
|
||||
: CachedNetworkImageProvider(widget.imageUrls[index]) as ImageProvider<Object>;
|
||||
|
||||
return PhotoViewGalleryPageOptions(
|
||||
imageProvider: widget.isLocal
|
||||
? FileImage(
|
||||
File(widget.imageUrls[index]),
|
||||
) as ImageProvider
|
||||
: CachedNetworkImageProvider(
|
||||
widget.imageUrls[index],
|
||||
),
|
||||
imageProvider: imageProvider,
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.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/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/message_action_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ChatBubbleWidget extends StatelessWidget {
|
||||
@@ -15,6 +22,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
required this.isCurrentUser,
|
||||
required this.mediaData,
|
||||
required this.isRead,
|
||||
required this.messageState,
|
||||
required this.messageUuid,
|
||||
required this.userSlug
|
||||
});
|
||||
|
||||
final DateTime sendTime;
|
||||
@@ -22,7 +32,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
final bool isCurrentUser;
|
||||
final bool isRead;
|
||||
final List<dynamic> mediaData;
|
||||
|
||||
final MessageState messageState;
|
||||
final String messageUuid;
|
||||
final String userSlug;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
@@ -36,17 +48,33 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('HH:mm', 'ru'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
),
|
||||
switch (messageState) {
|
||||
MessageState.loading => Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: SizedBox(width: 10, height: 10, child: CircularProgressIndicator(strokeWidth: 2, color: appTheme.blueA400)),
|
||||
),
|
||||
),
|
||||
|
||||
MessageState.sent => Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: Text(sendTime.format('HH:mm', 'ru'), style: const TextStyle(color: Color(0xFF7B8794), height: 1, fontSize: 10)),
|
||||
),
|
||||
|
||||
MessageState.error => GestureDetector(
|
||||
onTap: () {
|
||||
showMessageActionsBottomSheet(
|
||||
context,
|
||||
resend: () {
|
||||
context.read<ChatRoomCubit>().resendMessage(uuid: messageUuid, userSlug: userSlug);
|
||||
},
|
||||
delete: () {
|
||||
context.read<ChatRoomCubit>().deleteMessage(messageUuid);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(padding: const EdgeInsets.only(right: 4, bottom: 4), child: SvgPicture.asset('assets/icons/error_icon.svg')),
|
||||
),
|
||||
},
|
||||
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: size.width * 0.7),
|
||||
decoration: BoxDecoration(
|
||||
@@ -59,8 +87,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
mediaData.isNotEmpty ? const EdgeInsets.all(8) : EdgeInsets.only(top: 8, bottom: 8, left: isCurrentUser ? 16 : 8, right: isCurrentUser ? 8 : 16),
|
||||
padding: mediaData.isNotEmpty
|
||||
? const EdgeInsets.all(8)
|
||||
: EdgeInsets.only(top: 8, bottom: 8, left: isCurrentUser ? 16 : 8, right: isCurrentUser ? 8 : 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -84,32 +113,19 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
throw Exception('Could not launch ${mediaData[index].toString()}');
|
||||
}
|
||||
},
|
||||
leading: const Icon(
|
||||
Icons.file_copy_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.download_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
leading: const Icon(Icons.file_copy_outlined, color: Colors.black),
|
||||
trailing: const Icon(Icons.download_outlined, color: Colors.black),
|
||||
title: Text(
|
||||
mediaData[index].toString().replaceAll('chat_files/', ''),
|
||||
maxLines: 2,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
style: const TextStyle(color: Colors.black, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: mediaData.map((e) => '$e').toList(),
|
||||
index: index,
|
||||
),
|
||||
PhotoViewScreen(imageUrls: mediaData, index: index, isLocal: mediaData[index] is File ? true : false),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
@@ -117,9 +133,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: '${mediaData[index]}',
|
||||
),
|
||||
child: mediaData[index] is File ? Image.file(mediaData[index]) : CachedNetworkImage(imageUrl: '${mediaData[index]}'),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -131,11 +145,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (mediaData.isNotEmpty && text.isNotEmpty) const SizedBox(height: 8),
|
||||
if (text.isNotEmpty)
|
||||
Text(
|
||||
text,
|
||||
style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16),
|
||||
),
|
||||
if (text.isNotEmpty) Text(text, style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -145,12 +155,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('HH:mm', 'ru'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: const TextStyle(color: Color(0xFF7B8794), height: 1, fontSize: 10, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -24,7 +24,7 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': widget.chatRoom.id.toString(),
|
||||
'uuid': widget.chatRoom.id.toString(),
|
||||
},
|
||||
),
|
||||
child: Container(
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_action_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showMessageActionsBottomSheet(BuildContext context, {required void Function() resend, required void Function() delete}) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).padding.bottom + 20),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.whiteA700),
|
||||
child: MessageActionsBottomSheet(resend: resend, delete: delete),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MessageActionsBottomSheet extends StatefulWidget {
|
||||
const MessageActionsBottomSheet({super.key, required this.resend, required this.delete});
|
||||
final void Function() resend;
|
||||
final void Function() delete;
|
||||
|
||||
@override
|
||||
State<MessageActionsBottomSheet> createState() => _MessageActionsBottomSheet();
|
||||
}
|
||||
|
||||
class _MessageActionsBottomSheet extends State<MessageActionsBottomSheet> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 7),
|
||||
const BottomSheetHandlerBar(),
|
||||
BottomSheetActionWidget(text: s.resend, iconPath: 'assets/icons/resend.svg', onTap: widget.resend),
|
||||
const CustomDivider(),
|
||||
BottomSheetActionWidget(text: s.delete, iconPath: 'assets/icons/trash_can.svg', onTap: widget.delete, color: appTheme.red),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
|
||||
@@ -47,10 +48,11 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
await chatService.sendMessageByUserSlug(
|
||||
userSlug: slug,
|
||||
await chatService.sendMessage(
|
||||
slug: slug,
|
||||
data: data,
|
||||
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
|
||||
socketId: (await SocketIdStorage.getSocketIdFromStorage())!,
|
||||
);
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
@@ -58,10 +60,11 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ChatRoomModel> getChatRoom(String chatId) async {
|
||||
Future<ChatRoomModel> getChatRoom(String uuid) async {
|
||||
try {
|
||||
return (await chatService.getChatRoom(id: chatId))['chatRoom']!;
|
||||
} catch (e) {
|
||||
return (await chatService.getChatRoom(uuid: uuid));
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -74,9 +77,9 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ChatMessagesModel> getChatMessages(String chatId, int page) async {
|
||||
Future<ChatMessagesModel> getChatMessages(String id, int page) async {
|
||||
try {
|
||||
return (await chatService.getChatMessages(id: chatId, page: page));
|
||||
return (await chatService.getChatMessages(id: id, page: page));
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
@@ -90,9 +93,9 @@ class ChatRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markMessagesAsRead(String chatId, List<int> messageIds) async {
|
||||
Future<void> markMessagesAsRead(String id, List<int> messageIds) async {
|
||||
try {
|
||||
await chatService.markMessagesAsRead(id: chatId, body: {'messages_ids': messageIds});
|
||||
await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds});
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
@@ -13,32 +13,33 @@ part 'chat_service.g.dart';
|
||||
abstract class ChatService {
|
||||
factory ChatService(Dio dio, {String baseUrl}) = _ChatService;
|
||||
|
||||
@GET('/api/chat/index')
|
||||
@GET('/api/v2/chat/index')
|
||||
Future<ChatRoomPaginationModel> getChatRooms({
|
||||
@Query('page') required int page,
|
||||
@Query('find') String? query,
|
||||
@Query('filters[data][\$conversation_name]') String? query,
|
||||
});
|
||||
|
||||
@GET('/api/chat/show/{id}')
|
||||
Future<Map<String, ChatRoomModel>> getChatRoom({
|
||||
@Path() required String id,
|
||||
@GET('/api/v2/chat/show/{uuid}')
|
||||
Future<ChatRoomModel> getChatRoom({
|
||||
@Path() required String uuid,
|
||||
});
|
||||
|
||||
@POST('/api/v2/chat/message/send/{id}')
|
||||
@POST('/api/v2/chat/message/send/{slug}')
|
||||
@MultiPart()
|
||||
Future<void> sendMessage({
|
||||
@Path() required String id,
|
||||
@Header('X-Socket-ID') required String socketId,
|
||||
@Path() required String slug,
|
||||
@Part() String? data,
|
||||
@Part(name: 'media_data[]', contentType: 'multipart/form-data') List<MultipartFile>? mediaData,
|
||||
});
|
||||
|
||||
@GET('/api/chat/show/{id}/messages?page={page}')
|
||||
@GET('/api/v2/chat/show/{id}/messages?page={page}')
|
||||
Future<ChatMessagesModel> getChatMessages({
|
||||
@Path() required String id,
|
||||
@Path() required int page,
|
||||
});
|
||||
|
||||
@GET('/api/chat/get_by_user/{user}')
|
||||
@GET('/api/v2/chat/get_by_user/{user}')
|
||||
Future<ChatRoomModel> getChatRoomIdByUserSlug({
|
||||
@Path() required String user,
|
||||
});
|
||||
@@ -81,16 +82,8 @@ abstract class ChatService {
|
||||
@Body() required PlanComplainModel body,
|
||||
});
|
||||
|
||||
@GET('/api/v2/chat/get_by_user/{slug}')
|
||||
@GET('/api/chat/get_by_user/{slug}')
|
||||
Future<HttpResponse> getChatByUserSlug({
|
||||
@Path() required String slug,
|
||||
});
|
||||
|
||||
@POST('/api/v2/chat/message/send/{userSlug}')
|
||||
@MultiPart()
|
||||
Future<void> sendMessageByUserSlug({
|
||||
@Path() required String userSlug,
|
||||
@Part() String? data,
|
||||
@Part(name: 'media_data[]', contentType: 'multipart/form-data') List<MultipartFile>? mediaData,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -194,63 +195,74 @@ class _LawViolationWidgetState extends State<_LawViolationWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
s.lawViolation,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.what_do_you_want_to_complaint_about,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
...tags.mapIndexed((index, element) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
activeColor: appTheme.blueA400,
|
||||
side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray),
|
||||
value: element.$1,
|
||||
onChanged: (isChecked) {
|
||||
setState(() {
|
||||
if (isChecked == null) return;
|
||||
if (index == tags.length - 1 && !isChecked) {
|
||||
context.read<ComplainingCubit>().toggleDescription();
|
||||
}
|
||||
tags[index] = (isChecked, element.$2);
|
||||
final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList();
|
||||
context.read<ComplainingCubit>().setTags(activeTags);
|
||||
});
|
||||
return BlocBuilder<ComplainingCubit, ComplainingState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
s.lawViolation,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.what_do_you_want_to_complaint_about,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
...tags.mapIndexed((index, element) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
activeColor: appTheme.blueA400,
|
||||
side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray),
|
||||
value: element.$1,
|
||||
onChanged: (isChecked) {
|
||||
setState(() {
|
||||
if (isChecked == null) return;
|
||||
if (index == tags.length - 1 && !isChecked) {
|
||||
context.read<ComplainingCubit>().toggleDescription();
|
||||
}
|
||||
tags[index] = (isChecked, element.$2);
|
||||
final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList();
|
||||
context.read<ComplainingCubit>().setTags(activeTags);
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
element.$2,
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
if (tags.last.$1) ...[
|
||||
const SizedBox(height: 12),
|
||||
CustomTextFormField(
|
||||
hintText: s.type_here_your_complaint,
|
||||
hasError: (state.complainInfo.description?.length ?? 10) < 10,
|
||||
onChanged: (value) {
|
||||
context.read<ComplainingCubit>().setDescription(value);
|
||||
},
|
||||
),
|
||||
Text(
|
||||
element.$2,
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
if ((state.complainInfo.description?.length ?? 10) < 10) ...[
|
||||
const SizedBox(height: 4),
|
||||
const ValidationErrorWidget(error: 'Минимум 10 символов'),
|
||||
]
|
||||
],
|
||||
);
|
||||
}),
|
||||
if (tags.last.$1) ...[
|
||||
const SizedBox(height: 12),
|
||||
CustomTextFormField(
|
||||
hintText: s.type_here_your_complaint,
|
||||
onChanged: (value) {
|
||||
context.read<ComplainingCubit>().setDescription(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
const _ComplainButton(),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
_ComplainButton(
|
||||
isDisabled: (state.complainInfo.description?.length ?? 0) < 10,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -515,8 +527,8 @@ class _ComplaintFailedWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _ComplainButton extends StatelessWidget {
|
||||
const _ComplainButton();
|
||||
|
||||
const _ComplainButton({this.isDisabled = false});
|
||||
final bool isDisabled;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
@@ -534,6 +546,10 @@ class _ComplainButton extends StatelessWidget {
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (isDisabled) {
|
||||
context.read<ComplainingCubit>().setDescription('');
|
||||
return;
|
||||
}
|
||||
await context.read<ComplainingCubit>().complain();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -14,10 +14,7 @@ import 'package:go_router/go_router.dart';
|
||||
class ConsultationCardWidget extends StatefulWidget {
|
||||
final ConsultationModel consultationsModel;
|
||||
|
||||
const ConsultationCardWidget({
|
||||
super.key,
|
||||
required this.consultationsModel,
|
||||
});
|
||||
const ConsultationCardWidget({super.key, required this.consultationsModel});
|
||||
|
||||
@override
|
||||
State<ConsultationCardWidget> createState() => _ConsultationCardWidgetState();
|
||||
@@ -32,13 +29,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
'Дневник заполнен, прокомментирован консультантом и после этого клиент просмотрел комментарии.',
|
||||
];
|
||||
|
||||
final List<Color> legendColors = const [
|
||||
Colors.white,
|
||||
Color(0xFFF0C497),
|
||||
Color(0xFFFF9500),
|
||||
Color(0xFF2F80ED),
|
||||
Color(0xFF4CD964),
|
||||
];
|
||||
final List<Color> legendColors = const [Colors.white, Color(0xFFF0C497), Color(0xFFFF9500), Color(0xFF2F80ED), Color(0xFF4CD964)];
|
||||
|
||||
late final List<(int, DiaryModel)> givenDaysWithDayNumber;
|
||||
final List<List<DiaryStateEnum>> legendStates = [
|
||||
@@ -46,7 +37,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
[DiaryStateEnum.started, DiaryStateEnum.lastDayStarted],
|
||||
[DiaryStateEnum.finished, DiaryStateEnum.lastDayFinished],
|
||||
[DiaryStateEnum.commented, DiaryStateEnum.lastDayCommented],
|
||||
[DiaryStateEnum.reviewedByUser]
|
||||
[DiaryStateEnum.reviewedByUser],
|
||||
];
|
||||
@override
|
||||
void initState() {
|
||||
@@ -72,32 +63,19 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 30,
|
||||
backgroundImage: NetworkImage(baseUrl + widget.consultationsModel.userProfilePhotoUrl),
|
||||
),
|
||||
CircleAvatar(radius: 30, backgroundImage: NetworkImage(baseUrl + widget.consultationsModel.userProfilePhotoUrl)),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.consultationsModel.userName,
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: const TextStyle(height: 1, color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.consultationsModel.planTitle,
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Color(0xFF88949F),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: const TextStyle(height: 1, color: Color(0xFF88949F), fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -127,12 +105,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
children: [
|
||||
Text(
|
||||
'День ${widget.consultationsModel.currentDiaryDay} из ${widget.consultationsModel.duration}',
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: const TextStyle(height: 1, color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
@@ -149,12 +122,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
children: [
|
||||
const Text(
|
||||
'Легенда',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF323F4B),
|
||||
),
|
||||
style: TextStyle(height: 1, fontSize: 20, fontWeight: FontWeight.w700, color: Color(0xFF323F4B)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListView.separated(
|
||||
@@ -170,11 +138,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.help_outline_rounded,
|
||||
color: Color(0xFF323F4B),
|
||||
size: 20,
|
||||
),
|
||||
child: const Icon(Icons.help_outline_rounded, color: Color(0xFF323F4B), size: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -185,7 +149,8 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
children: List.generate(widget.consultationsModel.duration, (index) {
|
||||
final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index);
|
||||
final hasActivity = day != null && day.$2.state != DiaryStateEnum.init;
|
||||
final canConsultantOpenThisDay = day != null &&
|
||||
final canConsultantOpenThisDay =
|
||||
day != null &&
|
||||
(day.$2.state != DiaryStateEnum.init && day.$2.state != DiaryStateEnum.started && day.$2.state != DiaryStateEnum.lastDayStarted);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
@@ -195,81 +160,74 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (!hasActivity || !canConsultantOpenThisDay) {
|
||||
showErrorDialog(context,
|
||||
title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
|
||||
showErrorDialog(
|
||||
context,
|
||||
title: s.you_can_not_open_this_day,
|
||||
description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).push(UserDiaryScreen.path, extra: {
|
||||
'diaryId': day.$2.id,
|
||||
'currentDiaryDay': index + 1,
|
||||
});
|
||||
GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': day.$2.id, 'currentDiaryDay': index + 1});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: hasActivity ? appTheme.dividerColor : appTheme.dividerColor.withValues(alpha: 0.4),
|
||||
borderRadius: BorderRadius.circular(100)),
|
||||
color: hasActivity ? appTheme.dividerColor : appTheme.dividerColor.withValues(alpha: 0.4),
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
width: 58,
|
||||
height: 32,
|
||||
child: Center(
|
||||
child: Text(
|
||||
(index + 1).toString(),
|
||||
style: TextStyles.textMSemibold.copyWith(
|
||||
color: hasActivity ? appTheme.gray : appTheme.gray.withValues(alpha: 0.4),
|
||||
),
|
||||
style: TextStyles.textMSemibold.copyWith(color: hasActivity ? appTheme.gray : appTheme.gray.withValues(alpha: 0.4)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hasActivity) ...[
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
switch (day.$2.state) {
|
||||
DiaryStateEnum.init => const SizedBox.shrink(),
|
||||
DiaryStateEnum.started => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]),
|
||||
),
|
||||
DiaryStateEnum.lastDayStarted => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[1]),
|
||||
),
|
||||
DiaryStateEnum.finished => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]),
|
||||
),
|
||||
DiaryStateEnum.lastDayFinished => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[2]),
|
||||
),
|
||||
DiaryStateEnum.commented => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]),
|
||||
),
|
||||
DiaryStateEnum.lastDayCommented => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[3]),
|
||||
),
|
||||
DiaryStateEnum.reviewedByUser => Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[4]),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: legendColors[4]),
|
||||
),
|
||||
},
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
] else
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -280,11 +238,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'${widget.consultationsModel.consultationOptionPrice.toStringAsFixed(0)} ₽',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF019A3E),
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: const TextStyle(color: Color(0xFF019A3E), fontSize: 20, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
@@ -293,36 +247,26 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: () {
|
||||
final lastActiveDay = givenDaysWithDayNumber
|
||||
.where(
|
||||
(e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished,
|
||||
)
|
||||
.where((e) => e.$2.state == DiaryStateEnum.finished || e.$2.state == DiaryStateEnum.lastDayFinished)
|
||||
.lastOrNull;
|
||||
if (lastActiveDay != null) {
|
||||
GoRouter.of(context).push(UserDiaryScreen.path, extra: {
|
||||
'diaryId': lastActiveDay.$2.id,
|
||||
'currentDiaryDay': lastActiveDay.$1,
|
||||
});
|
||||
GoRouter.of(context).push(UserDiaryScreen.path, extra: lastActiveDay.$2.id);
|
||||
GoRouter.of(context).push(UserDiaryScreen.path, extra: {'diaryId': lastActiveDay.$2.id, 'currentDiaryDay': lastActiveDay.$1});
|
||||
return;
|
||||
}
|
||||
showErrorDialog(context,
|
||||
title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
|
||||
showErrorDialog(
|
||||
context,
|
||||
title: s.you_can_not_open_this_day,
|
||||
description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}',
|
||||
);
|
||||
},
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32 - 52 - 8,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Комментировать отчёт',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -334,16 +278,8 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
child: Ink(
|
||||
height: 52,
|
||||
width: 52,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: const Color(0xFFF6F6F6),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.chat_outlined,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)),
|
||||
child: const Center(child: Icon(Icons.chat_outlined, size: 24)),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -361,21 +297,13 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color,
|
||||
),
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF323F4B),
|
||||
),
|
||||
style: const TextStyle(height: 1, fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -77,7 +77,8 @@ class ConsultationService {
|
||||
final response = await dio.get(
|
||||
'/api/finances/my_finances',
|
||||
queryParameters: {
|
||||
if (dateComparison != null && date != null) 'filters[end_date]': date,
|
||||
if (date != null && dateComparison != null) 'filters[start_date][${dateComparison.value}]': date,
|
||||
'sort[]': 'start_date:desc',
|
||||
},
|
||||
);
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
@@ -96,7 +97,8 @@ class ConsultationService {
|
||||
final response = await dio.get(
|
||||
'/api/finances/my_purchases',
|
||||
queryParameters: {
|
||||
if (dateComparison != null && date != null) 'filters[start_date]': date,
|
||||
if (date != null && dateComparison != null) 'filters[start_date][${dateComparison.value}]': date,
|
||||
'sort[]': 'start_date:desc',
|
||||
},
|
||||
);
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
|
||||
@@ -57,7 +57,7 @@ class RecipeRepository {
|
||||
'type': recipe.type,
|
||||
'state': 'active',
|
||||
'title': recipe.title,
|
||||
'tags': recipe.tags,
|
||||
'tags[]': recipe.tags ?? [],
|
||||
'description_md': recipe.description,
|
||||
'video_url': recipe.videoUrl,
|
||||
'ingredients': ingredientsMap,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/dev_tools/test_cases.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
@@ -97,10 +98,15 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(padding: const EdgeInsets.all(8.0), child: Row(children: [Text('BaseUrl: $baseUrlForTest')])),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(children: [Text('BaseUrl: $baseUrlForTest')]),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: CustomTextFormField(controller: controller, hintText: 'type new base url')),
|
||||
Expanded(
|
||||
child: CustomTextFormField(controller: controller, hintText: 'type new base url'),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
@@ -116,13 +122,13 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
CustomOutlinedButton(
|
||||
text: baseUrl == 'https://dev2.eatvio.com' ? 'Set production base url' : 'Set dev2 base url',
|
||||
text: baseUrl == EnvConfig.devBaseUrl ? 'Set production base url' : 'Set dev2 base url',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (baseUrl == 'https://eatvio.ru') {
|
||||
baseUrlForTest = 'https://dev2.eatvio.com';
|
||||
if (baseUrl == EnvConfig.baseUrl) {
|
||||
baseUrlForTest = EnvConfig.devBaseUrl;
|
||||
} else {
|
||||
baseUrlForTest = 'https://eatvio.ru';
|
||||
baseUrlForTest = EnvConfig.baseUrl;
|
||||
}
|
||||
baseUrl = baseUrlForTest.toLowerCase();
|
||||
sl<Dio>().options.baseUrl = baseUrl;
|
||||
@@ -132,7 +138,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
buttonStyle: const ButtonStyle(),
|
||||
),
|
||||
SizedBox(height: baseUrlForTest != baseUrl ? 40 : 10),
|
||||
if (baseUrl.toLowerCase() != 'https://eatvio.ru')
|
||||
if (baseUrl.toLowerCase() != EnvConfig.baseUrl.toLowerCase())
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: testCases.length,
|
||||
@@ -175,12 +181,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Test Failed'),
|
||||
content: SingleChildScrollView(child: Text(result.error ?? 'No error details.')),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('OK'))],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -197,12 +198,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Test Failed'),
|
||||
content: SingleChildScrollView(child: Text('$e\n$stack')),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('OK'))],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -216,10 +212,10 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
TestState.fail => Container(width: 20, height: 20, color: Colors.red),
|
||||
TestState.notRun => Container(width: 20, height: 20, color: Colors.grey),
|
||||
TestState.loading => const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())),
|
||||
),
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())),
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
|
||||
@@ -35,6 +35,7 @@ import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
||||
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/settings/services/settings_service.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
class TestResult {
|
||||
final bool success;
|
||||
@@ -477,7 +478,9 @@ class AuthServiceTest implements TestCases {
|
||||
final dio = sl<Dio>();
|
||||
final authService = AuthService(dio, baseUrl: baseUrl);
|
||||
// Use dummy credentials, expecting failure but no crash
|
||||
await authService.getTokenByCredentials(credentials: const CredentialsModel(email: 'reshetnick.mike+user1@gmail.com', password: '1234567890'));
|
||||
await authService.getTokenByCredentials(
|
||||
credentials: CredentialsModel(email: EnvConfig.testEmail, password: EnvConfig.testPassword),
|
||||
);
|
||||
return TestResult(false, 'Should not succeed');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for dummy credentials');
|
||||
|
||||
@@ -1,784 +0,0 @@
|
||||
// dart format width=80
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'consultant_review_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ConsultantReviewModel {
|
||||
String get planName;
|
||||
int get planDuration;
|
||||
int get currentPlanDay;
|
||||
ConsultantComments get consultantComments;
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ConsultantReviewModelCopyWith<ConsultantReviewModel> get copyWith =>
|
||||
_$ConsultantReviewModelCopyWithImpl<ConsultantReviewModel>(
|
||||
this as ConsultantReviewModel, _$identity);
|
||||
|
||||
/// Serializes this ConsultantReviewModel to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is ConsultantReviewModel &&
|
||||
(identical(other.planName, planName) ||
|
||||
other.planName == planName) &&
|
||||
(identical(other.planDuration, planDuration) ||
|
||||
other.planDuration == planDuration) &&
|
||||
(identical(other.currentPlanDay, currentPlanDay) ||
|
||||
other.currentPlanDay == currentPlanDay) &&
|
||||
(identical(other.consultantComments, consultantComments) ||
|
||||
other.consultantComments == consultantComments));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, planName, planDuration, currentPlanDay, consultantComments);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConsultantReviewModel(planName: $planName, planDuration: $planDuration, currentPlanDay: $currentPlanDay, consultantComments: $consultantComments)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ConsultantReviewModelCopyWith<$Res> {
|
||||
factory $ConsultantReviewModelCopyWith(ConsultantReviewModel value,
|
||||
$Res Function(ConsultantReviewModel) _then) =
|
||||
_$ConsultantReviewModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String planName,
|
||||
int planDuration,
|
||||
int currentPlanDay,
|
||||
ConsultantComments consultantComments});
|
||||
|
||||
$ConsultantCommentsCopyWith<$Res> get consultantComments;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ConsultantReviewModelCopyWithImpl<$Res>
|
||||
implements $ConsultantReviewModelCopyWith<$Res> {
|
||||
_$ConsultantReviewModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ConsultantReviewModel _self;
|
||||
final $Res Function(ConsultantReviewModel) _then;
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? planName = null,
|
||||
Object? planDuration = null,
|
||||
Object? currentPlanDay = null,
|
||||
Object? consultantComments = null,
|
||||
}) {
|
||||
return _then(_self.copyWith(
|
||||
planName: null == planName
|
||||
? _self.planName
|
||||
: planName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
planDuration: null == planDuration
|
||||
? _self.planDuration
|
||||
: planDuration // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
currentPlanDay: null == currentPlanDay
|
||||
? _self.currentPlanDay
|
||||
: currentPlanDay // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
consultantComments: null == consultantComments
|
||||
? _self.consultantComments
|
||||
: consultantComments // ignore: cast_nullable_to_non_nullable
|
||||
as ConsultantComments,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ConsultantCommentsCopyWith<$Res> get consultantComments {
|
||||
return $ConsultantCommentsCopyWith<$Res>(_self.consultantComments, (value) {
|
||||
return _then(_self.copyWith(consultantComments: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _ConsultantReviewModel implements ConsultantReviewModel {
|
||||
_ConsultantReviewModel(
|
||||
{required this.planName,
|
||||
required this.planDuration,
|
||||
required this.currentPlanDay,
|
||||
required this.consultantComments});
|
||||
factory _ConsultantReviewModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$ConsultantReviewModelFromJson(json);
|
||||
|
||||
@override
|
||||
final String planName;
|
||||
@override
|
||||
final int planDuration;
|
||||
@override
|
||||
final int currentPlanDay;
|
||||
@override
|
||||
final ConsultantComments consultantComments;
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ConsultantReviewModelCopyWith<_ConsultantReviewModel> get copyWith =>
|
||||
__$ConsultantReviewModelCopyWithImpl<_ConsultantReviewModel>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$ConsultantReviewModelToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _ConsultantReviewModel &&
|
||||
(identical(other.planName, planName) ||
|
||||
other.planName == planName) &&
|
||||
(identical(other.planDuration, planDuration) ||
|
||||
other.planDuration == planDuration) &&
|
||||
(identical(other.currentPlanDay, currentPlanDay) ||
|
||||
other.currentPlanDay == currentPlanDay) &&
|
||||
(identical(other.consultantComments, consultantComments) ||
|
||||
other.consultantComments == consultantComments));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, planName, planDuration, currentPlanDay, consultantComments);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConsultantReviewModel(planName: $planName, planDuration: $planDuration, currentPlanDay: $currentPlanDay, consultantComments: $consultantComments)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ConsultantReviewModelCopyWith<$Res>
|
||||
implements $ConsultantReviewModelCopyWith<$Res> {
|
||||
factory _$ConsultantReviewModelCopyWith(_ConsultantReviewModel value,
|
||||
$Res Function(_ConsultantReviewModel) _then) =
|
||||
__$ConsultantReviewModelCopyWithImpl;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String planName,
|
||||
int planDuration,
|
||||
int currentPlanDay,
|
||||
ConsultantComments consultantComments});
|
||||
|
||||
@override
|
||||
$ConsultantCommentsCopyWith<$Res> get consultantComments;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$ConsultantReviewModelCopyWithImpl<$Res>
|
||||
implements _$ConsultantReviewModelCopyWith<$Res> {
|
||||
__$ConsultantReviewModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ConsultantReviewModel _self;
|
||||
final $Res Function(_ConsultantReviewModel) _then;
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$Res call({
|
||||
Object? planName = null,
|
||||
Object? planDuration = null,
|
||||
Object? currentPlanDay = null,
|
||||
Object? consultantComments = null,
|
||||
}) {
|
||||
return _then(_ConsultantReviewModel(
|
||||
planName: null == planName
|
||||
? _self.planName
|
||||
: planName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
planDuration: null == planDuration
|
||||
? _self.planDuration
|
||||
: planDuration // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
currentPlanDay: null == currentPlanDay
|
||||
? _self.currentPlanDay
|
||||
: currentPlanDay // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
consultantComments: null == consultantComments
|
||||
? _self.consultantComments
|
||||
: consultantComments // ignore: cast_nullable_to_non_nullable
|
||||
as ConsultantComments,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ConsultantCommentsCopyWith<$Res> get consultantComments {
|
||||
return $ConsultantCommentsCopyWith<$Res>(_self.consultantComments, (value) {
|
||||
return _then(_self.copyWith(consultantComments: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ConsultantComments {
|
||||
String? get dayComment;
|
||||
List<MealComment> get mealsComments;
|
||||
List<PortionComment> get portionsComments;
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ConsultantCommentsCopyWith<ConsultantComments> get copyWith =>
|
||||
_$ConsultantCommentsCopyWithImpl<ConsultantComments>(
|
||||
this as ConsultantComments, _$identity);
|
||||
|
||||
/// Serializes this ConsultantComments to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is ConsultantComments &&
|
||||
(identical(other.dayComment, dayComment) ||
|
||||
other.dayComment == dayComment) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.mealsComments, mealsComments) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.portionsComments, portionsComments));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
dayComment,
|
||||
const DeepCollectionEquality().hash(mealsComments),
|
||||
const DeepCollectionEquality().hash(portionsComments));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConsultantComments(dayComment: $dayComment, mealsComments: $mealsComments, portionsComments: $portionsComments)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ConsultantCommentsCopyWith<$Res> {
|
||||
factory $ConsultantCommentsCopyWith(
|
||||
ConsultantComments value, $Res Function(ConsultantComments) _then) =
|
||||
_$ConsultantCommentsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? dayComment,
|
||||
List<MealComment> mealsComments,
|
||||
List<PortionComment> portionsComments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ConsultantCommentsCopyWithImpl<$Res>
|
||||
implements $ConsultantCommentsCopyWith<$Res> {
|
||||
_$ConsultantCommentsCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ConsultantComments _self;
|
||||
final $Res Function(ConsultantComments) _then;
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? dayComment = freezed,
|
||||
Object? mealsComments = null,
|
||||
Object? portionsComments = null,
|
||||
}) {
|
||||
return _then(_self.copyWith(
|
||||
dayComment: freezed == dayComment
|
||||
? _self.dayComment
|
||||
: dayComment // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
mealsComments: null == mealsComments
|
||||
? _self.mealsComments
|
||||
: mealsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<MealComment>,
|
||||
portionsComments: null == portionsComments
|
||||
? _self.portionsComments
|
||||
: portionsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<PortionComment>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _ConsultantComments implements ConsultantComments {
|
||||
_ConsultantComments(
|
||||
{required this.dayComment,
|
||||
required final List<MealComment> mealsComments,
|
||||
required final List<PortionComment> portionsComments})
|
||||
: _mealsComments = mealsComments,
|
||||
_portionsComments = portionsComments;
|
||||
factory _ConsultantComments.fromJson(Map<String, dynamic> json) =>
|
||||
_$ConsultantCommentsFromJson(json);
|
||||
|
||||
@override
|
||||
final String? dayComment;
|
||||
final List<MealComment> _mealsComments;
|
||||
@override
|
||||
List<MealComment> get mealsComments {
|
||||
if (_mealsComments is EqualUnmodifiableListView) return _mealsComments;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_mealsComments);
|
||||
}
|
||||
|
||||
final List<PortionComment> _portionsComments;
|
||||
@override
|
||||
List<PortionComment> get portionsComments {
|
||||
if (_portionsComments is EqualUnmodifiableListView)
|
||||
return _portionsComments;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_portionsComments);
|
||||
}
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ConsultantCommentsCopyWith<_ConsultantComments> get copyWith =>
|
||||
__$ConsultantCommentsCopyWithImpl<_ConsultantComments>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$ConsultantCommentsToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _ConsultantComments &&
|
||||
(identical(other.dayComment, dayComment) ||
|
||||
other.dayComment == dayComment) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._mealsComments, _mealsComments) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._portionsComments, _portionsComments));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
dayComment,
|
||||
const DeepCollectionEquality().hash(_mealsComments),
|
||||
const DeepCollectionEquality().hash(_portionsComments));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConsultantComments(dayComment: $dayComment, mealsComments: $mealsComments, portionsComments: $portionsComments)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ConsultantCommentsCopyWith<$Res>
|
||||
implements $ConsultantCommentsCopyWith<$Res> {
|
||||
factory _$ConsultantCommentsCopyWith(
|
||||
_ConsultantComments value, $Res Function(_ConsultantComments) _then) =
|
||||
__$ConsultantCommentsCopyWithImpl;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? dayComment,
|
||||
List<MealComment> mealsComments,
|
||||
List<PortionComment> portionsComments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$ConsultantCommentsCopyWithImpl<$Res>
|
||||
implements _$ConsultantCommentsCopyWith<$Res> {
|
||||
__$ConsultantCommentsCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ConsultantComments _self;
|
||||
final $Res Function(_ConsultantComments) _then;
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$Res call({
|
||||
Object? dayComment = freezed,
|
||||
Object? mealsComments = null,
|
||||
Object? portionsComments = null,
|
||||
}) {
|
||||
return _then(_ConsultantComments(
|
||||
dayComment: freezed == dayComment
|
||||
? _self.dayComment
|
||||
: dayComment // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
mealsComments: null == mealsComments
|
||||
? _self._mealsComments
|
||||
: mealsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<MealComment>,
|
||||
portionsComments: null == portionsComments
|
||||
? _self._portionsComments
|
||||
: portionsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<PortionComment>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$MealComment {
|
||||
String get comment;
|
||||
int get typeNo;
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$MealCommentCopyWith<MealComment> get copyWith =>
|
||||
_$MealCommentCopyWithImpl<MealComment>(this as MealComment, _$identity);
|
||||
|
||||
/// Serializes this MealComment to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is MealComment &&
|
||||
(identical(other.comment, comment) || other.comment == comment) &&
|
||||
(identical(other.typeNo, typeNo) || other.typeNo == typeNo));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, comment, typeNo);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MealComment(comment: $comment, typeNo: $typeNo)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $MealCommentCopyWith<$Res> {
|
||||
factory $MealCommentCopyWith(
|
||||
MealComment value, $Res Function(MealComment) _then) =
|
||||
_$MealCommentCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({String comment, int typeNo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$MealCommentCopyWithImpl<$Res> implements $MealCommentCopyWith<$Res> {
|
||||
_$MealCommentCopyWithImpl(this._self, this._then);
|
||||
|
||||
final MealComment _self;
|
||||
final $Res Function(MealComment) _then;
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? comment = null,
|
||||
Object? typeNo = null,
|
||||
}) {
|
||||
return _then(_self.copyWith(
|
||||
comment: null == comment
|
||||
? _self.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
typeNo: null == typeNo
|
||||
? _self.typeNo
|
||||
: typeNo // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _MealComment implements MealComment {
|
||||
_MealComment({required this.comment, required this.typeNo});
|
||||
factory _MealComment.fromJson(Map<String, dynamic> json) =>
|
||||
_$MealCommentFromJson(json);
|
||||
|
||||
@override
|
||||
final String comment;
|
||||
@override
|
||||
final int typeNo;
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$MealCommentCopyWith<_MealComment> get copyWith =>
|
||||
__$MealCommentCopyWithImpl<_MealComment>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$MealCommentToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _MealComment &&
|
||||
(identical(other.comment, comment) || other.comment == comment) &&
|
||||
(identical(other.typeNo, typeNo) || other.typeNo == typeNo));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, comment, typeNo);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MealComment(comment: $comment, typeNo: $typeNo)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$MealCommentCopyWith<$Res>
|
||||
implements $MealCommentCopyWith<$Res> {
|
||||
factory _$MealCommentCopyWith(
|
||||
_MealComment value, $Res Function(_MealComment) _then) =
|
||||
__$MealCommentCopyWithImpl;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String comment, int typeNo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$MealCommentCopyWithImpl<$Res> implements _$MealCommentCopyWith<$Res> {
|
||||
__$MealCommentCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _MealComment _self;
|
||||
final $Res Function(_MealComment) _then;
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$Res call({
|
||||
Object? comment = null,
|
||||
Object? typeNo = null,
|
||||
}) {
|
||||
return _then(_MealComment(
|
||||
comment: null == comment
|
||||
? _self.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
typeNo: null == typeNo
|
||||
? _self.typeNo
|
||||
: typeNo // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PortionComment {
|
||||
@JsonKey(name: 'id')
|
||||
int get portionId;
|
||||
String get comment;
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PortionCommentCopyWith<PortionComment> get copyWith =>
|
||||
_$PortionCommentCopyWithImpl<PortionComment>(
|
||||
this as PortionComment, _$identity);
|
||||
|
||||
/// Serializes this PortionComment to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is PortionComment &&
|
||||
(identical(other.portionId, portionId) ||
|
||||
other.portionId == portionId) &&
|
||||
(identical(other.comment, comment) || other.comment == comment));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, portionId, comment);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PortionComment(portionId: $portionId, comment: $comment)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PortionCommentCopyWith<$Res> {
|
||||
factory $PortionCommentCopyWith(
|
||||
PortionComment value, $Res Function(PortionComment) _then) =
|
||||
_$PortionCommentCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({@JsonKey(name: 'id') int portionId, String comment});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$PortionCommentCopyWithImpl<$Res>
|
||||
implements $PortionCommentCopyWith<$Res> {
|
||||
_$PortionCommentCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PortionComment _self;
|
||||
final $Res Function(PortionComment) _then;
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? portionId = null,
|
||||
Object? comment = null,
|
||||
}) {
|
||||
return _then(_self.copyWith(
|
||||
portionId: null == portionId
|
||||
? _self.portionId
|
||||
: portionId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
comment: null == comment
|
||||
? _self.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _PortionComment implements PortionComment {
|
||||
_PortionComment(
|
||||
{@JsonKey(name: 'id') required this.portionId, required this.comment});
|
||||
factory _PortionComment.fromJson(Map<String, dynamic> json) =>
|
||||
_$PortionCommentFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: 'id')
|
||||
final int portionId;
|
||||
@override
|
||||
final String comment;
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PortionCommentCopyWith<_PortionComment> get copyWith =>
|
||||
__$PortionCommentCopyWithImpl<_PortionComment>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$PortionCommentToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _PortionComment &&
|
||||
(identical(other.portionId, portionId) ||
|
||||
other.portionId == portionId) &&
|
||||
(identical(other.comment, comment) || other.comment == comment));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, portionId, comment);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PortionComment(portionId: $portionId, comment: $comment)';
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PortionCommentCopyWith<$Res>
|
||||
implements $PortionCommentCopyWith<$Res> {
|
||||
factory _$PortionCommentCopyWith(
|
||||
_PortionComment value, $Res Function(_PortionComment) _then) =
|
||||
__$PortionCommentCopyWithImpl;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({@JsonKey(name: 'id') int portionId, String comment});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$PortionCommentCopyWithImpl<$Res>
|
||||
implements _$PortionCommentCopyWith<$Res> {
|
||||
__$PortionCommentCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PortionComment _self;
|
||||
final $Res Function(_PortionComment) _then;
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$Res call({
|
||||
Object? portionId = null,
|
||||
Object? comment = null,
|
||||
}) {
|
||||
return _then(_PortionComment(
|
||||
portionId: null == portionId
|
||||
? _self.portionId
|
||||
: portionId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
comment: null == comment
|
||||
? _self.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,67 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'consultant_review_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_ConsultantReviewModel _$ConsultantReviewModelFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_ConsultantReviewModel(
|
||||
planName: json['planName'] as String,
|
||||
planDuration: (json['planDuration'] as num).toInt(),
|
||||
currentPlanDay: (json['currentPlanDay'] as num).toInt(),
|
||||
consultantComments: ConsultantComments.fromJson(
|
||||
json['consultantComments'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ConsultantReviewModelToJson(
|
||||
_ConsultantReviewModel instance) =>
|
||||
<String, dynamic>{
|
||||
'planName': instance.planName,
|
||||
'planDuration': instance.planDuration,
|
||||
'currentPlanDay': instance.currentPlanDay,
|
||||
'consultantComments': instance.consultantComments,
|
||||
};
|
||||
|
||||
_ConsultantComments _$ConsultantCommentsFromJson(Map<String, dynamic> json) =>
|
||||
_ConsultantComments(
|
||||
dayComment: json['dayComment'] as String?,
|
||||
mealsComments: (json['mealsComments'] as List<dynamic>)
|
||||
.map((e) => MealComment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
portionsComments: (json['portionsComments'] as List<dynamic>)
|
||||
.map((e) => PortionComment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ConsultantCommentsToJson(_ConsultantComments instance) =>
|
||||
<String, dynamic>{
|
||||
'dayComment': instance.dayComment,
|
||||
'mealsComments': instance.mealsComments,
|
||||
'portionsComments': instance.portionsComments,
|
||||
};
|
||||
|
||||
_MealComment _$MealCommentFromJson(Map<String, dynamic> json) => _MealComment(
|
||||
comment: json['comment'] as String,
|
||||
typeNo: (json['typeNo'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$MealCommentToJson(_MealComment instance) =>
|
||||
<String, dynamic>{
|
||||
'comment': instance.comment,
|
||||
'typeNo': instance.typeNo,
|
||||
};
|
||||
|
||||
_PortionComment _$PortionCommentFromJson(Map<String, dynamic> json) =>
|
||||
_PortionComment(
|
||||
portionId: (json['id'] as num).toInt(),
|
||||
comment: json['comment'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PortionCommentToJson(_PortionComment instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.portionId,
|
||||
'comment': instance.comment,
|
||||
};
|
||||
@@ -136,15 +136,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
final fifthMeal = MealModel(id: 0, time: '0', typeNumber: 5, satiety: 0, mood: 0, portions: fifthMealPortions ?? [], photos: []);
|
||||
emit(
|
||||
DiaryState.loadedTomorrowWithDiet(
|
||||
diary: diaryDay.copyWith(
|
||||
meals: [
|
||||
firstMeal,
|
||||
secondMeal,
|
||||
thirdMeal,
|
||||
fourthMeal,
|
||||
fifthMeal,
|
||||
],
|
||||
),
|
||||
diary: diaryDay.copyWith(meals: [firstMeal, secondMeal, thirdMeal, fourthMeal, fifthMeal]),
|
||||
selectedDay: selectedDay,
|
||||
selectedMeal: selectedMeal,
|
||||
),
|
||||
@@ -156,7 +148,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
|
||||
Future<void> fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async {
|
||||
this.diaryId ??= diaryId;
|
||||
|
||||
final isConsultantView = this.diaryId != null;
|
||||
if (needLoading) {
|
||||
emit(const DiaryState.loading());
|
||||
selectedMeal = 1;
|
||||
@@ -179,17 +171,22 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
int? currentPlanDay;
|
||||
String? planName;
|
||||
if (diaryDay.chosenPlanId != null) {
|
||||
consultantReview = await diaryRepository.getDiaryComments(planId: diaryDay.chosenPlanId!, diaryId: diaryDay.id);
|
||||
final chosenPlanModel = await chatRepository.getChosenPlan(id: diaryDay.chosenPlanId!);
|
||||
if (!isConsultantView &&
|
||||
(diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented)) {
|
||||
await diaryRepository.viewCommentOfDiaryDayByConsultant(diaryId: diaryDay.id);
|
||||
}
|
||||
consultantReview = await diaryRepository.getDiaryComments(planId: diaryDay.chosenPlanId!, diaryId: diaryDay.id);
|
||||
planDuration = chosenPlanModel.plan.duration;
|
||||
planName = chosenPlanModel.plan.name;
|
||||
currentPlanDay = currentDay ?? DateTime.now().difference(chosenPlanModel.startDate).inDays + 1;
|
||||
currentPlanDay = consultantReview.currentPlanDay;
|
||||
}
|
||||
final nutrition = await getNutrition(diaryDay.id);
|
||||
|
||||
final isDiaryFinished = diaryDay.typeOfDiary.diaryState == DiaryStateEnum.finished || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayFinished;
|
||||
|
||||
final isDiaryCommented = diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented ||
|
||||
final isDiaryCommented =
|
||||
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented ||
|
||||
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented ||
|
||||
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser;
|
||||
emit(
|
||||
@@ -198,7 +195,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
nutrition: nutrition,
|
||||
selectedDay: selectedDay,
|
||||
selectedMeal: selectedMeal,
|
||||
isConsultantView: this.diaryId != null,
|
||||
isConsultantView: isConsultantView,
|
||||
consultantReview: consultantReview,
|
||||
isDiaryFinished: isDiaryFinished,
|
||||
isDiaryCommented: isDiaryCommented,
|
||||
@@ -206,7 +203,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
chosenPlanDuration: planDuration,
|
||||
chosenPlanName: planName,
|
||||
isMoodEnabled: isMoodEnabled,
|
||||
isWakeUpAndRetireEnabled: isWakeUpAndRetireTimeEnabled
|
||||
isWakeUpAndRetireEnabled: isWakeUpAndRetireTimeEnabled,
|
||||
),
|
||||
);
|
||||
} catch (e, st) {
|
||||
@@ -215,17 +212,8 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> 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,
|
||||
);
|
||||
Future<void> 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);
|
||||
await fetchDiary();
|
||||
}
|
||||
|
||||
@@ -241,6 +229,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
required int quantity,
|
||||
int? productNutritionalValueId,
|
||||
}) async {
|
||||
emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true));
|
||||
await diaryRepository.addPortionToMeal(
|
||||
diaryId: diaryId,
|
||||
mealId: mealId,
|
||||
|
||||
@@ -29,6 +29,7 @@ abstract class DiaryState with _$DiaryState {
|
||||
@Default(false) bool isDiaryCommented,
|
||||
@Default(false) bool isWakeUpAndRetireEnabled,
|
||||
@Default(false) bool isMoodEnabled,
|
||||
@Default(false) bool isLoadingAddingProductInMeal,
|
||||
}) = DiaryStateLoaded;
|
||||
|
||||
const factory DiaryState.loadedTomorrowWithDiet({
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProductLoaderWidget extends StatefulWidget {
|
||||
const ProductLoaderWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProductLoaderWidget> createState() => _ProductLoaderWidgetState();
|
||||
}
|
||||
|
||||
class _ProductLoaderWidgetState extends State<ProductLoaderWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 0,
|
||||
contentPadding: const EdgeInsets.only(right: 24, left: 12),
|
||||
title: const CupertinoActivityIndicator(),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
tileColor: const Color(0xFFF6FAFF),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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/widgets/product_loader_widget.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/product_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -25,6 +26,11 @@ class _ProductsListWidgetState extends State<ProductsListWidget> {
|
||||
ProductWidget(portion: state.diary.meals[mealIndex].portions[i]),
|
||||
i < state.diary.meals[mealIndex].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
|
||||
],
|
||||
if (state.isLoadingAddingProductInMeal)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: ProductLoaderWidget(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,4 +297,12 @@ class DiaryRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> viewCommentOfDiaryDayByConsultant({required int diaryId}) async {
|
||||
try {
|
||||
await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,4 +150,9 @@ abstract class DiaryService {
|
||||
@Path() required int planId,
|
||||
@Path() required int diaryId,
|
||||
});
|
||||
|
||||
@POST('/api/diary/{diaryId}/consultant/comment/view')
|
||||
Future<void> viewCommentOfDiaryDayByConsultant({
|
||||
@Path() required int diaryId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/widgets/add_item_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_search_bar.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||
@@ -32,24 +33,12 @@ class FeedScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<RecipesCubit>(
|
||||
create: (context) => sl<RecipesCubit>()..loadRecipesList(),
|
||||
),
|
||||
BlocProvider<FeedProfileCubit>(
|
||||
create: (context) => sl<FeedProfileCubit>()..getProfile(),
|
||||
),
|
||||
BlocProvider<FeedCubit>(
|
||||
create: (context) => sl<FeedCubit>()..loadFeedBlogPostList(),
|
||||
),
|
||||
BlocProvider<DietsCubit>(
|
||||
create: (context) => sl<DietsCubit>()..loadDiets(),
|
||||
),
|
||||
BlocProvider<PlansCubit>(
|
||||
create: (context) => sl<PlansCubit>()..loadPlans(),
|
||||
),
|
||||
BlocProvider<ConsultantsCubit>(
|
||||
create: (context) => sl<ConsultantsCubit>()..getConsultants(),
|
||||
),
|
||||
BlocProvider<RecipesCubit>(create: (context) => sl<RecipesCubit>()..loadRecipesList()),
|
||||
BlocProvider<FeedProfileCubit>(create: (context) => sl<FeedProfileCubit>()..getProfile()),
|
||||
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
|
||||
BlocProvider<DietsCubit>(create: (context) => sl<DietsCubit>()..loadDiets()),
|
||||
if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()),
|
||||
BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()),
|
||||
],
|
||||
child: const FeedScreenContent(),
|
||||
);
|
||||
@@ -69,7 +58,8 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
final dietsController = TextEditingController();
|
||||
final plansController = TextEditingController();
|
||||
final consultantsController = TextEditingController();
|
||||
final List<String> chipsLabelList = ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
|
||||
|
||||
late final List<String> chipsLabelList;
|
||||
int _selectedChipIndex = 0;
|
||||
final blogPostsScrollController = ScrollController();
|
||||
final recipesScrollController = ScrollController();
|
||||
@@ -78,12 +68,13 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
final consultantsScrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
void initState() {
|
||||
// Initialize chipsLabelList based on hidePlans setting
|
||||
chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
|
||||
|
||||
consultantsScrollController.addListener(_onScrollConsultants);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void _onScrollConsultants() {
|
||||
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
||||
@@ -108,45 +99,36 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
heroTag: 'recipesTagsFilter',
|
||||
backgroundColor: appTheme.blueA400,
|
||||
shape: const CircleBorder(),
|
||||
child: const Icon(
|
||||
Icons.filter_list_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: const Icon(Icons.filter_list_rounded, color: Colors.white),
|
||||
onPressed: () async {
|
||||
final recipesCubit = context.read<RecipesCubit>();
|
||||
await showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (context) => TagsModalWidget(
|
||||
recipesCubit: recipesCubit,
|
||||
),
|
||||
builder: (context) => TagsModalWidget(recipesCubit: recipesCubit),
|
||||
);
|
||||
recipesCubit.loadRecipesList();
|
||||
},
|
||||
)
|
||||
: null,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
toolbarHeight: 0,
|
||||
),
|
||||
appBar: AppBar(backgroundColor: Colors.white, toolbarHeight: 0),
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: NestedScrollView(
|
||||
controller: switch (_selectedChipIndex) {
|
||||
0 => blogPostsScrollController,
|
||||
1 => recipesScrollController,
|
||||
2 => dietsScrollController,
|
||||
3 => plansScrollController,
|
||||
4 => consultantsScrollController,
|
||||
_ => blogPostsScrollController
|
||||
3 when !hidePlans => plansScrollController,
|
||||
3 when hidePlans => consultantsScrollController,
|
||||
4 when !hidePlans => consultantsScrollController,
|
||||
_ => blogPostsScrollController,
|
||||
},
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
BlocBuilder<FeedProfileCubit, ProfileState>(
|
||||
builder: (context, state) {
|
||||
return SliverAppBar(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
@@ -172,78 +154,71 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
searchController: switch (_selectedChipIndex) {
|
||||
1 => recipesController,
|
||||
2 => dietsController,
|
||||
3 => plansController,
|
||||
4 => consultantsController,
|
||||
3 when !hidePlans => plansController,
|
||||
3 when hidePlans => consultantsController,
|
||||
4 when !hidePlans => consultantsController,
|
||||
_ => postsController,
|
||||
},
|
||||
onChanged: (query) {
|
||||
if (query.length > 2) {
|
||||
EasyDebounce.debounce(
|
||||
'searchFeed',
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
switch (_selectedChipIndex) {
|
||||
case 0:
|
||||
context.read<FeedCubit>().loadFeedBlogPostList(query: query);
|
||||
break;
|
||||
case 1:
|
||||
context.read<RecipesCubit>().loadRecipesList(query: query);
|
||||
break;
|
||||
case 2:
|
||||
context.read<DietsCubit>().loadDiets(query: query);
|
||||
break;
|
||||
case 3:
|
||||
context.read<PlansCubit>().loadPlans(query: query);
|
||||
break;
|
||||
case 4:
|
||||
context.read<ConsultantsCubit>().getConsultants();
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
EasyDebounce.debounce('searchFeed', const Duration(milliseconds: 500), () {
|
||||
switch (_selectedChipIndex) {
|
||||
case 0:
|
||||
context.read<FeedCubit>().loadFeedBlogPostList(query: query);
|
||||
break;
|
||||
case 1:
|
||||
context.read<RecipesCubit>().loadRecipesList(query: query);
|
||||
break;
|
||||
case 2:
|
||||
context.read<DietsCubit>().loadDiets(query: query);
|
||||
break;
|
||||
case 3 when !hidePlans:
|
||||
context.read<PlansCubit>().loadPlans(query: query);
|
||||
break;
|
||||
case 3 when hidePlans:
|
||||
case 4 when !hidePlans:
|
||||
context.read<ConsultantsCubit>().getConsultants();
|
||||
break;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
EasyDebounce.debounce(
|
||||
'searchFeed',
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
switch (_selectedChipIndex) {
|
||||
case 0:
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
break;
|
||||
case 1:
|
||||
context.read<RecipesCubit>().loadRecipesList();
|
||||
break;
|
||||
case 2:
|
||||
context.read<DietsCubit>().loadDiets();
|
||||
break;
|
||||
case 3:
|
||||
context.read<PlansCubit>().loadPlans();
|
||||
break;
|
||||
case 4:
|
||||
context.read<ConsultantsCubit>().getConsultants();
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
EasyDebounce.debounce('searchFeed', const Duration(milliseconds: 500), () {
|
||||
switch (_selectedChipIndex) {
|
||||
case 0:
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
break;
|
||||
case 1:
|
||||
context.read<RecipesCubit>().loadRecipesList();
|
||||
break;
|
||||
case 2:
|
||||
context.read<DietsCubit>().loadDiets();
|
||||
break;
|
||||
case 3 when !hidePlans:
|
||||
context.read<PlansCubit>().loadPlans();
|
||||
break;
|
||||
case 3 when hidePlans:
|
||||
case 4 when !hidePlans:
|
||||
context.read<ConsultantsCubit>().getConsultants();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
switch (_selectedChipIndex) {
|
||||
1 => AddItemButton(
|
||||
onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name),
|
||||
),
|
||||
1 => AddItemButton(onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name)),
|
||||
2 => AddItemButton(
|
||||
onTap: state.profile?.roles.contains('consultant') ?? false
|
||||
? () async {
|
||||
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
3 => AddItemButton(
|
||||
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
|
||||
),
|
||||
_ => const AddItemButton()
|
||||
onTap: state.profile?.roles.contains('consultant') ?? false
|
||||
? () async {
|
||||
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
3 when !hidePlans => AddItemButton(
|
||||
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
|
||||
),
|
||||
_ => const AddItemButton(),
|
||||
},
|
||||
],
|
||||
),
|
||||
@@ -256,8 +231,9 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
0 => const BlogPosts(),
|
||||
1 => const Recipes(),
|
||||
2 => const Diets(),
|
||||
3 => const Plans(),
|
||||
4 => const Consultants(),
|
||||
3 when !hidePlans => const Plans(),
|
||||
3 when hidePlans => const Consultants(),
|
||||
4 when !hidePlans => const Consultants(),
|
||||
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
|
||||
},
|
||||
),
|
||||
|
||||
@@ -2,12 +2,7 @@ import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ChipGroup extends StatelessWidget {
|
||||
const ChipGroup({
|
||||
super.key,
|
||||
required this.selectedChipIndex,
|
||||
required this.chipsLabelList,
|
||||
required this.setSelectedChipIndex,
|
||||
});
|
||||
const ChipGroup({super.key, required this.selectedChipIndex, required this.chipsLabelList, required this.setSelectedChipIndex});
|
||||
final int selectedChipIndex;
|
||||
final List<String> chipsLabelList;
|
||||
final void Function(int) setSelectedChipIndex;
|
||||
@@ -22,7 +17,7 @@ class ChipGroup extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
...List.generate(
|
||||
5,
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: ChoiceChipWidget(
|
||||
|
||||
@@ -14,7 +14,7 @@ abstract class ConsultationsArchiveState with _$ConsultationsArchiveState {
|
||||
@Default([]) List<ConsultationModel> consultations,
|
||||
@Default(false) isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(PeriodFilterStatus.all) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(null) ConsultationFilterStatus? consultationFilterStatus,
|
||||
required StatisticModel statisticModel,
|
||||
}) = _ConsultationsArchiveState;
|
||||
|
||||
@@ -10,7 +10,7 @@ abstract class ConsultantFinancesState with _$ConsultantFinancesState {
|
||||
@Default([]) List<ConsultantFinanceModel> financesModels,
|
||||
@Default(false) isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(PeriodFilterStatus.all) PeriodFilterStatus? periodFilterStatus,
|
||||
}) = _ConsultantFinancesState;
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ abstract class UserPurchasesState with _$UserPurchasesState {
|
||||
@Default([]) List<UserPurchasesModel> purchasesModels,
|
||||
@Default(false) isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||
@Default(PeriodFilterStatus.all) PeriodFilterStatus? periodFilterStatus,
|
||||
}) = _UserPurchasesState;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
|
||||
if (value == null) return;
|
||||
context.read<ConsultantFinancesCubit>().setFinancesPeriod(value);
|
||||
},
|
||||
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
|
||||
value: state.periodFilterStatus ?? PeriodFilterStatus.all,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (state.financesModels.isNotEmpty)
|
||||
@@ -129,7 +129,7 @@ class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
|
||||
);
|
||||
}),
|
||||
Text(
|
||||
state.financesModels[index].startDate.format('yyyy.MM.dd'),
|
||||
state.financesModels[index].startDate.format('dd.MM.yy'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF6D6F71),
|
||||
height: 1,
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
class InternetConnectionCheckerScreen extends StatefulWidget {
|
||||
const InternetConnectionCheckerScreen({super.key, required this.child});
|
||||
@@ -66,12 +67,7 @@ class _InternetConnectionCheckerScreenState extends State<InternetConnectionChec
|
||||
return false;
|
||||
}
|
||||
|
||||
final uris = [
|
||||
Uri.parse('https://google.com'),
|
||||
Uri.parse('https://yandex.ru'),
|
||||
Uri.parse('https://dev2.eatvio.com'),
|
||||
Uri.parse('https://eatvio.ru'),
|
||||
];
|
||||
final uris = [Uri.parse(EnvConfig.googleCheckUrl), Uri.parse(EnvConfig.yandexCheckUrl), Uri.parse(EnvConfig.devBaseUrl), Uri.parse(EnvConfig.baseUrl)];
|
||||
for (final uri in uris) {
|
||||
try {
|
||||
final response = await http.get(uri).timeout(const Duration(seconds: 3));
|
||||
@@ -98,13 +94,9 @@ class _InternetConnectionCheckerScreenState extends State<InternetConnectionChec
|
||||
if (!_hasInternet)
|
||||
NoInternetScreen(
|
||||
onTryAgain: () async {
|
||||
EasyDebounce.debounce(
|
||||
'internet connection check',
|
||||
const Duration(seconds: 2),
|
||||
() async {
|
||||
await _checkConnection();
|
||||
},
|
||||
);
|
||||
EasyDebounce.debounce('internet connection check', const Duration(seconds: 2), () async {
|
||||
await _checkConnection();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -129,9 +121,7 @@ class NoInternetScreen extends StatelessWidget {
|
||||
child: Text(
|
||||
'Отсутствует подключение \nк интернету',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.headingXLSemibold.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -140,9 +130,7 @@ class NoInternetScreen extends StatelessWidget {
|
||||
child: Text(
|
||||
'Проверьте соединение с сетью\nи попробуйте ещё раз',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(
|
||||
color: appTheme.gray,
|
||||
),
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
@@ -153,10 +141,7 @@ class NoInternetScreen extends StatelessWidget {
|
||||
height: 52,
|
||||
text: 'Попробовать ещё раз',
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
buttonStyle: ButtonStyle(backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), side: const WidgetStatePropertyAll(BorderSide.none)),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
@@ -14,6 +15,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class MenuScreen extends StatelessWidget {
|
||||
final ProfileModel profileModel;
|
||||
@@ -37,18 +39,20 @@ class MenuScreen extends StatelessWidget {
|
||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||
if (profileModel.roles.contains('consultant'))
|
||||
_tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)),
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: 'assets/icons/finance.svg',
|
||||
title: 'Мои финансы',
|
||||
onTap: () {
|
||||
if (profileModel.roles.contains('consultant')) {
|
||||
GoRouter.of(context).push(ConsultantFinancesScreen.path);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).push(UserPurchasesScreen.path);
|
||||
},
|
||||
),
|
||||
if (!hidePlans) ...[
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: 'assets/icons/finance.svg',
|
||||
title: 'Мои финансы',
|
||||
onTap: () {
|
||||
if (profileModel.roles.contains('consultant')) {
|
||||
GoRouter.of(context).push(ConsultantFinancesScreen.path);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).push(UserPurchasesScreen.path);
|
||||
},
|
||||
),
|
||||
],
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
|
||||
_divider(context),
|
||||
@@ -65,6 +69,8 @@ class MenuScreen extends StatelessWidget {
|
||||
// ),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
||||
const SizedBox(height: 10),
|
||||
const AppInfo(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -122,3 +128,36 @@ class MenuScreen extends StatelessWidget {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class AppInfo extends StatefulWidget {
|
||||
const AppInfo({super.key});
|
||||
|
||||
@override
|
||||
State<AppInfo> createState() => _AppInfoState();
|
||||
}
|
||||
|
||||
class _AppInfoState extends State<AppInfo> {
|
||||
String version = '';
|
||||
String buildNumber = '';
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
|
||||
setState(() {
|
||||
version = packageInfo.version;
|
||||
buildNumber = packageInfo.buildNumber;
|
||||
});
|
||||
});
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 16),
|
||||
child: Text('Версия приложения: $version ($buildNumber)', style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
|
||||
sealed class BaseNotificationModel {
|
||||
abstract final NotificationType type;
|
||||
abstract final bool isRead;
|
||||
abstract final DateTime createdAt;
|
||||
}
|
||||
|
||||
class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationModel {
|
||||
@@ -13,6 +12,8 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
|
||||
final String planName;
|
||||
final String customerName;
|
||||
final String chatId;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
PlanSubscriptionStartedToAuthorNotification({
|
||||
required this.type,
|
||||
@@ -20,15 +21,17 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
|
||||
required this.customerName,
|
||||
required this.chatId,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return PlanSubscriptionStartedToAuthorNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
customerName: json['user_name'],
|
||||
chatId: json['chat_id'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,20 +43,24 @@ class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationMo
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
final String customerName;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
PlanSubscriptionCanceledToAuthorNotification({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.customerName,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return PlanSubscriptionCanceledToAuthorNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
customerName: json['user_name'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -65,20 +72,18 @@ class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
final int planId;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ChosenPlanStartedToClientNotification({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.planId,
|
||||
required this.isRead,
|
||||
});
|
||||
ChosenPlanStartedToClientNotification({required this.type, required this.planName, required this.planId, required this.isRead, required this.createdAt});
|
||||
|
||||
factory ChosenPlanStartedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory ChosenPlanStartedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanStartedToClientNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
planId: json['plan_id'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -89,15 +94,18 @@ class ChosenPlanTomorrowReminderToClientNotification implements BaseNotification
|
||||
@override
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ChosenPlanTomorrowReminderToClientNotification({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.isRead,
|
||||
});
|
||||
ChosenPlanTomorrowReminderToClientNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
|
||||
|
||||
factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
return ChosenPlanTomorrowReminderToClientNotification(type: type, planName: json['plan_name'], isRead: isRead ?? false);
|
||||
factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanTomorrowReminderToClientNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,12 +114,17 @@ class DiaryReminderNotification implements BaseNotificationModel {
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
|
||||
|
||||
DiaryReminderNotification({required this.type, required this.isRead});
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
return DiaryReminderNotification(type: type, isRead: isRead ?? false);
|
||||
DiaryReminderNotification({required this.type, required this.isRead, required this.createdAt});
|
||||
|
||||
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return DiaryReminderNotification(
|
||||
type: type,
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,18 +134,17 @@ class ConsultantLeftCommentNotification implements BaseNotificationModel {
|
||||
@override
|
||||
final bool isRead;
|
||||
final DateTime date;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ConsultantLeftCommentNotification({
|
||||
required this.type,
|
||||
required this.date,
|
||||
required this.isRead,
|
||||
});
|
||||
ConsultantLeftCommentNotification({required this.type, required this.date, required this.isRead, required this.createdAt});
|
||||
|
||||
factory ConsultantLeftCommentNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory ConsultantLeftCommentNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ConsultantLeftCommentNotification(
|
||||
type: type,
|
||||
date: parseDate(json['diary_date']),
|
||||
date: parseDate(json['diary_date'] ?? DateTime.now().toIso8601String()),
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -142,16 +154,16 @@ class PersonalizedDietIsDefinedNotification implements BaseNotificationModel {
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
PersonalizedDietIsDefinedNotification({
|
||||
required this.type,
|
||||
required this.isRead,
|
||||
});
|
||||
PersonalizedDietIsDefinedNotification({required this.type, required this.isRead, required this.createdAt});
|
||||
|
||||
factory PersonalizedDietIsDefinedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory PersonalizedDietIsDefinedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return PersonalizedDietIsDefinedNotification(
|
||||
type: type,
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -162,17 +174,16 @@ class ChosenPlanFinishedToClientNotification implements BaseNotificationModel {
|
||||
@override
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
ChosenPlanFinishedToClientNotification({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.isRead,
|
||||
});
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
ChosenPlanFinishedToClientNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
|
||||
|
||||
factory ChosenPlanFinishedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory ChosenPlanFinishedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanFinishedToClientNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -183,17 +194,16 @@ class PlanSubscriptionCanceledToCustomerNotification implements BaseNotification
|
||||
@override
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
PlanSubscriptionCanceledToCustomerNotification({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.isRead,
|
||||
});
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
PlanSubscriptionCanceledToCustomerNotification({required this.type, required this.planName, required this.isRead, required this.createdAt});
|
||||
|
||||
factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return PlanSubscriptionCanceledToCustomerNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -205,19 +215,23 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
final String customerName;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
ChosenPlanStartedToConsultantNotification({
|
||||
required this.type,
|
||||
required this.isRead,
|
||||
required this.planName,
|
||||
required this.customerName,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ChosenPlanStartedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory ChosenPlanStartedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanStartedToConsultantNotification(
|
||||
type: type,
|
||||
isRead: isRead ?? false,
|
||||
planName: json['plan_name'],
|
||||
customerName: json['client_name'],
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -229,19 +243,23 @@ class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotifica
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
final String customerName;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
ChosenPlanTomorrowReminderToConsultantNotification({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.customerName,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanTomorrowReminderToConsultantNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
customerName: json['client_name'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -253,19 +271,23 @@ class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
final String customerName;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
PlanSubscriptionPaidToAuthorNotification({
|
||||
required this.type,
|
||||
required this.isRead,
|
||||
required this.planName,
|
||||
required this.customerName,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory PlanSubscriptionPaidToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory PlanSubscriptionPaidToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return PlanSubscriptionPaidToAuthorNotification(
|
||||
type: type,
|
||||
isRead: isRead ?? false,
|
||||
planName: json['plan_name'],
|
||||
customerName: json['user_name'],
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -278,21 +300,240 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
|
||||
final String planName;
|
||||
final String customerName;
|
||||
final int planId;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
ChosenPlanFinishedToConsultantNotification({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.customerName,
|
||||
required this.planId,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ChosenPlanFinishedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
factory ChosenPlanFinishedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanFinishedToConsultantNotification(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
customerName: json['client_name'],
|
||||
planId: json['plan_id'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChosenPlanLastDayFinishedForUser implements BaseNotificationModel {
|
||||
@override
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ChosenPlanLastDayFinishedForUser({required this.type, required this.isRead, required this.createdAt});
|
||||
|
||||
factory ChosenPlanLastDayFinishedForUser.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanLastDayFinishedForUser(
|
||||
type: type,
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChosenPlanConsultantLastDayStarted implements BaseNotificationModel {
|
||||
@override
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
final String planName;
|
||||
final int planId;
|
||||
final String chatId;
|
||||
final String chatUrl;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ChosenPlanConsultantLastDayStarted({
|
||||
required this.type,
|
||||
required this.planName,
|
||||
required this.planId,
|
||||
required this.chatId,
|
||||
required this.chatUrl,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ChosenPlanConsultantLastDayStarted.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ChosenPlanConsultantLastDayStarted(
|
||||
type: type,
|
||||
planName: json['plan_name'],
|
||||
planId: json['plan_id'],
|
||||
chatId: json['chat_id'],
|
||||
chatUrl: json['chat_url'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ComplaintToModerationForUser implements BaseNotificationModel {
|
||||
@override
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
final String message;
|
||||
final int entityId;
|
||||
final String entityType;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ComplaintToModerationForUser({
|
||||
required this.type,
|
||||
required this.message,
|
||||
required this.entityId,
|
||||
required this.entityType,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ComplaintToModerationForUser.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ComplaintToModerationForUser(
|
||||
type: type,
|
||||
message: json['message'],
|
||||
entityId: json['entity_id'],
|
||||
entityType: json['entity_type'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CommentReplied implements BaseNotificationModel {
|
||||
@override
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
final String entityName;
|
||||
final int commentedEntityId;
|
||||
final String commentedEntityType;
|
||||
final String author;
|
||||
final int authorId;
|
||||
final int parentCommentId;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
CommentReplied({
|
||||
required this.type,
|
||||
required this.entityName,
|
||||
required this.commentedEntityId,
|
||||
required this.commentedEntityType,
|
||||
required this.author,
|
||||
required this.authorId,
|
||||
required this.parentCommentId,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory CommentReplied.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return CommentReplied(
|
||||
type: type,
|
||||
entityName: json['entity_name'],
|
||||
commentedEntityId: json['commented_entity_id'],
|
||||
commentedEntityType: json['commented_entity_type'],
|
||||
author: json['author'],
|
||||
authorId: json['author_id'],
|
||||
parentCommentId: json['parrent_comment_id'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScoreAdded implements BaseNotificationModel {
|
||||
@override
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
final String scoreKey;
|
||||
final int scorableId;
|
||||
final String scorableType;
|
||||
final String scorableName;
|
||||
final String scorableSlug;
|
||||
final int scoreValue;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ScoreAdded({
|
||||
required this.type,
|
||||
required this.scoreKey,
|
||||
required this.scorableId,
|
||||
required this.scorableType,
|
||||
required this.scorableName,
|
||||
required this.scorableSlug,
|
||||
required this.scoreValue,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ScoreAdded.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return ScoreAdded(
|
||||
type: type,
|
||||
scoreKey: json['score_key'],
|
||||
scorableId: json['scorable_id'],
|
||||
scorableType: json['scorable_type'],
|
||||
scorableName: json['scorable_name'] ?? '',
|
||||
scorableSlug: json['scorable_slug'] ?? '',
|
||||
scoreValue: json['score_value'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EntityCommented implements BaseNotificationModel {
|
||||
@override
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
final String entityName;
|
||||
final int entityId;
|
||||
final String entityType;
|
||||
final String entityTranslatedType;
|
||||
final String entityUrl;
|
||||
final String authorName;
|
||||
final int authorId;
|
||||
final String authorSlug;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
EntityCommented({
|
||||
required this.type,
|
||||
required this.entityName,
|
||||
required this.entityId,
|
||||
required this.entityType,
|
||||
required this.entityTranslatedType,
|
||||
required this.entityUrl,
|
||||
required this.authorName,
|
||||
required this.authorId,
|
||||
required this.authorSlug,
|
||||
required this.isRead,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory EntityCommented.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead, String? createdAt}) {
|
||||
return EntityCommented(
|
||||
type: type,
|
||||
entityName: json['entity_name'],
|
||||
entityId: json['entity_id'],
|
||||
entityType: json['entity_type'],
|
||||
entityTranslatedType: json['entity_traslated_type'],
|
||||
entityUrl: json['entity_url'],
|
||||
authorName: json['author_name'],
|
||||
authorId: json['author_id'],
|
||||
authorSlug: json['author_slug'],
|
||||
isRead: isRead ?? false,
|
||||
createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -302,11 +543,10 @@ class UnknownNotification implements BaseNotificationModel {
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
UnknownNotification({
|
||||
required this.type,
|
||||
this.isRead = true,
|
||||
});
|
||||
UnknownNotification({required this.type, this.isRead = true, DateTime? createdAt}) : createdAt = createdAt ?? DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
|
||||
enum NotificationType {
|
||||
@@ -323,22 +563,38 @@ enum NotificationType {
|
||||
chosenPlanTomorrowReminderToConsultantNotification,
|
||||
planSubscriptionPaidToAuthor,
|
||||
chosenPlanFinishedToConsultant,
|
||||
chosenPlanLastDayFinishedForUser,
|
||||
// Новые типы
|
||||
chosenPlanConsultantLastDayStarted,
|
||||
complaintToModerationForUser,
|
||||
commentReplied,
|
||||
scoreAdded,
|
||||
entityCommented,
|
||||
unknown;
|
||||
|
||||
static NotificationType getTypeFromString(String type) => switch (type) {
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor,
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor' => NotificationType.planSubscriptionCanceledToAuthor,
|
||||
'App\\Notifications\\Plan\\ChosenPlanStartedToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
|
||||
'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
|
||||
'App\\Notifications\\DiaryReminderNotification' => NotificationType.diaryReminderNotification,
|
||||
'App\\Notifications\\Plan\\ConsultantLeftComment' => NotificationType.consultantLeftComment,
|
||||
'App\\Notifications\\Plan\\PersonalizedDietIsDefined' => NotificationType.personalizedDietIsDefined,
|
||||
'App\\Notifications\\Plan\\ChosenPlanFinishedToClient' => NotificationType.chosenPlanFinishedToClient,
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionCanceledToCustomer' => NotificationType.planSubscriptionCanceledToCustomer,
|
||||
'App\\Notifications\\Plan\\ChosenPlanStartedToConsultantNotification' => NotificationType.chosenPlanStartedToConsultantNotification,
|
||||
'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToConsultantNotification' => NotificationType.chosenPlanTomorrowReminderToConsultantNotification,
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionPaidToAuthor' => NotificationType.planSubscriptionPaidToAuthor,
|
||||
'App\\Notifications\\Plan\\ChosenPlanFinishedToConsultant' => NotificationType.chosenPlanFinishedToConsultant,
|
||||
_ => NotificationType.unknown,
|
||||
};
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor,
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor' => NotificationType.planSubscriptionCanceledToAuthor,
|
||||
'App\\Notifications\\Plan\\ChosenPlanStartedToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
|
||||
'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToClientNotification' => NotificationType.chosenPlanStartedToClientNotification,
|
||||
'App\\Notifications\\DiaryReminderNotification' => NotificationType.diaryReminderNotification,
|
||||
'App\\Notifications\\Plan\\ConsultantLeftComment' => NotificationType.consultantLeftComment,
|
||||
'App\\Notifications\\Plan\\PersonalizedDietIsDefined' => NotificationType.personalizedDietIsDefined,
|
||||
'App\\Notifications\\Plan\\ChosenPlanFinishedToClient' => NotificationType.chosenPlanFinishedToClient,
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionCanceledToCustomer' => NotificationType.planSubscriptionCanceledToCustomer,
|
||||
'App\\Notifications\\Plan\\ChosenPlanStartedToConsultantNotification' => NotificationType.chosenPlanStartedToConsultantNotification,
|
||||
'App\\Notifications\\Plan\\ChosenPlanTomorrowReminderToConsultantNotification' => NotificationType.chosenPlanTomorrowReminderToConsultantNotification,
|
||||
'App\\Notifications\\Plan\\PlanSubscriptionPaidToAuthor' => NotificationType.planSubscriptionPaidToAuthor,
|
||||
'App\\Notifications\\Plan\\ChosenPlanFinishedToConsultant' => NotificationType.chosenPlanFinishedToConsultant,
|
||||
'App\\Notifications\\ChosenPlan\\LastDayFinishedForUser' => NotificationType.chosenPlanLastDayFinishedForUser,
|
||||
// Новые маппинги
|
||||
'App\\Notifications\\ChosenPlan\\LastDayStartedForConsultant' => NotificationType.chosenPlanConsultantLastDayStarted,
|
||||
'App\\Notifications\\ComplaintToModerationNotify' => NotificationType.complaintToModerationForUser,
|
||||
'App\\Notifications\\Comment\\Replied' => NotificationType.commentReplied,
|
||||
'App\\Notifications\\Score\\ScoreAdded' => NotificationType.scoreAdded,
|
||||
'App\\Notifications\\Comment\\EntityCommented' => NotificationType.entityCommented,
|
||||
_ => NotificationType.unknown,
|
||||
};
|
||||
}
|
||||
|
||||
DateTime parseDate(String date) => DateTime.parse(date).toLocal();
|
||||
|
||||
@@ -13,10 +13,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
||||
final WsManager wsManager;
|
||||
final NotificationsRepository notificationsRepository;
|
||||
StreamSubscription? _newNotificationsSubscription;
|
||||
NotificationsCubit({
|
||||
required this.wsManager,
|
||||
required this.notificationsRepository,
|
||||
}) : super(const NotificationsState.empty());
|
||||
NotificationsCubit({required this.wsManager, required this.notificationsRepository}) : super(const NotificationsState.empty());
|
||||
|
||||
Future<void> connectToWebSocket() async {
|
||||
await _newNotificationsSubscription?.cancel();
|
||||
@@ -27,21 +24,95 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
||||
|
||||
BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) {
|
||||
try {
|
||||
final createdAt = notificationJson['created_at'];
|
||||
final isRead = notificationJson['read_at'] != null;
|
||||
|
||||
return switch (type) {
|
||||
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson, type),
|
||||
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
|
||||
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(notificationJson, type),
|
||||
NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson, type),
|
||||
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type),
|
||||
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type),
|
||||
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(notificationJson, type),
|
||||
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(notificationJson, type),
|
||||
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
|
||||
NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(notificationJson, type),
|
||||
NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
|
||||
ChosenPlanTomorrowReminderToConsultantNotification.fromJson(notificationJson, type),
|
||||
NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(notificationJson, type),
|
||||
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(notificationJson, type),
|
||||
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanConsultantLastDayStarted => ChosenPlanConsultantLastDayStarted.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanTomorrowReminderToConsultantNotification => ChosenPlanTomorrowReminderToConsultantNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson(
|
||||
notificationJson,
|
||||
type,
|
||||
isRead: isRead,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
// New notification types
|
||||
NotificationType.complaintToModerationForUser => ComplaintToModerationForUser.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||
NotificationType.unknown => UnknownNotification(type: type),
|
||||
};
|
||||
} catch (e) {
|
||||
@@ -55,32 +126,80 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
||||
final type = NotificationType.getTypeFromString(stringifiedType);
|
||||
final notification = _parseNotification(json, type);
|
||||
_notifications.insert(0, notification);
|
||||
emit(
|
||||
NotificationsState.loaded(
|
||||
notifications: _notifications,
|
||||
notificationsCount: _notifications.length,
|
||||
hasUnreadNotifications: true,
|
||||
),
|
||||
);
|
||||
|
||||
final currentState = state;
|
||||
if (currentState is NotificationsStateLoaded) {
|
||||
emit(currentState.copyWith(notifications: _notifications, notificationsCount: _notifications.length, hasUnreadNotifications: true));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getNotifications() async {
|
||||
try {
|
||||
final notifications = await notificationsRepository.getNotifications(page: 2);
|
||||
_notifications.clear();
|
||||
_notifications.addAll(notifications ?? []);
|
||||
emit(
|
||||
NotificationsState.loaded(
|
||||
notifications: _notifications,
|
||||
notificationsCount: _notifications.length,
|
||||
hasUnreadNotifications: (notifications ?? []).where((elem) => elem.isRead == false).isNotEmpty,
|
||||
),
|
||||
);
|
||||
final result = await notificationsRepository.getNotifications(page: 1);
|
||||
if (result != null) {
|
||||
final notifications = result['notifications'] as List<BaseNotificationModel>;
|
||||
final pagination = result['pagination'] as Map<String, dynamic>;
|
||||
|
||||
_notifications.clear();
|
||||
_notifications.addAll(notifications);
|
||||
|
||||
final currentPage = pagination['current_page'] as int;
|
||||
final lastPage = pagination['last_page'] as int;
|
||||
final hasNextPage = currentPage < lastPage;
|
||||
|
||||
emit(
|
||||
NotificationsState.loaded(
|
||||
notifications: _notifications,
|
||||
notificationsCount: _notifications.length,
|
||||
hasUnreadNotifications: notifications.where((elem) => elem.isRead == false).isNotEmpty,
|
||||
currentPage: currentPage,
|
||||
hasNextPage: hasNextPage,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadMoreNotifications() async {
|
||||
final currentState = state;
|
||||
if (currentState is NotificationsStateLoaded && currentState.hasNextPage && !currentState.isLoadingMore) {
|
||||
try {
|
||||
emit(currentState.copyWith(isLoadingMore: true));
|
||||
|
||||
final nextPage = currentState.currentPage + 1;
|
||||
final result = await notificationsRepository.getNotifications(page: nextPage);
|
||||
|
||||
if (result != null) {
|
||||
final newNotifications = result['notifications'] as List<BaseNotificationModel>;
|
||||
final pagination = result['pagination'] as Map<String, dynamic>;
|
||||
|
||||
_notifications.addAll(newNotifications);
|
||||
|
||||
final currentPage = pagination['current_page'] as int;
|
||||
final lastPage = pagination['last_page'] as int;
|
||||
final hasNextPage = currentPage < lastPage;
|
||||
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
notifications: _notifications,
|
||||
notificationsCount: _notifications.length,
|
||||
currentPage: currentPage,
|
||||
hasNextPage: hasNextPage,
|
||||
isLoadingMore: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(currentState.copyWith(isLoadingMore: false));
|
||||
}
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
emit(currentState.copyWith(isLoadingMore: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markNotificationsAsRead() async {
|
||||
final currentState = state;
|
||||
if (currentState is NotificationsStateLoaded) {
|
||||
|
||||
@@ -14,6 +14,9 @@ abstract class NotificationsState with _$NotificationsState {
|
||||
required int notificationsCount,
|
||||
@Default(false) bool isLoading,
|
||||
@Default(false) bool hasUnreadNotifications,
|
||||
@Default(1) int currentPage,
|
||||
@Default(true) bool hasNextPage,
|
||||
@Default(false) bool isLoadingMore,
|
||||
}) = NotificationsStateLoaded;
|
||||
|
||||
const factory NotificationsState.error({
|
||||
|
||||
@@ -10,9 +10,38 @@ import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NotificationsScreen extends StatelessWidget {
|
||||
class NotificationsScreen extends StatefulWidget {
|
||||
const NotificationsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<NotificationsScreen> createState() => _NotificationsScreenState();
|
||||
}
|
||||
|
||||
class _NotificationsScreenState extends State<NotificationsScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) {
|
||||
final cubit = context.read<NotificationsCubit>();
|
||||
final state = cubit.state;
|
||||
if (state is NotificationsStateLoaded && state.hasNextPage && !state.isLoadingMore) {
|
||||
cubit.loadMoreNotifications();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
@@ -20,62 +49,65 @@ class NotificationsScreen extends StatelessWidget {
|
||||
builder: (BuildContext context, NotificationsState state) {
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
title: const Text('Уведомления'),
|
||||
),
|
||||
appBar: AppBar(title: const Text('Уведомления')),
|
||||
body: state is NotificationsStateLoaded
|
||||
? Stack(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.whiteA700,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
? Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.whiteA700,
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
|
||||
),
|
||||
child: state.isLoading
|
||||
? const Center(child: Loader())
|
||||
: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
SliverList.separated(
|
||||
separatorBuilder: (context, index) =>
|
||||
const Padding(padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: CustomDivider()),
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: NotificationWidget(notification: state.notifications[index]),
|
||||
);
|
||||
},
|
||||
itemCount: state.notifications.length,
|
||||
),
|
||||
if (state.isLoadingMore)
|
||||
const SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(child: Loader()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
|
||||
child: CustomOutlinedButton(
|
||||
text: s.markAsRead,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () async {
|
||||
context.read<NotificationsCubit>().markNotificationsAsRead();
|
||||
},
|
||||
),
|
||||
),
|
||||
child: state.isLoading
|
||||
? const Center(child: Loader())
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
SliverList.separated(
|
||||
separatorBuilder: (context, index) => const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: CustomDivider(),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: NotificationWidget(
|
||||
notification: state.notifications[index],
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: state.notifications.length,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
|
||||
child: CustomOutlinedButton(
|
||||
text: s.markAsRead,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle:
|
||||
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
context.read<NotificationsCubit>().markNotificationsAsRead();
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
])
|
||||
],
|
||||
)
|
||||
: const Placeholder(),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -15,45 +15,39 @@ class NotificationWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return switch (notification) {
|
||||
PlanSubscriptionStartedToAuthorNotification() => _PlanSubscriptionStartedToAuthorWidget(
|
||||
notification: notification as PlanSubscriptionStartedToAuthorNotification,
|
||||
),
|
||||
notification: notification as PlanSubscriptionStartedToAuthorNotification,
|
||||
),
|
||||
PlanSubscriptionCanceledToAuthorNotification() => _PlanSubscriptionCanceledToAuthorWidget(
|
||||
notification: notification as PlanSubscriptionCanceledToAuthorNotification,
|
||||
),
|
||||
ChosenPlanStartedToClientNotification() => _ChosenPlanStartedToClientWidget(
|
||||
notification: notification as ChosenPlanStartedToClientNotification,
|
||||
),
|
||||
notification: notification as PlanSubscriptionCanceledToAuthorNotification,
|
||||
),
|
||||
ChosenPlanStartedToClientNotification() => _ChosenPlanStartedToClientWidget(notification: notification as ChosenPlanStartedToClientNotification),
|
||||
ChosenPlanTomorrowReminderToClientNotification() => _ChosenPlanTomorrowReminderToClientWidget(
|
||||
notification: notification as ChosenPlanTomorrowReminderToClientNotification,
|
||||
),
|
||||
DiaryReminderNotification() => _DiaryReminderWidget(
|
||||
notification: notification as DiaryReminderNotification,
|
||||
),
|
||||
ConsultantLeftCommentNotification() => _ConsultantLeftCommentWidget(
|
||||
notification: notification as ConsultantLeftCommentNotification,
|
||||
),
|
||||
PersonalizedDietIsDefinedNotification() => _PersonalizedDietIsDefinedWidget(
|
||||
notification: notification as PersonalizedDietIsDefinedNotification,
|
||||
),
|
||||
ChosenPlanFinishedToClientNotification() => _ChosenPlanFinishedToClientWidget(
|
||||
notification: notification as ChosenPlanFinishedToClientNotification,
|
||||
),
|
||||
notification: notification as ChosenPlanTomorrowReminderToClientNotification,
|
||||
),
|
||||
DiaryReminderNotification() => _DiaryReminderWidget(notification: notification as DiaryReminderNotification),
|
||||
ConsultantLeftCommentNotification() => _ConsultantLeftCommentWidget(notification: notification as ConsultantLeftCommentNotification),
|
||||
PersonalizedDietIsDefinedNotification() => _PersonalizedDietIsDefinedWidget(notification: notification as PersonalizedDietIsDefinedNotification),
|
||||
ChosenPlanFinishedToClientNotification() => _ChosenPlanFinishedToClientWidget(notification: notification as ChosenPlanFinishedToClientNotification),
|
||||
PlanSubscriptionCanceledToCustomerNotification() => _PlanSubscriptionCanceledToCustomerWidget(
|
||||
notification: notification as PlanSubscriptionCanceledToCustomerNotification,
|
||||
),
|
||||
notification: notification as PlanSubscriptionCanceledToCustomerNotification,
|
||||
),
|
||||
ChosenPlanStartedToConsultantNotification() => _ChosenPlanStartedToConsultantWidget(
|
||||
notification: notification as ChosenPlanStartedToConsultantNotification,
|
||||
),
|
||||
notification: notification as ChosenPlanStartedToConsultantNotification,
|
||||
),
|
||||
ChosenPlanTomorrowReminderToConsultantNotification() => _ChosenPlanTomorrowReminderToConsultantWidget(
|
||||
notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
|
||||
),
|
||||
PlanSubscriptionPaidToAuthorNotification() => _PlanSubscriptionPaidToAuthorWidget(
|
||||
notification: notification as PlanSubscriptionPaidToAuthorNotification,
|
||||
),
|
||||
notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
|
||||
),
|
||||
PlanSubscriptionPaidToAuthorNotification() => _PlanSubscriptionPaidToAuthorWidget(notification: notification as PlanSubscriptionPaidToAuthorNotification),
|
||||
ChosenPlanFinishedToConsultantNotification() => _ChosenPlanFinishedToConsultantWidget(
|
||||
notification: notification as ChosenPlanFinishedToConsultantNotification,
|
||||
),
|
||||
UnknownNotification() => const SizedBox.shrink(),
|
||||
notification: notification as ChosenPlanFinishedToConsultantNotification,
|
||||
),
|
||||
ChosenPlanLastDayFinishedForUser() => _ChosenPlanLastDayFinishedForUserWidget(notification: notification as ChosenPlanLastDayFinishedForUser),
|
||||
ChosenPlanConsultantLastDayStarted() => _ChosenPlanConsultantLastDayStartedWidget(notification: notification as ChosenPlanConsultantLastDayStarted),
|
||||
ComplaintToModerationForUser() => _ComplaintToModerationForUserWidget(notification: notification as ComplaintToModerationForUser),
|
||||
CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied),
|
||||
ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded),
|
||||
EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented),
|
||||
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -67,7 +61,8 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
|
||||
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.fillYasterday,
|
||||
notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -81,7 +76,8 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
|
||||
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.fillYasterday,
|
||||
notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -93,10 +89,7 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.fillYasterday,
|
||||
);
|
||||
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.planStarted(notification.planName), createdAt: notification.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +102,8 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
|
||||
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.fillYasterday,
|
||||
notificationsText: s.tomorrowYourPlanWillStart(notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -120,10 +114,7 @@ class _DiaryReminderWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.fillYasterday,
|
||||
);
|
||||
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.fillYasterday, createdAt: notification.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +128,7 @@ class _ConsultantLeftCommentWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.consultantLeftComment(notification.date.format('dd.MM.yyyy')),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -146,7 +138,7 @@ class _PersonalizedDietIsDefinedWidget extends StatelessWidget {
|
||||
final PersonalizedDietIsDefinedNotification notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Персональная диета определена', createdAt: notification.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +152,7 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -174,6 +167,7 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.youCanceledSubscriptionToPlan(notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -188,6 +182,7 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.planWithUserStarted(notification.planName, notification.customerName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -202,6 +197,7 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -216,6 +212,7 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.userPaidThePlan(notification.customerName, notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -226,58 +223,223 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(children: [
|
||||
Text(
|
||||
'${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
s.userConfirmedPlanCompletionWithoutSum,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomOutlinedButton(
|
||||
text: s.collectCharge,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pushNamed(CardSettingsScreen.name, extra: notification.planId);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
]);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('${s.congratsYouHaveFinishedPlan} «${notification.planName}»', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 12),
|
||||
Text(s.userConfirmedPlanCompletionWithoutSum, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 12),
|
||||
CustomOutlinedButton(
|
||||
text: s.collectCharge,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pushNamed(CardSettingsScreen.name, extra: notification.planId);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!notification.isRead)
|
||||
Container(
|
||||
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
||||
width: 8,
|
||||
height: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${notification.createdAt.day} ${_getMonthName(notification.createdAt.month)} ${notification.createdAt.hour.toString().padLeft(2, '0')}:${notification.createdAt.minute.toString().padLeft(2, '0')}',
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.gray600),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _getMonthName(int month) {
|
||||
switch (month) {
|
||||
case 1:
|
||||
return 'янв';
|
||||
case 2:
|
||||
return 'фев';
|
||||
case 3:
|
||||
return 'мар';
|
||||
case 4:
|
||||
return 'апр';
|
||||
case 5:
|
||||
return 'май';
|
||||
case 6:
|
||||
return 'июн';
|
||||
case 7:
|
||||
return 'июл';
|
||||
case 8:
|
||||
return 'авг';
|
||||
case 9:
|
||||
return 'сен';
|
||||
case 10:
|
||||
return 'окт';
|
||||
case 11:
|
||||
return 'ноя';
|
||||
case 12:
|
||||
return 'дек';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CardSettingsScreen
|
||||
class _BaseNotificationWidget extends StatelessWidget {
|
||||
const _BaseNotificationWidget({required this.notificationsText, required this.isRead});
|
||||
const _BaseNotificationWidget({required this.notificationsText, required this.isRead, required this.createdAt});
|
||||
final String notificationsText;
|
||||
final bool isRead;
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.6,
|
||||
child: Text(
|
||||
notificationsText,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
if (!isRead) ...[
|
||||
const Spacer(),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400,
|
||||
shape: BoxShape.circle,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(notificationsText, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
)
|
||||
]
|
||||
if (!isRead)
|
||||
Container(
|
||||
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
||||
width: 8,
|
||||
height: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${createdAt.day} ${_getMonthName(createdAt.month)} ${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}',
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.gray600),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _getMonthName(int month) {
|
||||
switch (month) {
|
||||
case 1:
|
||||
return 'янв';
|
||||
case 2:
|
||||
return 'фев';
|
||||
case 3:
|
||||
return 'мар';
|
||||
case 4:
|
||||
return 'апр';
|
||||
case 5:
|
||||
return 'май';
|
||||
case 6:
|
||||
return 'июн';
|
||||
case 7:
|
||||
return 'июл';
|
||||
case 8:
|
||||
return 'авг';
|
||||
case 9:
|
||||
return 'сен';
|
||||
case 10:
|
||||
return 'окт';
|
||||
case 11:
|
||||
return 'ноя';
|
||||
case 12:
|
||||
return 'дек';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ChosenPlanLastDayFinishedForUserWidget extends StatelessWidget {
|
||||
const _ChosenPlanLastDayFinishedForUserWidget({required this.notification});
|
||||
final ChosenPlanLastDayFinishedForUser notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Последний день плана завершен', createdAt: notification.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChosenPlanConsultantLastDayStartedWidget extends StatelessWidget {
|
||||
const _ChosenPlanConsultantLastDayStartedWidget({required this.notification});
|
||||
final ChosenPlanConsultantLastDayStarted notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: 'Последний день плана начался: ${notification.planName}',
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ComplaintToModerationForUserWidget extends StatelessWidget {
|
||||
const _ComplaintToModerationForUserWidget({required this.notification});
|
||||
final ComplaintToModerationForUser notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Жалоба отправлена на модерацию', createdAt: notification.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
class _CommentRepliedWidget extends StatelessWidget {
|
||||
const _CommentRepliedWidget({required this.notification});
|
||||
final CommentReplied notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: 'Ответ на комментарий: ${notification.entityName}',
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScoreAddedWidget extends StatelessWidget {
|
||||
const _ScoreAddedWidget({required this.notification});
|
||||
final ScoreAdded notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: 'Добавлены баллы: ${notification.scoreValue}',
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EntityCommentedWidget extends StatelessWidget {
|
||||
const _EntityCommentedWidget({required this.notification});
|
||||
final EntityCommented notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: 'Комментарий к ${notification.entityTranslatedType}: ${notification.entityName}',
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UnknownNotificationWidget extends StatelessWidget {
|
||||
const _UnknownNotificationWidget({required this.notification});
|
||||
final UnknownNotification notification;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: 'Неизвестное уведомление: ${notification.type.toString()}',
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
|
||||
import 'dart:developer';
|
||||
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
|
||||
|
||||
class NotificationsRepository {
|
||||
final NotificationsService notificationsService;
|
||||
NotificationsRepository({required this.notificationsService});
|
||||
|
||||
Future<List<BaseNotificationModel>?> getNotifications({required int page}) {
|
||||
Future<Map<String, dynamic>?> getNotifications({required int page}) {
|
||||
try {
|
||||
return notificationsService.getNotifications(page: page);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,48 +5,137 @@ class NotificationsService {
|
||||
final Dio dio;
|
||||
NotificationsService(this.dio);
|
||||
|
||||
Future<List<BaseNotificationModel>?> getNotifications({required int page}) async {
|
||||
Future<Map<String, dynamic>?> getNotifications({required int page}) async {
|
||||
try {
|
||||
final response = await dio.get('/api/profile/notifications?page=$page');
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data['data'] as List<dynamic>;
|
||||
|
||||
final notifications = data.map((notificationJson) {
|
||||
try {
|
||||
final stringifiedType = notificationJson['data']['notification_class_name'];
|
||||
final readDate = notificationJson['read_at'];
|
||||
final createdAt = notificationJson['created_at'];
|
||||
final type = NotificationType.getTypeFromString(stringifiedType);
|
||||
|
||||
return switch (type) {
|
||||
NotificationType.planSubscriptionStartedToAuthor =>
|
||||
PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.planSubscriptionCanceledToAuthor =>
|
||||
PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.chosenPlanStartedToClientNotification =>
|
||||
ChosenPlanStartedToClientNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.chosenPlanTomorrowReminderToClientNotification =>
|
||||
ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.personalizedDietIsDefined =>
|
||||
PersonalizedDietIsDefinedNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.chosenPlanFinishedToClient =>
|
||||
ChosenPlanFinishedToClientNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.planSubscriptionCanceledToCustomer =>
|
||||
PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.chosenPlanStartedToConsultantNotification =>
|
||||
ChosenPlanStartedToConsultantNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
|
||||
ChosenPlanTomorrowReminderToConsultantNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.planSubscriptionPaidToAuthor =>
|
||||
PlanSubscriptionPaidToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.chosenPlanFinishedToConsultant =>
|
||||
ChosenPlanFinishedToConsultantNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||
NotificationType.unknown => UnknownNotification(type: type),
|
||||
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanTomorrowReminderToConsultantNotification => ChosenPlanTomorrowReminderToConsultantNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
// Новые типы уведомлений
|
||||
NotificationType.chosenPlanConsultantLastDayStarted => ChosenPlanConsultantLastDayStarted.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.complaintToModerationForUser => ComplaintToModerationForUser.fromJson(
|
||||
notificationJson['data'],
|
||||
type,
|
||||
isRead: readDate != null,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)),
|
||||
};
|
||||
} catch (e) {
|
||||
return UnknownNotification(type: NotificationType.unknown);
|
||||
return UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now());
|
||||
}
|
||||
}).toList();
|
||||
return notifications.where((notification) => notification.type != NotificationType.unknown).toList();
|
||||
|
||||
// Извлекаем информацию о пагинации из корня ответа
|
||||
final currentPage = response.data['current_page'] as int;
|
||||
final lastPage = response.data['last_page'] as int;
|
||||
final perPage = response.data['per_page'] as int;
|
||||
final total = response.data['total'] as int;
|
||||
|
||||
return {
|
||||
'notifications': notifications,
|
||||
'pagination': {'current_page': currentPage, 'last_page': lastPage, 'per_page': perPage, 'total': total},
|
||||
};
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
@@ -62,3 +151,5 @@ class NotificationsService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DateTime parseDate(String date) => DateTime.parse(date).toLocal();
|
||||
|
||||
@@ -208,7 +208,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
||||
GoRouter.of(context).pushReplacementNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chosenPlan.chatId,
|
||||
'uuid': chosenPlan.chatId,
|
||||
'sendInitialMessage': true,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -14,9 +14,11 @@ abstract class ProfileModel with _$ProfileModel {
|
||||
@JsonKey(name: "email") required String email,
|
||||
@JsonKey(name: "rating_place") int? ratingPlace,
|
||||
@JsonKey(name: "email_verified_at") DateTime? emailVerifiedAt,
|
||||
@JsonKey(name: "phone_confirmation_state") required String phoneConfirmationState,
|
||||
@JsonKey(name: "created_at") DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile", fromJson: parseBool) required bool isPublicProfile,
|
||||
@JsonKey(name: "is_blocked_by_me", fromJson: parseBool) required bool isBlockedByMe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required String slug,
|
||||
@JsonKey(name: "profile_photo_url") required String profilePhotoUrl,
|
||||
|
||||
@@ -25,6 +25,7 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
}) : super(const ProfileState.loading());
|
||||
|
||||
Future<void> getProfileBySlug(String slug) async {
|
||||
emit(const ProfileState.loading());
|
||||
try {
|
||||
final profile = await profileRepository.getProfileBySlug(slug);
|
||||
emit(
|
||||
@@ -44,6 +45,7 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
Future<void> getMyProfile({
|
||||
String period = '1',
|
||||
}) async {
|
||||
emit(const ProfileState.loading());
|
||||
_currentPeriod = period;
|
||||
try {
|
||||
final profile = await profileRepository.getMyProfile();
|
||||
@@ -123,7 +125,7 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
AppRouter.navigatorKey.currentContext?.pushNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chatIdOrError,
|
||||
'uuid': chatIdOrError,
|
||||
},
|
||||
);
|
||||
return chatIdOrError;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,7 @@ class _SubActionButtonWidgetState extends State<SubActionButtonWidget> {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': await sl<ProfileCubit>().getChatRoomIdBySlug(
|
||||
'uuid': await sl<ProfileCubit>().getChatRoomIdBySlug(
|
||||
author: Author(
|
||||
authorSlug: widget.slug,
|
||||
authorName: widget.name,
|
||||
|
||||
@@ -203,4 +203,12 @@ class ProfileRepository {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAccount() async {
|
||||
try {
|
||||
await profileService.deleteAccount();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,4 +119,7 @@ abstract class ProfileService {
|
||||
Future<void> tryVerifyEmailByCode({
|
||||
@Body() required String code,
|
||||
});
|
||||
|
||||
@DELETE('/api/profile/delete_my_account')
|
||||
Future<void> deleteAccount();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart';
|
||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||
@@ -51,25 +52,19 @@ class RegistrationCubit extends Cubit<RegistrationState> {
|
||||
void updateSheet(UserRegistrationSheet sheet) => emit(RegistrationState(sheet: sheet));
|
||||
|
||||
Future<void> tryRegistration() async {
|
||||
if (await registrationRepository.emailIsReachable(state.sheet.email)) {
|
||||
try {
|
||||
final token = await registrationRepository.tryRegister(
|
||||
state.sheet.copyWith(
|
||||
phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''),
|
||||
responseBody: '{}',
|
||||
),
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
AppRouter.navigatorKey.currentContext?.pushNamed('verifyEmailScreen');
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
} else {
|
||||
showErrorDialog(
|
||||
AppRouter.navigatorKey.currentContext!,
|
||||
title: 'Ошибка регистрации',
|
||||
description: 'Несуществующая почта.',
|
||||
try {
|
||||
final token = await registrationRepository.tryRegister(
|
||||
state.sheet.copyWith(
|
||||
phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''),
|
||||
responseBody: '{}',
|
||||
),
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
AppRouter.navigatorKey.currentContext?.replaceNamed('verifyEmailScreen');
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
emit(RegistrationState(sheet: state.sheet, error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@ part 'registration_state.freezed.dart';
|
||||
abstract class RegistrationState with _$RegistrationState {
|
||||
const factory RegistrationState({
|
||||
required UserRegistrationSheet sheet,
|
||||
String? error,
|
||||
}) = _RegistrationState;
|
||||
}
|
||||
|
||||
@@ -43,142 +43,161 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<RegistrationCubit>(
|
||||
create: (BuildContext context) => registrationCubit,
|
||||
child: BlocBuilder<RegistrationCubit, RegistrationState>(
|
||||
builder: (BuildContext context, RegistrationState state) {
|
||||
return Scaffold(
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: Row(
|
||||
children: [
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width * 0.2,
|
||||
padding: const EdgeInsets.only(left: 16, right: 4),
|
||||
child: FloatingActionButton.extended(
|
||||
heroTag: 'backButtonRegistration',
|
||||
highlightElevation: 0,
|
||||
focusElevation: 0,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor: const Color(0xFFEAF2FD),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
},
|
||||
label: Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
child: BlocListener<RegistrationCubit, RegistrationState>(
|
||||
listener: (context, state) {
|
||||
if (state.error != null && state.error!.isNotEmpty) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Ошибка при регистрации'),
|
||||
content: Text(state.error!),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
padding: const EdgeInsets.only(left: 4, right: 16),
|
||||
child: FloatingActionButton.extended(
|
||||
heroTag: 'nextButtonRegistration',
|
||||
highlightElevation: 0,
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
onPressed: () async {
|
||||
if (await canGoNext(state, context)) {
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<RegistrationCubit, RegistrationState>(
|
||||
builder: (BuildContext context, RegistrationState state) {
|
||||
return Scaffold(
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: Row(
|
||||
children: [
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width * 0.2,
|
||||
padding: const EdgeInsets.only(left: 16, right: 4),
|
||||
child: FloatingActionButton.extended(
|
||||
heroTag: 'backButtonRegistration',
|
||||
highlightElevation: 0,
|
||||
focusElevation: 0,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor: const Color(0xFFEAF2FD),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
pageController.nextPage(
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
label: Text(
|
||||
'Продолжить',
|
||||
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
||||
},
|
||||
label: Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: Builder(
|
||||
builder: (context) => IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
onPressed: () => GoRouter.of(context).pop(),
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: SvgPicture.asset(
|
||||
ImageConstant.imgLogoEatvio,
|
||||
height: 48,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Text('шаг $step из 15'),
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size(MediaQuery.of(context).size.width, 32),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: TweenAnimationBuilder<double>(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
tween: Tween<double>(
|
||||
begin: 1 / 15,
|
||||
end: progressValue,
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
padding: const EdgeInsets.only(left: 4, right: 16),
|
||||
child: FloatingActionButton.extended(
|
||||
heroTag: 'nextButtonRegistration',
|
||||
highlightElevation: 0,
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
onPressed: () async {
|
||||
if (await canGoNext(state, context)) {
|
||||
setState(() {
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
label: Text(
|
||||
'Продолжить',
|
||||
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
builder: (context, value, _) => LinearProgressIndicator(
|
||||
value: value,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
minHeight: 8,
|
||||
backgroundColor: Colors.grey.shade100,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
|
||||
),
|
||||
],
|
||||
),
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: Builder(
|
||||
builder: (context) => IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
onPressed: () => GoRouter.of(context).pop(),
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: SvgPicture.asset(
|
||||
ImageConstant.imgLogoEatvio,
|
||||
height: 48,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Text('шаг $step из 15'),
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size(MediaQuery.of(context).size.width, 32),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: TweenAnimationBuilder<double>(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
tween: Tween<double>(
|
||||
begin: 1 / 15,
|
||||
end: progressValue,
|
||||
),
|
||||
builder: (context, value, _) => LinearProgressIndicator(
|
||||
value: value,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
minHeight: 8,
|
||||
backgroundColor: Colors.grey.shade100,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: pageController,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
step = index + 1;
|
||||
progressValue = step / 15;
|
||||
});
|
||||
},
|
||||
children: [
|
||||
// LanguageSelectScreen(sheet: state.sheet),
|
||||
PhoneScreen(sheet: state.sheet),
|
||||
CredentialsScreen(sheet: state.sheet),
|
||||
NameScreen(sheet: state.sheet),
|
||||
GenderScreen(sheet: state.sheet),
|
||||
DateOfBirthScreen(sheet: state.sheet),
|
||||
CityOfLivingScreen(sheet: state.sheet),
|
||||
BodyCircumferencesScreen(sheet: state.sheet),
|
||||
WeightScreen(sheet: state.sheet),
|
||||
HeightScreen(sheet: state.sheet),
|
||||
BloodPressureScreen(sheet: state.sheet),
|
||||
AllergiesScreen(sheet: state.sheet),
|
||||
MedicineIntakeScreen(sheet: state.sheet),
|
||||
FavoriteSportsScreen(sheet: state.sheet),
|
||||
FavoriteFoodsScreen(sheet: state.sheet),
|
||||
AdditionalInformationScreen(sheet: state.sheet),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
body: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: pageController,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
step = index + 1;
|
||||
progressValue = step / 15;
|
||||
});
|
||||
},
|
||||
children: [
|
||||
// LanguageSelectScreen(sheet: state.sheet),
|
||||
PhoneScreen(sheet: state.sheet),
|
||||
CredentialsScreen(sheet: state.sheet),
|
||||
NameScreen(sheet: state.sheet),
|
||||
GenderScreen(sheet: state.sheet),
|
||||
DateOfBirthScreen(sheet: state.sheet),
|
||||
CityOfLivingScreen(sheet: state.sheet),
|
||||
BodyCircumferencesScreen(sheet: state.sheet),
|
||||
WeightScreen(sheet: state.sheet),
|
||||
HeightScreen(sheet: state.sheet),
|
||||
BloodPressureScreen(sheet: state.sheet),
|
||||
AllergiesScreen(sheet: state.sheet),
|
||||
MedicineIntakeScreen(sheet: state.sheet),
|
||||
FavoriteSportsScreen(sheet: state.sheet),
|
||||
FavoriteFoodsScreen(sheet: state.sheet),
|
||||
AdditionalInformationScreen(sheet: state.sheet),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
@@ -19,6 +20,7 @@ class VerifyScreen extends StatefulWidget {
|
||||
|
||||
class _VerifyScreenState extends State<VerifyScreen> {
|
||||
late final WebViewController _controller;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -27,6 +29,11 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onPageFinished: (url) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
},
|
||||
onNavigationRequest: (NavigationRequest request) async {
|
||||
if (request.url.contains('user_detailz') && !request.url.contains('change-phone-verify') || request.url.contains('email/verify')) {
|
||||
GoRouter.of(context).pop();
|
||||
@@ -105,11 +112,17 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
),
|
||||
appBar: AppBar(toolbarHeight: 0),
|
||||
body: SafeArea(
|
||||
child: WebViewWidget(controller: _controller),
|
||||
child: Stack(
|
||||
children: [
|
||||
WebViewWidget(controller: _controller),
|
||||
if (_isLoading)
|
||||
const Center(
|
||||
child: Loader(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
|
||||
import 'package:eatvio_app/features/registration/models/location_model.dart';
|
||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
class RegistrationRepository {
|
||||
final RegistrationService registrationService;
|
||||
@@ -11,8 +12,56 @@ class RegistrationRepository {
|
||||
|
||||
Future<String> tryRegister(UserRegistrationSheet sheet) async {
|
||||
try {
|
||||
return (await registrationService.register(sheet: sheet.toJson()))['token']!;
|
||||
} catch (e) {
|
||||
final response = await registrationService.register(sheet: sheet.toJson());
|
||||
if (response.containsKey('token')) {
|
||||
return response['token']!;
|
||||
} else {
|
||||
// Если нет токена, но есть message или errors
|
||||
String errorMsg = '';
|
||||
if (response.containsKey('message')) {
|
||||
errorMsg += '${response['message']!}\n';
|
||||
}
|
||||
if (response.containsKey('errors')) {
|
||||
final errors = response['errors'];
|
||||
if (errors != null && errors is Map) {
|
||||
(errors as Map).forEach((key, value) {
|
||||
if (value is List && value.isNotEmpty) {
|
||||
errorMsg += '${value.join('\n')}\n';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (errorMsg.isNotEmpty) {
|
||||
throw errorMsg.trim();
|
||||
} else {
|
||||
throw 'Ошибка регистрации: $response';
|
||||
}
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && e.response?.statusCode == 422) {
|
||||
final data = e.response?.data;
|
||||
String errorMsg = '';
|
||||
if (data is Map<String, dynamic>) {
|
||||
if (data.containsKey('message')) {
|
||||
errorMsg += data['message'] + '\n';
|
||||
}
|
||||
if (data.containsKey('errors')) {
|
||||
final errors = data['errors'];
|
||||
if (errors != null && errors is Map) {
|
||||
(errors).forEach((key, value) {
|
||||
if (value is List && value.isNotEmpty) {
|
||||
errorMsg += '${value.join('\n')}\n';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errorMsg.isNotEmpty) {
|
||||
throw errorMsg.trim();
|
||||
} else {
|
||||
throw 'Ошибка регистрации: $data';
|
||||
}
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +84,7 @@ class RegistrationRepository {
|
||||
|
||||
Future<bool> emailIsReachable(String email) async {
|
||||
try {
|
||||
final response = await sl<Dio>().getUri(Uri.parse('https://api.validate.email/validate?email=$email'));
|
||||
final response = await sl<Dio>().getUri(Uri.parse('${EnvConfig.emailValidationApiUrl}?email=$email'));
|
||||
if (response.data['result']['reachable'] != 'invalid') {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,11 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
final profile = await profileRepository.getMyProfile();
|
||||
late final ConsultationRequestModel? consultantInfo;
|
||||
try {
|
||||
consultantInfo = await consultantRepository.getMyRequestStatus(profile.slug);
|
||||
if (profile.phoneConfirmationState.contains('confirmed')) {
|
||||
consultantInfo = await consultantRepository.getMyRequestStatus(profile.slug);
|
||||
} else {
|
||||
consultantInfo = null;
|
||||
}
|
||||
} catch (e) {
|
||||
consultantInfo = null;
|
||||
}
|
||||
@@ -67,4 +71,13 @@ class SettingsCubit extends Cubit<SettingsState> {
|
||||
await init();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deleteAccount() async {
|
||||
try {
|
||||
await profileRepository.deleteAccount();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
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/custom_outlined_button.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/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_state.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -60,7 +67,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_aboutEatvio(state),
|
||||
const SizedBox(height: 12),
|
||||
_becomeConsultantButton(state),
|
||||
_deleteAccountButton(state),
|
||||
_deleteAccountButton(state, settingsCubit),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -428,11 +435,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Padding _deleteAccountButton(SettingsStateLoaded state) {
|
||||
Padding _deleteAccountButton(SettingsStateLoaded state, SettingsCubit settingsCubit) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: DeleteAccountBottomSheet(
|
||||
cubit: settingsCubit,
|
||||
),
|
||||
);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
height: 52,
|
||||
@@ -495,3 +511,73 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteAccountBottomSheet extends StatefulWidget {
|
||||
const DeleteAccountBottomSheet({super.key, required this.cubit});
|
||||
final SettingsCubit cubit;
|
||||
|
||||
@override
|
||||
State<DeleteAccountBottomSheet> createState() => _DeleteAccountBottomSheet();
|
||||
}
|
||||
|
||||
class _DeleteAccountBottomSheet extends State<DeleteAccountBottomSheet> {
|
||||
bool? isFailure;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(s.accountDeleting, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
s.youShureYouWantToDeleteAccount,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
s.itCantBeRecovered,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomOutlinedButton(
|
||||
text: s.delete,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
final result = await widget.cubit.deleteAccount();
|
||||
if (result == true) {
|
||||
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
||||
return;
|
||||
}
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, description: s.cannotDeleteAccount);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
class SupportScreen extends StatelessWidget {
|
||||
const SupportScreen({super.key});
|
||||
@@ -15,48 +16,28 @@ class SupportScreen extends StatelessWidget {
|
||||
final s = S.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Поддержка'),
|
||||
),
|
||||
appBar: AppBar(title: const Text('Поддержка')),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Если у вас есть вопросы по работе нашего приложения, вы можете задать их, перейдя по ссылке:',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
child: Text('Если у вас есть вопросы по работе нашего приложения, вы можете задать их, перейдя по ссылке:', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
Uri.parse('https://t.me/eatvio'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
launchUrl(Uri.parse(EnvConfig.telegramSupportUrl), mode: LaunchMode.externalApplication);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Ink(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/telegram.svg',
|
||||
colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
|
||||
),
|
||||
SvgPicture.asset('assets/icons/telegram.svg', colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn)),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
'Telegram: t.me/eatvio',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Text('Telegram: ${EnvConfig.telegramSupportUrl.replaceFirst('https://', '')}', style: const TextStyle(fontSize: 16)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -64,26 +45,19 @@ class SupportScreen extends StatelessWidget {
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Clipboard.setData(const ClipboardData(text: 'help@eatv.io'));
|
||||
Clipboard.setData(ClipboardData(text: EnvConfig.supportEmail));
|
||||
showInfoToast(s.mail_was_copied);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.alternate_email_rounded),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
'E-mail: help@eatv.io',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.alternate_email_rounded),
|
||||
const SizedBox(width: 12),
|
||||
Text('E-mail: ${EnvConfig.supportEmail}', style: const TextStyle(fontSize: 16)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -92,13 +66,7 @@ class SupportScreen extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Правовые документы',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
child: Text('Правовые документы', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
InkWell(
|
||||
@@ -106,12 +74,7 @@ class SupportScreen extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
'Публичная офёрта',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
child: Text('Публичная офёрта', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -120,12 +83,7 @@ class SupportScreen extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
'Политика конфиденциальности',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
child: Text('Политика конфиденциальности', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -134,12 +92,7 @@ class SupportScreen extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
'О компании',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
child: Text('О компании', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../core/theme/theme_helper.dart';
|
||||
import '../../../../core/env_config.dart';
|
||||
|
||||
class UpdateScreen extends StatelessWidget {
|
||||
const UpdateScreen({super.key});
|
||||
@@ -17,16 +18,7 @@ class UpdateScreen extends StatelessWidget {
|
||||
body: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Positioned(
|
||||
left: -20,
|
||||
top: -20,
|
||||
right: -20,
|
||||
bottom: -20,
|
||||
child: Image.asset(
|
||||
'assets/images/bg_welcome.png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Positioned(left: -20, top: -20, right: -20, bottom: -20, child: Image.asset('assets/images/bg_welcome.png', fit: BoxFit.cover)),
|
||||
Positioned.fill(
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
@@ -48,47 +40,26 @@ class UpdateScreen extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Пора обновиться! ⚡',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w800,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
const Text('Пора обновиться! ⚡', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800, height: 1)),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
'Мы выпустили важное обновление\nс новыми функциями и исправлениями.\nЧтобы продолжить пользоваться\nприложением без сбоев, обновите\nего прямо сейчас.',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.15,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1.15),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
final Uri url = Platform.isAndroid
|
||||
? Uri.parse('https://www.rustore.ru/catalog/app/com.eatvioapp.app')
|
||||
: Uri.parse('https://apps.apple.com/app/idYOUR_APP_ID');
|
||||
final Uri url = Platform.isAndroid ? Uri.parse(EnvConfig.rustoreUrl) : Uri.parse(EnvConfig.appStoreUrl);
|
||||
await launchUrl(url);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Ink(
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(8)),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Обновить',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1,
|
||||
color: Colors.white,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, height: 1, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -20,19 +20,11 @@ class UserDetailsCubit extends Cubit<UserDetailsState> {
|
||||
}
|
||||
}
|
||||
|
||||
void getChartData({
|
||||
String period = '1',
|
||||
required String slug,
|
||||
}) async {
|
||||
void getChartData({String period = '1', required String slug}) async {
|
||||
try {
|
||||
final weightPoints = await profileRepository.getWeight(period: period);
|
||||
final circumferencePoints = await profileRepository.getCircumferences(period: period);
|
||||
emit(
|
||||
state.copyWith(
|
||||
weightPoints: weightPoints,
|
||||
circumferencePoints: circumferencePoints,
|
||||
),
|
||||
);
|
||||
final weightPoints = await profileRepository.getWeight(period: period, slug: slug);
|
||||
final circumferencePoints = await profileRepository.getCircumferences(period: period, slug: slug);
|
||||
emit(state.copyWith(weightPoints: weightPoints, circumferencePoints: circumferencePoints));
|
||||
} catch (e, stackTrace) {
|
||||
log(e.toString(), stackTrace: stackTrace);
|
||||
emit(state.copyWith(error: e.toString(), isLoading: false));
|
||||
|
||||
@@ -31,9 +31,7 @@ class UserDetailsScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<UserDetailsCubit>()..getUserDetails(slug),
|
||||
child: UserDetailsContent(
|
||||
slug: slug,
|
||||
),
|
||||
child: UserDetailsContent(slug: slug),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -79,27 +77,15 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
actions: [
|
||||
PortalTarget(
|
||||
visible: isMoreOptionsVisible,
|
||||
portalFollower: MoreOptions(
|
||||
path: 'profile/${widget.slug}',
|
||||
complainedContentType: ComplainedContent.profile,
|
||||
slug: widget.slug,
|
||||
),
|
||||
anchor: const Aligned(
|
||||
shiftToWithinBound: AxisFlag(x: true, y: true),
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomRight,
|
||||
),
|
||||
portalFollower: MoreOptions(path: 'profile/${widget.slug}', complainedContentType: ComplainedContent.profile, slug: widget.slug),
|
||||
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||||
});
|
||||
},
|
||||
icon: SvgPicture.asset(
|
||||
'assets/icons/more_button.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
icon: SvgPicture.asset('assets/icons/more_button.svg', width: 20, height: 20),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -124,25 +110,16 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
initiallyExpanded: true,
|
||||
title: s.general_info,
|
||||
children: [
|
||||
Text(
|
||||
s.date_of_birth,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.date_of_birth, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: DateTime.parse(profileDetails.birthdate).format('dd.MM.yyyy')),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.sex,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.sex, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.gender.parseGender()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.gender.parseGender())),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
@@ -155,15 +132,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.height,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.height, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.height.toString()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.height.toString())),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -172,15 +143,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.weight,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.weight, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.weight.toStringAsFixed(0)),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.weight.toStringAsFixed(0))),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -193,15 +158,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.chest_circumference,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.chest_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.chestCircumference.toString()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.chestCircumference.toString())),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -210,15 +169,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.waist_circumference,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.waist_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.waistCircumference.toString()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.waistCircumference.toString())),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -227,15 +180,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.hip_circumference,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.hip_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.hipCircumference.toString()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hipCircumference.toString())),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -253,15 +200,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.upper,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.upper, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.bloodPressureUp.toString()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.bloodPressureUp.toString())),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -270,10 +211,7 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.lower,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.lower, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
@@ -291,15 +229,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.has_hypo,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.has_hypo, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.hasHypo.toString()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHypo.toString())),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -308,15 +240,9 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.has_hyper,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.has_hyper, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.hasHyper.toString()),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHyper.toString())),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -328,60 +254,27 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
ExpandableBouble(
|
||||
title: s.other_info,
|
||||
children: [
|
||||
Text(
|
||||
s.has_alergy,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.has_alergy, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.aboutAlergy),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutAlergy)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.takes_pills,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.takes_pills, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.aboutTakingPils),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutTakingPils)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.sport_hobbies,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.sport_hobbies, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.aboutSportHobbies),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutSportHobbies)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.liked_groceries,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.liked_groceries, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.favoriteFoods),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.favoriteFoods)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.not_liked_groceries,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.not_liked_groceries, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
readOnly: true,
|
||||
controller: TextEditingController(text: profileDetails.unfavoriteFoods),
|
||||
),
|
||||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.unfavoriteFoods)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.additional_information,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(s.additional_information, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
height: 112,
|
||||
@@ -413,10 +306,7 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: const Loader(),
|
||||
);
|
||||
return Scaffold(appBar: AppBar(), body: const Loader());
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -432,31 +322,17 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
_choiceChipWidget(0),
|
||||
const SizedBox(width: 8),
|
||||
_choiceChipWidget(1),
|
||||
const SizedBox(width: 8),
|
||||
_choiceChipWidget(2),
|
||||
],
|
||||
),
|
||||
child: Row(children: [_choiceChipWidget(0), const SizedBox(width: 8), _choiceChipWidget(1), const SizedBox(width: 8), _choiceChipWidget(2)]),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
switch (_selectedChipIndex) {
|
||||
case 0:
|
||||
return WeightChartWidget(
|
||||
weightPoints: weightPoints,
|
||||
);
|
||||
return WeightChartWidget(weightPoints: weightPoints);
|
||||
case 1:
|
||||
return CircumferenceChartWidget(
|
||||
circumferencePoints: circumferencePoints,
|
||||
);
|
||||
return CircumferenceChartWidget(circumferencePoints: circumferencePoints);
|
||||
case 2:
|
||||
return ImtChartWidget(
|
||||
weightPoints: weightPoints,
|
||||
);
|
||||
return ImtChartWidget(weightPoints: weightPoints);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -489,16 +365,10 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
pressElevation: 0,
|
||||
showCheckmark: false,
|
||||
side: BorderSide.none,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
label: Text(chipsIntervalLabelList[index]),
|
||||
labelStyle: TextStyle(
|
||||
color: appTheme.gray600,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
),
|
||||
labelStyle: TextStyle(color: appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12),
|
||||
selected: _selectedIntervalChipIndex == index,
|
||||
onSelected: (bool selected) {
|
||||
context.read<UserDetailsCubit>().getChartData(period: chipsIntervalList[index], slug: widget.slug);
|
||||
@@ -515,16 +385,10 @@ class _UserDetailsContentState extends State<UserDetailsContent> {
|
||||
pressElevation: 0,
|
||||
showCheckmark: false,
|
||||
side: BorderSide.none,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
label: Text(chipsLabelList[index]),
|
||||
labelStyle: TextStyle(
|
||||
color: index == _selectedChipIndex ? Colors.white : appTheme.gray600,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
),
|
||||
labelStyle: TextStyle(color: index == _selectedChipIndex ? Colors.white : appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12),
|
||||
selected: _selectedChipIndex == index,
|
||||
backgroundColor: appTheme.gray100,
|
||||
onSelected: (bool selected) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
@@ -43,28 +43,26 @@ class DefaultFirebaseOptions {
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
throw UnsupportedError('DefaultFirebaseOptions are not supported for this platform.');
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBUe1UqAAMfgMcd4hWdRoLF1Os-yn-HE9k',
|
||||
appId: '1:613980912922:android:4de95e5f2d50ba69ecc0a3',
|
||||
messagingSenderId: '613980912922',
|
||||
projectId: 'eatvio-3467b',
|
||||
databaseURL: 'https://eatvio-3467b-default-rtdb.europe-west1.firebasedatabase.app',
|
||||
storageBucket: 'eatvio-3467b.firebasestorage.app',
|
||||
static FirebaseOptions get android => FirebaseOptions(
|
||||
apiKey: EnvConfig.firebaseAndroidApiKey,
|
||||
appId: EnvConfig.firebaseAndroidAppId,
|
||||
messagingSenderId: EnvConfig.firebaseMessagingSenderId,
|
||||
projectId: EnvConfig.firebaseProjectId,
|
||||
databaseURL: EnvConfig.firebaseDatabaseUrl,
|
||||
storageBucket: EnvConfig.firebaseStorageBucket,
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBnE8tQjL9_1HuXrTnqNy6qbXpoenO36yQ',
|
||||
appId: '1:613980912922:ios:c557d140a1212e63ecc0a3',
|
||||
messagingSenderId: '613980912922',
|
||||
projectId: 'eatvio-3467b',
|
||||
databaseURL: 'https://eatvio-3467b-default-rtdb.europe-west1.firebasedatabase.app',
|
||||
storageBucket: 'eatvio-3467b.firebasestorage.app',
|
||||
iosBundleId: 'com.eatvioapp.app',
|
||||
static FirebaseOptions get ios => FirebaseOptions(
|
||||
apiKey: EnvConfig.firebaseIosApiKey,
|
||||
appId: EnvConfig.firebaseIosAppId,
|
||||
messagingSenderId: EnvConfig.firebaseMessagingSenderId,
|
||||
projectId: EnvConfig.firebaseProjectId,
|
||||
databaseURL: EnvConfig.firebaseDatabaseUrl,
|
||||
storageBucket: EnvConfig.firebaseStorageBucket,
|
||||
iosBundleId: EnvConfig.firebaseIosBundleId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"),
|
||||
"about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"),
|
||||
"about_recipe": MessageLookupByLibrary.simpleMessage("Об этом рецепте"),
|
||||
"accountDeleting": MessageLookupByLibrary.simpleMessage(
|
||||
"Удаление аккаунта",
|
||||
),
|
||||
"active": MessageLookupByLibrary.simpleMessage("Активен"),
|
||||
"active_consultations_count": MessageLookupByLibrary.simpleMessage(
|
||||
"Кол-во активных консультаций",
|
||||
@@ -124,6 +127,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"),
|
||||
"cancel_order": MessageLookupByLibrary.simpleMessage("Отменить заказ"),
|
||||
"cannotDeleteAccount": MessageLookupByLibrary.simpleMessage(
|
||||
"Не получилось удалить ваш аккаунт",
|
||||
),
|
||||
"carb_type": MessageLookupByLibrary.simpleMessage("Мучное"),
|
||||
"carb_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
|
||||
@@ -256,6 +262,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"days": MessageLookupByLibrary.simpleMessage("дней"),
|
||||
"deception": MessageLookupByLibrary.simpleMessage("Обман"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
|
||||
"deleteComment": MessageLookupByLibrary.simpleMessage(
|
||||
"Удалить комментарий",
|
||||
),
|
||||
@@ -411,6 +418,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"invited_people_count": MessageLookupByLibrary.simpleMessage(
|
||||
"Количество приглашенных",
|
||||
),
|
||||
"itCantBeRecovered": MessageLookupByLibrary.simpleMessage(
|
||||
"Его нельзя будет восстановить",
|
||||
),
|
||||
"label": MessageLookupByLibrary.simpleMessage("Название"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Язык"),
|
||||
"last_name": MessageLookupByLibrary.simpleMessage("Фамилия"),
|
||||
@@ -624,6 +634,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Снять с публикации",
|
||||
),
|
||||
"reply": MessageLookupByLibrary.simpleMessage("Ответить"),
|
||||
"resend": MessageLookupByLibrary.simpleMessage("Отправить повторно"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
|
||||
"send": MessageLookupByLibrary.simpleMessage("Отправить"),
|
||||
"send_complain": MessageLookupByLibrary.simpleMessage("Отправить жалобу"),
|
||||
@@ -780,6 +791,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"youShureYouWantToCancelOrder": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите отменить заказ?",
|
||||
),
|
||||
"youShureYouWantToDeleteAccount": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите удалить аккаунт?",
|
||||
),
|
||||
"you_can_not_open_this_day": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы не можете перейти в этот день",
|
||||
),
|
||||
|
||||
@@ -3414,6 +3414,61 @@ class S {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Удаление аккаунта`
|
||||
String get accountDeleting {
|
||||
return Intl.message(
|
||||
'Удаление аккаунта',
|
||||
name: 'accountDeleting',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Вы уверены, что хотите удалить аккаунт?`
|
||||
String get youShureYouWantToDeleteAccount {
|
||||
return Intl.message(
|
||||
'Вы уверены, что хотите удалить аккаунт?',
|
||||
name: 'youShureYouWantToDeleteAccount',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Его нельзя будет восстановить`
|
||||
String get itCantBeRecovered {
|
||||
return Intl.message(
|
||||
'Его нельзя будет восстановить',
|
||||
name: 'itCantBeRecovered',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Удалить`
|
||||
String get delete {
|
||||
return Intl.message('Удалить', name: 'delete', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Не получилось удалить ваш аккаунт`
|
||||
String get cannotDeleteAccount {
|
||||
return Intl.message(
|
||||
'Не получилось удалить ваш аккаунт',
|
||||
name: 'cannotDeleteAccount',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Отправить повторно`
|
||||
String get resend {
|
||||
return Intl.message(
|
||||
'Отправить повторно',
|
||||
name: 'resend',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||
|
||||
@@ -402,10 +402,15 @@
|
||||
"allowAcessToGaleryDescription": "Предоставление доступа позволит вам добавлять фото к записям",
|
||||
"allowAccessToCamera": "Разрешите доступ к камере",
|
||||
"allowAccessToCameraDescription": "Предоставление доступа позволит вам делать фотографии и добавлять к записям",
|
||||
"allowAccess": "Разрешить доступ",
|
||||
"allowAccess": "Разрешить доступ" ,
|
||||
"copy": "Скопировать",
|
||||
"reply": "Ответить",
|
||||
"deleteComment": "Удалить комментарий"
|
||||
|
||||
"deleteComment": "Удалить комментарий",
|
||||
"accountDeleting": "Удаление аккаунта",
|
||||
"youShureYouWantToDeleteAccount": "Вы уверены, что хотите удалить аккаунт?",
|
||||
"itCantBeRecovered": "Его нельзя будет восстановить",
|
||||
"delete": "Удалить",
|
||||
"cannotDeleteAccount": "Не получилось удалить ваш аккаунт",
|
||||
"resend": "Отправить повторно"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:io' as io;
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart' as di;
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
@@ -11,6 +12,8 @@ import 'package:eatvio_app/features/internet_connection_checker/internet_connect
|
||||
import 'package:eatvio_app/firebase_options.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
@@ -18,22 +21,28 @@ import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsBinding widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
|
||||
await dotenv.load(fileName: "env/.env");
|
||||
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
await FirebaseMessaging.instance.requestPermission();
|
||||
await di.init();
|
||||
|
||||
await SentryFlutter.init(
|
||||
(options) {
|
||||
options.dsn = 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2';
|
||||
options.tracesSampleRate = 1.0;
|
||||
options.debug = false;
|
||||
},
|
||||
appRunner: () => runApp(const MyApp()),
|
||||
);
|
||||
if (kDebugMode) {
|
||||
io.HttpClient.enableTimelineLogging = true;
|
||||
}
|
||||
|
||||
await SentryFlutter.init((options) {
|
||||
options.dsn = EnvConfig.sentryDsn;
|
||||
options.tracesSampleRate = 1.0;
|
||||
options.debug = false;
|
||||
}, appRunner: () => runApp(const MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
||||
45
pubspec.yaml
45
pubspec.yaml
@@ -1,10 +1,10 @@
|
||||
name: eatvio_app
|
||||
description: A new Flutter project.
|
||||
version: 1.1.16+46
|
||||
version: 1.2.8+68
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.4 <4.0.0'
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -13,33 +13,37 @@ dependencies:
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
flutter_secure_storage: ^9.2.4
|
||||
flutter_secure_storage:
|
||||
git:
|
||||
url: https://github.com/sherlockvn/flutter_secure_storage.git
|
||||
ref: remove-old-encrypt-algorithm
|
||||
path: flutter_secure_storage
|
||||
flutter_bloc: ^9.1.1
|
||||
cached_network_image: ^3.4.1
|
||||
flutter_svg: ^2.2.0
|
||||
equatable: ^2.0.7
|
||||
freezed_annotation: ^3.0.0
|
||||
freezed_annotation: ^3.1.0
|
||||
json_annotation: ^4.9.0
|
||||
validators: ^3.0.0
|
||||
path_provider: ^2.1.5
|
||||
carousel_slider_plus: ^7.1.1
|
||||
flutter_form_builder: ^10.0.1
|
||||
flutter_form_builder: ^10.1.0
|
||||
image_picker: ^1.1.2
|
||||
flutter_typeahead: ^5.2.0
|
||||
go_router: ^16.0.0
|
||||
keyboard_dismisser: ^3.0.0
|
||||
retrofit: ^4.5.0
|
||||
get_it: ^8.0.3
|
||||
retrofit: ^4.7.0
|
||||
get_it: ^8.1.0
|
||||
dio: ^5.8.0+1
|
||||
dart_pusher_channels: ^1.2.3
|
||||
photo_view: ^0.15.0
|
||||
dismissible_page: ^1.0.2
|
||||
url_launcher: ^6.3.1
|
||||
url_launcher: ^6.3.2
|
||||
easy_debounce: ^2.0.3
|
||||
badges: ^3.1.2
|
||||
google_fonts: ^6.2.1
|
||||
syncfusion_flutter_charts: ^30.1.38
|
||||
syncfusion_flutter_gauges: ^30.1.38
|
||||
syncfusion_flutter_charts: ^30.1.42
|
||||
syncfusion_flutter_gauges: ^30.1.42
|
||||
table_calendar: ^3.2.0
|
||||
flutter_keyboard_visibility: ^6.0.0
|
||||
share_plus: ^11.0.0
|
||||
@@ -58,11 +62,11 @@ dependencies:
|
||||
http: ^1.4.0
|
||||
flutter_native_splash: ^2.4.6
|
||||
webview_flutter: ^4.13.0
|
||||
sentry_flutter: ^8.14.2
|
||||
sentry_flutter: ^9.5.0
|
||||
connectivity_plus: ^6.1.4
|
||||
visibility_detector: ^0.4.0+2
|
||||
firebase_core: ^3.15.0
|
||||
firebase_messaging: ^15.2.8
|
||||
firebase_core: ^3.15.2
|
||||
firebase_messaging: ^15.2.10
|
||||
http_parser: ^4.1.2
|
||||
path: ^1.9.1
|
||||
collection: ^1.19.1
|
||||
@@ -70,15 +74,17 @@ dependencies:
|
||||
version: ^3.0.2
|
||||
package_info_plus: ^8.3.0
|
||||
device_info_plus: ^11.5.0
|
||||
permission_handler: ^12.0.0+1
|
||||
permission_handler: ^12.0.1
|
||||
qr_code_scanner_plus: ^2.0.10+1
|
||||
flutter_dotenv: ^5.1.0
|
||||
uuid: ^4.5.1
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.5.4
|
||||
freezed: ^3.0.6
|
||||
json_serializable: ^6.9.5
|
||||
retrofit_generator: ^9.2.0
|
||||
mockito: ^5.4.6
|
||||
build_runner: ^2.6.0
|
||||
freezed: ^3.2.0
|
||||
json_serializable: ^6.10.0
|
||||
retrofit_generator: ^10.0.1
|
||||
mockito: ^5.5.0
|
||||
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -88,6 +94,7 @@ dev_dependencies:
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- env/.env
|
||||
- assets/
|
||||
- assets/images/
|
||||
- assets/audio/
|
||||
|
||||
@@ -19,8 +19,10 @@ void main() {
|
||||
id: 0,
|
||||
name: 'Alexander Avanesyan',
|
||||
email: 'mso7nueh@mail.ru',
|
||||
phoneConfirmationState: '',
|
||||
isPublicProfile: true,
|
||||
isEntitySubscribedByMe: false,
|
||||
isBlockedByMe: false,
|
||||
slug: 'slug',
|
||||
profilePhotoUrl: '',
|
||||
roles: ['user'],
|
||||
|
||||
@@ -1,526 +0,0 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in eatvio_app/test/features/profile/services/profile_service_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i6;
|
||||
import 'dart:io' as _i10;
|
||||
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'
|
||||
as _i7;
|
||||
import 'package:eatvio_app/features/profile/models/chosen_diets_pagination_model.dart'
|
||||
as _i4;
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart'
|
||||
as _i9;
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart' as _i2;
|
||||
import 'package:eatvio_app/features/profile/models/sub_model.dart' as _i3;
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart' as _i8;
|
||||
import 'package:eatvio_app/features/profile/services/profile_service.dart'
|
||||
as _i5;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i11;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
|
||||
class _FakeProfileModel_0 extends _i1.SmartFake implements _i2.ProfileModel {
|
||||
_FakeProfileModel_0(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeProfileDetailsModel_1 extends _i1.SmartFake
|
||||
implements _i2.ProfileDetailsModel {
|
||||
_FakeProfileDetailsModel_1(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeSubPaginationModel_2 extends _i1.SmartFake
|
||||
implements _i3.SubPaginationModel {
|
||||
_FakeSubPaginationModel_2(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeChosenDietsPaginationModel_3 extends _i1.SmartFake
|
||||
implements _i4.ChosenDietsPaginationModel {
|
||||
_FakeChosenDietsPaginationModel_3(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [ProfileService].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockProfileService extends _i1.Mock implements _i5.ProfileService {
|
||||
@override
|
||||
_i6.Future<_i2.ProfileModel> getProfileBySlug({required String? slug}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getProfileBySlug,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
returnValue: _i6.Future<_i2.ProfileModel>.value(_FakeProfileModel_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getProfileBySlug,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<_i2.ProfileModel>.value(_FakeProfileModel_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getProfileBySlug,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<_i2.ProfileModel>);
|
||||
|
||||
@override
|
||||
_i6.Future<_i2.ProfileModel> getMyProfile() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMyProfile,
|
||||
[],
|
||||
),
|
||||
returnValue: _i6.Future<_i2.ProfileModel>.value(_FakeProfileModel_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getMyProfile,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<_i2.ProfileModel>.value(_FakeProfileModel_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getMyProfile,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<_i2.ProfileModel>);
|
||||
|
||||
@override
|
||||
_i6.Future<_i2.ProfileDetailsModel> getMyProfileDetails() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMyProfileDetails,
|
||||
[],
|
||||
),
|
||||
returnValue: _i6.Future<_i2.ProfileDetailsModel>.value(
|
||||
_FakeProfileDetailsModel_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getMyProfileDetails,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub: _i6.Future<_i2.ProfileDetailsModel>.value(
|
||||
_FakeProfileDetailsModel_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getMyProfileDetails,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<_i2.ProfileDetailsModel>);
|
||||
|
||||
@override
|
||||
_i6.Future<_i2.ProfileDetailsModel> getProfileDetailsBySlug(
|
||||
{required String? slug}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getProfileDetailsBySlug,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
returnValue: _i6.Future<_i2.ProfileDetailsModel>.value(
|
||||
_FakeProfileDetailsModel_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getProfileDetailsBySlug,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub: _i6.Future<_i2.ProfileDetailsModel>.value(
|
||||
_FakeProfileDetailsModel_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getProfileDetailsBySlug,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<_i2.ProfileDetailsModel>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> addCircumference({
|
||||
required int? chestCircumference,
|
||||
required int? waistCircumference,
|
||||
required int? hipCircumference,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addCircumference,
|
||||
[],
|
||||
{
|
||||
#chestCircumference: chestCircumference,
|
||||
#waistCircumference: waistCircumference,
|
||||
#hipCircumference: hipCircumference,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> addWeight({required double? weight}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addWeight,
|
||||
[],
|
||||
{#weight: weight},
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
_i6.Future<List<_i7.FeedBlogPostModel>> getBlogPostByAuthorSlug(
|
||||
{required String? slug}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getBlogPostByAuthorSlug,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
returnValue: _i6.Future<List<_i7.FeedBlogPostModel>>.value(
|
||||
<_i7.FeedBlogPostModel>[]),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<List<_i7.FeedBlogPostModel>>.value(
|
||||
<_i7.FeedBlogPostModel>[]),
|
||||
) as _i6.Future<List<_i7.FeedBlogPostModel>>);
|
||||
|
||||
@override
|
||||
_i6.Future<List<_i8.WeightModel>> getWeight({required String? period}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getWeight,
|
||||
[],
|
||||
{#period: period},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<List<_i8.WeightModel>>.value(<_i8.WeightModel>[]),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<List<_i8.WeightModel>>.value(<_i8.WeightModel>[]),
|
||||
) as _i6.Future<List<_i8.WeightModel>>);
|
||||
|
||||
@override
|
||||
_i6.Future<List<_i8.WeightModel>> getWeightBySlug({
|
||||
required String? period,
|
||||
required String? slug,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getWeightBySlug,
|
||||
[],
|
||||
{
|
||||
#period: period,
|
||||
#slug: slug,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<List<_i8.WeightModel>>.value(<_i8.WeightModel>[]),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<List<_i8.WeightModel>>.value(<_i8.WeightModel>[]),
|
||||
) as _i6.Future<List<_i8.WeightModel>>);
|
||||
|
||||
@override
|
||||
_i6.Future<List<_i9.CircumferenceModel>> getCircumferences(
|
||||
{required String? period}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getCircumferences,
|
||||
[],
|
||||
{#period: period},
|
||||
),
|
||||
returnValue: _i6.Future<List<_i9.CircumferenceModel>>.value(
|
||||
<_i9.CircumferenceModel>[]),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<List<_i9.CircumferenceModel>>.value(
|
||||
<_i9.CircumferenceModel>[]),
|
||||
) as _i6.Future<List<_i9.CircumferenceModel>>);
|
||||
|
||||
@override
|
||||
_i6.Future<List<_i9.CircumferenceModel>> getCircumferencesBySlug({
|
||||
required String? period,
|
||||
required String? slug,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getCircumferencesBySlug,
|
||||
[],
|
||||
{
|
||||
#period: period,
|
||||
#slug: slug,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<_i9.CircumferenceModel>>.value(
|
||||
<_i9.CircumferenceModel>[]),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<List<_i9.CircumferenceModel>>.value(
|
||||
<_i9.CircumferenceModel>[]),
|
||||
) as _i6.Future<List<_i9.CircumferenceModel>>);
|
||||
|
||||
@override
|
||||
_i6.Future<_i3.SubPaginationModel> getSubscribers({
|
||||
required String? slug,
|
||||
String? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSubscribers,
|
||||
[],
|
||||
{
|
||||
#slug: slug,
|
||||
#query: query,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<_i3.SubPaginationModel>.value(_FakeSubPaginationModel_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getSubscribers,
|
||||
[],
|
||||
{
|
||||
#slug: slug,
|
||||
#query: query,
|
||||
},
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<_i3.SubPaginationModel>.value(_FakeSubPaginationModel_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getSubscribers,
|
||||
[],
|
||||
{
|
||||
#slug: slug,
|
||||
#query: query,
|
||||
},
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<_i3.SubPaginationModel>);
|
||||
|
||||
@override
|
||||
_i6.Future<_i3.SubPaginationModel> getSubscriptions({
|
||||
required String? slug,
|
||||
String? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSubscriptions,
|
||||
[],
|
||||
{
|
||||
#slug: slug,
|
||||
#query: query,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<_i3.SubPaginationModel>.value(_FakeSubPaginationModel_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getSubscriptions,
|
||||
[],
|
||||
{
|
||||
#slug: slug,
|
||||
#query: query,
|
||||
},
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<_i3.SubPaginationModel>.value(_FakeSubPaginationModel_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getSubscriptions,
|
||||
[],
|
||||
{
|
||||
#slug: slug,
|
||||
#query: query,
|
||||
},
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<_i3.SubPaginationModel>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> subscribe({required String? slug}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#subscribe,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> unsubscribe({required String? slug}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unsubscribe,
|
||||
[],
|
||||
{#slug: slug},
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> uploadPhoto({required _i10.File? photo}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#uploadPhoto,
|
||||
[],
|
||||
{#photo: photo},
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> deletePhoto() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#deletePhoto,
|
||||
[],
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
_i6.Future<_i4.ChosenDietsPaginationModel> getChosenDiets() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getChosenDiets,
|
||||
[],
|
||||
),
|
||||
returnValue: _i6.Future<_i4.ChosenDietsPaginationModel>.value(
|
||||
_FakeChosenDietsPaginationModel_3(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getChosenDiets,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<_i4.ChosenDietsPaginationModel>.value(
|
||||
_FakeChosenDietsPaginationModel_3(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getChosenDiets,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<_i4.ChosenDietsPaginationModel>);
|
||||
|
||||
@override
|
||||
_i6.Future<String> getPassKey({required String? json}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getPassKey,
|
||||
[],
|
||||
{#json: json},
|
||||
),
|
||||
returnValue: _i6.Future<String>.value(_i11.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getPassKey,
|
||||
[],
|
||||
{#json: json},
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<String>.value(_i11.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getPassKey,
|
||||
[],
|
||||
{#json: json},
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<String>);
|
||||
|
||||
@override
|
||||
_i6.Future<String> getMyPhone() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMyPhone,
|
||||
[],
|
||||
),
|
||||
returnValue: _i6.Future<String>.value(_i11.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getMyPhone,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub:
|
||||
_i6.Future<String>.value(_i11.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getMyPhone,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
) as _i6.Future<String>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> sendEmailCode() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#sendEmailCode,
|
||||
[],
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
_i6.Future<void> tryVerifyEmailByCode({required String? code}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#tryVerifyEmailByCode,
|
||||
[],
|
||||
{#code: code},
|
||||
),
|
||||
returnValue: _i6.Future<void>.value(),
|
||||
returnValueForMissingStub: _i6.Future<void>.value(),
|
||||
) as _i6.Future<void>);
|
||||
}
|
||||
Reference in New Issue
Block a user