forked from eatvio/eatvio_app
Compare commits
88 Commits
diary
...
consultant
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96e80ced38 | ||
|
|
fa1192c5e5 | ||
|
|
41f948c8f7 | ||
|
|
896b2783ae | ||
|
|
9c004f8593 | ||
|
|
a577a8d58a | ||
|
|
3b2c4fdff5 | ||
|
|
44d6487709 | ||
|
|
319fd2dfe3 | ||
|
|
a98043758b | ||
|
|
a84d2d7daa | ||
|
|
4acb861b69 | ||
|
|
f163608a90 | ||
| 6bbec1f022 | |||
|
|
c0311bf213 | ||
|
|
9d1e621769 | ||
|
|
11b695cefd | ||
|
|
926f0de450 | ||
|
|
dc81085ae4 | ||
|
|
8c0efc1a34 | ||
|
|
2c3cc199c0 | ||
|
|
2584a70a09 | ||
|
|
483cf6d8ee | ||
|
|
87d3f058f6 | ||
|
|
7a3c6e6b07 | ||
|
|
e6bad9d35d | ||
|
|
72cf203658 | ||
|
|
086fd1cac4 | ||
| 88e33765d3 | |||
|
|
6fa4bffd7e | ||
|
|
70620d8d9e | ||
|
|
0b2464ea19 | ||
|
|
69359b01c5 | ||
|
|
ff40e74491 | ||
|
|
0270c940fe | ||
|
|
cacfaff4cf | ||
|
|
e728b0951a | ||
|
|
cf6ad1eafe | ||
|
|
bbba15b3c7 | ||
|
|
c5e0d233f0 | ||
|
|
da8d21bb08 | ||
|
|
c361e12d35 | ||
|
|
7c354cc0a3 | ||
|
|
f0a184b800 | ||
|
|
8a9a913420 | ||
|
|
74ecbbdbff | ||
|
|
c95b610c35 | ||
|
|
35fc032d31 | ||
|
|
1cb824573b | ||
|
|
77c52a50e1 | ||
|
|
7b05c7bd36 | ||
|
|
27cea212b0 | ||
|
|
252538f52d | ||
|
|
88bf79def4 | ||
|
|
7f29ade548 | ||
|
|
d405be1ea4 | ||
|
|
10ef59f6ea | ||
|
|
ee7b03c504 | ||
|
|
9f0ce55d47 | ||
|
|
5d1a1dccc8 | ||
|
|
a90efa8caa | ||
|
|
1db528838b | ||
|
|
3addd9d238 | ||
|
|
d645910554 | ||
|
|
820b4d0a8b | ||
|
|
cd4ca7288c | ||
|
|
ac46cd159b | ||
|
|
dc1e8d5b71 | ||
|
|
db73865278 | ||
|
|
2cd4054f29 | ||
|
|
b9611bb5a1 | ||
|
|
036aca7d08 | ||
|
|
8f9aeb7b63 | ||
|
|
38031c89c2 | ||
| 64bf6e33d7 | |||
|
|
b80b4e3a9e | ||
|
|
566a96b18e | ||
|
|
242cb474cc | ||
|
|
67bf7b0510 | ||
|
|
51d3c1ad2f | ||
|
|
f38eead280 | ||
|
|
cf5b459756 | ||
|
|
29b3de325c | ||
|
|
1e1005a09c | ||
|
|
7df1323f7d | ||
|
|
8f70e9620b | ||
|
|
50b63fea7d | ||
|
|
1dfaa7cd93 |
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
|
|||||||
on: [ pull_request ]
|
on: [ pull_request ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.29.1"
|
flutter_version: "3.32.0"
|
||||||
java_version: "21.x"
|
java_version: "21.x"
|
||||||
flutter_channel: "stable"
|
flutter_channel: "stable"
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: Eatvio App Android Test on custom docker
|
|||||||
on: [ pull_request ]
|
on: [ pull_request ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.29.1"
|
flutter_version: "3.32.0"
|
||||||
java_version: "21.x"
|
java_version: "21.x"
|
||||||
flutter_channel: "stable"
|
flutter_channel: "stable"
|
||||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
my_local_cache_dir: "/opt/hostedtoolcache/"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
|
|||||||
on: [ pull_request ]
|
on: [ pull_request ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.29.3"
|
flutter_version: "3.32.0"
|
||||||
java_version: "21.x"
|
java_version: "21.x"
|
||||||
flutter_channel: "stable"
|
flutter_channel: "stable"
|
||||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
my_local_cache_dir: "/opt/hostedtoolcache/"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ plugins {
|
|||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.7.3" apply false
|
id "com.android.application" version "8.7.3" apply false
|
||||||
// START: FlutterFire Configuration
|
// START: FlutterFire Configuration
|
||||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
id "com.google.gms.google-services" version "4.4.2" apply false
|
||||||
// END: FlutterFire Configuration
|
// END: FlutterFire Configuration
|
||||||
id "org.jetbrains.kotlin.android" version "2.0.21" apply false
|
id "org.jetbrains.kotlin.android" version "2.0.21" apply false
|
||||||
}
|
}
|
||||||
|
|||||||
1
assets/lottie_animations/loading.json
Normal file
1
assets/lottie_animations/loading.json
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1,2 +1,3 @@
|
|||||||
extensions:
|
extensions:
|
||||||
- provider: true
|
- provider: true
|
||||||
|
- shared_preferences: true
|
||||||
11
ios/Podfile
11
ios/Podfile
@@ -40,6 +40,17 @@ post_install do |installer|
|
|||||||
target.build_configurations.each do |config|
|
target.build_configurations.each do |config|
|
||||||
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
||||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
|
||||||
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
|
'$(inherited)',
|
||||||
|
## dart: PermissionGroup.camera
|
||||||
|
'PERMISSION_CAMERA=1',
|
||||||
|
## dart: PermissionGroup.photos
|
||||||
|
'PERMISSION_PHOTOS=1',
|
||||||
|
'PERMISSION_NOTIFICATIONS=1',
|
||||||
|
## dart: PermissionGroup.mediaLibrary
|
||||||
|
'PERMISSION_MEDIA_LIBRARY=1',
|
||||||
|
]
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -388,7 +388,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 16;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -538,7 +538,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 16;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -576,7 +576,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 16;
|
CURRENT_PROJECT_VERSION = 27;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>16</string>
|
<string>27</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
@@ -32,11 +32,11 @@
|
|||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Нужно для загрузки фото</string>
|
<string>Предоставление доступа позволит Вам делать фотографии и добавлять к записям</string>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>Нужно для загрузки фото</string>
|
<string>Предоставление доступа позволит Вам добавлять фото к записям</string>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
||||||
@@ -16,6 +20,8 @@ import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.da
|
|||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
||||||
@@ -32,7 +38,7 @@ import 'package:eatvio_app/features/glossary/presentation/pages/glossary_screen.
|
|||||||
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
|
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
|
||||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/presentation/pages/consultant_finance_screen.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
|
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
|
||||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
|
||||||
@@ -40,9 +46,11 @@ import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
|||||||
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||||
import 'package:eatvio_app/features/recipe/presentation/about_recipe_screen.dart';
|
import 'package:eatvio_app/features/recipe/presentation/pages/about_recipe_screen.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
|
||||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||||
import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart';
|
import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart';
|
||||||
@@ -76,8 +84,11 @@ class AppRouter {
|
|||||||
name: 'authScreen',
|
name: 'authScreen',
|
||||||
builder: (context, state) => const AuthScreen(),
|
builder: (context, state) => const AuthScreen(),
|
||||||
routes: [
|
routes: [
|
||||||
// GoRoute(path: 'webView-registration-screen', name: 'webViewRegistrationScreen', builder: (context, state) => const WebViewRegistrationScreen()),
|
GoRoute(
|
||||||
GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
path: 'registration-screen',
|
||||||
|
name: 'registrationScreen',
|
||||||
|
builder: (context, state) => const RegistrationScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
@@ -95,10 +106,6 @@ class AppRouter {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/my-profile-screen',
|
path: '/my-profile-screen',
|
||||||
name: 'myProfileScreen',
|
name: 'myProfileScreen',
|
||||||
redirect: (context, state) async {
|
|
||||||
await sl<WsManager>().initialize();
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
return const NoTransitionPage(child: ProfileScreen());
|
return const NoTransitionPage(child: ProfileScreen());
|
||||||
},
|
},
|
||||||
@@ -108,11 +115,21 @@ class AppRouter {
|
|||||||
name: 'feedScreen',
|
name: 'feedScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
|
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
await sl<WsManager>().initialize();
|
try {
|
||||||
return null;
|
await sl<WsManager>().initialize();
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
return '/feed-screen';
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(path: '/diary-screen', name: 'diaryScreen', pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen())),
|
GoRoute(
|
||||||
|
path: '/diary-screen',
|
||||||
|
name: 'diaryScreen',
|
||||||
|
pageBuilder: (context, state) => const NoTransitionPage(
|
||||||
|
child: DiaryScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: ConsultationsScreen.path,
|
path: ConsultationsScreen.path,
|
||||||
name: ConsultationsScreen.name,
|
name: ConsultationsScreen.name,
|
||||||
@@ -143,6 +160,23 @@ class AppRouter {
|
|||||||
name: 'welcomeScreen',
|
name: 'welcomeScreen',
|
||||||
builder: (context, state) => const WelcomeScreen(),
|
builder: (context, state) => const WelcomeScreen(),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
|
try {
|
||||||
|
if (await TokenStorage.haveTokenInStorage()) {
|
||||||
|
final firebaseApi = sl<FirebaseApi>();
|
||||||
|
final fbToken = await firebaseApi.getToken();
|
||||||
|
final deviceInfo = DeviceInfoPlugin();
|
||||||
|
await sl<Dio>().post(
|
||||||
|
'/api/profile/notification/token/register',
|
||||||
|
data: {
|
||||||
|
'platform': Platform.isIOS ? 'ios': 'android',
|
||||||
|
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||||||
|
'token': fbToken,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed');
|
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed');
|
||||||
if (welcomeScreenWasShowed == true) {
|
if (welcomeScreenWasShowed == true) {
|
||||||
@@ -170,9 +204,19 @@ class AppRouter {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/consultant-finance-screen',
|
path: ConsultationsArchiveScreen.path,
|
||||||
name: 'consultantFinanceScreen',
|
name: ConsultationsArchiveScreen.name,
|
||||||
builder: (context, state) => const ConsultantFinanceScreen(),
|
builder: (context, state) => const ConsultationsArchiveScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: ConsultantFinancesScreen.path,
|
||||||
|
name: ConsultantFinancesScreen.name,
|
||||||
|
builder: (context, state) => const ConsultantFinancesScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: UserPurchasesScreen.path,
|
||||||
|
name: UserPurchasesScreen.name,
|
||||||
|
builder: (context, state) => const UserPurchasesScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/user-finance-screen',
|
path: '/user-finance-screen',
|
||||||
@@ -201,7 +245,11 @@ class AppRouter {
|
|||||||
name: 'aboutRecipesScreen',
|
name: 'aboutRecipesScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return AboutRecipeScreen(recipe: data['recipe'] as Recipe, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
return AboutRecipeScreen(
|
||||||
|
recipe: data['recipe'] as Recipe,
|
||||||
|
index: data['index'] as int?,
|
||||||
|
navigateToComments: data['navigateToComments'] as bool,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -284,7 +332,12 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
GoRoute(path: '/become-consultant-screen', name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
|
GoRoute(path: '/become-consultant-screen', name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/stepper-become-consultant-screen', name: 'stepperBecomeConsultantScreen', builder: (context, state) => const StepperBecomeConsultantScreen()),
|
path: '/stepper-become-consultant-screen',
|
||||||
|
name: 'stepperBecomeConsultantScreen',
|
||||||
|
builder: (context, state) => StepperBecomeConsultantScreen(
|
||||||
|
slug: state.extra == null ? null : state.extra as String,
|
||||||
|
),
|
||||||
|
),
|
||||||
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
||||||
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -344,9 +397,12 @@ class AppRouter {
|
|||||||
path: UserDiaryScreen.path,
|
path: UserDiaryScreen.path,
|
||||||
name: UserDiaryScreen.name,
|
name: UserDiaryScreen.name,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final diaryId = state.extra as int;
|
final data = state.extra as Map<String, int>;
|
||||||
|
final diaryId = data['diaryId'] as int;
|
||||||
|
final currentPlanDay = data['currentDiaryDay'] as int;
|
||||||
return UserDiaryScreen(
|
return UserDiaryScreen(
|
||||||
diaryId: diaryId,
|
diaryId: diaryId,
|
||||||
|
currentPlanDay: currentPlanDay,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -355,6 +411,16 @@ class AppRouter {
|
|||||||
name: 'verifyScreen',
|
name: 'verifyScreen',
|
||||||
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
|
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/verify-email-screen',
|
||||||
|
name: 'verifyEmailScreen',
|
||||||
|
builder: (context, state) => const VerifyEmailScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: MyRecipesScreen.path,
|
||||||
|
name: MyRecipesScreen.name,
|
||||||
|
builder: (context, state) => const MyRecipesScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@@ -13,6 +14,8 @@ class PhoneVerifyErrorInterceptor extends Interceptor {
|
|||||||
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
|
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
|
||||||
final String phone = await sl<ProfileRepository>().getMyPhone();
|
final String phone = await sl<ProfileRepository>().getMyPhone();
|
||||||
final String passKey = await sl<ProfileRepository>().getPassKey(phone);
|
final String passKey = await sl<ProfileRepository>().getPassKey(phone);
|
||||||
|
await showErrorDialog(AppRouter.navigatorKey.currentContext!,
|
||||||
|
title: 'Привяжите номер телефона', description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона');
|
||||||
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
|
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
|
||||||
return handler.reject(
|
return handler.reject(
|
||||||
DioException(
|
DioException(
|
||||||
|
|||||||
13
lib/core/enums/filters_comparison_enum.dart
Normal file
13
lib/core/enums/filters_comparison_enum.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
enum FiltersComparisonEnum {
|
||||||
|
equal('\$eq'),
|
||||||
|
notEqual('\$ne'),
|
||||||
|
greaterThan('\$gt'),
|
||||||
|
lessThan('\$lt'),
|
||||||
|
greaterThanOrEqual('\$gte'),
|
||||||
|
lessThanOrEqual('\$lte'),
|
||||||
|
inArray('\$in'),
|
||||||
|
notInArray('\$notIn');
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
const FiltersComparisonEnum(this.value);
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
|
||||||
class FirebaseApi {
|
class FirebaseApi {
|
||||||
|
|
||||||
final _firebaseMessaging = FirebaseMessaging.instance;
|
final _firebaseMessaging = FirebaseMessaging.instance;
|
||||||
|
|
||||||
Future<String?> getToken() async{
|
Future<String?> getToken() async {
|
||||||
await _firebaseMessaging.requestPermission();
|
await _firebaseMessaging.requestPermission();
|
||||||
return await _firebaseMessaging.getToken();
|
return await _firebaseMessaging.getToken();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
|
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
|
||||||
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||||
@@ -13,9 +14,13 @@ import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan
|
|||||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
||||||
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||||
@@ -29,6 +34,8 @@ import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit
|
|||||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
||||||
@@ -81,9 +88,13 @@ import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recip
|
|||||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
||||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
|
||||||
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
|
||||||
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
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/presentation/bloc/registration_cubit/registration_cubit.dart';
|
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
||||||
@@ -115,9 +126,16 @@ Future<void> init() async {
|
|||||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
|
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
|
||||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
||||||
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl()));
|
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl()));
|
||||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
||||||
sl.registerFactory<DiaryCubit>(() => DiaryCubit(diaryRepository: sl(), glossaryRepository: sl(), profileRepository: sl(), dietRepository: sl()));
|
sl.registerFactory<DiaryCubit>(() => DiaryCubit(
|
||||||
|
chatRepository: sl(),
|
||||||
|
diaryRepository: sl(),
|
||||||
|
glossaryRepository: sl(),
|
||||||
|
profileRepository: sl(),
|
||||||
|
dietRepository: sl(),
|
||||||
|
settingsRepository: sl(),
|
||||||
|
));
|
||||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
||||||
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
||||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||||
@@ -138,6 +156,12 @@ Future<void> init() async {
|
|||||||
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
|
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
|
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
|
||||||
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
|
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
|
||||||
|
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
|
||||||
|
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
|
||||||
|
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
|
||||||
|
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
|
||||||
|
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
|
||||||
|
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
|
||||||
|
|
||||||
// Repository
|
// Repository
|
||||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||||
@@ -165,6 +189,8 @@ Future<void> init() async {
|
|||||||
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
|
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
|
||||||
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
|
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
|
||||||
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
|
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
|
||||||
|
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
|
||||||
|
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||||
@@ -193,6 +219,9 @@ Future<void> init() async {
|
|||||||
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
||||||
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
||||||
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
||||||
|
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
||||||
|
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
||||||
|
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
||||||
|
|
||||||
// External
|
// External
|
||||||
sl.registerLazySingleton(
|
sl.registerLazySingleton(
|
||||||
|
|||||||
23
lib/core/permission_handler/permission_handler.dart
Normal file
23
lib/core/permission_handler/permission_handler.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
class PermissionHandler {
|
||||||
|
static Future<void> checkPermission({
|
||||||
|
required Permission permission,
|
||||||
|
required String title,
|
||||||
|
required String description,
|
||||||
|
required Future<void> Function() onGranted,
|
||||||
|
required BuildContext context,
|
||||||
|
}) async {
|
||||||
|
final permissionState = await permission.request();
|
||||||
|
if (permissionState.isGranted) {
|
||||||
|
await onGranted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
showErrorDialog(context, title: title, description: description, buttonTitle: S.of(context).allowAccess, needToOpenSettings: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,7 +102,6 @@ class PrimaryColors {
|
|||||||
Color get blue400 => const Color(0XFF43A6F2);
|
Color get blue400 => const Color(0XFF43A6F2);
|
||||||
|
|
||||||
Color get blueA400 => const Color(0XFF2F80ED);
|
Color get blueA400 => const Color(0XFF2F80ED);
|
||||||
|
|
||||||
// BlueGray
|
// BlueGray
|
||||||
Color get blueGray400 => const Color(0XFF7B8794);
|
Color get blueGray400 => const Color(0XFF7B8794);
|
||||||
|
|
||||||
|
|||||||
@@ -8,31 +8,39 @@ extension DateTimeExtension on DateTime {
|
|||||||
String pattern = dateTimeFormatPattern,
|
String pattern = dateTimeFormatPattern,
|
||||||
String? locale,
|
String? locale,
|
||||||
]) {
|
]) {
|
||||||
|
final localDate = isUtc ? toLocal() : this;
|
||||||
if (locale != null && locale.isNotEmpty) {
|
if (locale != null && locale.isNotEmpty) {
|
||||||
initializeDateFormatting(locale);
|
initializeDateFormatting(locale);
|
||||||
}
|
}
|
||||||
return DateFormat(pattern, locale).format(this);
|
return DateFormat(pattern, locale).format(localDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isYesterday(DateTime date) {
|
bool isYesterday() {
|
||||||
final DateTime localDate = date.isUtc ? date.toLocal() : date;
|
final localDate = isUtc ? toLocal() : this;
|
||||||
final yesterday = DateTime.now().subtract(const Duration(days: 1));
|
|
||||||
return yesterday.day == localDate.day && yesterday.month == localDate.month && yesterday.year == localDate.year;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isToday(DateTime date) {
|
|
||||||
final DateTime localDate = date.isUtc ? date.toLocal() : date;
|
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
return now.day == localDate.day && now.month == localDate.month && now.year == localDate.year;
|
final yesterday = DateTime(now.year, now.month, now.day).subtract(const Duration(days: 1));
|
||||||
|
return localDate.year == yesterday.year &&
|
||||||
|
localDate.month == yesterday.month &&
|
||||||
|
localDate.day == yesterday.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isToday() {
|
||||||
|
final localDate = isUtc ? toLocal() : this;
|
||||||
|
final now = DateTime.now();
|
||||||
|
return now.year == localDate.year &&
|
||||||
|
now.month == localDate.month &&
|
||||||
|
now.day == localDate.day;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String calculateDifference(DateTime dateTime) {
|
String calculateDifference(DateTime dateTime) {
|
||||||
if (dateTime.isToday(dateTime)) {
|
final localDateTime = dateTime.isUtc ? dateTime.toLocal() : dateTime;
|
||||||
|
|
||||||
|
if (localDateTime.isToday()) {
|
||||||
return 'Сегодня';
|
return 'Сегодня';
|
||||||
} else if (dateTime.isYesterday(dateTime)) {
|
} else if (localDateTime.isYesterday()) {
|
||||||
return 'Вчера';
|
return 'Вчера';
|
||||||
} else {
|
} else {
|
||||||
return dateTime.format('d MMMM', 'ru');
|
return localDateTime.format('d MMMM', 'ru');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,4 +135,16 @@ class WsManager {
|
|||||||
void unsubscribeDirectChatChannel() {
|
void unsubscribeDirectChatChannel() {
|
||||||
_privateDirectChatChannel?.unsubscribe();
|
_privateDirectChatChannel?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> clear() async{
|
||||||
|
await client?.disconnect();
|
||||||
|
client = null;
|
||||||
|
userId = null;
|
||||||
|
_privateDirectChatChannel?.unsubscribe();
|
||||||
|
_privateMessagesChannel?.unsubscribe();
|
||||||
|
_privateNotificationChannel?.unsubscribe();
|
||||||
|
_privateDirectChatChannel = null;
|
||||||
|
_privateMessagesChannel = null;
|
||||||
|
_privateNotificationChannel = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,11 @@ import 'package:eatvio_app/core/utils/toasts.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
|
||||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
|
||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
|
||||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -100,37 +97,10 @@ class CustomPopup extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addItem('assets/icons/edit_paint.svg', 'Редактировать', () async {
|
if (complainedContentType == ComplainedContent.posts) {
|
||||||
if (complainedContentType == ComplainedContent.posts) {
|
addItem('assets/icons/edit_paint.svg', 'Редактировать', () async {
|
||||||
GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedBlogPostModel);
|
if (complainedContentType == ComplainedContent.posts) {
|
||||||
} else if (complainedContentType == ComplainedContent.recipe) {
|
GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedBlogPostModel);
|
||||||
GoRouter.of(context).pushNamed(RecipeCreateScreen.name, extra: recipe);
|
|
||||||
} else if (complainedContentType == ComplainedContent.diet) {
|
|
||||||
await showErrorDialog(context,
|
|
||||||
title: 'Предупреждение', description: 'Диета будет перенесена в архив. Вы создаете новую диету на основе существующей.');
|
|
||||||
await sl<MyDietsCubit>().unpublishDiet(id!);
|
|
||||||
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: diet);
|
|
||||||
} else {
|
|
||||||
showInfoToast('В разработке');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (complainedContentType == ComplainedContent.plan || complainedContentType == ComplainedContent.diet) {
|
|
||||||
addItem('assets/icons/remove_from_public.svg', 'Снять с публикации', () async {
|
|
||||||
if (complainedContentType == ComplainedContent.diet) {
|
|
||||||
final result = await sl<MyDietsCubit>().unpublishDiet(id!);
|
|
||||||
if (!result) {
|
|
||||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showSuccessToast('Диета перенесена в архив, обновите ленту');
|
|
||||||
} else if (complainedContentType == ComplainedContent.plan) {
|
|
||||||
final result = await sl<MyPlansCubit>().unpublish(slug!);
|
|
||||||
if (!result) {
|
|
||||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showSuccessToast('План перенесен в архив, обновите ленту');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -145,21 +115,21 @@ class CustomPopup extends StatelessWidget {
|
|||||||
} else if (complainedContentType == ComplainedContent.recipe) {
|
} else if (complainedContentType == ComplainedContent.recipe) {
|
||||||
final result = await sl<RecipesCubit>().deleteRecipe(slug!);
|
final result = await sl<RecipesCubit>().deleteRecipe(slug!);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
showErrorDialog(context, title: 'Не удалось удалить рецепт');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSuccessToast('Рецепт удален, обновите ленту');
|
showSuccessToast('Рецепт удален, обновите ленту');
|
||||||
} else if (complainedContentType == ComplainedContent.diet) {
|
} else if (complainedContentType == ComplainedContent.diet) {
|
||||||
final result = await sl<DietsCubit>().deleteDiet(id!);
|
final result = await sl<DietsCubit>().deleteDiet(id!);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
showErrorDialog(context, title: 'Не удалось удалить диету');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSuccessToast('Диета удалена, обновите ленту');
|
showSuccessToast('Диета удалена, обновите ленту');
|
||||||
} else if (complainedContentType == ComplainedContent.plan) {
|
} else if (complainedContentType == ComplainedContent.plan) {
|
||||||
final result = await sl<MyPlansCubit>().deletePlan(slug!);
|
final result = await sl<MyPlansCubit>().deletePlan(slug!);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
showErrorDialog(context, title: 'Не удалось удалить план');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSuccessToast('План удален, обновите ленту');
|
showSuccessToast('План удален, обновите ленту');
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:rive/rive.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
class Loader extends StatelessWidget {
|
class Loader extends StatelessWidget {
|
||||||
const Loader({super.key});
|
const Loader({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Center(
|
return Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 32,
|
height: 42,
|
||||||
width: 32,
|
width: 42,
|
||||||
child: RiveAnimation.asset(
|
child: Lottie.asset(
|
||||||
'assets/rive_animations/loading.riv',
|
'assets/lottie_animations/loading.json',
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
|||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
Future<void> showErrorDialog(BuildContext context, {String? title, String? description}) async {
|
Future<void> showErrorDialog(BuildContext context, {String? title, String? description, String? buttonTitle, bool? needToOpenSettings}) async {
|
||||||
return await showDialog<void>(
|
return await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierColor: Colors.transparent,
|
barrierColor: Colors.transparent,
|
||||||
@@ -43,8 +44,13 @@ Future<void> showErrorDialog(BuildContext context, {String? title, String? descr
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => AppRouter.navigatorKey.currentContext?.pop(),
|
onPressed: () {
|
||||||
child: Text(s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
if (needToOpenSettings ?? false) {
|
||||||
|
openAppSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(buttonTitle ?? s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.dart';
|
||||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
class AuthCubit extends Cubit<AuthState> {
|
class AuthCubit extends Cubit<AuthState> {
|
||||||
final AuthRepository authRepository;
|
final AuthRepository authRepository;
|
||||||
@@ -28,9 +27,7 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
token: token,
|
token: token,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
showSuccessToast('Успешный вход');
|
|
||||||
await TokenStorage.saveTokenInStorage(token);
|
await TokenStorage.saveTokenInStorage(token);
|
||||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen');
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_debounce/easy_debounce.dart';
|
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@@ -20,8 +23,6 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../bloc/auth_state.dart';
|
|
||||||
|
|
||||||
class AuthScreen extends StatefulWidget {
|
class AuthScreen extends StatefulWidget {
|
||||||
const AuthScreen({super.key});
|
const AuthScreen({super.key});
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
late StreamSubscription<bool> keyboardSubscription;
|
late StreamSubscription<bool> keyboardSubscription;
|
||||||
bool isFocused = false;
|
bool isFocused = false;
|
||||||
int tapsCount = 0;
|
int tapsCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -49,6 +51,20 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _reconnectWebSocket(BuildContext context) async {
|
||||||
|
await sl<WsManager>().clear();
|
||||||
|
await sl<WsManager>().initialize();
|
||||||
|
if (context.mounted) {
|
||||||
|
context.read<ProfileAvatarCubit>().getMyProfile();
|
||||||
|
context.read<ChatsCubit>().getChatRoomsWithLoading();
|
||||||
|
context.read<ChatsCubit>().connectToWebSocket();
|
||||||
|
context.read<NotificationsCubit>().getNotifications();
|
||||||
|
context.read<NotificationsCubit>().connectToWebSocket();
|
||||||
|
showSuccessToast('Успешный вход');
|
||||||
|
GoRouter.of(context).go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
@@ -66,11 +82,8 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
tapsCount++;
|
tapsCount++;
|
||||||
if (tapsCount == 6) {
|
if (tapsCount == 6) {
|
||||||
tapsCount = 0;
|
tapsCount = 0;
|
||||||
// showModalBottomSheet(context: context, builder: (context) => );
|
|
||||||
}
|
}
|
||||||
EasyDebounce.debounce('change env', Durations.medium4, () {
|
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||||
tapsCount = 0;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
ImageConstant.imgLogoEatvio,
|
ImageConstant.imgLogoEatvio,
|
||||||
@@ -136,7 +149,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
child: RichText(
|
child: RichText(
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: 'Нажимая "Войти", Вы соглашаетесь \n',
|
text: 'Нажимая "Войти" или "Зарегистрироваться", Вы соглашаетесь \n',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@@ -153,6 +166,17 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
),
|
),
|
||||||
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
|
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
|
||||||
),
|
),
|
||||||
|
const TextSpan(text: ' и '),
|
||||||
|
TextSpan(
|
||||||
|
text: 'Политикой конфиденциальности\nи защитой персональных данных',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
decorationColor: appTheme.blueA400,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -179,8 +203,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
);
|
);
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.read<ProfileAvatarCubit>().getMyProfile();
|
_reconnectWebSocket(context);
|
||||||
context.read<ChatsCubit>().getChatRoomsWithLoading();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
foregroundImage: NetworkImage(
|
foregroundImage: NetworkImage(
|
||||||
widget.blogPost.author.authorProfileIcon,
|
baseUrl + widget.blogPost.author.authorProfileIcon,
|
||||||
),
|
),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
),
|
),
|
||||||
@@ -215,7 +215,10 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
|||||||
child: Html(data: widget.blogPost.description, style: {"*": Style(color: Colors.black87)}),
|
child: Html(data: widget.blogPost.description, style: {"*": Style(color: Colors.black87)}),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CachedNetworkImage(imageUrl: '$baseUrl/storage/${widget.blogPost.image}'),
|
if (widget.blogPost.image.isNotEmpty)
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: '$baseUrl/storage/${widget.blogPost.image}',
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||||
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
||||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||||
@@ -17,6 +19,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class BlogPostCreateScreen extends StatefulWidget {
|
class BlogPostCreateScreen extends StatefulWidget {
|
||||||
final FeedBlogPostModel? feedBlogPostModel;
|
final FeedBlogPostModel? feedBlogPostModel;
|
||||||
@@ -80,47 +83,65 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
|||||||
_showImagePicker() {
|
_showImagePicker() {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CupertinoActionSheet(
|
builder: (context) {
|
||||||
actions: [
|
final s = S.of(context);
|
||||||
CupertinoActionSheetAction(
|
return CupertinoActionSheet(
|
||||||
onPressed: () async {
|
actions: [
|
||||||
final picker = ImagePicker();
|
CupertinoActionSheetAction(
|
||||||
image = await picker.pickImage(source: ImageSource.camera);
|
onPressed: () async {
|
||||||
setState(() {});
|
PermissionHandler.checkPermission(
|
||||||
if (!context.mounted) return;
|
permission: Permission.camera,
|
||||||
Navigator.of(context).pop();
|
title: s.allowAccessToCamera,
|
||||||
},
|
description: s.allowAccessToCameraDescription,
|
||||||
child: const Row(
|
onGranted: () async {
|
||||||
children: [
|
final picker = ImagePicker();
|
||||||
Icon(Icons.photo_camera),
|
image = await picker.pickImage(source: ImageSource.camera);
|
||||||
SizedBox(width: 8),
|
setState(() {});
|
||||||
Text('Фото'),
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
],
|
},
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo_camera),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Фото'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
CupertinoActionSheetAction(
|
||||||
CupertinoActionSheetAction(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
PermissionHandler.checkPermission(
|
||||||
final picker = ImagePicker();
|
permission: Permission.photos,
|
||||||
image = await picker.pickImage(source: ImageSource.gallery);
|
title: s.allowAcessToGalery,
|
||||||
setState(() {});
|
description: s.allowAcessToGaleryDescription,
|
||||||
if (!context.mounted) return;
|
onGranted: () async {
|
||||||
Navigator.of(context).pop();
|
final picker = ImagePicker();
|
||||||
},
|
image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
child: const Row(
|
setState(() {});
|
||||||
children: [
|
if (!context.mounted) return;
|
||||||
Icon(Icons.photo),
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
SizedBox(width: 8),
|
},
|
||||||
Text('Галерея'),
|
context: context,
|
||||||
],
|
);
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Галерея'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
isDestructiveAction: true,
|
||||||
|
child: const Text('Отмена'),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
cancelButton: CupertinoActionSheetAction(
|
},
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
isDestructiveAction: true,
|
|
||||||
child: const Text('Отмена'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class BlogPostRepository {
|
|||||||
'video_url': videoUrl,
|
'video_url': videoUrl,
|
||||||
'image': image != null
|
'image': image != null
|
||||||
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name)
|
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name)
|
||||||
: oldImageUrl != null
|
: oldImageUrl != '' && oldImageUrl != null
|
||||||
? await MultipartFile.fromFile((await downloadImage(oldImageUrl))?.path ?? '', filename: oldImageUrl.split('/').last)
|
? await MultipartFile.fromFile((await downloadImage(oldImageUrl))?.path ?? '', filename: oldImageUrl.split('/').last)
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
|||||||
getMyProfile() async {
|
getMyProfile() async {
|
||||||
emit(const ProfileAvatarState.loading());
|
emit(const ProfileAvatarState.loading());
|
||||||
try {
|
try {
|
||||||
emit(ProfileAvatarState.loaded(profile: await profileRepository.getMyProfile()));
|
emit(
|
||||||
|
ProfileAvatarState.loaded(
|
||||||
|
profile: await profileRepository.getMyProfile(),
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:badges/badges.dart' as badges;
|
import 'package:badges/badges.dart' as badges;
|
||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
|
||||||
@@ -81,7 +82,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
label: '',
|
label: '',
|
||||||
icon: CircleAvatar(
|
icon: CircleAvatar(
|
||||||
foregroundImage: NetworkImage(widget.profileModel.profilePhotoUrl),
|
foregroundImage: NetworkImage(baseUrl + widget.profileModel.profilePhotoUrl),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
radius: 16,
|
radius: 16,
|
||||||
),
|
),
|
||||||
@@ -202,9 +203,10 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
return badges.Badge(
|
return badges.Badge(
|
||||||
position: badges.BadgePosition.topEnd(),
|
position: badges.BadgePosition.topEnd(),
|
||||||
badgeAnimation: const badges.BadgeAnimation.fade(),
|
badgeAnimation: const badges.BadgeAnimation.fade(),
|
||||||
badgeContent: Text(
|
showBadge: state is NotificationsStateLoaded && state.hasUnreadNotifications,
|
||||||
(state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0',
|
badgeContent: const Text(
|
||||||
style: const TextStyle(
|
'',
|
||||||
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ abstract class ChatUserModel with _$ChatUserModel {
|
|||||||
required int id,
|
required int id,
|
||||||
required String name,
|
required String name,
|
||||||
required String slug,
|
required String slug,
|
||||||
@JsonKey(name: 'profile_photo_path') String? profilePhotoPath,
|
|
||||||
@JsonKey(name: 'profile_photo_url') String? profilePhotoUrl,
|
@JsonKey(name: 'profile_photo_url') String? profilePhotoUrl,
|
||||||
}) = _ChatUserModel;
|
}) = _ChatUserModel;
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,22 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
late int lastPage;
|
late int lastPage;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
ChatRoomCubit({required this.chatRepository, required this.wsManager, required this.cardsRepository}) : super(const ChatRoomState.loading());
|
ChatRoomCubit({
|
||||||
|
required this.chatRepository,
|
||||||
|
required this.wsManager,
|
||||||
|
required this.cardsRepository,
|
||||||
|
}) : super(const ChatRoomState.loading());
|
||||||
|
|
||||||
|
void setTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 5), (_) => updateChosenPlanState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
loadNextPage(String chatId) async {
|
loadNextPage(String chatId) async {
|
||||||
if (currentPage < lastPage && !isLoading) {
|
if (currentPage < lastPage && !isLoading) {
|
||||||
@@ -94,13 +108,9 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (chatRoom.chosenPlan?.consultantId == chatRoom.users.receiver.id) {
|
if (chosenPlan?.consultantId == chatRoom.users.sender.id) {
|
||||||
await checkCards();
|
await checkCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sendInitialMessage) {
|
|
||||||
await chatRepository.sendMessage(chatId: chatId, data: 'Здравствуйте, хочу заказать у вас: ${chosenPlan?.plan.name ?? 'план'}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
typingSubscription?.cancel();
|
typingSubscription?.cancel();
|
||||||
@@ -237,6 +247,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
wsManager.unsubscribeDirectChatChannel();
|
wsManager.unsubscribeDirectChatChannel();
|
||||||
|
_timer?.cancel();
|
||||||
messagesSubscription?.cancel();
|
messagesSubscription?.cancel();
|
||||||
typingSubscription?.cancel();
|
typingSubscription?.cancel();
|
||||||
super.close();
|
super.close();
|
||||||
|
|||||||
@@ -14,18 +14,14 @@ class ChatsCubit extends Cubit<ChatsState> {
|
|||||||
final ChatRepository chatRepository;
|
final ChatRepository chatRepository;
|
||||||
final ProfileRepository profileRepository;
|
final ProfileRepository profileRepository;
|
||||||
final WsManager wsManager;
|
final WsManager wsManager;
|
||||||
late StreamSubscription? newIncomingMessagesSubscription;
|
StreamSubscription? newIncomingMessagesSubscription;
|
||||||
late StreamSubscription? newLastMessageSubscription;
|
StreamSubscription? newLastMessageSubscription;
|
||||||
|
|
||||||
ChatsCubit({
|
ChatsCubit({
|
||||||
required this.chatRepository,
|
required this.chatRepository,
|
||||||
required this.wsManager,
|
required this.wsManager,
|
||||||
required this.profileRepository,
|
required this.profileRepository,
|
||||||
}) : super(ChatsState()) {
|
}) : super(ChatsState()) {
|
||||||
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
|
|
||||||
_handleNewMessageViaSocket(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((message) {
|
newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((message) {
|
||||||
_setNewLastMessage(message);
|
_setNewLastMessage(message);
|
||||||
});
|
});
|
||||||
@@ -33,6 +29,13 @@ class ChatsCubit extends Cubit<ChatsState> {
|
|||||||
String? _query;
|
String? _query;
|
||||||
int page = 1;
|
int page = 1;
|
||||||
late int lastPage;
|
late int lastPage;
|
||||||
|
Future<void> connectToWebSocket() async {
|
||||||
|
await newIncomingMessagesSubscription?.cancel();
|
||||||
|
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
|
||||||
|
_handleNewMessageViaSocket(event.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getChatRoomsWithLoading({String? query}) async {
|
Future<void> getChatRoomsWithLoading({String? query}) async {
|
||||||
page = 1;
|
page = 1;
|
||||||
_query = query;
|
_query = query;
|
||||||
|
|||||||
@@ -43,12 +43,24 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final List<({int id, int index})> unreadMessagesIds = [];
|
final List<({int id, int index})> unreadMessagesIds = [];
|
||||||
final List<({int id, int index})> unreadMessagesIdsCopy = [];
|
final List<({int id, int index})> unreadMessagesIdsCopy = [];
|
||||||
final showableChatBadgeStates = ChosenPlanStateEnum.getShowableChatBadgeStates();
|
final showableChatBadgeStates = ChosenPlanStateEnum.getShowableChatBadgeStates();
|
||||||
|
late AppLifecycleListener _listener;
|
||||||
|
|
||||||
|
void _onHide() {
|
||||||
|
chatRoomCubit.cancelTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onResume() {
|
||||||
|
chatRoomCubit.updateChosenPlanState();
|
||||||
|
chatRoomCubit.setTimer();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
scrollController.addListener(_onScroll);
|
scrollController.addListener(_onScroll);
|
||||||
chatRoomCubit.initChat(widget.chatId, sendInitialMessage: widget.sendInitialMessage ?? false);
|
chatRoomCubit.initChat(widget.chatId, sendInitialMessage: widget.sendInitialMessage ?? false);
|
||||||
|
chatRoomCubit.setTimer();
|
||||||
|
_listener = AppLifecycleListener(onHide: _onHide, onResume: _onResume);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onScroll() {
|
_onScroll() {
|
||||||
@@ -60,6 +72,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
|
_listener.dispose();
|
||||||
chatRoomCubit.close();
|
chatRoomCubit.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -95,7 +108,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final showAboutOrderButton =
|
final showAboutOrderButton =
|
||||||
state.chatRoom.chosenPlan != null && state.chatRoom.chosenPlan!.chosenPlanState != ChosenPlanStateEnum.stateCanceledByTheInitiator;
|
state.chatRoom.chosenPlan != null && state.chatRoom.chosenPlan!.chosenPlanState != ChosenPlanStateEnum.stateCanceledByTheInitiator;
|
||||||
BannerType? bannerType;
|
BannerType? bannerType;
|
||||||
if (chosenPlan == null) {
|
if (chosenPlan == null || chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) {
|
||||||
bannerType = null;
|
bannerType = null;
|
||||||
} else if (amIConsultant && !state.hasCard) {
|
} else if (amIConsultant && !state.hasCard) {
|
||||||
bannerType = BannerType.card;
|
bannerType = BannerType.card;
|
||||||
@@ -104,7 +117,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
} else if (!amIConsultant &&
|
} else if (!amIConsultant &&
|
||||||
(chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet || chosenPlan.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) {
|
(chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet || chosenPlan.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) {
|
||||||
bannerType = BannerType.payment;
|
bannerType = BannerType.payment;
|
||||||
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateDone) {
|
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateDone && amIConsultant) {
|
||||||
bannerType = BannerType.collectCharge;
|
bannerType = BannerType.collectCharge;
|
||||||
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateLastDayFinished && !amIConsultant) {
|
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateLastDayFinished && !amIConsultant) {
|
||||||
bannerType = BannerType.finishPlan;
|
bannerType = BannerType.finishPlan;
|
||||||
@@ -302,17 +315,17 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (bannerType != null)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
||||||
child: NotificationBanner(
|
|
||||||
bannerType: bannerType,
|
|
||||||
chosenPlanId: chosenPlan?.id,
|
|
||||||
planName: chosenPlan?.plan.name,
|
|
||||||
planPrice: chosenOption?.price.toDouble(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
|
if (bannerType != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||||
|
child: NotificationBanner(
|
||||||
|
bannerType: bannerType,
|
||||||
|
chosenPlanId: chosenPlan?.id,
|
||||||
|
planName: chosenPlan?.plan.name,
|
||||||
|
planPrice: chosenOption?.price.toDouble(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
@@ -40,7 +39,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
sendTime.format('hh:mm'),
|
sendTime.format('HH:mm', 'ru'),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFF7B8794),
|
color: Color(0xFF7B8794),
|
||||||
height: 1,
|
height: 1,
|
||||||
@@ -81,7 +80,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
contentPadding: const EdgeInsets.all(4),
|
contentPadding: const EdgeInsets.all(4),
|
||||||
horizontalTitleGap: 8,
|
horizontalTitleGap: 8,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (!await launchUrl(Uri.parse('$baseUrl/storage/${mediaData[index]}'))) {
|
if (!await launchUrl(Uri.parse('${mediaData[index]}'))) {
|
||||||
throw Exception('Could not launch ${mediaData[index].toString()}');
|
throw Exception('Could not launch ${mediaData[index].toString()}');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -108,18 +107,18 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushTransparentRoute(
|
context.pushTransparentRoute(
|
||||||
PhotoViewScreen(
|
PhotoViewScreen(
|
||||||
imageUrls: mediaData.map((e) => '$baseUrl/storage/$e').toList(),
|
imageUrls: mediaData.map((e) => '$e').toList(),
|
||||||
index: index,
|
index: index,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Hero(
|
child: Hero(
|
||||||
tag: '$baseUrl/storage/${mediaData[index]}',
|
tag: '${mediaData[index]}',
|
||||||
transitionOnUserGestures: true,
|
transitionOnUserGestures: true,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: '$baseUrl/storage/${mediaData[index]}',
|
imageUrl: '${mediaData[index]}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -145,7 +144,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
sendTime.format('hh:mm'),
|
sendTime.format('HH:mm', 'ru'),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFF7B8794),
|
color: Color(0xFF7B8794),
|
||||||
height: 1,
|
height: 1,
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ part 'consultation_request_model.g.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
abstract class ConsultationRequestModel with _$ConsultationRequestModel {
|
abstract class ConsultationRequestModel with _$ConsultationRequestModel {
|
||||||
const factory ConsultationRequestModel({
|
const factory ConsultationRequestModel({
|
||||||
@JsonKey(name: 'rating_place') required int ratingPlace,
|
@JsonKey(name: 'rating_place') int? ratingPlace,
|
||||||
@JsonKey(name: 'education_resource') required EducationModel educationModel,
|
@JsonKey(name: 'education_resource') required EducationModel educationModel,
|
||||||
@JsonKey(name: 'cert_photos') required List<CertPhotosModel> photos,
|
@JsonKey(name: 'cert_photos') required List<CertPhotosModel> photos,
|
||||||
|
@JsonKey(name: 'canceled_moderation') CanceledModerationModel? canceledModeration,
|
||||||
}) = _ConsultationRequestModel;
|
}) = _ConsultationRequestModel;
|
||||||
|
|
||||||
factory ConsultationRequestModel.fromJson(Map<String, dynamic> json) => _$ConsultationRequestModelFromJson(json);
|
factory ConsultationRequestModel.fromJson(Map<String, dynamic> json) => _$ConsultationRequestModelFromJson(json);
|
||||||
@@ -24,3 +25,20 @@ abstract class CertPhotosModel with _$CertPhotosModel {
|
|||||||
|
|
||||||
factory CertPhotosModel.fromJson(Map<String, dynamic> json) => _$CertPhotosModelFromJson(json);
|
factory CertPhotosModel.fromJson(Map<String, dynamic> json) => _$CertPhotosModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ModerationStatus {
|
||||||
|
approved,
|
||||||
|
created,
|
||||||
|
canceled
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class CanceledModerationModel with _$CanceledModerationModel {
|
||||||
|
const factory CanceledModerationModel({
|
||||||
|
required String title,
|
||||||
|
required ModerationStatus status,
|
||||||
|
required List<String>? additional,
|
||||||
|
}) = _CanceledModerationModel;
|
||||||
|
|
||||||
|
factory CanceledModerationModel.fromJson(Map<String, dynamic> json) => _$CanceledModerationModelFromJson(json);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
||||||
final ConsultantRepository consultantRepository;
|
final ConsultantRepository consultantRepository;
|
||||||
|
bool _update = false;
|
||||||
|
|
||||||
RegisterConsultantCubit({
|
RegisterConsultantCubit({
|
||||||
required this.consultantRepository,
|
required this.consultantRepository,
|
||||||
@@ -17,12 +19,80 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
|||||||
socialLinks: SocialLinksModel(),
|
socialLinks: SocialLinksModel(),
|
||||||
hasMedicalEducation: true,
|
hasMedicalEducation: true,
|
||||||
),
|
),
|
||||||
|
loading: true,
|
||||||
acceptOffer: false,
|
acceptOffer: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<File> downloadImageAsFile(String imageUrl) async {
|
||||||
|
final response = await http.get(Uri.parse(imageUrl));
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
|
||||||
|
final file = File(filePath);
|
||||||
|
return await file.writeAsBytes(response.bodyBytes);
|
||||||
|
} else {
|
||||||
|
throw Exception('Не удалось загрузить изображение');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMyRequestStatus(String slug) async {
|
||||||
|
_update = true;
|
||||||
|
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
|
||||||
|
|
||||||
|
final List<String> photos = await Future.wait(
|
||||||
|
myRequestStatus.photos.map(
|
||||||
|
(e) async => (await downloadImageAsFile(e.url)).path,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<CertificatesModel>? certificates = myRequestStatus.educationModel.completedCourses?.map((courseMap) {
|
||||||
|
final course = courseMap.values.first;
|
||||||
|
|
||||||
|
return CertificatesModel(
|
||||||
|
title: course.title,
|
||||||
|
date: course.date,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
String? sanitizeLink(String? link) {
|
||||||
|
return (link != null && link.isNotEmpty) ? link : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(
|
||||||
|
RegisterConsultantState(
|
||||||
|
consultant: ConsultantProfileModel(
|
||||||
|
photos: photos,
|
||||||
|
certificates: certificates,
|
||||||
|
socialLinks: SocialLinksModel(
|
||||||
|
vkLink: sanitizeLink(myRequestStatus.educationModel.vkLink),
|
||||||
|
tgLink: sanitizeLink(myRequestStatus.educationModel.tgLink),
|
||||||
|
fbLink: sanitizeLink(myRequestStatus.educationModel.fbLink),
|
||||||
|
instaLink: sanitizeLink(myRequestStatus.educationModel.instaLink),
|
||||||
|
personalSiteLink: sanitizeLink(myRequestStatus.educationModel.personalSiteLink),
|
||||||
|
),
|
||||||
|
hasMedicalEducation: myRequestStatus.educationModel.hasMedicalEducation == 1,
|
||||||
|
),
|
||||||
|
acceptOffer: false,
|
||||||
|
loading: false,
|
||||||
|
requestModel: myRequestStatus,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
disableLoading() {
|
||||||
|
emit(state.copyWith(loading: false));
|
||||||
|
}
|
||||||
|
|
||||||
updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
|
updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
|
||||||
emit(RegisterConsultantState(consultant: consultant, acceptOffer: acceptOffer));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
consultant: consultant,
|
||||||
|
acceptOffer: acceptOffer,
|
||||||
|
loading: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerConsultant() async {
|
registerConsultant() async {
|
||||||
@@ -35,7 +105,7 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await consultantRepository.registerConsultant(consultant: state.consultant);
|
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> uploadPhotos(List<File> files) async {
|
Future<List<String>> uploadPhotos(List<File> files) async {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'register_consultant_state.freezed.dart';
|
part 'register_consultant_state.freezed.dart';
|
||||||
@@ -8,5 +9,7 @@ abstract class RegisterConsultantState with _$RegisterConsultantState {
|
|||||||
const factory RegisterConsultantState({
|
const factory RegisterConsultantState({
|
||||||
required ConsultantProfileModel consultant,
|
required ConsultantProfileModel consultant,
|
||||||
required bool acceptOffer,
|
required bool acceptOffer,
|
||||||
|
required bool loading,
|
||||||
|
ConsultationRequestModel? requestModel,
|
||||||
}) = _RegisterConsultantState;
|
}) = _RegisterConsultantState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,19 +30,20 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (context.read<RegisterConsultantCubit>().state.consultant.certificates != null) {
|
if (context.read<RegisterConsultantCubit>().state.consultant.certificates != null) {
|
||||||
diplomasList = context
|
diplomasList = [
|
||||||
.read<RegisterConsultantCubit>()
|
...context.read<RegisterConsultantCubit>().state.consultant.certificates!.map(
|
||||||
.state
|
(e) => DiplomaEntity(
|
||||||
.consultant
|
date: DateTime.tryParse(e.date),
|
||||||
.certificates!
|
textEditingController: TextEditingController(text: e.title),
|
||||||
.map(
|
enableDeleteButton: true,
|
||||||
(e) => DiplomaEntity(
|
),
|
||||||
date: DateTime.tryParse(e.date),
|
|
||||||
textEditingController: TextEditingController(text: e.title),
|
|
||||||
enableDeleteButton: true,
|
|
||||||
),
|
),
|
||||||
)
|
DiplomaEntity(
|
||||||
.toList();
|
textEditingController: TextEditingController(),
|
||||||
|
enableDeleteButton: false,
|
||||||
|
date: null,
|
||||||
|
),
|
||||||
|
];
|
||||||
diplomasList.last.enableDeleteButton = false;
|
diplomasList.last.enableDeleteButton = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/additional_settings_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/certificates_and_diplomas_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/last_step_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/last_step_screen.dart';
|
||||||
@@ -15,7 +18,9 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
class StepperBecomeConsultantScreen extends StatefulWidget {
|
class StepperBecomeConsultantScreen extends StatefulWidget {
|
||||||
const StepperBecomeConsultantScreen({super.key});
|
final String? slug;
|
||||||
|
|
||||||
|
const StepperBecomeConsultantScreen({super.key, this.slug});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StepperBecomeConsultantScreen> createState() => _StepperBecomeConsultantScreenState();
|
State<StepperBecomeConsultantScreen> createState() => _StepperBecomeConsultantScreenState();
|
||||||
@@ -25,249 +30,328 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
|
|||||||
double progressValue = 1 / 6;
|
double progressValue = 1 / 6;
|
||||||
int step = 1;
|
int step = 1;
|
||||||
final pageController = PageController();
|
final pageController = PageController();
|
||||||
final registrationConsultantCubit = sl<RegisterConsultantCubit>();
|
late final RegisterConsultantCubit registrationConsultantCubit;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.slug == null) {
|
||||||
|
registrationConsultantCubit = sl<RegisterConsultantCubit>()..disableLoading();
|
||||||
|
} else {
|
||||||
|
registrationConsultantCubit = sl<RegisterConsultantCubit>()..getMyRequestStatus(widget.slug!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<RegisterConsultantCubit>(
|
return BlocProvider<RegisterConsultantCubit>(
|
||||||
create: (context) => registrationConsultantCubit,
|
create: (context) => registrationConsultantCubit,
|
||||||
child: Scaffold(
|
child: BlocBuilder<RegisterConsultantCubit, RegisterConsultantState>(
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
builder: (context, state) {
|
||||||
floatingActionButton: Row(
|
if (state.loading == false) {
|
||||||
children: [
|
return Scaffold(
|
||||||
Container(
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
height: 48,
|
floatingActionButton: Row(
|
||||||
width: MediaQuery.of(context).size.width * 0.2,
|
children: [
|
||||||
padding: const EdgeInsets.only(left: 16, right: 4),
|
Container(
|
||||||
child: FloatingActionButton.extended(
|
height: 48,
|
||||||
heroTag: 'backButtonRegistration',
|
width: MediaQuery.of(context).size.width * 0.2,
|
||||||
highlightElevation: 0,
|
padding: const EdgeInsets.only(left: 16, right: 4),
|
||||||
focusElevation: 0,
|
child: FloatingActionButton.extended(
|
||||||
elevation: 0,
|
heroTag: 'backButtonRegistration',
|
||||||
shape: RoundedRectangleBorder(
|
highlightElevation: 0,
|
||||||
borderRadius: BorderRadius.circular(20),
|
focusElevation: 0,
|
||||||
),
|
elevation: 0,
|
||||||
backgroundColor: const Color(0xFFEAF2FD),
|
shape: RoundedRectangleBorder(
|
||||||
onPressed: () async {
|
borderRadius: BorderRadius.circular(20),
|
||||||
setState(() {
|
),
|
||||||
pageController.previousPage(
|
backgroundColor: const Color(0xFFEAF2FD),
|
||||||
duration: const Duration(milliseconds: 350),
|
onPressed: () async {
|
||||||
curve: Curves.easeInOut,
|
setState(() {
|
||||||
);
|
pageController.previousPage(
|
||||||
});
|
duration: const Duration(milliseconds: 350),
|
||||||
},
|
curve: Curves.easeInOut,
|
||||||
label: Icon(
|
);
|
||||||
Icons.arrow_back_ios_new_rounded,
|
});
|
||||||
color: appTheme.blueA400,
|
},
|
||||||
),
|
label: Icon(
|
||||||
),
|
Icons.arrow_back_ios_new_rounded,
|
||||||
),
|
color: appTheme.blueA400,
|
||||||
Container(
|
),
|
||||||
height: 48,
|
),
|
||||||
width: MediaQuery.of(context).size.width * 0.8,
|
),
|
||||||
padding: const EdgeInsets.only(left: 4, right: 16),
|
if (state.requestModel?.educationModel.requestState == ModerationStatus.canceled)
|
||||||
child: FloatingActionButton.extended(
|
InkWell(
|
||||||
heroTag: 'nextButtonRegistration',
|
onTap: () {
|
||||||
highlightElevation: 0,
|
showDialog(
|
||||||
elevation: 0,
|
context: context,
|
||||||
focusElevation: 0,
|
builder: (context) => AlertDialog(
|
||||||
shape: RoundedRectangleBorder(
|
title: Text(state.requestModel!.canceledModeration!.title),
|
||||||
borderRadius: BorderRadius.circular(20),
|
content: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
backgroundColor: isLoading ? Colors.grey : appTheme.blueA400,
|
mainAxisSize: MainAxisSize.min,
|
||||||
onPressed: isLoading
|
children: [
|
||||||
? null
|
for (int i = 0; i < state.requestModel!.canceledModeration!.additional!.length; i++) ...[
|
||||||
: () async {
|
Text('• ${state.requestModel!.canceledModeration!.additional![i]}'),
|
||||||
if (step == 6) {
|
]
|
||||||
if (registrationConsultantCubit.state.acceptOffer) {
|
],
|
||||||
setState(() {
|
),
|
||||||
isLoading = true;
|
actions: [
|
||||||
});
|
TextButton(
|
||||||
try {
|
onPressed: () => Navigator.pop(context),
|
||||||
await registrationConsultantCubit.registerConsultant();
|
child: const Text('OK'),
|
||||||
if (!context.mounted) return;
|
),
|
||||||
await showModalBottomSheet(
|
],
|
||||||
useRootNavigator: true,
|
),
|
||||||
showDragHandle: true,
|
);
|
||||||
context: context,
|
},
|
||||||
builder: (context) => Container(
|
borderRadius: BorderRadius.circular(20),
|
||||||
padding: const EdgeInsets.all(16),
|
child: Ink(
|
||||||
width: MediaQuery.of(context).size.width,
|
height: 48,
|
||||||
child: Column(
|
width: MediaQuery.of(context).size.width * 0.6,
|
||||||
mainAxisSize: MainAxisSize.min,
|
decoration: BoxDecoration(
|
||||||
children: [
|
borderRadius: BorderRadius.circular(20),
|
||||||
const Text(
|
color: const Color(0x33FFCC00),
|
||||||
'Спасибо!\nВаши данные отправлены на модерацию',
|
),
|
||||||
textAlign: TextAlign.center,
|
child: const Center(
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
fontSize: 20,
|
'Комментарий',
|
||||||
height: 1,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 18,
|
||||||
),
|
fontWeight: FontWeight.w600,
|
||||||
),
|
color: Color(0xFF323F4B),
|
||||||
const SizedBox(height: 16),
|
height: 1,
|
||||||
Lottie.asset(
|
),
|
||||||
'assets/lottie_animations/success.json',
|
),
|
||||||
width: 160,
|
),
|
||||||
height: 160,
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
Container(
|
||||||
const Text(
|
height: 48,
|
||||||
'В течении трех рабочих дней Вам придет уведомление о смене статуса',
|
width: state.requestModel?.educationModel.requestState == ModerationStatus.canceled
|
||||||
textAlign: TextAlign.center,
|
? MediaQuery.of(context).size.width * 0.2
|
||||||
style: TextStyle(
|
: MediaQuery.of(context).size.width * 0.8,
|
||||||
fontSize: 16,
|
padding: const EdgeInsets.only(left: 4, right: 16),
|
||||||
height: 1,
|
child: FloatingActionButton.extended(
|
||||||
fontWeight: FontWeight.w500,
|
heroTag: 'nextButtonRegistration',
|
||||||
),
|
highlightElevation: 0,
|
||||||
),
|
elevation: 0,
|
||||||
const SizedBox(height: 16),
|
focusElevation: 0,
|
||||||
InkWell(
|
shape: RoundedRectangleBorder(
|
||||||
onTap: () {
|
borderRadius: BorderRadius.circular(20),
|
||||||
Navigator.of(context).pop();
|
),
|
||||||
GoRouter.of(context).pop();
|
backgroundColor: isLoading
|
||||||
},
|
? Colors.grey
|
||||||
child: Ink(
|
: state.requestModel?.educationModel.requestState == ModerationStatus.canceled
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
? const Color(0xFFEAF2FD)
|
||||||
height: 52,
|
: appTheme.blueA400,
|
||||||
decoration: BoxDecoration(
|
onPressed: isLoading
|
||||||
borderRadius: BorderRadius.circular(20),
|
? null
|
||||||
color: appTheme.blueA400,
|
: () async {
|
||||||
),
|
if (step == 6) {
|
||||||
child: const Center(
|
if (registrationConsultantCubit.state.acceptOffer) {
|
||||||
child: Text(
|
setState(() {
|
||||||
'Закрыть',
|
isLoading = true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await registrationConsultantCubit.registerConsultant();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
await showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
showDragHandle: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Спасибо!\nВаши данные отправлены на модерацию',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
fontSize: 20,
|
||||||
fontSize: 18,
|
height: 1,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
|
Lottie.asset(
|
||||||
|
'assets/lottie_animations/success.json',
|
||||||
|
width: 160,
|
||||||
|
height: 160,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'В течении трех рабочих дней Вам придет уведомление о смене статуса',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Ink(
|
||||||
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
height: 52,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
'Закрыть',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
} catch (e) {
|
||||||
),
|
if (!context.mounted) return;
|
||||||
);
|
showDialog(
|
||||||
} catch (e) {
|
context: context,
|
||||||
if (!context.mounted) return;
|
builder: (context) => AlertDialog(
|
||||||
showDialog(
|
actionsPadding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
context: context,
|
content: Text(e.toString()),
|
||||||
builder: (context) => AlertDialog(
|
actions: [
|
||||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 4),
|
TextButton(
|
||||||
content: Text(e.toString()),
|
child: const Text('Ок'),
|
||||||
actions: [
|
onPressed: () {
|
||||||
TextButton(
|
Navigator.of(context).pop();
|
||||||
child: const Text('Ок'),
|
},
|
||||||
onPressed: () {
|
),
|
||||||
Navigator.of(context).pop();
|
],
|
||||||
},
|
),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
actionsPadding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
content: const Text('Примите договор публичной офёрты'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: const Text('Ок'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
setState(() {
|
||||||
|
pageController.nextPage(
|
||||||
|
duration: const Duration(milliseconds: 350),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: isLoading
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: state.requestModel?.educationModel.requestState == ModerationStatus.canceled
|
||||||
|
? Icon(
|
||||||
|
Icons.arrow_forward_ios_outlined,
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
'Продолжить',
|
||||||
|
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
} finally {
|
),
|
||||||
setState(() {
|
],
|
||||||
isLoading = false;
|
),
|
||||||
});
|
appBar: AppBar(
|
||||||
}
|
automaticallyImplyLeading: false,
|
||||||
} else {
|
leading: Builder(
|
||||||
showDialog(
|
builder: (context) => IconButton(
|
||||||
context: context,
|
icon: const Icon(Icons.close_rounded),
|
||||||
builder: (context) => AlertDialog(
|
onPressed: () => GoRouter.of(context).pop(),
|
||||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 4),
|
),
|
||||||
content: const Text('Примите договор публичной офёрты'),
|
),
|
||||||
actions: [
|
centerTitle: true,
|
||||||
TextButton(
|
title: SvgPicture.asset(
|
||||||
child: const Text('Ок'),
|
ImageConstant.imgLogoEatvio,
|
||||||
onPressed: () {
|
height: 48,
|
||||||
Navigator.of(context).pop();
|
fit: BoxFit.contain,
|
||||||
},
|
),
|
||||||
),
|
actions: [
|
||||||
],
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(right: 16),
|
||||||
);
|
child: Text('шаг $step из 6'),
|
||||||
}
|
),
|
||||||
} else {
|
],
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
bottom: PreferredSize(
|
||||||
setState(() {
|
preferredSize: Size(MediaQuery.of(context).size.width, 32),
|
||||||
pageController.nextPage(
|
child: Padding(
|
||||||
duration: const Duration(milliseconds: 350),
|
padding: const EdgeInsets.all(16),
|
||||||
curve: Curves.easeInOut,
|
child: TweenAnimationBuilder<double>(
|
||||||
);
|
duration: const Duration(milliseconds: 350),
|
||||||
});
|
curve: Curves.easeInOut,
|
||||||
}
|
tween: Tween<double>(
|
||||||
},
|
begin: 1 / 6,
|
||||||
label: isLoading ? const CircularProgressIndicator() : Text(
|
end: progressValue,
|
||||||
'Продолжить',
|
),
|
||||||
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
builder: (context, value, _) => LinearProgressIndicator(
|
||||||
|
value: value,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
minHeight: 8,
|
||||||
|
backgroundColor: Colors.grey.shade100,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
body: PageView(
|
||||||
],
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
),
|
controller: pageController,
|
||||||
appBar: AppBar(
|
onPageChanged: (index) {
|
||||||
automaticallyImplyLeading: false,
|
setState(() {
|
||||||
leading: Builder(
|
step = index + 1;
|
||||||
builder: (context) => IconButton(
|
progressValue = step / 6;
|
||||||
icon: const Icon(Icons.close_rounded),
|
});
|
||||||
onPressed: () => GoRouter.of(context).pop(),
|
},
|
||||||
),
|
children: const [
|
||||||
),
|
CertificatesAndDiplomasScreen(),
|
||||||
centerTitle: true,
|
PhotoOrScansScreen(),
|
||||||
title: SvgPicture.asset(
|
AdditionalSettingsScreen(),
|
||||||
ImageConstant.imgLogoEatvio,
|
SocialNetworksScreen(),
|
||||||
height: 48,
|
RegistrationAsScreen(),
|
||||||
fit: BoxFit.contain,
|
LastStepScreen(),
|
||||||
),
|
],
|
||||||
actions: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 16),
|
|
||||||
child: Text('шаг $step из 6'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: Size(MediaQuery.of(context).size.width, 32),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: TweenAnimationBuilder<double>(
|
|
||||||
duration: const Duration(milliseconds: 350),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
tween: Tween<double>(
|
|
||||||
begin: 1 / 6,
|
|
||||||
end: progressValue,
|
|
||||||
),
|
|
||||||
builder: (context, value, _) => LinearProgressIndicator(
|
|
||||||
value: value,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
minHeight: 8,
|
|
||||||
backgroundColor: Colors.grey.shade100,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
} else {
|
||||||
),
|
return const Scaffold(
|
||||||
body: PageView(
|
body: Loader(),
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
);
|
||||||
controller: pageController,
|
}
|
||||||
onPageChanged: (index) {
|
},
|
||||||
setState(() {
|
|
||||||
step = index + 1;
|
|
||||||
progressValue = step / 6;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
children: const [
|
|
||||||
CertificatesAndDiplomasScreen(),
|
|
||||||
PhotoOrScansScreen(),
|
|
||||||
AdditionalSettingsScreen(),
|
|
||||||
SocialNetworksScreen(),
|
|
||||||
RegistrationAsScreen(),
|
|
||||||
LastStepScreen(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,18 @@ class ConsultantRepository {
|
|||||||
|
|
||||||
Future<void> registerConsultant({
|
Future<void> registerConsultant({
|
||||||
required ConsultantProfileModel consultant,
|
required ConsultantProfileModel consultant,
|
||||||
|
bool update = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
return await consultantService.consultantRegistration(
|
if (update) {
|
||||||
consultantModel: consultant,
|
return await consultantService.consultantUpdate(
|
||||||
);
|
consultantModel: consultant,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return await consultantService.consultantRegistration(
|
||||||
|
consultantModel: consultant,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -57,4 +64,12 @@ class ConsultantRepository {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ConsultationRequestModel> getMyRequestStatus(String slug) async {
|
||||||
|
try {
|
||||||
|
return await consultantService.getMyRequestStatus(slug: slug);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,18 @@ abstract class ConsultantService {
|
|||||||
@Part(name: 'photos[]', contentType: 'multipart/form-data') List<MultipartFile>? photos,
|
@Part(name: 'photos[]', contentType: 'multipart/form-data') List<MultipartFile>? photos,
|
||||||
});
|
});
|
||||||
|
|
||||||
@GET('/api/consultant_request/{slug}/status')
|
@GET('/api/profile/consultant/{slug}/status')
|
||||||
Future<ConsultationRequestModel> getConsultantRequestStatus({
|
Future<ConsultationRequestModel> getConsultantRequestStatus({
|
||||||
@Path() required String slug,
|
@Path() required String slug,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@GET('/api/consultant_request/{slug}/status')
|
||||||
|
Future<ConsultationRequestModel> getMyRequestStatus({
|
||||||
|
@Path() required String slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
@PUT('/api/consultant_request/update')
|
||||||
|
Future<void> consultantUpdate({
|
||||||
|
@Body() required ConsultantProfileModel consultantModel,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,42 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'rating_model.freezed.dart';
|
part 'rating_model.freezed.dart';
|
||||||
part 'rating_model.g.dart';
|
part 'rating_model.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class RatingModel with _$RatingModel {
|
abstract class RatingPaginationModel with _$RatingPaginationModel {
|
||||||
|
factory RatingPaginationModel({
|
||||||
|
required List<RatingModel> data,
|
||||||
|
}) = _RatingPaginationModel;
|
||||||
|
|
||||||
|
factory RatingPaginationModel.fromJson(Map<String, dynamic> json) => _$RatingPaginationModelFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class RatingModel with _$RatingModel {
|
||||||
factory RatingModel({
|
factory RatingModel({
|
||||||
required DateTime date,
|
@JsonKey(name: 'created_at', fromJson: parseDate) required DateTime date,
|
||||||
required String title,
|
@JsonKey(name: 'log_data') required RatingEnum type,
|
||||||
required int amount,
|
@JsonKey(name: 'log_scores') required int amount,
|
||||||
}) = _RatingModel;
|
}) = _RatingModel;
|
||||||
|
|
||||||
factory RatingModel.fromJson(Map<String, dynamic> json) => _$RatingModelFromJson(json);
|
factory RatingModel.fromJson(Map<String, dynamic> json) => _$RatingModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RatingEnum {
|
||||||
|
@JsonValue('ADDED_RECIPE')
|
||||||
|
addedRecipe,
|
||||||
|
@JsonValue('ADDED_PUBLIC_DIET')
|
||||||
|
addedPublicDiet,
|
||||||
|
@JsonValue('ADDED_BLOG_POST')
|
||||||
|
addedBlogPost,
|
||||||
|
@JsonValue('INVITED_A_NEW_USER')
|
||||||
|
invitedUser,
|
||||||
|
@JsonValue('HAS_MEDICAL_DEGREE')
|
||||||
|
hasMedicalDegree,
|
||||||
|
@JsonValue('CONSULTED')
|
||||||
|
consulted,
|
||||||
|
@JsonValue('PRODUCT_NUTRITINAL_VALUE_APPROVED')
|
||||||
|
addedGlossary;
|
||||||
|
}
|
||||||
|
|||||||
19
lib/features/consultant_rating/models/statistics_model.dart
Normal file
19
lib/features/consultant_rating/models/statistics_model.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'statistics_model.freezed.dart';
|
||||||
|
part 'statistics_model.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class StatisticsModel with _$StatisticsModel {
|
||||||
|
|
||||||
|
factory StatisticsModel({
|
||||||
|
@JsonKey(name: 'count_referal') int? countReferal,
|
||||||
|
@JsonKey(name: 'count_blog_posts') int? blogPostsCount,
|
||||||
|
@JsonKey(name: 'count_recipes') int? recipesCount,
|
||||||
|
@JsonKey(name: 'count_diets') int? dietsCount,
|
||||||
|
@JsonKey(name: 'count_consultants') int? consultationsCount,
|
||||||
|
@JsonKey(name: 'count_products_nutritional_values') int? qwe,
|
||||||
|
}) = _StatisticsModel;
|
||||||
|
|
||||||
|
factory StatisticsModel.fromJson(Map<String, dynamic> json) => _$StatisticsModelFromJson(json);
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_widget.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_widget.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class ConsultantRatingScreen extends StatelessWidget {
|
class ConsultantRatingScreen extends StatelessWidget {
|
||||||
static const name = 'consultantRatingScreen';
|
static const name = 'consultantRatingScreen';
|
||||||
@@ -15,7 +19,10 @@ class ConsultantRatingScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const ConsultantRatingPageContent();
|
return BlocProvider<ConsultantRatingCubit>(
|
||||||
|
create: (context) => sl<ConsultantRatingCubit>()..getConsultantStatistics()..getScoreLogs(),
|
||||||
|
child: const ConsultantRatingPageContent(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,52 +32,56 @@ class ConsultantRatingPageContent extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return Scaffold(
|
return BlocBuilder<ConsultantRatingCubit, ConsultantRatingState>(
|
||||||
backgroundColor: appTheme.gray100,
|
builder: (context, state) {
|
||||||
appBar: AppBar(
|
return Scaffold(
|
||||||
centerTitle: true,
|
backgroundColor: appTheme.gray100,
|
||||||
title: Text(
|
appBar: AppBar(
|
||||||
s.rating_and_statistics,
|
centerTitle: true,
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
title: Text(
|
||||||
),
|
s.rating_and_statistics,
|
||||||
),
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const RatingWidget(
|
|
||||||
rating: 1058,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Padding(
|
body: state.error != null
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
? DataErrorWidget(onReload: () async {
|
||||||
child: CustomOutlinedButton(
|
await context.read<ConsultantRatingCubit>().getConsultantStatistics();
|
||||||
text: s.copy_referral_link,
|
if (!context.mounted) return;
|
||||||
height: 52,
|
await context.read<ConsultantRatingCubit>().getScoreLogs();
|
||||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
})
|
||||||
buttonStyle: ButtonStyle(
|
: SingleChildScrollView(
|
||||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
child: Column(
|
||||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const RatingWidget(
|
||||||
|
rating: 1058,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
text: s.copy_referral_link,
|
||||||
|
height: 52,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
StatisticWidget(isLoading: state.isStatisticsLoading, statistics: state.statistics,),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
RatingHistoryWidget(
|
||||||
|
isLoading: state.isRatingLoading,
|
||||||
|
ratingHistory: state.ratings,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {},
|
);
|
||||||
),
|
},
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const StatisticWidget(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
RatingHistoryWidget(
|
|
||||||
ratingHistory: [
|
|
||||||
RatingModel(date: DateTime.now(), title: ' djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 201300),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 89323),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 20),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 1212),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'qweqweeq', amount: 323),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'ee', amount: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
|
||||||
|
final ConsultantRatingRepository repository;
|
||||||
|
ConsultantRatingCubit(this.repository) : super(ConsultantRatingState());
|
||||||
|
|
||||||
|
Future<void> getConsultantStatistics({String? date}) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isStatisticsLoading: true));
|
||||||
|
final statistics = await repository.getConsultantStatistics(date: date);
|
||||||
|
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null));
|
||||||
|
} catch (e, st) {
|
||||||
|
emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getScoreLogs({String? date}) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isRatingLoading: true));
|
||||||
|
final scoreLogs = await repository.getScoreLogs(date: date);
|
||||||
|
emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null));
|
||||||
|
} catch (e, st) {
|
||||||
|
emit(state.copyWith(isRatingLoading: true, error: e.toString()));
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'consultant_rating_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ConsultantRatingState with _$ConsultantRatingState {
|
||||||
|
factory ConsultantRatingState({
|
||||||
|
@Default(false) bool isStatisticsLoading,
|
||||||
|
@Default(false) bool isRatingLoading,
|
||||||
|
@Default(null) StatisticsModel? statistics,
|
||||||
|
@Default(null) String? error,
|
||||||
|
@Default(<RatingModel>[]) List<RatingModel> ratings,
|
||||||
|
}) = _ConsultantRatingState;
|
||||||
|
}
|
||||||
@@ -1,42 +1,29 @@
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class PeriodFilterWidget extends StatefulWidget {
|
class PeriodFilterWidget extends StatefulWidget {
|
||||||
const PeriodFilterWidget({super.key});
|
const PeriodFilterWidget({super.key, required this.isStatistic});
|
||||||
|
final bool isStatistic;
|
||||||
@override
|
@override
|
||||||
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
|
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
||||||
final List<String> periodDropDownTitles = [];
|
PeriodFilterStatus _currentState = PeriodFilterStatus.all;
|
||||||
int periodDropDownIndex = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
final s = S.of(context);
|
|
||||||
if (periodDropDownTitles.isEmpty) {
|
|
||||||
periodDropDownTitles.addAll([
|
|
||||||
s.for_whole_period_of_time,
|
|
||||||
s.for_a_year,
|
|
||||||
s.for_a_month,
|
|
||||||
s.for_a_week,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CustomDropDownButton(
|
CustomDropDownButton<PeriodFilterStatus>(
|
||||||
hint: Text(
|
hint: Text(
|
||||||
s.choose_diet_duration,
|
s.choose_diet_duration,
|
||||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||||
@@ -44,24 +31,59 @@ class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
periodDropDownIndex = periodDropDownTitles.indexOf(value);
|
_currentState = value;
|
||||||
});
|
});
|
||||||
|
if (widget.isStatistic) {
|
||||||
|
if (value == PeriodFilterStatus.all) {
|
||||||
|
context.read<ConsultantRatingCubit>().getConsultantStatistics();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<ConsultantRatingCubit>().getConsultantStatistics(date: value.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value == PeriodFilterStatus.all) {
|
||||||
|
context.read<ConsultantRatingCubit>().getScoreLogs();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<ConsultantRatingCubit>().getScoreLogs(date: value.value);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: periodDropDownTitles
|
items: PeriodFilterStatus.values
|
||||||
.mapIndexed(
|
.mapIndexed(
|
||||||
(index, value) => DropdownMenuItem(
|
(index, value) => DropdownMenuItem(
|
||||||
value: periodDropDownTitles[index],
|
value: PeriodFilterStatus.values[index],
|
||||||
child: Text(
|
child: Text(
|
||||||
periodDropDownTitles[index],
|
PeriodFilterStatus.values[index].name,
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
value: periodDropDownTitles[periodDropDownIndex],
|
value: _currentState,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PeriodFilterStatus {
|
||||||
|
all,
|
||||||
|
month,
|
||||||
|
week,
|
||||||
|
year;
|
||||||
|
|
||||||
|
String get name => switch (this) {
|
||||||
|
PeriodFilterStatus.all => 'Все периоды',
|
||||||
|
PeriodFilterStatus.month => 'За месяц',
|
||||||
|
PeriodFilterStatus.week => 'За неделю',
|
||||||
|
PeriodFilterStatus.year => 'За год',
|
||||||
|
};
|
||||||
|
|
||||||
|
String? get value => switch (this) {
|
||||||
|
PeriodFilterStatus.all => null,
|
||||||
|
PeriodFilterStatus.month => DateTime.now().subtract(const Duration(days: 30)).format('yyyy-MM-dd'),
|
||||||
|
PeriodFilterStatus.week => DateTime.now().subtract(const Duration(days: 7)).format('yyyy-MM-dd'),
|
||||||
|
PeriodFilterStatus.year => DateTime.now().subtract(const Duration(days: 365)).format('yyyy-MM-dd'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class RatingHistoryItem extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 80,
|
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: appTheme.gray100,
|
color: appTheme.gray100,
|
||||||
@@ -29,7 +28,7 @@ class RatingHistoryItem extends StatelessWidget {
|
|||||||
date.format('dd.MM.yyyy'),
|
date.format('dd.MM.yyyy'),
|
||||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray),
|
style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
||||||
@@ -9,7 +10,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class RatingHistoryWidget extends StatelessWidget {
|
class RatingHistoryWidget extends StatelessWidget {
|
||||||
const RatingHistoryWidget({super.key, required this.ratingHistory});
|
const RatingHistoryWidget({super.key, required this.ratingHistory, required this.isLoading});
|
||||||
|
final bool isLoading;
|
||||||
final List<RatingModel> ratingHistory;
|
final List<RatingModel> ratingHistory;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -20,50 +22,60 @@ class RatingHistoryWidget extends StatelessWidget {
|
|||||||
right: 16,
|
right: 16,
|
||||||
top: 16,
|
top: 16,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: isLoading
|
||||||
children: [
|
? const Center(child: Loader())
|
||||||
Row(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
s.rating_history,
|
children: [
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
Text(
|
||||||
),
|
s.rating_history,
|
||||||
const Spacer(),
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
SvgPicture.asset(
|
),
|
||||||
'assets/icons/question_mark.svg',
|
const Spacer(),
|
||||||
height: 22,
|
SvgPicture.asset(
|
||||||
width: 22,
|
'assets/icons/question_mark.svg',
|
||||||
colorFilter: ColorFilter.mode(
|
height: 22,
|
||||||
appTheme.blueA400,
|
width: 22,
|
||||||
BlendMode.srcIn,
|
colorFilter: ColorFilter.mode(
|
||||||
|
appTheme.blueA400,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(
|
||||||
],
|
height: 16,
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
const PeriodFilterWidget(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
ListView.builder(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Padding(
|
|
||||||
padding: index == ratingHistory.length - 1 ? EdgeInsets.zero : const EdgeInsets.only(bottom: 12),
|
|
||||||
child: RatingHistoryItem(
|
|
||||||
title: ratingHistory[index].title,
|
|
||||||
amount: ratingHistory[index].amount,
|
|
||||||
date: ratingHistory[index].date,
|
|
||||||
),
|
),
|
||||||
);
|
const PeriodFilterWidget(isStatistic: false),
|
||||||
},
|
const SizedBox(
|
||||||
itemCount: ratingHistory.length,
|
height: 12,
|
||||||
shrinkWrap: true,
|
),
|
||||||
),
|
ListView.builder(
|
||||||
],
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
|
return Padding(
|
||||||
|
padding: index == ratingHistory.length - 1 ? EdgeInsets.zero : const EdgeInsets.only(bottom: 12),
|
||||||
|
child: RatingHistoryItem(
|
||||||
|
title: switch (ratingHistory[index].type) {
|
||||||
|
RatingEnum.addedRecipe => 'Добавление рецепта',
|
||||||
|
RatingEnum.addedPublicDiet => 'Добавление публичной диеты',
|
||||||
|
RatingEnum.addedBlogPost => 'Добавление поста',
|
||||||
|
RatingEnum.invitedUser => 'Регистрация по реф. ссылке',
|
||||||
|
RatingEnum.hasMedicalDegree => 'Получение мед. степени',
|
||||||
|
RatingEnum.consulted => 'Консультация',
|
||||||
|
RatingEnum.addedGlossary => 'Добавление продукта в глоссарий',
|
||||||
|
},
|
||||||
|
amount: ratingHistory[index].amount,
|
||||||
|
date: ratingHistory[index].date,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: ratingHistory.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_bouble.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_bouble.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
@@ -8,14 +10,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class StatisticWidget extends StatelessWidget {
|
class StatisticWidget extends StatelessWidget {
|
||||||
const StatisticWidget({super.key});
|
const StatisticWidget({super.key, required this.isLoading, required this.statistics});
|
||||||
|
final bool isLoading;
|
||||||
|
final StatisticsModel? statistics;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return MainBackgroundBouble(
|
return MainBackgroundBouble(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: isLoading ? const Center(child: Loader()) : Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -38,15 +41,17 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
const PeriodFilterWidget(),
|
const PeriodFilterWidget(
|
||||||
|
isStatistic: true,
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.invited_people_count)),
|
Expanded(child: StatisticBouble(number: statistics?.countReferal ?? 0, title: s.invited_people_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.published_posts_count)),
|
Expanded(child: StatisticBouble(number: statistics?.blogPostsCount ?? 0, title: s.published_posts_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -54,9 +59,9 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.published_recipes_count)),
|
Expanded(child: StatisticBouble(number: statistics?.recipesCount ?? 0, title: s.published_recipes_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.published_diets_count)),
|
Expanded(child: StatisticBouble(number: statistics?.dietsCount ?? 0, title: s.published_diets_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -64,9 +69,9 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.consultations_count)),
|
Expanded(child: StatisticBouble(number: statistics?.consultationsCount ?? 0, title: s.consultations_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.active_consultations_count)),
|
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.active_consultations_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
||||||
|
|
||||||
|
class ConsultantRatingRepository {
|
||||||
|
final ConsultantRatingService service;
|
||||||
|
|
||||||
|
ConsultantRatingRepository(this.service);
|
||||||
|
|
||||||
|
Future<StatisticsModel> getConsultantStatistics({String? date}) async {
|
||||||
|
try {
|
||||||
|
return await service.getConsultantStatistics(date: date);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<RatingModel>> getScoreLogs({String? date}) async {
|
||||||
|
try {
|
||||||
|
final data = (await service.getScoreLogs(date: date)).data;
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
|
|
||||||
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
|
part 'consultant_rating_service.g.dart';
|
||||||
|
|
||||||
|
@RestApi()
|
||||||
|
abstract class ConsultantRatingService {
|
||||||
|
factory ConsultantRatingService(Dio dio, {String baseUrl}) = _ConsultantRatingService;
|
||||||
|
|
||||||
|
@GET('/api/consultants/rating/statistics')
|
||||||
|
Future<StatisticsModel> getConsultantStatistics({@Query('filters[duration][\$gte]') String? date});
|
||||||
|
|
||||||
|
@GET('/api/consultants/score/logs')
|
||||||
|
Future<RatingPaginationModel> getScoreLogs({@Query('filters[duration][\$gte]') String? date});
|
||||||
|
}
|
||||||
@@ -10,12 +10,14 @@ abstract class ConsultationModel with _$ConsultationModel {
|
|||||||
factory ConsultationModel({
|
factory ConsultationModel({
|
||||||
required int id,
|
required int id,
|
||||||
@JsonKey(name: 'last_state') required ChosenPlanStateEnum chosenPlanState,
|
@JsonKey(name: 'last_state') required ChosenPlanStateEnum chosenPlanState,
|
||||||
required String planTitle,
|
@JsonKey(name: 'plan_title') required String planTitle,
|
||||||
required String userName,
|
@JsonKey(name: 'user_name') required String userName,
|
||||||
required String userSlug,
|
@JsonKey(name: 'user_slug') required String userSlug,
|
||||||
required double consultantPayment,
|
@JsonKey(name: 'consultant_payment') required double consultantPayment,
|
||||||
required int duration,
|
required int duration,
|
||||||
required int currentDiaryDay,
|
@JsonKey(name: 'current_diary_day') required int currentDiaryDay,
|
||||||
|
@JsonKey(name: 'user_profile_photo_url') required String userProfilePhotoUrl,
|
||||||
|
@JsonKey(name: 'consultation_option_price') required int consultationOptionPrice,
|
||||||
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
|
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
|
||||||
@JsonKey(name: 'end_date', fromJson: parseDate) required DateTime endDate,
|
@JsonKey(name: 'end_date', fromJson: parseDate) required DateTime endDate,
|
||||||
@JsonKey(name: 'consultant_id') required int consultantId,
|
@JsonKey(name: 'consultant_id') required int consultantId,
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
|
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart';
|
import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class ConsultationsScreen extends StatelessWidget {
|
class ConsultationsScreen extends StatelessWidget {
|
||||||
static const path = '/consultations-screen';
|
static const path = '/consultations-screen';
|
||||||
@@ -42,17 +45,53 @@ class ConsultationsContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: switch (state.state) {
|
body: switch (state.state) {
|
||||||
BaseState.loading => const Loader(),
|
BaseState.loading => const Loader(),
|
||||||
BaseState.error => DataErrorWidget(onReload: () async {}),
|
BaseState.error => DataErrorWidget(onReload: () async {
|
||||||
|
context.read<ConsultationsCubit>().loadConsultations();
|
||||||
|
}),
|
||||||
BaseState.initial => const Loader(),
|
BaseState.initial => const Loader(),
|
||||||
BaseState.success => ListView.separated(
|
BaseState.success => SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
child: Column(
|
||||||
itemBuilder: (context, index) => ConsultationCardWidget(
|
children: [
|
||||||
consultationsModel: state.consultations[index],
|
ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) => ConsultationCardWidget(
|
||||||
|
consultationsModel: state.consultations[index],
|
||||||
|
),
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: state.consultations.length,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(ConsultationsArchiveScreen.name);
|
||||||
|
},
|
||||||
|
child: Ink(
|
||||||
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
'Архив консультаций',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
separatorBuilder: (context, index) => const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
itemCount: state.consultations.length,
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||||
@@ -71,8 +72,9 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
|
backgroundImage: NetworkImage(baseUrl + widget.consultationsModel.userProfilePhotoUrl),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Column(
|
Column(
|
||||||
@@ -183,6 +185,8 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
children: List.generate(widget.consultationsModel.duration, (index) {
|
children: List.generate(widget.consultationsModel.duration, (index) {
|
||||||
final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index);
|
final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index);
|
||||||
final hasActivity = day != null && day.$2.state != DiaryStateEnum.init;
|
final hasActivity = day != null && day.$2.state != DiaryStateEnum.init;
|
||||||
|
final canConsultantOpenThisDay = day != null &&
|
||||||
|
(day.$2.state != DiaryStateEnum.init && day.$2.state != DiaryStateEnum.started && day.$2.state != DiaryStateEnum.lastDayStarted);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.only(right: 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -190,12 +194,15 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (!hasActivity) {
|
if (!hasActivity || !canConsultantOpenThisDay) {
|
||||||
showErrorDialog(context,
|
showErrorDialog(context,
|
||||||
title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
|
title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GoRouter.of(context).push(UserDiaryScreen.path, extra: day.$2.id);
|
GoRouter.of(context).push(UserDiaryScreen.path, extra: {
|
||||||
|
'diaryId': day.$2.id,
|
||||||
|
'currentDiaryDay': index + 1,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(100),
|
borderRadius: BorderRadius.circular(100),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
@@ -272,7 +279,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
const CustomDivider(),
|
const CustomDivider(),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'${widget.consultationsModel.consultantPayment.toStringAsFixed(0)} ₽',
|
'${widget.consultationsModel.consultationOptionPrice.toStringAsFixed(0)} ₽',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFF019A3E),
|
color: Color(0xFF019A3E),
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
@@ -291,6 +298,10 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
)
|
)
|
||||||
.lastOrNull;
|
.lastOrNull;
|
||||||
if (lastActiveDay != null) {
|
if (lastActiveDay != null) {
|
||||||
|
GoRouter.of(context).push(UserDiaryScreen.path, extra: {
|
||||||
|
'diaryId': lastActiveDay.$2.id,
|
||||||
|
'currentDiaryDay': lastActiveDay.$1,
|
||||||
|
});
|
||||||
GoRouter.of(context).push(UserDiaryScreen.path, extra: lastActiveDay.$2.id);
|
GoRouter.of(context).push(UserDiaryScreen.path, extra: lastActiveDay.$2.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
||||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
|
||||||
|
|
||||||
class ConsultationsRepository {
|
class ConsultationsRepository {
|
||||||
final ConsultationService consultationService;
|
final ConsultationService consultationService;
|
||||||
@@ -13,4 +17,47 @@ class ConsultationsRepository {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<ConsultationModel>> getFilteredConsultations({
|
||||||
|
String? state,
|
||||||
|
String? date,
|
||||||
|
FiltersComparisonEnum? stateComparison,
|
||||||
|
FiltersComparisonEnum? dateComparison,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final model = await consultationService.getFilteredConsultations(
|
||||||
|
state: state,
|
||||||
|
date: date,
|
||||||
|
stateComparison: stateComparison,
|
||||||
|
dateComparison: dateComparison,
|
||||||
|
);
|
||||||
|
return model.data;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StatisticModel> getStatistic() async {
|
||||||
|
try {
|
||||||
|
return await consultationService.getStatistics();
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async {
|
||||||
|
try {
|
||||||
|
return await consultationService.getConsultantFinances(date, dateComparison);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async {
|
||||||
|
try {
|
||||||
|
return await consultationService.getUserPurchases(date, dateComparison);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,109 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
||||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||||
import 'package:retrofit/retrofit.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
|
||||||
|
|
||||||
part 'consultation_service.g.dart';
|
class ConsultationService {
|
||||||
|
final Dio dio;
|
||||||
|
ConsultationService(this.dio);
|
||||||
|
|
||||||
@RestApi()
|
Future<ConsultationPaginationModel> getConsultations() async {
|
||||||
abstract class ConsultationService {
|
try {
|
||||||
factory ConsultationService(Dio dio, {String baseUrl}) = _ConsultationService;
|
final response = await dio.get('/api/consultations', queryParameters: {
|
||||||
|
'filters[last_state][\$notIn][]': const [
|
||||||
|
'initied',
|
||||||
|
'in_coordination',
|
||||||
|
'start_date_has_been_set',
|
||||||
|
'pending_payment',
|
||||||
|
'canceled_by_the_initiator',
|
||||||
|
'charge_was_collected',
|
||||||
|
],
|
||||||
|
'sort[]': 'created_at:desc'
|
||||||
|
});
|
||||||
|
return ConsultationPaginationModel.fromJson(response.data);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GET('/api/consultations')
|
Future<ConsultationPaginationModel> getFilteredConsultations({
|
||||||
Future<ConsultationPaginationModel> getConsultations();
|
String? state,
|
||||||
|
String? date,
|
||||||
|
FiltersComparisonEnum? stateComparison,
|
||||||
|
FiltersComparisonEnum? dateComparison,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await dio.get(
|
||||||
|
'/api/consultations',
|
||||||
|
queryParameters: {
|
||||||
|
if (stateComparison == FiltersComparisonEnum.notInArray)
|
||||||
|
'filters[last_state][\$notIn][]': const [
|
||||||
|
'initied',
|
||||||
|
'in_coordination',
|
||||||
|
'start_date_has_been_set',
|
||||||
|
'pending_payment',
|
||||||
|
'canceled_by_the_initiator',
|
||||||
|
'charge_was_collected',
|
||||||
|
]
|
||||||
|
else if (state != null && stateComparison != null)
|
||||||
|
'filters[last_state][${stateComparison.value}]': state,
|
||||||
|
if (dateComparison != null && date != null) 'filters[start_date][${dateComparison.value}]': date,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return ConsultationPaginationModel.fromJson(response.data);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StatisticModel> getStatistics() async {
|
||||||
|
try {
|
||||||
|
final response = await dio.get(
|
||||||
|
'/api/consultations/statistics',
|
||||||
|
);
|
||||||
|
return StatisticModel.fromJson(response.data);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ConsultantFinanceModel>> getConsultantFinances(
|
||||||
|
String? date,
|
||||||
|
FiltersComparisonEnum? dateComparison,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await dio.get(
|
||||||
|
'/api/finances/my_finances',
|
||||||
|
queryParameters: {
|
||||||
|
if (dateComparison != null && date != null) 'filters[end_date]': date,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final data = response.data as Map<String, dynamic>;
|
||||||
|
final consultations = data['data'] as List<dynamic>;
|
||||||
|
return consultations.map((consultation) => ConsultantFinanceModel.fromJson(consultation as Map<String, dynamic>)).toList();
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<UserPurchasesModel>> getUserPurchases(
|
||||||
|
String? date,
|
||||||
|
FiltersComparisonEnum? dateComparison,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await dio.get(
|
||||||
|
'/api/finances/my_purchases',
|
||||||
|
queryParameters: {
|
||||||
|
if (dateComparison != null && date != null) 'filters[start_date]': date,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final data = response.data as Map<String, dynamic>;
|
||||||
|
final purchases = data['data'] as List<dynamic>;
|
||||||
|
return purchases.map((purchase) => UserPurchasesModel.fromJson(purchase as Map<String, dynamic>)).toList();
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
|||||||
final diet = Diet(
|
final diet = Diet(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: state.dietData.name ?? '',
|
name: state.dietData.name ?? '',
|
||||||
|
slug: '',
|
||||||
image: imageInBase64,
|
image: imageInBase64,
|
||||||
duration: state.dietData.duration ?? 0,
|
duration: state.dietData.duration ?? 0,
|
||||||
state: state.dietData.state ?? DietState.active,
|
state: state.dietData.state ?? DietState.active,
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
isAuthor: widget.diet.author.authorIsMe,
|
||||||
path: 'diet/${widget.diet.id}',
|
path: 'diet/${widget.diet.slug}',
|
||||||
complainedContentType: ComplainedContent.diet,
|
complainedContentType: ComplainedContent.diet,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dotted_border/dotted_border.dart';
|
import 'package:dotted_border/dotted_border.dart';
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
|
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||||
@@ -11,7 +13,9 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class DietImagePickerWidget extends StatefulWidget {
|
class DietImagePickerWidget extends StatefulWidget {
|
||||||
const DietImagePickerWidget({super.key});
|
const DietImagePickerWidget({super.key});
|
||||||
@@ -28,11 +32,18 @@ class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
|
|||||||
actions: [
|
actions: [
|
||||||
CupertinoActionSheetAction(
|
CupertinoActionSheetAction(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final picker = ImagePicker();
|
PermissionHandler.checkPermission(
|
||||||
final image = await picker.pickImage(source: ImageSource.camera);
|
permission: Permission.camera,
|
||||||
cubit.setImage(image);
|
title: s.allowAccessToCamera,
|
||||||
if (!context.mounted) return;
|
description: s.allowAccessToCameraDescription,
|
||||||
Navigator.of(context).pop();
|
onGranted: () async {
|
||||||
|
final picker = ImagePicker();
|
||||||
|
final image = await picker.pickImage(source: ImageSource.camera);
|
||||||
|
cubit.setImage(image);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
},
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -44,11 +55,18 @@ class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
|
|||||||
),
|
),
|
||||||
CupertinoActionSheetAction(
|
CupertinoActionSheetAction(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final picker = ImagePicker();
|
PermissionHandler.checkPermission(
|
||||||
final image = await picker.pickImage(source: ImageSource.gallery);
|
permission: Permission.photos,
|
||||||
cubit.setImage(image);
|
title: s.allowAcessToGalery,
|
||||||
if (!context.mounted) return;
|
description: s.allowAcessToGaleryDescription,
|
||||||
Navigator.of(context).pop();
|
onGranted: () async {
|
||||||
|
final picker = ImagePicker();
|
||||||
|
final image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
|
cubit.setImage(image);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
},
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -21,21 +21,45 @@ class OptionWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _OptionWidgetState extends State<OptionWidget> {
|
class _OptionWidgetState extends State<OptionWidget> {
|
||||||
final customerPriceController = TextEditingController()..text = '1200₽';
|
final firstOptionCustomerPriceController = TextEditingController()..text = '1200₽';
|
||||||
|
final secondOptionCustomerPriceController = TextEditingController()..text = '1200₽';
|
||||||
|
final thirdOptionCustomerPriceController = TextEditingController()..text = '1200₽';
|
||||||
|
|
||||||
final creatorPriceController = TextEditingController()..text = '1000₽';
|
final creatorPriceController = TextEditingController()..text = '1000₽';
|
||||||
final optionTitleController = TextEditingController();
|
|
||||||
final optionDescriptionController = TextEditingController();
|
final firstOptionTitleController = TextEditingController();
|
||||||
|
final secondOptionTitleController = TextEditingController();
|
||||||
|
final thirdOptionTitleController = TextEditingController();
|
||||||
|
|
||||||
|
final firstOptionDescriptionController = TextEditingController();
|
||||||
|
final secondOptionDescriptionController = TextEditingController();
|
||||||
|
final thirdOptionDescriptionController = TextEditingController();
|
||||||
|
|
||||||
bool firstExplanationVisible = false;
|
bool firstExplanationVisible = false;
|
||||||
bool isChecked = false;
|
bool isChecked = false;
|
||||||
bool secondExplanationVisible = false;
|
bool secondExplanationVisible = false;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
customerPriceController.addListener(() {
|
firstOptionCustomerPriceController.addListener(() {
|
||||||
if (!customerPriceController.text.endsWith('₽')) {
|
if (!firstOptionCustomerPriceController.text.endsWith('₽')) {
|
||||||
final text = customerPriceController.text.replaceAll('₽', '');
|
final text = firstOptionCustomerPriceController.text.replaceAll('₽', '');
|
||||||
customerPriceController.text = '$text₽';
|
firstOptionCustomerPriceController.text = '$text₽';
|
||||||
customerPriceController.selection = TextSelection.collapsed(offset: text.length);
|
firstOptionCustomerPriceController.selection = TextSelection.collapsed(offset: text.length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
secondOptionCustomerPriceController.addListener(() {
|
||||||
|
if (!secondOptionCustomerPriceController.text.endsWith('₽')) {
|
||||||
|
final text = secondOptionCustomerPriceController.text.replaceAll('₽', '');
|
||||||
|
secondOptionCustomerPriceController.text = '$text₽';
|
||||||
|
secondOptionCustomerPriceController.selection = TextSelection.collapsed(offset: text.length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thirdOptionCustomerPriceController.addListener(() {
|
||||||
|
if (!thirdOptionCustomerPriceController.text.endsWith('₽')) {
|
||||||
|
final text = thirdOptionCustomerPriceController.text.replaceAll('₽', '');
|
||||||
|
thirdOptionCustomerPriceController.text = '$text₽';
|
||||||
|
thirdOptionCustomerPriceController.selection = TextSelection.collapsed(offset: text.length);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -45,15 +69,32 @@ class _OptionWidgetState extends State<OptionWidget> {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final optionNameHintText = switch (state.selectedOptionIndex ?? 1) {
|
final selectedOptionIndex = state.selectedOptionIndex ?? 0;
|
||||||
|
final optionNameHintText = switch (selectedOptionIndex) {
|
||||||
0 => s.economy,
|
0 => s.economy,
|
||||||
2 => s.comfort,
|
2 => s.comfort,
|
||||||
_ => s.standard,
|
_ => s.standard,
|
||||||
};
|
};
|
||||||
final selectedOptionIndex = state.selectedOptionIndex ?? 0;
|
final optionTitleController = switch (selectedOptionIndex) {
|
||||||
|
0 => firstOptionTitleController,
|
||||||
|
2 => thirdOptionTitleController,
|
||||||
|
_ => secondOptionTitleController,
|
||||||
|
};
|
||||||
|
final optionDescriptionController = switch (selectedOptionIndex) {
|
||||||
|
0 => firstOptionDescriptionController,
|
||||||
|
2 => thirdOptionDescriptionController,
|
||||||
|
_ => secondOptionDescriptionController,
|
||||||
|
};
|
||||||
|
final customerPriceController = switch (selectedOptionIndex) {
|
||||||
|
0 => firstOptionCustomerPriceController,
|
||||||
|
2 => thirdOptionCustomerPriceController,
|
||||||
|
_ => secondOptionCustomerPriceController,
|
||||||
|
};
|
||||||
|
|
||||||
optionTitleController.text = state.planData.options[selectedOptionIndex].short;
|
final price = double.tryParse(customerPriceController.text.replaceAll('₽', ''));
|
||||||
optionDescriptionController.text = state.planData.options[selectedOptionIndex].description;
|
creatorPriceController.text = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽';
|
||||||
|
firstOptionTitleController.text = state.planData.options[selectedOptionIndex].short;
|
||||||
|
firstOptionDescriptionController.text = state.planData.options[selectedOptionIndex].description;
|
||||||
return MainBackgroundBouble(
|
return MainBackgroundBouble(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -151,7 +192,6 @@ class _OptionWidgetState extends State<OptionWidget> {
|
|||||||
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].priceHasError,
|
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].priceHasError,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
final price = double.tryParse(value.replaceAll('₽', ''));
|
final price = double.tryParse(value.replaceAll('₽', ''));
|
||||||
creatorPriceController.text = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽';
|
|
||||||
if (price != null) {
|
if (price != null) {
|
||||||
context.read<CreatePlanCubit>().setPrice(price);
|
context.read<CreatePlanCubit>().setPrice(price);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dotted_border/dotted_border.dart';
|
import 'package:dotted_border/dotted_border.dart';
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
|
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||||
@@ -11,7 +13,9 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class PlanImagePickerWidget extends StatefulWidget {
|
class PlanImagePickerWidget extends StatefulWidget {
|
||||||
const PlanImagePickerWidget({super.key});
|
const PlanImagePickerWidget({super.key});
|
||||||
@@ -28,11 +32,18 @@ class _PlanImagePickerWidgetState extends State<PlanImagePickerWidget> {
|
|||||||
actions: [
|
actions: [
|
||||||
CupertinoActionSheetAction(
|
CupertinoActionSheetAction(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final picker = ImagePicker();
|
PermissionHandler.checkPermission(
|
||||||
final image = await picker.pickImage(source: ImageSource.camera);
|
permission: Permission.camera,
|
||||||
cubit.setImage(image);
|
title: s.allowAccessToCamera,
|
||||||
if (!context.mounted) return;
|
description: s.allowAccessToCameraDescription,
|
||||||
Navigator.of(context).pop();
|
onGranted: () async {
|
||||||
|
final picker = ImagePicker();
|
||||||
|
final image = await picker.pickImage(source: ImageSource.camera);
|
||||||
|
cubit.setImage(image);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
},
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -44,11 +55,18 @@ class _PlanImagePickerWidgetState extends State<PlanImagePickerWidget> {
|
|||||||
),
|
),
|
||||||
CupertinoActionSheetAction(
|
CupertinoActionSheetAction(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final picker = ImagePicker();
|
PermissionHandler.checkPermission(
|
||||||
final image = await picker.pickImage(source: ImageSource.gallery);
|
permission: Permission.photos,
|
||||||
cubit.setImage(image);
|
title: s.allowAcessToGalery,
|
||||||
if (!context.mounted) return;
|
description: s.allowAcessToGaleryDescription,
|
||||||
Navigator.of(context).pop();
|
onGranted: () async {
|
||||||
|
final picker = ImagePicker();
|
||||||
|
final image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
|
cubit.setImage(image);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
},
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||||
final RecipeRepository recipeRepository;
|
final RecipeRepository recipeRepository;
|
||||||
final GlossaryRepository glossaryRepository;
|
final GlossaryRepository glossaryRepository;
|
||||||
|
|
||||||
CreateRecipeCubit({
|
CreateRecipeCubit({
|
||||||
required this.recipeRepository,
|
required this.recipeRepository,
|
||||||
required this.glossaryRepository,
|
required this.glossaryRepository,
|
||||||
@@ -192,7 +193,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
recipe.steps!.map(
|
recipe.steps!.map(
|
||||||
(e) async => StepEntity(
|
(e) async => StepEntity(
|
||||||
description: e.description,
|
description: e.description,
|
||||||
image: await downloadImageAsFile('$baseUrl/storage/${e.image}'),
|
image: e.image.isEmpty ? null : await downloadImageAsFile('$baseUrl/storage/${e.image}'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
|
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class ImageWidget extends StatefulWidget {
|
class ImageWidget extends StatefulWidget {
|
||||||
const ImageWidget({super.key, required this.image});
|
const ImageWidget({super.key, required this.image});
|
||||||
@@ -67,55 +72,72 @@ class _ImageWidgetState extends State<ImageWidget> {
|
|||||||
_showImagePicker() {
|
_showImagePicker() {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CupertinoActionSheet(
|
builder: (context) {
|
||||||
actions: [
|
final s = S.of(context);
|
||||||
CupertinoActionSheetAction(
|
return CupertinoActionSheet(
|
||||||
onPressed: () async {
|
actions: [
|
||||||
final picker = ImagePicker();
|
CupertinoActionSheetAction(
|
||||||
final image = await picker.pickImage(source: ImageSource.camera);
|
onPressed: () async {
|
||||||
setState(() {});
|
PermissionHandler.checkPermission(
|
||||||
if (image != null) {
|
permission: Permission.camera,
|
||||||
createRecipeCubit.replaceImage(File(image.path));
|
title: s.allowAccessToCamera,
|
||||||
createRecipeCubit.validateImage();
|
description: s.allowAccessToCameraDescription,
|
||||||
}
|
onGranted: () async {
|
||||||
if (!context.mounted) return;
|
final picker = ImagePicker();
|
||||||
Navigator.of(context).pop();
|
final image = await picker.pickImage(source: ImageSource.camera);
|
||||||
},
|
setState(() {});
|
||||||
child: const Row(
|
if (image != null) {
|
||||||
children: [
|
createRecipeCubit.replaceImage(File(image.path));
|
||||||
Icon(Icons.photo_camera),
|
createRecipeCubit.validateImage();
|
||||||
SizedBox(width: 8),
|
}
|
||||||
Text('Фото'),
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
],
|
},
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo_camera),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Фото'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
CupertinoActionSheetAction(
|
||||||
CupertinoActionSheetAction(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
PermissionHandler.checkPermission(
|
||||||
final picker = ImagePicker();
|
permission: Permission.photos,
|
||||||
final image = await picker.pickImage(source: ImageSource.gallery);
|
title: s.allowAcessToGalery,
|
||||||
setState(() {});
|
description: s.allowAcessToGaleryDescription,
|
||||||
if (image != null) {
|
onGranted: () async {
|
||||||
createRecipeCubit.replaceImage(File(image.path));
|
final picker = ImagePicker();
|
||||||
createRecipeCubit.validateImage();
|
final image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
}
|
setState(() {});
|
||||||
if (!context.mounted) return;
|
if (image != null) {
|
||||||
Navigator.of(context).pop();
|
createRecipeCubit.replaceImage(File(image.path));
|
||||||
},
|
createRecipeCubit.validateImage();
|
||||||
child: const Row(
|
}
|
||||||
children: [
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
Icon(Icons.photo),
|
},
|
||||||
SizedBox(width: 8),
|
context: context,
|
||||||
Text('Галерея'),
|
);
|
||||||
],
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Галерея'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
isDestructiveAction: true,
|
||||||
|
child: const Text('Отмена'),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
cancelButton: CupertinoActionSheetAction(
|
},
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
isDestructiveAction: true,
|
|
||||||
child: const Text('Отмена'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
|
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class StepImageWidget extends StatefulWidget {
|
class StepImageWidget extends StatefulWidget {
|
||||||
final CreateRecipeCubit createRecipeCubit;
|
final CreateRecipeCubit createRecipeCubit;
|
||||||
@@ -12,7 +17,8 @@ class StepImageWidget extends StatefulWidget {
|
|||||||
|
|
||||||
const StepImageWidget({
|
const StepImageWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.index, required this.createRecipeCubit,
|
required this.index,
|
||||||
|
required this.createRecipeCubit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -79,53 +85,70 @@ class _StepImageWidgetState extends State<StepImageWidget> {
|
|||||||
_showImagePicker() {
|
_showImagePicker() {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CupertinoActionSheet(
|
builder: (context) {
|
||||||
actions: [
|
final s = S.of(context);
|
||||||
CupertinoActionSheetAction(
|
return CupertinoActionSheet(
|
||||||
onPressed: () async {
|
actions: [
|
||||||
final picker = ImagePicker();
|
CupertinoActionSheetAction(
|
||||||
image = await picker.pickImage(source: ImageSource.camera);
|
onPressed: () async {
|
||||||
setState(() {});
|
PermissionHandler.checkPermission(
|
||||||
if (image != null) {
|
permission: Permission.camera,
|
||||||
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
title: s.allowAccessToCamera,
|
||||||
}
|
description: s.allowAccessToCameraDescription,
|
||||||
if (!context.mounted) return;
|
onGranted: () async {
|
||||||
Navigator.of(context).pop();
|
final picker = ImagePicker();
|
||||||
},
|
image = await picker.pickImage(source: ImageSource.camera);
|
||||||
child: const Row(
|
setState(() {});
|
||||||
children: [
|
if (image != null) {
|
||||||
Icon(Icons.photo_camera),
|
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||||
SizedBox(width: 8),
|
}
|
||||||
Text('Фото'),
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
],
|
},
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo_camera),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Фото'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
CupertinoActionSheetAction(
|
||||||
CupertinoActionSheetAction(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
PermissionHandler.checkPermission(
|
||||||
final picker = ImagePicker();
|
permission: Permission.photos,
|
||||||
image = await picker.pickImage(source: ImageSource.gallery);
|
title: s.allowAcessToGalery,
|
||||||
setState(() {});
|
description: s.allowAcessToGaleryDescription,
|
||||||
if (image != null) {
|
onGranted: () async {
|
||||||
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
final picker = ImagePicker();
|
||||||
}
|
image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
if (!context.mounted) return;
|
setState(() {});
|
||||||
Navigator.of(context).pop();
|
if (image != null) {
|
||||||
},
|
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||||
child: const Row(
|
}
|
||||||
children: [
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
Icon(Icons.photo),
|
},
|
||||||
SizedBox(width: 8),
|
context: context,
|
||||||
Text('Галерея'),
|
);
|
||||||
],
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Галерея'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
isDestructiveAction: true,
|
||||||
|
child: const Text('Отмена'),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
cancelButton: CupertinoActionSheetAction(
|
},
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
isDestructiveAction: true,
|
|
||||||
child: const Text('Отмена'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||||
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
|
||||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||||
@@ -13,6 +14,7 @@ import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
|||||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class DiaryCubit extends Cubit<DiaryState> {
|
class DiaryCubit extends Cubit<DiaryState> {
|
||||||
@@ -23,9 +25,17 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
final GlossaryRepository glossaryRepository;
|
final GlossaryRepository glossaryRepository;
|
||||||
final ProfileRepository profileRepository;
|
final ProfileRepository profileRepository;
|
||||||
final DietRepository dietRepository;
|
final DietRepository dietRepository;
|
||||||
|
final ChatRepository chatRepository;
|
||||||
|
final SettingsRepository settingsRepository;
|
||||||
int? diaryId;
|
int? diaryId;
|
||||||
DiaryCubit({required this.diaryRepository, required this.glossaryRepository, required this.profileRepository, required this.dietRepository})
|
DiaryCubit({
|
||||||
: super(const DiaryState.loading()) {
|
required this.chatRepository,
|
||||||
|
required this.diaryRepository,
|
||||||
|
required this.glossaryRepository,
|
||||||
|
required this.profileRepository,
|
||||||
|
required this.dietRepository,
|
||||||
|
required this.settingsRepository,
|
||||||
|
}) : super(const DiaryState.loading()) {
|
||||||
today = DateTime.now();
|
today = DateTime.now();
|
||||||
selectedDay = today;
|
selectedDay = today;
|
||||||
diaryRepository.forceFetchStream.listen((_) {
|
diaryRepository.forceFetchStream.listen((_) {
|
||||||
@@ -144,23 +154,36 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchDiary([bool needLoading = false, int? diaryId]) async {
|
fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async {
|
||||||
this.diaryId ??= diaryId;
|
this.diaryId ??= diaryId;
|
||||||
|
|
||||||
if (needLoading) {
|
if (needLoading) {
|
||||||
emit(const DiaryState.loading());
|
emit(const DiaryState.loading());
|
||||||
selectedMeal = 1;
|
selectedMeal = 1;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final DiaryDayModel diaryDay;
|
final DiaryDayModel diaryDay;
|
||||||
|
bool isMoodEnabled = false;
|
||||||
|
bool isWakeUpAndRetireTimeEnabled = false;
|
||||||
if (diaryId != null) {
|
if (diaryId != null) {
|
||||||
diaryDay = await diaryRepository.getDiaryDay(diaryId);
|
diaryDay = await diaryRepository.getDiaryDay(diaryId);
|
||||||
} else {
|
} else {
|
||||||
final stringDate = selectedDay.format('yyyy-MM-dd');
|
final stringDate = selectedDay.format('yyyy-MM-dd');
|
||||||
diaryDay = await diaryRepository.getDiaryByDate(stringDate);
|
diaryDay = await diaryRepository.getDiaryByDate(stringDate);
|
||||||
|
final profileSettings = await settingsRepository.getProfileSettings();
|
||||||
|
isMoodEnabled = profileSettings.isMoodFillable;
|
||||||
|
isWakeUpAndRetireTimeEnabled = profileSettings.isBedtimeFillable;
|
||||||
}
|
}
|
||||||
ConsultantReviewModel? consultantReview;
|
ConsultantReviewModel? consultantReview;
|
||||||
|
int? planDuration;
|
||||||
|
int? currentPlanDay;
|
||||||
|
String? planName;
|
||||||
if (diaryDay.chosenPlanId != null) {
|
if (diaryDay.chosenPlanId != null) {
|
||||||
consultantReview = await diaryRepository.getDiaryComments(planId: diaryDay.chosenPlanId!, diaryId: diaryDay.id);
|
consultantReview = await diaryRepository.getDiaryComments(planId: diaryDay.chosenPlanId!, diaryId: diaryDay.id);
|
||||||
|
final chosenPlanModel = await chatRepository.getChosenPlan(id: diaryDay.chosenPlanId!);
|
||||||
|
planDuration = chosenPlanModel.plan.duration;
|
||||||
|
planName = chosenPlanModel.plan.name;
|
||||||
|
currentPlanDay = currentDay ?? DateTime.now().difference(chosenPlanModel.startDate).inDays + 1;
|
||||||
}
|
}
|
||||||
final nutrition = await getNutrition(diaryDay.id);
|
final nutrition = await getNutrition(diaryDay.id);
|
||||||
|
|
||||||
@@ -179,6 +202,11 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
consultantReview: consultantReview,
|
consultantReview: consultantReview,
|
||||||
isDiaryFinished: isDiaryFinished,
|
isDiaryFinished: isDiaryFinished,
|
||||||
isDiaryCommented: isDiaryCommented,
|
isDiaryCommented: isDiaryCommented,
|
||||||
|
chosenPlanCurrentDay: currentPlanDay,
|
||||||
|
chosenPlanDuration: planDuration,
|
||||||
|
chosenPlanName: planName,
|
||||||
|
isMoodEnabled: isMoodEnabled,
|
||||||
|
isWakeUpAndRetireEnabled: isWakeUpAndRetireTimeEnabled
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
|||||||
@@ -20,10 +20,15 @@ abstract class DiaryState with _$DiaryState {
|
|||||||
required NutritionDataModel nutrition,
|
required NutritionDataModel nutrition,
|
||||||
required DateTime selectedDay,
|
required DateTime selectedDay,
|
||||||
required int selectedMeal,
|
required int selectedMeal,
|
||||||
@Default(false) bool isConsultantView,
|
int? chosenPlanCurrentDay,
|
||||||
|
String? chosenPlanName,
|
||||||
|
int? chosenPlanDuration,
|
||||||
ConsultantReviewModel? consultantReview,
|
ConsultantReviewModel? consultantReview,
|
||||||
|
@Default(false) bool isConsultantView,
|
||||||
@Default(false) bool isDiaryFinished,
|
@Default(false) bool isDiaryFinished,
|
||||||
@Default(false) bool isDiaryCommented,
|
@Default(false) bool isDiaryCommented,
|
||||||
|
@Default(false) bool isWakeUpAndRetireEnabled,
|
||||||
|
@Default(false) bool isMoodEnabled,
|
||||||
}) = DiaryStateLoaded;
|
}) = DiaryStateLoaded;
|
||||||
|
|
||||||
const factory DiaryState.loadedTomorrowWithDiet({
|
const factory DiaryState.loadedTomorrowWithDiet({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:developer';
|
|||||||
|
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
@@ -34,24 +35,33 @@ class DiaryScreen extends StatelessWidget {
|
|||||||
class UserDiaryScreen extends StatelessWidget {
|
class UserDiaryScreen extends StatelessWidget {
|
||||||
static const path = '/user-diary-screen';
|
static const path = '/user-diary-screen';
|
||||||
static const name = 'userDiaryScreen';
|
static const name = 'userDiaryScreen';
|
||||||
const UserDiaryScreen({super.key, required this.diaryId});
|
const UserDiaryScreen({
|
||||||
|
super.key,
|
||||||
|
required this.diaryId,
|
||||||
|
required this.currentPlanDay,
|
||||||
|
});
|
||||||
final int diaryId;
|
final int diaryId;
|
||||||
|
final int currentPlanDay;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DiaryScreenContent(diaryId: diaryId);
|
return DiaryScreenContent(
|
||||||
|
diaryId: diaryId,
|
||||||
|
currentPlanDay: currentPlanDay,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiaryScreenContent extends StatelessWidget {
|
class DiaryScreenContent extends StatelessWidget {
|
||||||
const DiaryScreenContent({super.key, this.diaryId});
|
const DiaryScreenContent({super.key, this.diaryId, this.currentPlanDay});
|
||||||
final int? diaryId;
|
final int? diaryId;
|
||||||
|
final int? currentPlanDay;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<DiaryCubit>(
|
BlocProvider<DiaryCubit>(
|
||||||
create: (BuildContext context) => sl<DiaryCubit>()..fetchDiary(true, diaryId),
|
create: (BuildContext context) => sl<DiaryCubit>()..fetchDiary(true, diaryId, currentPlanDay),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => sl<ConsultantCommentsCubit>(),
|
create: (context) => sl<ConsultantCommentsCubit>(),
|
||||||
@@ -70,11 +80,19 @@ class DiaryScreenContent extends StatelessWidget {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.grey.shade200,
|
backgroundColor: Colors.grey.shade200,
|
||||||
appBar: AppBar(
|
appBar: diaryId == null
|
||||||
toolbarHeight: 0,
|
? AppBar(
|
||||||
),
|
toolbarHeight: 0,
|
||||||
|
)
|
||||||
|
: AppBar(
|
||||||
|
title: Text(
|
||||||
|
state is DiaryStateLoaded ? DateTime.parse(state.diary.date).toLocal().format('d MMMM') : '',
|
||||||
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.only(bottom: 16, top: 8),
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -87,13 +105,13 @@ class DiaryScreenContent extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
if (state is DiaryStateLoaded)
|
if (state is DiaryStateLoaded)
|
||||||
WakeupAndRetireWidget(
|
WakeupAndRetireWidget(
|
||||||
showRetire: state.diary.typeOfDiary.hasChosenPlan,
|
isEnabled: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isWakeUpAndRetireEnabled,
|
||||||
disable: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented,
|
disable: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
if (state is DiaryStateLoaded)
|
if (state is DiaryStateLoaded)
|
||||||
MealWidget(
|
MealWidget(
|
||||||
showMood: state.diary.typeOfDiary.hasChosenPlan,
|
showMood: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isMoodEnabled,
|
||||||
showAddButton: !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented,
|
showAddButton: !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented,
|
||||||
),
|
),
|
||||||
if (state is DiaryStateLoadedTomorrowWithDiet)
|
if (state is DiaryStateLoadedTomorrowWithDiet)
|
||||||
|
|||||||
@@ -89,7 +89,9 @@ class _CalendarWidgetState extends State<CalendarWidget> {
|
|||||||
return isSameDay(_selectedDay, day);
|
return isSameDay(_selectedDay, day);
|
||||||
},
|
},
|
||||||
onPageChanged: (focusedDay) {
|
onPageChanged: (focusedDay) {
|
||||||
_focusedDay = focusedDay;
|
setState(() {
|
||||||
|
_focusedDay = focusedDay;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onFormatChanged: (format) {
|
onFormatChanged: (format) {
|
||||||
if (_calendarFormat != format) {
|
if (_calendarFormat != format) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class CaloriesChartWidget extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const AlertDialog(
|
builder: (context) => const AlertDialog(
|
||||||
content: Text(
|
content: Text(
|
||||||
'График отображает сколько калорий вам нужно ежедневно для похуденияи поддержания веса, согласно формуле Харриса-Бенедикта.\nИзменить коэффициент физической нагрузки можно в настройках.',
|
'График отображает сколько калорий Вам нужно ежедневно для поддержания веса, согласно формуле Харриса-Бенедикта. Изменить коэффициент физической нагрузки можно в настройках',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF323F4B),
|
color: Color(0xFF323F4B),
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ChosenPlanWidget extends StatelessWidget {
|
||||||
|
const ChosenPlanWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<DiaryCubit, DiaryState>(builder: (context, state) {
|
||||||
|
if (state is DiaryStateLoaded) {
|
||||||
|
if (state.diary.typeOfDiary.hasChosenPlan) {
|
||||||
|
return ChosenPlanWidgetContent(
|
||||||
|
currentPlanDay: state.chosenPlanCurrentDay ?? 0,
|
||||||
|
planName: state.chosenPlanName ?? '',
|
||||||
|
planDuration: state.chosenPlanDuration ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChosenPlanWidgetContent extends StatelessWidget {
|
||||||
|
const ChosenPlanWidgetContent({
|
||||||
|
super.key,
|
||||||
|
required this.currentPlanDay,
|
||||||
|
required this.planName,
|
||||||
|
required this.planDuration,
|
||||||
|
});
|
||||||
|
final int currentPlanDay;
|
||||||
|
final String planName;
|
||||||
|
final int planDuration;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = MediaQuery.sizeOf(context);
|
||||||
|
final s = S.of(context);
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: size.width * 0.4,
|
||||||
|
child: Text(planName, style: TextStyles.textLMedium.copyWith(color: appTheme.black)),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(color: appTheme.dividerColor, borderRadius: BorderRadius.circular(12)),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||||
|
child: Text('${s.day} $currentPlanDay из $planDuration', style: TextStyles.textMSemibold.copyWith(color: appTheme.gray)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
|||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/widgets/add_product_button_widget.dart';
|
import 'package:eatvio_app/features/diary/presentation/widgets/add_product_button_widget.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/widgets/chosen_diet_widget.dart';
|
import 'package:eatvio_app/features/diary/presentation/widgets/chosen_diet_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/presentation/widgets/chosen_plan_widget.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/widgets/comment_container.dart';
|
import 'package:eatvio_app/features/diary/presentation/widgets/comment_container.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart';
|
import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/widgets/meal_comment_widget.dart';
|
import 'package:eatvio_app/features/diary/presentation/widgets/meal_comment_widget.dart';
|
||||||
@@ -58,8 +59,8 @@ class _MealWidgetState extends State<MealWidget> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const TimeOfMealPickerWidget(),
|
const TimeOfMealPickerWidget(),
|
||||||
|
|
||||||
const ChosenDietWidget(),
|
const ChosenDietWidget(),
|
||||||
|
const ChosenPlanWidget(),
|
||||||
const ProductsListWidget(),
|
const ProductsListWidget(),
|
||||||
if (widget.showAddButton) ...[
|
if (widget.showAddButton) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class PhotoGalleryWidget extends StatefulWidget {
|
class PhotoGalleryWidget extends StatefulWidget {
|
||||||
const PhotoGalleryWidget({super.key});
|
const PhotoGalleryWidget({super.key});
|
||||||
@@ -32,55 +37,75 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
|
|||||||
showImagePicker(DiaryDayModel diaryDay) {
|
showImagePicker(DiaryDayModel diaryDay) {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CupertinoActionSheet(
|
builder: (context) {
|
||||||
actions: [
|
final s = S.of(context);
|
||||||
CupertinoActionSheetAction(
|
return CupertinoActionSheet(
|
||||||
onPressed: () async {
|
actions: [
|
||||||
Navigator.of(context).pop();
|
CupertinoActionSheetAction(
|
||||||
final picker = ImagePicker();
|
onPressed: () async {
|
||||||
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
PermissionHandler.checkPermission(
|
||||||
if (image != null) {
|
permission: Permission.camera,
|
||||||
diaryCubit.uploadPhotos(
|
title: s.allowAccessToCamera,
|
||||||
files: [File(image.path)],
|
description: s.allowAccessToCameraDescription,
|
||||||
diaryId: diaryDay.id,
|
onGranted: () async {
|
||||||
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
|
||||||
|
final picker = ImagePicker();
|
||||||
|
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
||||||
|
if (image != null) {
|
||||||
|
diaryCubit.uploadPhotos(
|
||||||
|
files: [File(image.path)],
|
||||||
|
diaryId: diaryDay.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context: context,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
child: const Row(
|
||||||
child: const Row(
|
children: [
|
||||||
children: [
|
Icon(Icons.photo_camera),
|
||||||
Icon(Icons.photo_camera),
|
SizedBox(width: 8),
|
||||||
SizedBox(width: 8),
|
Text('Фото'),
|
||||||
Text('Фото'),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
CupertinoActionSheetAction(
|
||||||
CupertinoActionSheetAction(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
PermissionHandler.checkPermission(
|
||||||
Navigator.of(context).pop();
|
permission: Permission.photos,
|
||||||
final picker = ImagePicker();
|
title: s.allowAcessToGalery,
|
||||||
List<XFile>? images = await picker.pickMultiImage();
|
description: s.allowAcessToGaleryDescription,
|
||||||
if (images.isNotEmpty) {
|
onGranted: () async {
|
||||||
diaryCubit.uploadPhotos(
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
files: images.map((f) => File(f.path)).toList(),
|
final picker = ImagePicker();
|
||||||
diaryId: diaryDay.id,
|
List<XFile>? images = await picker.pickMultiImage();
|
||||||
|
if (images.isNotEmpty) {
|
||||||
|
diaryCubit.uploadPhotos(
|
||||||
|
files: images.map((f) => File(f.path)).toList(),
|
||||||
|
diaryId: diaryDay.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context: context,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
child: const Row(
|
||||||
child: const Row(
|
children: [
|
||||||
children: [
|
Icon(Icons.photo),
|
||||||
Icon(Icons.photo),
|
SizedBox(width: 8),
|
||||||
SizedBox(width: 8),
|
Text('Галерея'),
|
||||||
Text('Галерея'),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
isDestructiveAction: true,
|
||||||
|
child: const Text('Отмена'),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
cancelButton: CupertinoActionSheetAction(
|
},
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
isDestructiveAction: true,
|
|
||||||
child: const Text('Отмена'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +117,9 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
|
|||||||
int mealIndex = state.selectedMeal - 1;
|
int mealIndex = state.selectedMeal - 1;
|
||||||
if (state.diary.meals[mealIndex].photos.isEmpty && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) {
|
if (state.diary.meals[mealIndex].photos.isEmpty && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => showImagePicker(state.diary),
|
onTap: () async {
|
||||||
|
showImagePicker(state.diary);
|
||||||
|
},
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
'assets/no_one_image_was_added.svg',
|
'assets/no_one_image_was_added.svg',
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
import '../../../../core/theme/theme_helper.dart';
|
import '../../../../core/theme/theme_helper.dart';
|
||||||
|
|
||||||
class WakeupAndRetireWidget extends StatefulWidget {
|
class WakeupAndRetireWidget extends StatefulWidget {
|
||||||
const WakeupAndRetireWidget({super.key, required this.showRetire, this.disable = false});
|
const WakeupAndRetireWidget({super.key, required this.isEnabled, this.disable = false});
|
||||||
final bool showRetire;
|
final bool isEnabled;
|
||||||
final bool disable;
|
final bool disable;
|
||||||
@override
|
@override
|
||||||
State<WakeupAndRetireWidget> createState() => _WakeupAndRetireWidgetState();
|
State<WakeupAndRetireWidget> createState() => _WakeupAndRetireWidgetState();
|
||||||
@@ -94,6 +94,9 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<DiaryCubit, DiaryState>(
|
return BlocBuilder<DiaryCubit, DiaryState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
if (!widget.isEnabled) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
if (state is DiaryStateLoaded) {
|
if (state is DiaryStateLoaded) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
@@ -157,67 +160,65 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.showRetire) ...[
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
Expanded(
|
||||||
Expanded(
|
child: Material(
|
||||||
child: Material(
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderRadius: BorderRadius.circular(12),
|
color: Colors.white,
|
||||||
color: Colors.white,
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 8,
|
||||||
horizontal: 8,
|
vertical: 12,
|
||||||
vertical: 12,
|
),
|
||||||
),
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: [
|
||||||
children: [
|
SvgPicture.asset('assets/icons/retire.svg'),
|
||||||
SvgPicture.asset('assets/icons/retire.svg'),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
const Text(
|
||||||
const Text(
|
'Уход ко сну',
|
||||||
'Уход ко сну',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 16,
|
||||||
fontSize: 16,
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: widget.disable ? null : () => showTimePicker(state.diary.id, false),
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
child: Ink(
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
color: const Color(0xFFF1F2F6),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text(
|
||||||
|
state.diary.retire.toString().substring(
|
||||||
|
0,
|
||||||
|
state.diary.retire.toString().length - 3,
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF6D6F71),
|
||||||
|
fontSize: 17,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
InkWell(
|
|
||||||
onTap: widget.disable ? null : () => showTimePicker(state.diary.id, false),
|
|
||||||
borderRadius: BorderRadius.circular(100),
|
|
||||||
child: Ink(
|
|
||||||
height: 32,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(100),
|
|
||||||
color: const Color(0xFFF1F2F6),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Text(
|
|
||||||
state.diary.retire.toString().substring(
|
|
||||||
0,
|
|
||||||
state.diary.retire.toString().length - 3,
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Color(0xFF6D6F71),
|
|
||||||
fontSize: 17,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
isAuthor: widget.diet.author.authorIsMe,
|
||||||
path: 'diet/${widget.diet.id}',
|
path: 'diet/${widget.diet.slug}',
|
||||||
complainedContentType: ComplainedContent.diet,
|
complainedContentType: ComplainedContent.diet,
|
||||||
id: widget.diet.id,
|
id: widget.diet.id,
|
||||||
diet: widget.diet,
|
diet: widget.diet,
|
||||||
@@ -499,7 +499,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
isAuthor: widget.diet.author.authorIsMe,
|
||||||
path: 'diet/${widget.diet.id}',
|
path: 'diet/${widget.diet.slug}',
|
||||||
complainedContentType: ComplainedContent.diet,
|
complainedContentType: ComplainedContent.diet,
|
||||||
id: widget.diet.id,
|
id: widget.diet.id,
|
||||||
diet: widget.diet,
|
diet: widget.diet,
|
||||||
@@ -526,7 +526,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
isAuthor: widget.diet.author.authorIsMe,
|
||||||
path: 'diet/${widget.diet.id}',
|
path: 'diet/${widget.diet.slug}',
|
||||||
complainedContentType: ComplainedContent.diet,
|
complainedContentType: ComplainedContent.diet,
|
||||||
id: widget.diet.id,
|
id: widget.diet.id,
|
||||||
diet: widget.diet,
|
diet: widget.diet,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
|
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
@@ -36,7 +37,7 @@ class AuthorCard extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: NetworkImage(author.authorProfileIcon),
|
image: NetworkImage(baseUrl + author.authorProfileIcon),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ abstract class DietsPagination with _$DietsPagination {
|
|||||||
abstract class Diet with _$Diet {
|
abstract class Diet with _$Diet {
|
||||||
const factory Diet({
|
const factory Diet({
|
||||||
@JsonKey(name: "id") required int id,
|
@JsonKey(name: "id") required int id,
|
||||||
|
@JsonKey(name: "slug") required String slug,
|
||||||
@JsonKey(name: "name") required String name,
|
@JsonKey(name: "name") required String name,
|
||||||
@JsonKey(name: "image") required String image,
|
@JsonKey(name: "image") required String image,
|
||||||
@JsonKey(name: "duration") required int duration,
|
@JsonKey(name: "duration") required int duration,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ abstract class Recipe with _$Recipe {
|
|||||||
@JsonKey(name: 'description_md') required String description,
|
@JsonKey(name: 'description_md') required String description,
|
||||||
required List<Step>? steps,
|
required List<Step>? steps,
|
||||||
@JsonKey(name: 'video_url') required String? videoUrl,
|
@JsonKey(name: 'video_url') required String? videoUrl,
|
||||||
@JsonKey(name: 'created_at') required DateTime createdAt,
|
@JsonKey(name: 'created_at') DateTime? createdAt,
|
||||||
@JsonKey(name: 'updated_at') required DateTime updatedAt,
|
@JsonKey(name: 'updated_at') DateTime? updatedAt,
|
||||||
required String? title,
|
required String? title,
|
||||||
required String? type,
|
required String? type,
|
||||||
required String slug,
|
required String slug,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
@@ -77,7 +79,13 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
likePost(int id) async => await feedRepository.likeBlogPost(id: id);
|
likePost(int id) async {
|
||||||
|
try {
|
||||||
|
await feedRepository.likeBlogPost(id: id);
|
||||||
|
} catch (e, st) {
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id);
|
unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id);
|
||||||
|
|
||||||
|
|||||||
@@ -56,91 +56,95 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
GestureDetector(
|
children: [
|
||||||
onTap: () {
|
GestureDetector(
|
||||||
GoRouter.of(context).pushNamed(
|
onTap: () {
|
||||||
'profileScreen',
|
GoRouter.of(context).pushNamed(
|
||||||
extra: Author(
|
'profileScreen',
|
||||||
authorSlug: state.feedBlogPostList[widget.index].author.authorSlug,
|
extra: Author(
|
||||||
authorName: state.feedBlogPostList[widget.index].author.authorName,
|
authorSlug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||||
authorProfileIcon: state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
authorName: state.feedBlogPostList[widget.index].author.authorName,
|
||||||
authorIsMe: state.feedBlogPostList[widget.index].author.authorIsMe,
|
authorProfileIcon: state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||||
),
|
authorIsMe: state.feedBlogPostList[widget.index].author.authorIsMe,
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
CircleAvatar(
|
|
||||||
radius: 20,
|
|
||||||
backgroundColor: Colors.black12,
|
|
||||||
foregroundImage: NetworkImage(
|
|
||||||
state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
|
||||||
),
|
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
state.feedBlogPostList[widget.index].author.authorName,
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.black87,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${calculateDifference(state.feedBlogPostList[widget.index].createdAt)} в ${state.feedBlogPostList[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.feedBlogPostList[widget.index].createdAt.minute.toString().padLeft(2, '0')}',
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.black45,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
if (!(state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe ?? false) &&
|
|
||||||
!state.feedBlogPostList[widget.index].author.authorIsMe)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8),
|
|
||||||
child: SubscribeButtonWidget(
|
|
||||||
slug: state.feedBlogPostList[widget.index].author.authorSlug,
|
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundColor: Colors.black12,
|
||||||
|
foregroundImage: NetworkImage(
|
||||||
|
baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||||
),
|
),
|
||||||
CustomPopup(
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
isAuthor: state.feedBlogPostList[widget.index].author.authorIsMe,
|
|
||||||
complainedContentType: ComplainedContent.posts,
|
|
||||||
path: 'blog_post/${(state.feedBlogPostList[widget.index].id)}',
|
|
||||||
id: state.feedBlogPostList[widget.index].id,
|
|
||||||
feedBlogPostModel: state.feedBlogPostList[widget.index],
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
|
// Имя и дата внутри Expanded
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
state.feedBlogPostList[widget.index].author.authorName,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
height: 1,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${calculateDifference(state.feedBlogPostList[widget.index].createdAt)} в ${state.feedBlogPostList[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.feedBlogPostList[widget.index].createdAt.minute.toString().padLeft(2, '0')}',
|
||||||
|
style: const TextStyle(
|
||||||
|
height: 1,
|
||||||
|
color: Colors.black45,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Кнопка подписки (если нужно)
|
||||||
|
if (!(state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe ?? false) &&
|
||||||
|
!state.feedBlogPostList[widget.index].author.authorIsMe)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: SubscribeButtonWidget(
|
||||||
|
slug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Кнопка меню
|
||||||
|
CustomPopup(
|
||||||
|
isAuthor: state.feedBlogPostList[widget.index].author.authorIsMe,
|
||||||
|
complainedContentType: ComplainedContent.posts,
|
||||||
|
path: 'blog_post/${(state.feedBlogPostList[widget.index].id)}',
|
||||||
|
id: state.feedBlogPostList[widget.index].id,
|
||||||
|
feedBlogPostModel: state.feedBlogPostList[widget.index],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
if (state.feedBlogPostList[widget.index].title.isNotEmpty || state.feedBlogPostList[widget.index].description.isNotEmpty)
|
),
|
||||||
|
if (state.feedBlogPostList[widget.index].title.isNotEmpty) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Text(
|
child: Text(
|
||||||
state.feedBlogPostList[widget.index].title,
|
state.feedBlogPostList[widget.index].title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
if (state.feedBlogPostList[widget.index].title.isEmpty) const SizedBox(height: 8),
|
||||||
if (state.feedBlogPostList[widget.index].title.isNotEmpty && state.feedBlogPostList[widget.index].description.isNotEmpty)
|
if (state.feedBlogPostList[widget.index].title.isNotEmpty && state.feedBlogPostList[widget.index].description.isNotEmpty)
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (state.feedBlogPostList[widget.index].description.isNotEmpty)
|
if (state.feedBlogPostList[widget.index].description.isNotEmpty)
|
||||||
@@ -245,7 +249,9 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
final box = context.findRenderObject() as RenderBox?;
|
final box = context.findRenderObject() as RenderBox?;
|
||||||
SharePlus.instance.share(
|
SharePlus.instance.share(
|
||||||
ShareParams(
|
ShareParams(
|
||||||
uri: Uri.parse('$baseUrl/ru/blog_post/${state.feedBlogPostList[widget.index].id}',),
|
uri: Uri.parse(
|
||||||
|
'$baseUrl/ru/blog_post/${state.feedBlogPostList[widget.index].id}',
|
||||||
|
),
|
||||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ class BlogPosts extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == state.feedBlogPostList.length) {
|
if (index == state.feedBlogPostList.length) {
|
||||||
@@ -32,9 +31,7 @@ class BlogPosts extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return BlogPostWidget(index: index);
|
return BlogPostWidget(index: index);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||||
return const SizedBox(height: 8);
|
|
||||||
},
|
|
||||||
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
|
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -53,3 +50,4 @@ class BlogPosts extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
@@ -77,7 +78,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
foregroundImage: NetworkImage(comment.authorProfileIcon),
|
foregroundImage: NetworkImage(baseUrl + comment.authorProfileIcon),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
radius: radius,
|
radius: radius,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class ConsultantWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
foregroundImage: NetworkImage(consultant.photo),
|
foregroundImage: NetworkImage(baseUrl + consultant.photo),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class _DietsWidgetState extends State<DietsWidget> {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: CustomPopup(
|
child: CustomPopup(
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
isAuthor: widget.diet.author.authorIsMe,
|
||||||
path: 'diet/${widget.diet.id}',
|
path: 'diet/${widget.diet.slug}',
|
||||||
id: widget.diet.id,
|
id: widget.diet.id,
|
||||||
complainedContentType: ComplainedContent.diet,
|
complainedContentType: ComplainedContent.diet,
|
||||||
diet: widget.diet,
|
diet: widget.diet,
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ class _PlanWidgetState extends State<PlanWidget> {
|
|||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: '$baseUrl/storage/${widget.plan.image}',
|
imageUrl: '$baseUrl/storage/${widget.plan.image}',
|
||||||
fit: BoxFit.fitWidth,
|
fit: BoxFit.cover,
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: 426,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -95,7 +96,7 @@ class _PlanWidgetState extends State<PlanWidget> {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: CustomPopup(
|
child: CustomPopup(
|
||||||
isAuthor: widget.plan.author.authorIsMe,
|
isAuthor: widget.plan.author.authorIsMe,
|
||||||
path: 'plan/${widget.plan.id}',
|
path: 'plan/${widget.plan.slug}',
|
||||||
id: widget.plan.id,
|
id: widget.plan.id,
|
||||||
slug: widget.plan.slug,
|
slug: widget.plan.slug,
|
||||||
complainedContentType: ComplainedContent.plan,
|
complainedContentType: ComplainedContent.plan,
|
||||||
@@ -185,7 +186,7 @@ class _PlanWidgetState extends State<PlanWidget> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: NetworkImage(widget.plan.author.authorProfileIcon),
|
image: NetworkImage(baseUrl + widget.plan.author.authorProfileIcon),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
||||||
|
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_state.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
|
||||||
|
final ConsultationsRepository consultationsRepository;
|
||||||
|
ConsultationsArchiveCubit({
|
||||||
|
required this.consultationsRepository,
|
||||||
|
}) : super(
|
||||||
|
ConsultationsArchiveState(
|
||||||
|
statisticModel: StatisticModel(
|
||||||
|
total: 0,
|
||||||
|
canceled: 0,
|
||||||
|
inProgress: 0,
|
||||||
|
finished: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> getConsultations() async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
final consultations = await consultationsRepository.getFilteredConsultations(
|
||||||
|
date: state.periodFilterStatus?.value,
|
||||||
|
state: state.consultationFilterStatus?.value,
|
||||||
|
dateComparison: state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
|
||||||
|
stateComparison: state.consultationFilterStatus == ConsultationFilterStatus.all
|
||||||
|
? null
|
||||||
|
: state.consultationFilterStatus == ConsultationFilterStatus.inProgress
|
||||||
|
? FiltersComparisonEnum.notInArray
|
||||||
|
: FiltersComparisonEnum.equal,
|
||||||
|
);
|
||||||
|
emit(state.copyWith(consultations: consultations, isLoading: false));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(isLoading: false));
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setConsultationsPeriod(PeriodFilterStatus period) async {
|
||||||
|
emit(state.copyWith(periodFilterStatus: period));
|
||||||
|
await getConsultations();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setConsultationsStatus(ConsultationFilterStatus status) async {
|
||||||
|
emit(state.copyWith(consultationFilterStatus: status));
|
||||||
|
await getConsultations();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getStatistic() async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
final statistic = await consultationsRepository.getStatistic();
|
||||||
|
emit(state.copyWith(statisticModel: statistic, isLoading: false));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(isLoading: false));
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConsultationFilterStatus {
|
||||||
|
all,
|
||||||
|
canceled,
|
||||||
|
finished,
|
||||||
|
inProgress;
|
||||||
|
|
||||||
|
String get name => switch (this) {
|
||||||
|
ConsultationFilterStatus.all => 'Все консультации',
|
||||||
|
ConsultationFilterStatus.canceled => 'Отменён',
|
||||||
|
ConsultationFilterStatus.finished => 'Завершён',
|
||||||
|
ConsultationFilterStatus.inProgress => 'В процессе',
|
||||||
|
};
|
||||||
|
|
||||||
|
String? get value => switch (this) {
|
||||||
|
ConsultationFilterStatus.all => null,
|
||||||
|
ConsultationFilterStatus.canceled => 'canceled_by_the_initiator',
|
||||||
|
ConsultationFilterStatus.finished => 'charge_was_collected',
|
||||||
|
ConsultationFilterStatus.inProgress => 'active',
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'consultations_archive_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ConsultationsArchiveState with _$ConsultationsArchiveState {
|
||||||
|
|
||||||
|
factory ConsultationsArchiveState({
|
||||||
|
@Default([]) List<ConsultationModel> consultations,
|
||||||
|
@Default(false) isLoading,
|
||||||
|
@Default(null) String? error,
|
||||||
|
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||||
|
@Default(null) ConsultationFilterStatus? consultationFilterStatus,
|
||||||
|
required StatisticModel statisticModel,
|
||||||
|
}) = _ConsultationsArchiveState;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
||||||
|
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> {
|
||||||
|
final ConsultationsRepository consultationsRepository;
|
||||||
|
ConsultantFinancesCubit({
|
||||||
|
required this.consultationsRepository,
|
||||||
|
}) : super(
|
||||||
|
ConsultantFinancesState(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> getConsultantFinances() async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
final consultantFinances = await consultationsRepository.getConsultantFinances(
|
||||||
|
state.periodFilterStatus?.value,
|
||||||
|
state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
|
||||||
|
);
|
||||||
|
emit(state.copyWith(financesModels: consultantFinances, isLoading: false));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(financesModels: [], isLoading: false));
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setFinancesPeriod(PeriodFilterStatus period) async {
|
||||||
|
emit(state.copyWith(periodFilterStatus: period));
|
||||||
|
await getConsultantFinances();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'consultant_finances_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ConsultantFinancesState with _$ConsultantFinancesState {
|
||||||
|
factory ConsultantFinancesState({
|
||||||
|
@Default([]) List<ConsultantFinanceModel> financesModels,
|
||||||
|
@Default(false) isLoading,
|
||||||
|
@Default(null) String? error,
|
||||||
|
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||||
|
}) = _ConsultantFinancesState;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
||||||
|
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class UserPurchasesCubit extends Cubit<UserPurchasesState> {
|
||||||
|
final ConsultationsRepository consultationsRepository;
|
||||||
|
UserPurchasesCubit({
|
||||||
|
required this.consultationsRepository,
|
||||||
|
}) : super(
|
||||||
|
UserPurchasesState(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> getUserPurchases() async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
final purchasesModels = await consultationsRepository.getUserPurchases(
|
||||||
|
state.periodFilterStatus?.value,
|
||||||
|
state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(purchasesModels: [], isLoading: false));
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setPurchasesPeriod(PeriodFilterStatus period) async {
|
||||||
|
emit(state.copyWith(periodFilterStatus: period));
|
||||||
|
await getUserPurchases();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'user_purchases_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class UserPurchasesState with _$UserPurchasesState {
|
||||||
|
factory UserPurchasesState({
|
||||||
|
@Default([]) List<UserPurchasesModel> purchasesModels,
|
||||||
|
@Default(false) isLoading,
|
||||||
|
@Default(null) String? error,
|
||||||
|
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||||
|
}) = _UserPurchasesState;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'consultant_finance_model.freezed.dart';
|
||||||
|
part 'consultant_finance_model.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ConsultantFinanceModel with _$ConsultantFinanceModel {
|
||||||
|
factory ConsultantFinanceModel({
|
||||||
|
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
|
||||||
|
@JsonKey(name: 'bank_id') required String bankId,
|
||||||
|
@JsonKey(name: 'title') required String title,
|
||||||
|
@JsonKey(name: 'clientName') required String clientName,
|
||||||
|
@JsonKey(name: 'duration') required int duration,
|
||||||
|
@JsonKey(name: 'payment_price', fromJson: _parsePayment) required String paymentPrice,
|
||||||
|
required PaymentStatus? paymentStatus,
|
||||||
|
}) = _ConsultantFinanceModel;
|
||||||
|
|
||||||
|
factory ConsultantFinanceModel.fromJson(Map<String, dynamic> json) => _$ConsultantFinanceModelFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _parsePayment(json) {
|
||||||
|
return ((json as int) / 100).toStringAsFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PaymentStatus {
|
||||||
|
@JsonValue('COMPLETED')
|
||||||
|
completed,
|
||||||
|
@JsonValue('REJECTED')
|
||||||
|
rejected,
|
||||||
|
@JsonValue('CHECKED')
|
||||||
|
checked
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'statistic_model.freezed.dart';
|
||||||
|
part 'statistic_model.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class StatisticModel with _$StatisticModel {
|
||||||
|
factory StatisticModel({
|
||||||
|
required int total,
|
||||||
|
@JsonKey(name: 'in_progress') required int inProgress,
|
||||||
|
required int finished,
|
||||||
|
required int canceled,
|
||||||
|
}) = _StatisticModel;
|
||||||
|
|
||||||
|
factory StatisticModel.fromJson(Map<String, dynamic> json) => _$StatisticModelFromJson(json);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'user_purchases_model.freezed.dart';
|
||||||
|
part 'user_purchases_model.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class UserPurchasesModel with _$UserPurchasesModel {
|
||||||
|
factory UserPurchasesModel({
|
||||||
|
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
|
||||||
|
@JsonKey(name: 'bank_id') required String bankId,
|
||||||
|
@JsonKey(name: 'title') required String title,
|
||||||
|
@JsonKey(name: 'consultantName') required String consultantName,
|
||||||
|
@JsonKey(name: 'duration') required int duration,
|
||||||
|
@JsonKey(name: 'price') required int paymentPrice,
|
||||||
|
@JsonKey(name: 'chosen_plan_last_state') required ChosenPlanStateEnum chosenPlanState,
|
||||||
|
}) = _UserPurchasesModel;
|
||||||
|
|
||||||
|
factory UserPurchasesModel.fromJson(Map<String, dynamic> json) => _$UserPurchasesModelFromJson(json);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
|
|
||||||
|
enum PeriodFilterStatus {
|
||||||
|
all,
|
||||||
|
month,
|
||||||
|
week;
|
||||||
|
|
||||||
|
String get name => switch (this) {
|
||||||
|
PeriodFilterStatus.all => 'Все периоды',
|
||||||
|
PeriodFilterStatus.month => 'За месяц',
|
||||||
|
PeriodFilterStatus.week => 'За неделю',
|
||||||
|
};
|
||||||
|
|
||||||
|
String? get value => switch (this) {
|
||||||
|
PeriodFilterStatus.all => null,
|
||||||
|
PeriodFilterStatus.month => DateTime.now().subtract(const Duration(days: 30)).format('yyyy-MM-dd'),
|
||||||
|
PeriodFilterStatus.week => DateTime.now().subtract(const Duration(days: 7)).format('yyyy-MM-dd'),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
class ConsultantFinancesScreen extends StatefulWidget {
|
||||||
|
const ConsultantFinancesScreen({super.key});
|
||||||
|
static const name = 'consultantFinancesScreen';
|
||||||
|
static const path = '/consultant-finances-screen';
|
||||||
|
@override
|
||||||
|
State<ConsultantFinancesScreen> createState() => _ConsultantFinancesScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
|
return BlocProvider<ConsultantFinancesCubit>(
|
||||||
|
create: (context) => sl<ConsultantFinancesCubit>()..getConsultantFinances(),
|
||||||
|
child: BlocBuilder<ConsultantFinancesCubit, ConsultantFinancesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
appBar: AppBar(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadiusDirectional.vertical(
|
||||||
|
bottom: Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'Финансы',
|
||||||
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: state.isLoading
|
||||||
|
? const Center(
|
||||||
|
child: Loader(),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(CardSettingsScreen.name);
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Ink(
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||||
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
child: const Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Text('Управление картами', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Material(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
CustomDropDownButton<PeriodFilterStatus>(
|
||||||
|
items: PeriodFilterStatus.values
|
||||||
|
.map(
|
||||||
|
(value) => DropdownMenuItem(
|
||||||
|
value: value,
|
||||||
|
child: Text(
|
||||||
|
value.name,
|
||||||
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
context.read<ConsultantFinancesCubit>().setFinancesPeriod(value);
|
||||||
|
},
|
||||||
|
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
if (state.financesModels.isNotEmpty)
|
||||||
|
ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final Status modelStatus = switch (state.financesModels[index].paymentStatus) {
|
||||||
|
null => Status.inProgress,
|
||||||
|
PaymentStatus.completed => Status.success,
|
||||||
|
PaymentStatus.rejected => Status.canceled,
|
||||||
|
PaymentStatus.checked => Status.success,
|
||||||
|
};
|
||||||
|
return Material(
|
||||||
|
color: const Color(0xFFF6F6F6),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Builder(builder: (context) {
|
||||||
|
final idLen = state.financesModels[index].bankId.length;
|
||||||
|
return Text(
|
||||||
|
'${state.financesModels[index].bankId.substring(0, 4)}...${state.financesModels[index].bankId.substring(idLen - 4, idLen)} ',
|
||||||
|
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Text(
|
||||||
|
state.financesModels[index].startDate.format('yyyy.MM.dd'),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF6D6F71),
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
state.financesModels[index].title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
height: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Покупатель: ${state.financesModels[index].clientName}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'${state.financesModels[index].duration} ${getDayForm(state.financesModels[index].duration, s)}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
height: 1,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFFDEDEDF),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${state.financesModels[index].paymentPrice} ₽',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StatusWidget(
|
||||||
|
status: modelStatus,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||||
|
itemCount: state.financesModels.length,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 40.0),
|
||||||
|
child: Text(
|
||||||
|
'У вас нет таких консультаций',
|
||||||
|
style: TextStyles.textMMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_state.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ConsultationsArchiveScreen extends StatefulWidget {
|
||||||
|
const ConsultationsArchiveScreen({super.key});
|
||||||
|
static const name = 'consultationsArchiveScreen';
|
||||||
|
static const path = '/consultations-archive-screen';
|
||||||
|
@override
|
||||||
|
State<ConsultationsArchiveScreen> createState() => _ConsultationsArchiveScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultationsArchiveScreenState extends State<ConsultationsArchiveScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
|
return BlocProvider<ConsultationsArchiveCubit>(
|
||||||
|
create: (context) => sl<ConsultationsArchiveCubit>()
|
||||||
|
..getConsultations()
|
||||||
|
..getStatistic(),
|
||||||
|
child: BlocBuilder<ConsultationsArchiveCubit, ConsultationsArchiveState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
appBar: AppBar(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadiusDirectional.vertical(
|
||||||
|
bottom: Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: const Text('Архив консультаций'),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: state.isLoading
|
||||||
|
? const Center(
|
||||||
|
child: Loader(),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text(
|
||||||
|
'Всего консультаций: ${state.statisticModel.total}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFF6D6F71),
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Material(
|
||||||
|
color: const Color(0xFF0BB450).withAlpha(11),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
'Завершено: ${state.statisticModel.finished}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFF0BB450),
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Material(
|
||||||
|
color: const Color(0xFFD01717).withAlpha(11),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
'Отменено: ${state.statisticModel.canceled}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFFD01717),
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Material(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
CustomDropDownButton<PeriodFilterStatus>(
|
||||||
|
items: PeriodFilterStatus.values
|
||||||
|
.map(
|
||||||
|
(value) => DropdownMenuItem(
|
||||||
|
value: value,
|
||||||
|
child: Text(
|
||||||
|
value.name,
|
||||||
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
context.read<ConsultationsArchiveCubit>().setConsultationsPeriod(value);
|
||||||
|
},
|
||||||
|
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
CustomDropDownButton<ConsultationFilterStatus>(
|
||||||
|
items: ConsultationFilterStatus.values
|
||||||
|
.map(
|
||||||
|
(value) => DropdownMenuItem(
|
||||||
|
value: value,
|
||||||
|
child: Text(
|
||||||
|
value.name,
|
||||||
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
context.read<ConsultationsArchiveCubit>().setConsultationsStatus(value);
|
||||||
|
},
|
||||||
|
value: state.consultationFilterStatus ?? ConsultationFilterStatus.values.first,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
if (state.consultations.isNotEmpty)
|
||||||
|
ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final Status consultationStatus;
|
||||||
|
if (state.consultations[index].chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) {
|
||||||
|
consultationStatus = Status.canceled;
|
||||||
|
} else if (state.consultations[index].chosenPlanState == ChosenPlanStateEnum.stateChargeWasCollected) {
|
||||||
|
consultationStatus = Status.success;
|
||||||
|
} else {
|
||||||
|
consultationStatus = Status.inProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: const Color(0xFFF6F6F6),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
state.consultations[index].id.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
state.consultations[index].startDate.format('yyyy.MM.dd'),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF6D6F71),
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
state.consultations[index].planTitle,
|
||||||
|
style: TextStyle(
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
height: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Покупатель: ${state.consultations[index].userName}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'${state.consultations[index].duration} ${getDayForm(state.consultations[index].duration, s)}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
height: 1,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFFDEDEDF),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${state.consultations[index].consultantPayment.toStringAsFixed(2)} ₽',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StatusWidget(
|
||||||
|
status: consultationStatus,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||||
|
itemCount: state.consultations.length,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 40.0),
|
||||||
|
child: Text(
|
||||||
|
'У вас нет таких консультаций',
|
||||||
|
style: TextStyles.textMMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
|
||||||
|
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class UserPurchasesScreen extends StatefulWidget {
|
||||||
|
const UserPurchasesScreen({super.key});
|
||||||
|
static const name = 'userPurchasesScreen';
|
||||||
|
static const path = '/user-purchases-screen';
|
||||||
|
@override
|
||||||
|
State<UserPurchasesScreen> createState() => _UserPurchasesScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserPurchasesScreen extends State<UserPurchasesScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
|
return BlocProvider<UserPurchasesCubit>(
|
||||||
|
create: (context) => sl<UserPurchasesCubit>()..getUserPurchases(),
|
||||||
|
child: BlocBuilder<UserPurchasesCubit, UserPurchasesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
appBar: AppBar(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadiusDirectional.vertical(
|
||||||
|
bottom: Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'Финансы',
|
||||||
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: state.isLoading
|
||||||
|
? const Center(
|
||||||
|
child: Loader(),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Material(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
CustomDropDownButton<PeriodFilterStatus>(
|
||||||
|
items: PeriodFilterStatus.values
|
||||||
|
.map(
|
||||||
|
(value) => DropdownMenuItem(
|
||||||
|
value: value,
|
||||||
|
child: Text(
|
||||||
|
value.name,
|
||||||
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
context.read<UserPurchasesCubit>().setPurchasesPeriod(value);
|
||||||
|
},
|
||||||
|
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
if (state.purchasesModels.isNotEmpty)
|
||||||
|
ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final Status purchaseStatus;
|
||||||
|
if (state.purchasesModels[index].chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) {
|
||||||
|
purchaseStatus = Status.canceled;
|
||||||
|
} else if (state.purchasesModels[index].chosenPlanState == ChosenPlanStateEnum.stateChargeWasCollected) {
|
||||||
|
purchaseStatus = Status.success;
|
||||||
|
} else {
|
||||||
|
purchaseStatus = Status.inProgress;
|
||||||
|
}
|
||||||
|
return Material(
|
||||||
|
color: const Color(0xFFF6F6F6),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Builder(builder: (context) {
|
||||||
|
final idLen = state.purchasesModels[index].bankId.length;
|
||||||
|
return Text(
|
||||||
|
'${state.purchasesModels[index].bankId.substring(0, 4)}...${state.purchasesModels[index].bankId.substring(idLen - 4, idLen)} ',
|
||||||
|
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Text(
|
||||||
|
state.purchasesModels[index].startDate.format('yyyy.MM.dd'),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF6D6F71),
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
state.purchasesModels[index].title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
height: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Продавец: ${state.purchasesModels[index].consultantName}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'${state.purchasesModels[index].duration} ${getDayForm(state.purchasesModels[index].duration, s)}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
height: 1,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFFDEDEDF),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${state.purchasesModels[index].paymentPrice} ₽',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
height: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StatusWidget(
|
||||||
|
status: purchaseStatus,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||||
|
itemCount: state.purchasesModels.length,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 40.0),
|
||||||
|
child: Text(
|
||||||
|
'У вас нет таких консультаций',
|
||||||
|
style: TextStyles.textMMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user