forked from eatvio/eatvio_app
Merge pull request 'build 66' (#599) from main into test-flight
Reviewed-on: eatvio/eatvio_app#599
This commit is contained in:
@@ -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: |
|
||||
@@ -36,4 +60,4 @@ jobs:
|
||||
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 }}
|
||||
API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
|
||||
API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
|
||||
|
||||
@@ -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: |
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,3 +16,6 @@
|
||||
*.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`
|
||||
@@ -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">
|
||||
|
||||
@@ -396,7 +396,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 64;
|
||||
CURRENT_PROJECT_VERSION = 65;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -546,7 +546,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 64;
|
||||
CURRENT_PROJECT_VERSION = 65;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -584,7 +584,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 64;
|
||||
CURRENT_PROJECT_VERSION = 65;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
||||
@@ -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>64</string>
|
||||
<string>65</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -20,7 +20,13 @@ platform :ios do
|
||||
lane :beta do
|
||||
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")
|
||||
build_app(
|
||||
workspace: "Runner.xcworkspace",
|
||||
scheme: "Runner",
|
||||
export_method: "app-store",
|
||||
codesigning_identity: "Apple Distribution",
|
||||
build_args: "--dart-define=HIDE_PLANS=true"
|
||||
)
|
||||
app_store_connect_api_key(key_id: "325JS9JCZH", issuer_id: "dfa27d1d-2bb8-4d4d-847e-0b32f9b880b3", key_filepath: "./AuthKey.p8")
|
||||
upload_to_testflight
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
String baseUrl = 'https://eatvio.ru';
|
||||
bool hidePlans = true;
|
||||
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'] ?? 'https://eatvio.ru';
|
||||
static String get devBaseUrl => dotenv.env['DEV_BASE_URL'] ?? 'https://dev2.eatvio.com';
|
||||
|
||||
// Sentry Configuration
|
||||
static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2';
|
||||
|
||||
// Firebase Configuration
|
||||
static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? 'eatvio-3467b';
|
||||
static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? 'https://eatvio-3467b-default-rtdb.europe-west1.firebasedatabase.app';
|
||||
static String get firebaseStorageBucket => dotenv.env['FIREBASE_STORAGE_BUCKET'] ?? 'eatvio-3467b.firebasestorage.app';
|
||||
static String get firebaseMessagingSenderId => dotenv.env['FIREBASE_MESSAGING_SENDER_ID'] ?? '613980912922';
|
||||
|
||||
// Firebase Android Configuration
|
||||
static String get firebaseAndroidApiKey => dotenv.env['FIREBASE_ANDROID_API_KEY'] ?? 'AIzaSyBUe1UqAAMfgMcd4hWdRoLF1Os-yn-HE9k';
|
||||
static String get firebaseAndroidAppId => dotenv.env['FIREBASE_ANDROID_APP_ID'] ?? '1:613980912922:android:4de95e5f2d50ba69ecc0a3';
|
||||
|
||||
// Firebase iOS Configuration
|
||||
static String get firebaseIosApiKey => dotenv.env['FIREBASE_IOS_API_KEY'] ?? 'AIzaSyBnE8tQjL9_1HuXrTnqNy6qbXpoenO36yQ';
|
||||
static String get firebaseIosAppId => dotenv.env['FIREBASE_IOS_APP_ID'] ?? '1:613980912922:ios:c557d140a1212e63ecc0a3';
|
||||
static String get firebaseIosBundleId => dotenv.env['FIREBASE_IOS_BUNDLE_ID'] ?? 'com.eatvioapp.app';
|
||||
|
||||
// App Store URLs
|
||||
static String get appStoreUrl => dotenv.env['APP_STORE_URL'] ?? 'https://apps.apple.com/app/idYOUR_APP_ID';
|
||||
static String get rustoreUrl => dotenv.env['RUSTORE_URL'] ?? 'https://www.rustore.ru/catalog/app/com.eatvioapp.app';
|
||||
|
||||
// Telegram Support
|
||||
static String get telegramSupportUrl => dotenv.env['TELEGRAM_SUPPORT_URL'] ?? 'https://t.me/eatvio';
|
||||
static String get supportEmail => dotenv.env['SUPPORT_EMAIL'] ?? 'help@eatv.io';
|
||||
|
||||
// Test Credentials (for development only)
|
||||
static String get testEmail => dotenv.env['TEST_EMAIL'] ?? 'reshetnick.mike+user1@gmail.com';
|
||||
static String get testPassword => dotenv.env['TEST_PASSWORD'] ?? '1234567890';
|
||||
|
||||
// Email Validation API
|
||||
static String get emailValidationApiUrl => dotenv.env['EMAIL_VALIDATION_API_URL'] ?? 'https://api.validate.email/validate';
|
||||
|
||||
// Connection Check URLs
|
||||
static String get googleCheckUrl => dotenv.env['GOOGLE_CHECK_URL'] ?? 'https://google.com';
|
||||
static String get yandexCheckUrl => dotenv.env['YANDEX_CHECK_URL'] ?? 'https://yandex.ru';
|
||||
}
|
||||
@@ -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,35 +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: {'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),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -333,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)),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -360,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)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -37,18 +37,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),
|
||||
|
||||
@@ -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,11 +114,17 @@ class DiaryReminderNotification implements BaseNotificationModel {
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
DiaryReminderNotification({required this.type, required this.isRead});
|
||||
DiaryReminderNotification({required this.type, required this.isRead, required this.createdAt});
|
||||
|
||||
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
return DiaryReminderNotification(type: type, isRead: isRead ?? false);
|
||||
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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -141,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -161,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -182,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -204,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -228,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -252,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -277,21 +300,25 @@ 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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -301,16 +328,212 @@ class ChosenPlanLastDayFinishedForUser implements BaseNotificationModel {
|
||||
final NotificationType type;
|
||||
@override
|
||||
final bool isRead;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
ChosenPlanLastDayFinishedForUser({
|
||||
required this.type,
|
||||
required this.isRead,
|
||||
});
|
||||
ChosenPlanLastDayFinishedForUser({required this.type, required this.isRead, required this.createdAt});
|
||||
|
||||
factory ChosenPlanLastDayFinishedForUser.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||
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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -320,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 {
|
||||
@@ -342,23 +564,37 @@ enum NotificationType {
|
||||
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,
|
||||
'App\\Notifications\\ChosenPlan\\LastDayFinishedForUser' => NotificationType.chosenPlanLastDayFinishedForUser,
|
||||
_ => 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,22 +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.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.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) {
|
||||
@@ -56,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: 1);
|
||||
_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,46 +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,
|
||||
),
|
||||
ChosenPlanLastDayFinishedForUser() => Text((notification as ChosenPlanLastDayFinishedForUser).type.toString()),
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -69,6 +62,7 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -83,6 +77,7 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -94,10 +89,7 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.planStarted(notification.planName),
|
||||
);
|
||||
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.planStarted(notification.planName), createdAt: notification.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +103,7 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.tomorrowYourPlanWillStart(notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -121,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +128,7 @@ class _ConsultantLeftCommentWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.consultantLeftComment(notification.date.format('dd.MM.yyyy')),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -147,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +152,7 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -175,6 +167,7 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.youCanceledSubscriptionToPlan(notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -189,6 +182,7 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.planWithUserStarted(notification.planName, notification.customerName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -203,6 +197,7 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -217,6 +212,7 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget {
|
||||
return _BaseNotificationWidget(
|
||||
isRead: notification.isRead,
|
||||
notificationsText: s.userPaidThePlan(notification.customerName, notification.planName),
|
||||
createdAt: notification.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -227,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,53 +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.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,
|
||||
),
|
||||
NotificationType.unknown => UnknownNotification(type: type),
|
||||
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) {
|
||||
@@ -67,3 +151,5 @@ class NotificationsService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DateTime parseDate(String date) => DateTime.parse(date).toLocal();
|
||||
|
||||
@@ -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;
|
||||
@@ -83,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 {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -12,6 +13,7 @@ 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';
|
||||
@@ -19,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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: eatvio_app
|
||||
description: A new Flutter project.
|
||||
version: 1.2.6+64
|
||||
version: 1.2.7+66
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
@@ -72,6 +72,7 @@ dependencies:
|
||||
device_info_plus: ^11.5.0
|
||||
permission_handler: ^12.0.1
|
||||
qr_code_scanner_plus: ^2.0.10+1
|
||||
flutter_dotenv: ^5.1.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.6.0
|
||||
@@ -88,6 +89,7 @@ dev_dependencies:
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- env/.env
|
||||
- assets/
|
||||
- assets/images/
|
||||
- assets/audio/
|
||||
|
||||
Reference in New Issue
Block a user