forked from eatvio/eatvio_app
Compare commits
58 Commits
diary
...
diaryFixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3cc199c0 | ||
|
|
2584a70a09 | ||
|
|
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
|
||||||
@@ -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 = 24;
|
||||||
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 = 24;
|
||||||
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 = 24;
|
||||||
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>24</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|||||||
@@ -32,7 +32,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 +40,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 +78,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 +100,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 +109,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,
|
||||||
@@ -172,7 +183,7 @@ class AppRouter {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/consultant-finance-screen',
|
path: '/consultant-finance-screen',
|
||||||
name: 'consultantFinanceScreen',
|
name: 'consultantFinanceScreen',
|
||||||
builder: (context, state) => const ConsultantFinanceScreen(),
|
builder: (context, state) => const ConsultationsArchiveScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/user-finance-screen',
|
path: '/user-finance-screen',
|
||||||
@@ -201,7 +212,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 +299,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 +364,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 +378,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);
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import 'package:eatvio_app/features/complaining/services/complaining_service.dar
|
|||||||
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';
|
||||||
@@ -81,9 +82,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 +120,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 +150,9 @@ 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()));
|
||||||
|
|
||||||
// Repository
|
// Repository
|
||||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||||
@@ -165,6 +180,7 @@ 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()));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||||
@@ -193,6 +209,7 @@ 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<MyRecipesService>(() => MyRecipesService(sl()));
|
||||||
|
|
||||||
// External
|
// External
|
||||||
sl.registerLazySingleton(
|
sl.registerLazySingleton(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ 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/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
import 'package: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';
|
||||||
@@ -49,6 +52,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);
|
||||||
@@ -179,8 +196,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'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:easy_debounce/easy_debounce.dart';
|
import 'package:easy_debounce/easy_debounce.dart';
|
||||||
|
import 'package:eatvio_app/core/const.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';
|
||||||
@@ -43,12 +44,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 +73,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 +109,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 +118,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;
|
||||||
@@ -141,7 +155,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
if (state.chatRoom.users.receiver.profilePhotoUrl != null)
|
if (state.chatRoom.users.receiver.profilePhotoUrl != null)
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 20,
|
radius: 20,
|
||||||
foregroundImage: NetworkImage(state.chatRoom.users.receiver.profilePhotoUrl!),
|
foregroundImage: NetworkImage(baseUrl + state.chatRoom.users.receiver.profilePhotoUrl!),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
),
|
),
|
||||||
if (state.chatRoom.users.receiver.profilePhotoUrl == null)
|
if (state.chatRoom.users.receiver.profilePhotoUrl == null)
|
||||||
@@ -302,17 +316,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,3 +1,4 @@
|
|||||||
|
import 'package:eatvio_app/core/const.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/models/chat_room_pagination_model.dart';
|
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
@@ -41,7 +42,7 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 24,
|
radius: 24,
|
||||||
foregroundImage: NetworkImage(
|
foregroundImage: NetworkImage(
|
||||||
widget.chatRoom.users.toChatUser.profilePhotoUrl,
|
baseUrl + widget.chatRoom.users.toChatUser.profilePhotoUrl,
|
||||||
),
|
),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,5 +1,6 @@
|
|||||||
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';
|
||||||
@@ -7,6 +8,7 @@ import 'package:eatvio_app/features/consultations/presentation/cubit/consultatio
|
|||||||
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: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 +44,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('consultantFinanceScreen');
|
||||||
|
},
|
||||||
|
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(
|
||||||
@@ -195,7 +197,10 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
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 +277,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 +296,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,7 @@
|
|||||||
|
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/statistic_model.dart';
|
||||||
|
|
||||||
class ConsultationsRepository {
|
class ConsultationsRepository {
|
||||||
final ConsultationService consultationService;
|
final ConsultationService consultationService;
|
||||||
@@ -13,4 +15,31 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,69 @@
|
|||||||
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/statistic_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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") 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,31 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
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> getConsultations() async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(isLoading: false));
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setConsultationsPeriod(PeriodFilterStatus period) async {
|
||||||
|
emit(state.copyWith(periodFilterStatus: period));
|
||||||
|
await getConsultations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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<String> financesModels,
|
||||||
|
@Default(false) isLoading,
|
||||||
|
@Default(null) String? error,
|
||||||
|
@Default(null) PeriodFilterStatus? periodFilterStatus,
|
||||||
|
}) = _ConsultantFinancesState;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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,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,281 @@
|
|||||||
|
// 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/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/period_enum.dart';
|
||||||
|
// import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart';
|
||||||
|
// import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
// import 'package:flutter/material.dart';
|
||||||
|
// import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
// class ConsultantFinancesScreen extends StatefulWidget {
|
||||||
|
// const ConsultantFinancesScreen({super.key});
|
||||||
|
|
||||||
|
// @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>(),
|
||||||
|
// 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: 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,
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 12),
|
||||||
|
// if (state.consultations.isNotEmpty)
|
||||||
|
// ListView.separated(
|
||||||
|
// shrinkWrap: true,
|
||||||
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
|
// itemBuilder: (context, index) {
|
||||||
|
// 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: state.consultations[index].chosenPlanState,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// 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,281 @@
|
|||||||
|
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/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ConsultationsArchiveScreen extends StatefulWidget {
|
||||||
|
const ConsultationsArchiveScreen({super.key});
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
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: state.consultations[index].chosenPlanState,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
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,63 @@
|
|||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class StatusWidget extends StatelessWidget {
|
||||||
|
const StatusWidget({super.key, required this.status});
|
||||||
|
final ChosenPlanStateEnum status;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (status == ChosenPlanStateEnum.stateCanceledByTheInitiator) {
|
||||||
|
return Material(
|
||||||
|
color: appTheme.lightRed,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
'Отменён',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: appTheme.strongRed,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (status == ChosenPlanStateEnum.stateChargeWasCollected) {
|
||||||
|
return Material(
|
||||||
|
color: const Color(0xFF0BB450).withAlpha(11),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
'Успешно',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFF0BB450),
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Material(
|
||||||
|
color: appTheme.transparentBlue,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
'В процессе',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@ import 'package:eatvio_app/core/token_storage.dart';
|
|||||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||||
import 'package:eatvio_app/core/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/features/dev_tools/presentation/dev_tools_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_plans/presentation/my_plans_screen.dart';
|
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
@@ -28,7 +28,7 @@ class MenuScreen extends StatelessWidget {
|
|||||||
_tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
|
_tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
|
||||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||||
if (profileModel.roles.contains('consultant'))
|
if (profileModel.roles.contains('consultant'))
|
||||||
_tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push('/recipe-select-screen')),
|
_tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)),
|
||||||
if (profileModel.roles.contains('consultant')) _divider(context),
|
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||||
if (profileModel.roles.contains('consultant'))
|
if (profileModel.roles.contains('consultant'))
|
||||||
_tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
|
_tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
|
||||||
@@ -47,14 +47,14 @@ class MenuScreen extends StatelessWidget {
|
|||||||
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
|
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
|
||||||
_divider(context),
|
_divider(context),
|
||||||
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
||||||
_divider(context),
|
// _divider(context),
|
||||||
_tile(
|
// _tile(
|
||||||
leading: ImageConstant.icnDevTools,
|
// leading: ImageConstant.icnDevTools,
|
||||||
title: 'Для разработчиков',
|
// title: 'Для разработчиков',
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
_divider(context),
|
_divider(context),
|
||||||
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
|
|
||||||
class MyDietWidget extends StatefulWidget {
|
class MyDietWidget extends StatefulWidget {
|
||||||
final Diet diet;
|
final Diet diet;
|
||||||
|
|
||||||
const MyDietWidget({
|
const MyDietWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.diet,
|
required this.diet,
|
||||||
|
|||||||
@@ -1,259 +0,0 @@
|
|||||||
import 'package:collection/collection.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/widgets/custom_drop_down_button.dart';
|
|
||||||
import 'package:eatvio_app/features/my_finance/entities/consultant_finance_entity.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ConsultantFinanceScreen extends StatefulWidget {
|
|
||||||
const ConsultantFinanceScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ConsultantFinanceScreen> createState() => _ConsultantFinanceScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConsultantFinanceScreenState extends State<ConsultantFinanceScreen> {
|
|
||||||
final List<String> periods = ['За всё время', 'За месяц', 'За неделю'];
|
|
||||||
final List<String> statuses = ['Все консультации', 'Отменён', 'Завершён', 'В процессе'];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color(0xFFF0F2F5),
|
|
||||||
appBar: AppBar(
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadiusDirectional.vertical(
|
|
||||||
bottom: Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: const Text('Архив консультаций'),
|
|
||||||
centerTitle: true,
|
|
||||||
),
|
|
||||||
body: 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(
|
|
||||||
'Всего консультаций: ${testFinanceEntity.totalConsultationsCount}',
|
|
||||||
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(
|
|
||||||
'Завершено: ${testFinanceEntity.completedCount}',
|
|
||||||
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(
|
|
||||||
'Отменено: ${testFinanceEntity.canceledCount}',
|
|
||||||
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(
|
|
||||||
items: periods
|
|
||||||
.mapIndexed(
|
|
||||||
(index, value) => DropdownMenuItem(
|
|
||||||
value: periods[index],
|
|
||||||
child: Text(
|
|
||||||
periods[index],
|
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {},
|
|
||||||
value: periods.first,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
CustomDropDownButton(
|
|
||||||
items: statuses
|
|
||||||
.mapIndexed(
|
|
||||||
(index, value) => DropdownMenuItem(
|
|
||||||
value: statuses[index],
|
|
||||||
child: Text(
|
|
||||||
statuses[index],
|
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {},
|
|
||||||
value: statuses.first,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) => 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(
|
|
||||||
testFinanceEntity.consultationsList[index].id,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
height: 1,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
testFinanceEntity.consultationsList[index].date.format('dd.MM.yyy', 'RU'),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Color(0xFF6D6F71),
|
|
||||||
height: 1,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
testFinanceEntity.consultationsList[index].title,
|
|
||||||
style: TextStyle(
|
|
||||||
color: appTheme.blueA400,
|
|
||||||
height: 1,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'Покупатель: ${testFinanceEntity.consultationsList[index].clientName}',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
height: 1,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'${testFinanceEntity.consultationsList[index].planDuration} дней',
|
|
||||||
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(
|
|
||||||
'${testFinanceEntity.consultationsList[index].price} ₽',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
height: 1,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Material(
|
|
||||||
color: const Color(0xFF0BB450).withAlpha(11),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
|
||||||
child: Text(
|
|
||||||
testFinanceEntity.consultationsList[index].status.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Color(0xFF0BB450),
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
|
||||||
itemCount: testFinanceEntity.consultationsList.length,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ part 'my_plans_service.g.dart';
|
|||||||
abstract class MyPlansService {
|
abstract class MyPlansService {
|
||||||
factory MyPlansService(Dio dio, {String baseUrl}) = _MyPlansService;
|
factory MyPlansService(Dio dio, {String baseUrl}) = _MyPlansService;
|
||||||
|
|
||||||
@GET('/api/plans/index')
|
@GET('/api/profile/my_plans')
|
||||||
Future<PlanPaginationModel> getPlans({
|
Future<PlanPaginationModel> getPlans({
|
||||||
@Query('page') required int page,
|
@Query('page') required int page,
|
||||||
@Query('filters[name][\$startsWith]') String? query,
|
@Query('filters[name][\$startsWith]') String? query,
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
|
|
||||||
sealed class BaseNotificationModel {
|
sealed class BaseNotificationModel {
|
||||||
abstract final NotificationType type;
|
abstract final NotificationType type;
|
||||||
|
abstract final bool isRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationModel {
|
class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
final String customerName;
|
final String customerName;
|
||||||
final String chatId;
|
final String chatId;
|
||||||
@@ -14,14 +19,16 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
|
|||||||
required this.planName,
|
required this.planName,
|
||||||
required this.customerName,
|
required this.customerName,
|
||||||
required this.chatId,
|
required this.chatId,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory PlanSubscriptionStartedToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return PlanSubscriptionStartedToAuthorNotification(
|
return PlanSubscriptionStartedToAuthorNotification(
|
||||||
type: type,
|
type: type,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
customerName: json['user_name'],
|
customerName: json['user_name'],
|
||||||
chatId: json['chat_id'],
|
chatId: json['chat_id'],
|
||||||
|
isRead: isRead ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +36,8 @@ class PlanSubscriptionStartedToAuthorNotification implements BaseNotificationMod
|
|||||||
class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationModel {
|
class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
final String customerName;
|
final String customerName;
|
||||||
|
|
||||||
@@ -36,13 +45,15 @@ class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationMo
|
|||||||
required this.type,
|
required this.type,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
required this.customerName,
|
required this.customerName,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory PlanSubscriptionCanceledToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return PlanSubscriptionCanceledToAuthorNotification(
|
return PlanSubscriptionCanceledToAuthorNotification(
|
||||||
type: type,
|
type: type,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
customerName: json['user_name'],
|
customerName: json['user_name'],
|
||||||
|
isRead: isRead ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +61,8 @@ class PlanSubscriptionCanceledToAuthorNotification implements BaseNotificationMo
|
|||||||
class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
|
class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
final int planId;
|
final int planId;
|
||||||
|
|
||||||
@@ -57,13 +70,15 @@ class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
|
|||||||
required this.type,
|
required this.type,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
required this.planId,
|
required this.planId,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ChosenPlanStartedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory ChosenPlanStartedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return ChosenPlanStartedToClientNotification(
|
return ChosenPlanStartedToClientNotification(
|
||||||
type: type,
|
type: type,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
planId: json['plan_id'],
|
planId: json['plan_id'],
|
||||||
|
isRead: isRead ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,58 +86,70 @@ class ChosenPlanStartedToClientNotification implements BaseNotificationModel {
|
|||||||
class ChosenPlanTomorrowReminderToClientNotification implements BaseNotificationModel {
|
class ChosenPlanTomorrowReminderToClientNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
|
|
||||||
ChosenPlanTomorrowReminderToClientNotification({
|
ChosenPlanTomorrowReminderToClientNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory ChosenPlanTomorrowReminderToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return ChosenPlanTomorrowReminderToClientNotification(
|
return ChosenPlanTomorrowReminderToClientNotification(type: type, planName: json['plan_name'], isRead: isRead ?? false);
|
||||||
type: type,
|
|
||||||
planName: json['plan_name'],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiaryReminderNotification implements BaseNotificationModel {
|
class DiaryReminderNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
|
DiaryReminderNotification({required this.type, required this.isRead});
|
||||||
|
|
||||||
DiaryReminderNotification({required this.type});
|
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
|
return DiaryReminderNotification(type: type, isRead: isRead ?? false);
|
||||||
factory DiaryReminderNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
|
||||||
return DiaryReminderNotification(
|
|
||||||
type: type,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConsultantLeftCommentNotification implements BaseNotificationModel {
|
class ConsultantLeftCommentNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
|
|
||||||
ConsultantLeftCommentNotification({
|
ConsultantLeftCommentNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.date,
|
required this.date,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ConsultantLeftCommentNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory ConsultantLeftCommentNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return ConsultantLeftCommentNotification(type: type, date: json['diary_date']);
|
return ConsultantLeftCommentNotification(
|
||||||
|
type: type,
|
||||||
|
date: parseDate(json['diary_date']),
|
||||||
|
isRead: isRead ?? false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PersonalizedDietIsDefinedNotification implements BaseNotificationModel {
|
class PersonalizedDietIsDefinedNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
|
|
||||||
PersonalizedDietIsDefinedNotification({required this.type});
|
PersonalizedDietIsDefinedNotification({
|
||||||
|
required this.type,
|
||||||
|
required this.isRead,
|
||||||
|
});
|
||||||
|
|
||||||
factory PersonalizedDietIsDefinedNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory PersonalizedDietIsDefinedNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return PersonalizedDietIsDefinedNotification(
|
return PersonalizedDietIsDefinedNotification(
|
||||||
type: type,
|
type: type,
|
||||||
|
isRead: isRead ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,30 +157,41 @@ class PersonalizedDietIsDefinedNotification implements BaseNotificationModel {
|
|||||||
class ChosenPlanFinishedToClientNotification implements BaseNotificationModel {
|
class ChosenPlanFinishedToClientNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
ChosenPlanFinishedToClientNotification({
|
ChosenPlanFinishedToClientNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ChosenPlanFinishedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory ChosenPlanFinishedToClientNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return ChosenPlanFinishedToClientNotification(type: type, planName: json['plan_name']);
|
return ChosenPlanFinishedToClientNotification(
|
||||||
|
type: type,
|
||||||
|
planName: json['plan_name'],
|
||||||
|
isRead: isRead ?? false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlanSubscriptionCanceledToCustomerNotification implements BaseNotificationModel {
|
class PlanSubscriptionCanceledToCustomerNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
PlanSubscriptionCanceledToCustomerNotification({
|
PlanSubscriptionCanceledToCustomerNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory PlanSubscriptionCanceledToCustomerNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return PlanSubscriptionCanceledToCustomerNotification(
|
return PlanSubscriptionCanceledToCustomerNotification(
|
||||||
type: type,
|
type: type,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
|
isRead: isRead ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,17 +199,21 @@ class PlanSubscriptionCanceledToCustomerNotification implements BaseNotification
|
|||||||
class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel {
|
class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
final String customerName;
|
final String customerName;
|
||||||
ChosenPlanStartedToConsultantNotification({
|
ChosenPlanStartedToConsultantNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
|
required this.isRead,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
required this.customerName,
|
required this.customerName,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ChosenPlanStartedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory ChosenPlanStartedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return ChosenPlanStartedToConsultantNotification(
|
return ChosenPlanStartedToConsultantNotification(
|
||||||
type: type,
|
type: type,
|
||||||
|
isRead: isRead ?? false,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
customerName: json['client_name'],
|
customerName: json['client_name'],
|
||||||
);
|
);
|
||||||
@@ -181,19 +223,23 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel
|
|||||||
class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotificationModel {
|
class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
final String customerName;
|
final String customerName;
|
||||||
ChosenPlanTomorrowReminderToConsultantNotification({
|
ChosenPlanTomorrowReminderToConsultantNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
required this.customerName,
|
required this.customerName,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory ChosenPlanTomorrowReminderToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return ChosenPlanTomorrowReminderToConsultantNotification(
|
return ChosenPlanTomorrowReminderToConsultantNotification(
|
||||||
type: type,
|
type: type,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
customerName: json['client_name'],
|
customerName: json['client_name'],
|
||||||
|
isRead: isRead ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,17 +247,21 @@ class ChosenPlanTomorrowReminderToConsultantNotification implements BaseNotifica
|
|||||||
class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel {
|
class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
final String customerName;
|
final String customerName;
|
||||||
PlanSubscriptionPaidToAuthorNotification({
|
PlanSubscriptionPaidToAuthorNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
|
required this.isRead,
|
||||||
required this.planName,
|
required this.planName,
|
||||||
required this.customerName,
|
required this.customerName,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PlanSubscriptionPaidToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory PlanSubscriptionPaidToAuthorNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return PlanSubscriptionPaidToAuthorNotification(
|
return PlanSubscriptionPaidToAuthorNotification(
|
||||||
type: type,
|
type: type,
|
||||||
|
isRead: isRead ?? false,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
customerName: json['user_name'],
|
customerName: json['user_name'],
|
||||||
);
|
);
|
||||||
@@ -221,6 +271,8 @@ class PlanSubscriptionPaidToAuthorNotification implements BaseNotificationModel
|
|||||||
class ChosenPlanFinishedToConsultantNotification implements BaseNotificationModel {
|
class ChosenPlanFinishedToConsultantNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
final String planName;
|
final String planName;
|
||||||
final String customerName;
|
final String customerName;
|
||||||
final int planId;
|
final int planId;
|
||||||
@@ -229,14 +281,16 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
|
|||||||
required this.planName,
|
required this.planName,
|
||||||
required this.customerName,
|
required this.customerName,
|
||||||
required this.planId,
|
required this.planId,
|
||||||
|
required this.isRead,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ChosenPlanFinishedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type) {
|
factory ChosenPlanFinishedToConsultantNotification.fromJson(Map<String, dynamic> json, NotificationType type, {bool? isRead}) {
|
||||||
return ChosenPlanFinishedToConsultantNotification(
|
return ChosenPlanFinishedToConsultantNotification(
|
||||||
type: type,
|
type: type,
|
||||||
planName: json['plan_name'],
|
planName: json['plan_name'],
|
||||||
customerName: json['client_name'],
|
customerName: json['client_name'],
|
||||||
planId: json['plan_id'],
|
planId: json['plan_id'],
|
||||||
|
isRead: isRead ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,8 +298,12 @@ class ChosenPlanFinishedToConsultantNotification implements BaseNotificationMode
|
|||||||
class UnknownNotification implements BaseNotificationModel {
|
class UnknownNotification implements BaseNotificationModel {
|
||||||
@override
|
@override
|
||||||
final NotificationType type;
|
final NotificationType type;
|
||||||
|
@override
|
||||||
|
final bool isRead;
|
||||||
|
|
||||||
UnknownNotification({
|
UnknownNotification({
|
||||||
required this.type,
|
required this.type,
|
||||||
|
this.isRead = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
@@ -11,22 +12,26 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
final List<BaseNotificationModel> _notifications = [];
|
final List<BaseNotificationModel> _notifications = [];
|
||||||
final WsManager wsManager;
|
final WsManager wsManager;
|
||||||
final NotificationsRepository notificationsRepository;
|
final NotificationsRepository notificationsRepository;
|
||||||
|
StreamSubscription? _newNotificationsSubscription;
|
||||||
NotificationsCubit({
|
NotificationsCubit({
|
||||||
required this.wsManager,
|
required this.wsManager,
|
||||||
required this.notificationsRepository,
|
required this.notificationsRepository,
|
||||||
}) : super(const NotificationsState.empty()) {
|
}) : super(const NotificationsState.empty());
|
||||||
wsManager.newNotificationsStream?.listen((event) {
|
|
||||||
|
Future<void> connectToWebSocket() async {
|
||||||
|
await _newNotificationsSubscription?.cancel();
|
||||||
|
_newNotificationsSubscription = wsManager.newNotificationsStream?.listen((event) {
|
||||||
_handleNotification(event.data);
|
_handleNotification(event.data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) {
|
BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) {
|
||||||
try {
|
try {
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson, type),
|
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson, type),
|
||||||
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
|
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson, type),
|
||||||
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(notificationJson, type),
|
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(notificationJson, type),
|
||||||
NotificationType.chosenPlanTomorrowReminderToClientNotification =>
|
NotificationType.chosenPlanTomorrowReminderToClientNotification => ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson, type),
|
||||||
ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson, type),
|
|
||||||
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type),
|
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson, type),
|
||||||
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type),
|
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson, type),
|
||||||
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(notificationJson, type),
|
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(notificationJson, type),
|
||||||
@@ -54,6 +59,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
NotificationsState.loaded(
|
NotificationsState.loaded(
|
||||||
notifications: _notifications,
|
notifications: _notifications,
|
||||||
notificationsCount: _notifications.length,
|
notificationsCount: _notifications.length,
|
||||||
|
hasUnreadNotifications: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -67,10 +73,26 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
NotificationsState.loaded(
|
NotificationsState.loaded(
|
||||||
notifications: _notifications,
|
notifications: _notifications,
|
||||||
notificationsCount: _notifications.length,
|
notificationsCount: _notifications.length,
|
||||||
|
hasUnreadNotifications: (notifications ?? []).where((elem) => elem.isRead == false).isNotEmpty,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> markNotificationsAsRead() async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is NotificationsStateLoaded) {
|
||||||
|
try {
|
||||||
|
emit(currentState.copyWith(isLoading: true));
|
||||||
|
await notificationsRepository.markNotificationsAsRead();
|
||||||
|
await getNotifications();
|
||||||
|
emit(currentState.copyWith(isLoading: false, hasUnreadNotifications: false));
|
||||||
|
} catch (e, st) {
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
emit(currentState.copyWith(isLoading: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ abstract class NotificationsState with _$NotificationsState {
|
|||||||
const factory NotificationsState.loaded({
|
const factory NotificationsState.loaded({
|
||||||
required List<BaseNotificationModel> notifications,
|
required List<BaseNotificationModel> notifications,
|
||||||
required int notificationsCount,
|
required int notificationsCount,
|
||||||
|
@Default(false) bool isLoading,
|
||||||
|
@Default(false) bool hasUnreadNotifications,
|
||||||
}) = NotificationsStateLoaded;
|
}) = NotificationsStateLoaded;
|
||||||
|
|
||||||
const factory NotificationsState.error({
|
const factory NotificationsState.error({
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
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';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.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';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/widgets/notification_widget.dart';
|
import 'package:eatvio_app/features/notifications/presentation/widgets/notification_widget.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';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@@ -11,6 +15,7 @@ class NotificationsScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
return BlocBuilder<NotificationsCubit, NotificationsState>(
|
return BlocBuilder<NotificationsCubit, NotificationsState>(
|
||||||
builder: (BuildContext context, NotificationsState state) {
|
builder: (BuildContext context, NotificationsState state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -19,38 +24,58 @@ class NotificationsScreen extends StatelessWidget {
|
|||||||
title: const Text('Уведомления'),
|
title: const Text('Уведомления'),
|
||||||
),
|
),
|
||||||
body: state is NotificationsStateLoaded
|
body: state is NotificationsStateLoaded
|
||||||
? Padding(
|
? Stack(children: [
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
Padding(
|
||||||
child: Container(
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
padding: const EdgeInsets.only(top: 16),
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
padding: const EdgeInsets.only(top: 16),
|
||||||
color: appTheme.whiteA700,
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.only(
|
color: appTheme.whiteA700,
|
||||||
topLeft: Radius.circular(12),
|
borderRadius: const BorderRadius.only(
|
||||||
topRight: Radius.circular(12),
|
topLeft: Radius.circular(12),
|
||||||
|
topRight: Radius.circular(12),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: state.isLoading
|
||||||
child: CustomScrollView(
|
? const Center(child: Loader())
|
||||||
slivers: [
|
: CustomScrollView(
|
||||||
SliverList.separated(
|
slivers: [
|
||||||
separatorBuilder: (context, index) => const Padding(
|
SliverList.separated(
|
||||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
separatorBuilder: (context, index) => const Padding(
|
||||||
child: CustomDivider(),
|
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||||
),
|
child: CustomDivider(),
|
||||||
itemBuilder: (context, index) {
|
),
|
||||||
return Padding(
|
itemBuilder: (context, index) {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
return Padding(
|
||||||
child: NotificationWidget(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
notification: state.notifications[index],
|
child: NotificationWidget(
|
||||||
),
|
notification: state.notifications[index],
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
itemCount: state.notifications.length,
|
},
|
||||||
)
|
itemCount: state.notifications.length,
|
||||||
],
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
text: s.markAsRead,
|
||||||
|
height: 52,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
|
buttonStyle:
|
||||||
|
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||||
|
onPressed: () async {
|
||||||
|
context.read<NotificationsCubit>().markNotificationsAsRead();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
])
|
||||||
: const Placeholder(),
|
: const Placeholder(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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/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/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/notifications/models/notification_model.dart';
|
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
|
||||||
@@ -46,7 +47,7 @@ class NotificationWidget extends StatelessWidget {
|
|||||||
ChosenPlanTomorrowReminderToConsultantNotification() => _ChosenPlanTomorrowReminderToConsultantWidget(
|
ChosenPlanTomorrowReminderToConsultantNotification() => _ChosenPlanTomorrowReminderToConsultantWidget(
|
||||||
notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
|
notification: notification as ChosenPlanTomorrowReminderToConsultantNotification,
|
||||||
),
|
),
|
||||||
PlanSubscriptionPaidToAuthorNotification() => throw _PlanSubscriptionPaidToAuthorWidget(
|
PlanSubscriptionPaidToAuthorNotification() => _PlanSubscriptionPaidToAuthorWidget(
|
||||||
notification: notification as PlanSubscriptionPaidToAuthorNotification,
|
notification: notification as PlanSubscriptionPaidToAuthorNotification,
|
||||||
),
|
),
|
||||||
ChosenPlanFinishedToConsultantNotification() => _ChosenPlanFinishedToConsultantWidget(
|
ChosenPlanFinishedToConsultantNotification() => _ChosenPlanFinishedToConsultantWidget(
|
||||||
@@ -65,7 +66,8 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName),
|
isRead: notification.isRead,
|
||||||
|
notificationsText: s.fillYasterday,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +80,8 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName),
|
isRead: notification.isRead,
|
||||||
|
notificationsText: s.fillYasterday,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +94,8 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
notificationsText: s.planStarted(notification.planName),
|
isRead: notification.isRead,
|
||||||
|
notificationsText: s.fillYasterday,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +108,8 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
notificationsText: s.tomorrowYourPlanWillStart(notification.planName),
|
isRead: notification.isRead,
|
||||||
|
notificationsText: s.fillYasterday,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +121,7 @@ class _DiaryReminderWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
notificationsText: s.fillYasterday,
|
notificationsText: s.fillYasterday,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -129,7 +135,8 @@ class _ConsultantLeftCommentWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
notificationsText: s.consultantLeftComment(notification.date),
|
isRead: notification.isRead,
|
||||||
|
notificationsText: s.consultantLeftComment(notification.date.format('dd.MM.yyyy')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,6 +158,7 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
|
notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -164,6 +172,7 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
notificationsText: s.youCanceledSubscriptionToPlan(notification.planName),
|
notificationsText: s.youCanceledSubscriptionToPlan(notification.planName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -177,6 +186,7 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
notificationsText: s.planWithUserStarted(notification.planName, notification.customerName),
|
notificationsText: s.planWithUserStarted(notification.planName, notification.customerName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -190,6 +200,7 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName),
|
notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -203,6 +214,7 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget {
|
|||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return _BaseNotificationWidget(
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
notificationsText: s.userPaidThePlan(notification.customerName, notification.planName),
|
notificationsText: s.userPaidThePlan(notification.customerName, notification.planName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -240,16 +252,32 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget {
|
|||||||
|
|
||||||
// CardSettingsScreen
|
// CardSettingsScreen
|
||||||
class _BaseNotificationWidget extends StatelessWidget {
|
class _BaseNotificationWidget extends StatelessWidget {
|
||||||
const _BaseNotificationWidget({required this.notificationsText});
|
const _BaseNotificationWidget({required this.notificationsText, required this.isRead});
|
||||||
final String notificationsText;
|
final String notificationsText;
|
||||||
|
final bool isRead;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return Row(
|
||||||
width: MediaQuery.sizeOf(context).width * 0.6,
|
children: [
|
||||||
child: Text(
|
SizedBox(
|
||||||
notificationsText,
|
width: MediaQuery.sizeOf(context).width * 0.6,
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
child: Text(
|
||||||
),
|
notificationsText,
|
||||||
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isRead) ...[
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,12 @@ class NotificationsRepository {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> markNotificationsAsRead() async {
|
||||||
|
try {
|
||||||
|
await notificationsService.markNotificationsAsRead();
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,23 +13,33 @@ class NotificationsService {
|
|||||||
final notifications = data.map((notificationJson) {
|
final notifications = data.map((notificationJson) {
|
||||||
try {
|
try {
|
||||||
final stringifiedType = notificationJson['data']['notification_class_name'];
|
final stringifiedType = notificationJson['data']['notification_class_name'];
|
||||||
|
final readDate = notificationJson['read_at'];
|
||||||
final type = NotificationType.getTypeFromString(stringifiedType);
|
final type = NotificationType.getTypeFromString(stringifiedType);
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson['data'], type),
|
NotificationType.planSubscriptionStartedToAuthor =>
|
||||||
NotificationType.planSubscriptionCanceledToAuthor => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type),
|
PlanSubscriptionStartedToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.chosenPlanStartedToClientNotification => ChosenPlanStartedToClientNotification.fromJson(notificationJson['data'], type),
|
NotificationType.planSubscriptionCanceledToAuthor =>
|
||||||
|
PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
|
NotificationType.chosenPlanStartedToClientNotification =>
|
||||||
|
ChosenPlanStartedToClientNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.chosenPlanTomorrowReminderToClientNotification =>
|
NotificationType.chosenPlanTomorrowReminderToClientNotification =>
|
||||||
ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson['data'], type),
|
ChosenPlanTomorrowReminderToClientNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson['data'], type),
|
NotificationType.diaryReminderNotification => DiaryReminderNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson['data'], type),
|
NotificationType.consultantLeftComment => ConsultantLeftCommentNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.personalizedDietIsDefined => PersonalizedDietIsDefinedNotification.fromJson(notificationJson['data'], type),
|
NotificationType.personalizedDietIsDefined =>
|
||||||
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(notificationJson['data'], type),
|
PersonalizedDietIsDefinedNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type),
|
NotificationType.chosenPlanFinishedToClient =>
|
||||||
NotificationType.chosenPlanStartedToConsultantNotification => ChosenPlanStartedToConsultantNotification.fromJson(notificationJson['data'], type),
|
ChosenPlanFinishedToClientNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
|
NotificationType.planSubscriptionCanceledToCustomer =>
|
||||||
|
PlanSubscriptionCanceledToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
|
NotificationType.chosenPlanStartedToConsultantNotification =>
|
||||||
|
ChosenPlanStartedToConsultantNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
|
NotificationType.chosenPlanTomorrowReminderToConsultantNotification =>
|
||||||
ChosenPlanTomorrowReminderToConsultantNotification.fromJson(notificationJson['data'], type),
|
ChosenPlanTomorrowReminderToConsultantNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.planSubscriptionPaidToAuthor => PlanSubscriptionPaidToAuthorNotification.fromJson(notificationJson['data'], type),
|
NotificationType.planSubscriptionPaidToAuthor =>
|
||||||
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(notificationJson['data'], type),
|
PlanSubscriptionPaidToAuthorNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
|
NotificationType.chosenPlanFinishedToConsultant =>
|
||||||
|
ChosenPlanFinishedToConsultantNotification.fromJson(notificationJson['data'], type, isRead: readDate != null),
|
||||||
NotificationType.unknown => UnknownNotification(type: type),
|
NotificationType.unknown => UnknownNotification(type: type),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -43,4 +53,12 @@ class NotificationsService {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> markNotificationsAsRead() async {
|
||||||
|
try {
|
||||||
|
await dio.post('/api/profile/mark_notifications_as_readed');
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,4 +55,16 @@ enum ChosenPlanStateEnum {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<ChosenPlanStateEnum> getActiveStates() {
|
||||||
|
return [
|
||||||
|
ChosenPlanStateEnum.statePaid,
|
||||||
|
ChosenPlanStateEnum.stateStarted,
|
||||||
|
ChosenPlanStateEnum.stateInProgress,
|
||||||
|
ChosenPlanStateEnum.stateLastDayStarted,
|
||||||
|
ChosenPlanStateEnum.stateLastDayFinished,
|
||||||
|
ChosenPlanStateEnum.stateComplained,
|
||||||
|
ChosenPlanStateEnum.stateDone
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.plan.author.authorIsMe,
|
isAuthor: widget.plan.author.authorIsMe,
|
||||||
path: 'plan/${widget.plan.id}',
|
path: 'plan/${widget.plan.slug}',
|
||||||
complainedContentType: ComplainedContent.plan,
|
complainedContentType: ComplainedContent.plan,
|
||||||
slug: widget.plan.slug,
|
slug: widget.plan.slug,
|
||||||
),
|
),
|
||||||
@@ -143,7 +143,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.plan.author.authorIsMe,
|
isAuthor: widget.plan.author.authorIsMe,
|
||||||
path: 'plan/${widget.plan.id}',
|
path: 'plan/${widget.plan.slug}',
|
||||||
complainedContentType: ComplainedContent.plan,
|
complainedContentType: ComplainedContent.plan,
|
||||||
slug: widget.plan.slug,
|
slug: widget.plan.slug,
|
||||||
),
|
),
|
||||||
@@ -362,7 +362,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.plan.author.authorIsMe,
|
isAuthor: widget.plan.author.authorIsMe,
|
||||||
path: 'plan/${widget.plan.id}',
|
path: 'plan/${widget.plan.slug}',
|
||||||
complainedContentType: ComplainedContent.plan,
|
complainedContentType: ComplainedContent.plan,
|
||||||
slug: widget.plan.slug,
|
slug: widget.plan.slug,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
||||||
import 'package:eatvio_app/features/profile/models/badge_model.dart';
|
import 'package:eatvio_app/features/profile/models/badge_model.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@@ -50,7 +51,7 @@ abstract class EducationModel with _$EducationModel {
|
|||||||
@JsonKey(name: "fb_link") String? fbLink,
|
@JsonKey(name: "fb_link") String? fbLink,
|
||||||
@JsonKey(name: "tg_link") String? tgLink,
|
@JsonKey(name: "tg_link") String? tgLink,
|
||||||
@JsonKey(name: "personal_site_link") String? personalSiteLink,
|
@JsonKey(name: "personal_site_link") String? personalSiteLink,
|
||||||
@JsonKey(name: "request_state") String? requestState,
|
@JsonKey(name: "request_state") required ModerationStatus requestState,
|
||||||
}) = _EducationModel;
|
}) = _EducationModel;
|
||||||
|
|
||||||
factory EducationModel.fromJson(Map<String, dynamic> json) => _$EducationModelFromJson(json);
|
factory EducationModel.fromJson(Map<String, dynamic> json) => _$EducationModelFromJson(json);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class ProfileCubit extends Cubit<ProfileState> {
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ProfileState.error(errorMessage: e.toString()));
|
emit(ProfileState.error(errorMessage: e.toString()));
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import 'package:flutter_native_splash/flutter_native_splash.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
@@ -92,12 +93,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
}
|
}
|
||||||
return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state);
|
return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state);
|
||||||
} else if (state is ProfileStateError) {
|
} else if (state is ProfileStateError) {
|
||||||
return DataErrorWidget(
|
return Scaffold(
|
||||||
onReload: () async {
|
appBar: AppBar(),
|
||||||
widget.author == null || widget.author!.authorIsMe
|
body: DataErrorWidget(
|
||||||
? await profileCubit.getMyProfile()
|
onReload: () async {
|
||||||
: await profileCubit.getProfileBySlug(widget.author!.authorSlug);
|
widget.author == null || widget.author!.authorIsMe
|
||||||
},
|
? await profileCubit.getMyProfile()
|
||||||
|
: await profileCubit.getProfileBySlug(widget.author!.authorSlug);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -329,7 +333,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 378,
|
height: 781,
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return PlanWidget(
|
return PlanWidget(
|
||||||
@@ -347,6 +351,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_bullets(_currentBullet, state.plansList.length),
|
_bullets(_currentBullet, state.plansList.length),
|
||||||
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -435,12 +440,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
padding: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
const Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(
|
||||||
'Дипломы\nи сертификаты',
|
'Дипломы\nи сертификаты',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFF323F4B),
|
color: Color(0xFF323F4B),
|
||||||
@@ -449,14 +454,19 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
// GestureDetector(
|
||||||
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
|
// onTap: () {
|
||||||
style: const TextStyle(
|
// GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
|
||||||
color: Color(0xFF2F80ED),
|
// },
|
||||||
fontSize: 16,
|
// child: Text(
|
||||||
fontWeight: FontWeight.w500,
|
// widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
|
||||||
),
|
// style: const TextStyle(
|
||||||
),
|
// color: Color(0xFF2F80ED),
|
||||||
|
// fontSize: 16,
|
||||||
|
// fontWeight: FontWeight.w500,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -531,12 +541,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
padding: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
const Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(
|
||||||
'Образование',
|
'Образование',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFF323F4B),
|
color: Color(0xFF323F4B),
|
||||||
@@ -544,14 +554,19 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
// GestureDetector(
|
||||||
widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
|
// onTap: () {
|
||||||
style: const TextStyle(
|
// GoRouter.of(context).pushNamed('stepperBecomeConsultantScreen', extra: state.profile.slug);
|
||||||
color: Color(0xFF2F80ED),
|
// },
|
||||||
fontSize: 16,
|
// child: Text(
|
||||||
fontWeight: FontWeight.w500,
|
// widget.author == null || widget.author!.authorIsMe ? 'Редактировать' : '',
|
||||||
),
|
// style: const TextStyle(
|
||||||
),
|
// color: Color(0xFF2F80ED),
|
||||||
|
// fontSize: 16,
|
||||||
|
// fontWeight: FontWeight.w500,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -562,7 +577,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 20),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListView.separated(
|
ListView.separated(
|
||||||
@@ -584,6 +599,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_educationBlock(String title, String date) {
|
_educationBlock(String title, String date) {
|
||||||
|
final parsedDate = DateTime.tryParse(date);
|
||||||
|
final formattedDate = parsedDate != null
|
||||||
|
? DateFormat('d MMMM y', 'ru_RU').format(parsedDate)
|
||||||
|
: date;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Material(
|
child: Material(
|
||||||
@@ -605,7 +625,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
date,
|
formattedDate,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Color(0xFF6D6F71),
|
color: Color(0xFF6D6F71),
|
||||||
@@ -620,6 +640,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_socialLinks(ProfileStateLoaded state) {
|
_socialLinks(ProfileStateLoaded state) {
|
||||||
if (!state.profile.roles.contains('consultant')) {
|
if (!state.profile.roles.contains('consultant')) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -964,7 +985,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
context.pushTransparentRoute(
|
context.pushTransparentRoute(
|
||||||
rootNavigator: true,
|
rootNavigator: true,
|
||||||
PhotoViewScreen(
|
PhotoViewScreen(
|
||||||
imageUrls: [state.profile.profilePhotoUrl],
|
imageUrls: [baseUrl + state.profile.profilePhotoUrl],
|
||||||
index: 0,
|
index: 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1044,7 +1065,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
tag: state.profile.profilePhotoUrl,
|
tag: state.profile.profilePhotoUrl,
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 32,
|
radius: 32,
|
||||||
foregroundImage: NetworkImage(state.profile.profilePhotoUrl),
|
foregroundImage: NetworkImage(baseUrl + state.profile.profilePhotoUrl),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
|||||||
radius: 20,
|
radius: 20,
|
||||||
backgroundColor: Colors.black12,
|
backgroundColor: Colors.black12,
|
||||||
foregroundImage: NetworkImage(
|
foregroundImage: NetworkImage(
|
||||||
state.blogPosts[widget.index].author.authorProfileIcon,
|
baseUrl + state.blogPosts[widget.index].author.authorProfileIcon,
|
||||||
),
|
),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:eatvio_app/core/const.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/profile/models/sub_model.dart';
|
import 'package:eatvio_app/features/profile/models/sub_model.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/widgets/sub_action_button_widget.dart';
|
import 'package:eatvio_app/features/profile/presentation/widgets/sub_action_button_widget.dart';
|
||||||
@@ -35,7 +36,7 @@ class _SubWidgetState extends State<SubWidget> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
foregroundImage: NetworkImage(widget.subModel.profilePhotoUrl),
|
foregroundImage: NetworkImage(baseUrl + widget.subModel.profilePhotoUrl),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
radius: 28,
|
radius: 28,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -183,7 +183,8 @@ class ProfileRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getPassKey(String phone) async {
|
Future<String> getPassKey(String phone) async {
|
||||||
return (jsonDecode((await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!;
|
return (jsonDecode(
|
||||||
|
(await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getMyPhone() async {
|
Future<String> getMyPhone() async {
|
||||||
@@ -194,7 +195,12 @@ class ProfileRepository {
|
|||||||
return await profileService.sendEmailCode();
|
return await profileService.sendEmailCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tryVerifyEmailByCode(String code) async {
|
Future<bool> tryVerifyEmailByCode(String code) async {
|
||||||
return await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}");
|
try {
|
||||||
|
await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}");
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
lib/features/recipe/models/my_recipe_model.dart
Normal file
36
lib/features/recipe/models/my_recipe_model.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'my_recipe_model.g.dart';
|
||||||
|
|
||||||
|
part 'my_recipe_model.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MyRecipePaginationModel with _$MyRecipePaginationModel {
|
||||||
|
const factory MyRecipePaginationModel({
|
||||||
|
required List<MyRecipeModel> data,
|
||||||
|
}) = _MyRecipePaginationModel;
|
||||||
|
|
||||||
|
factory MyRecipePaginationModel.fromJson(Map<String, Object?> json) => _$MyRecipePaginationModelFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MyRecipeModel with _$MyRecipeModel {
|
||||||
|
const factory MyRecipeModel({
|
||||||
|
required int id,
|
||||||
|
required String image,
|
||||||
|
required String state,
|
||||||
|
@JsonKey(name: 'cooking_time') required int cookingTime,
|
||||||
|
@JsonKey(name: 'active_time') required int activeCookingTime,
|
||||||
|
@JsonKey(name: 'description_md') required String description,
|
||||||
|
required List<Step>? steps,
|
||||||
|
@JsonKey(name: 'video_url') required String? videoUrl,
|
||||||
|
required String? title,
|
||||||
|
required String slug,
|
||||||
|
@JsonKey(name: 'energy_per_serving') required double calories,
|
||||||
|
@JsonKey(name: 'total_likes') required int likesCount,
|
||||||
|
@JsonKey(name: 'total_views') required int viewsCount,
|
||||||
|
}) = _MyRecipeModel;
|
||||||
|
|
||||||
|
factory MyRecipeModel.fromJson(Map<String, Object?> json) => _$MyRecipeModelFromJson(json);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.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:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class MyRecipesCubit extends Cubit<MyRecipesState> {
|
||||||
|
final MyRecipesRepository myRecipesRepository;
|
||||||
|
final AboutRecipeRepository aboutRecipeRepository;
|
||||||
|
|
||||||
|
static const String active = 'active';
|
||||||
|
static const String archived = 'archived';
|
||||||
|
|
||||||
|
MyRecipesCubit({
|
||||||
|
required this.myRecipesRepository,
|
||||||
|
required this.aboutRecipeRepository,
|
||||||
|
}) : super(const MyRecipesState.loading());
|
||||||
|
|
||||||
|
getMyRecipes() async {
|
||||||
|
emit(const MyRecipesState.loading());
|
||||||
|
try {
|
||||||
|
final myRecipes = await myRecipesRepository.getMyRecipes();
|
||||||
|
final List<MyRecipeModel> activeRecipes = myRecipes.where((recipe) => recipe.state == active).toList();
|
||||||
|
emit(MyRecipesState.loaded(recipes: activeRecipes, selectedState: active));
|
||||||
|
} catch (e) {
|
||||||
|
emit(const MyRecipesState.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveState() async {
|
||||||
|
emit(const MyRecipesState.loading());
|
||||||
|
try {
|
||||||
|
final myRecipes = await myRecipesRepository.getMyRecipes();
|
||||||
|
final List<MyRecipeModel> activeRecipes = myRecipes.where((recipe) => recipe.state == active).toList();
|
||||||
|
emit(MyRecipesState.loaded(recipes: activeRecipes, selectedState: active));
|
||||||
|
} catch (e) {
|
||||||
|
emit(const MyRecipesState.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setArchivedState() async {
|
||||||
|
emit(const MyRecipesState.loading());
|
||||||
|
try {
|
||||||
|
final myRecipes = await myRecipesRepository.getMyRecipes();
|
||||||
|
final List<MyRecipeModel> archivedRecipes = myRecipes.where((recipe) => recipe.state == archived).toList();
|
||||||
|
emit(MyRecipesState.loaded(recipes: archivedRecipes, selectedState: archived));
|
||||||
|
} catch (e) {
|
||||||
|
emit(const MyRecipesState.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Recipe> getRecipeById(String slug) async {
|
||||||
|
return await aboutRecipeRepository.getRecipe(slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'my_recipes_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class MyRecipesState with _$MyRecipesState {
|
||||||
|
const factory MyRecipesState() = _MyRecipesState;
|
||||||
|
|
||||||
|
const factory MyRecipesState.loading() = MyRecipesStateLoading;
|
||||||
|
|
||||||
|
const factory MyRecipesState.loaded({
|
||||||
|
required List<MyRecipeModel> recipes,
|
||||||
|
required String selectedState,
|
||||||
|
}) = MyRecipesStateLoaded;
|
||||||
|
|
||||||
|
const factory MyRecipesState.error() = MyRecipesStateError;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipe_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class MyRecipesScreen extends StatelessWidget {
|
||||||
|
const MyRecipesScreen({super.key});
|
||||||
|
|
||||||
|
static const name = 'myRecipesScreen';
|
||||||
|
static const path = '/my-recipes-screen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: appTheme.gray100,
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'Мои рецепты',
|
||||||
|
maxLines: 2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: true,
|
||||||
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: BlocProvider<MyRecipesCubit>(
|
||||||
|
create: (BuildContext context) => sl<MyRecipesCubit>()..getMyRecipes(),
|
||||||
|
child: BlocBuilder<MyRecipesCubit, MyRecipesState>(
|
||||||
|
builder: (BuildContext context, MyRecipesState state) {
|
||||||
|
if (state is MyRecipesStateLoaded) {
|
||||||
|
return ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
itemBuilder: (context, index) => MyRecipeWidget(recipe: state.recipes[index]),
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||||
|
itemCount: state.recipes.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
218
lib/features/recipe/presentation/widgets/my_recipe_widget.dart
Normal file
218
lib/features/recipe/presentation/widgets/my_recipe_widget.dart
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
class MyRecipeWidget extends StatefulWidget {
|
||||||
|
final MyRecipeModel recipe;
|
||||||
|
|
||||||
|
const MyRecipeWidget({
|
||||||
|
super.key,
|
||||||
|
required this.recipe,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyRecipeWidget> createState() => _MyRecipeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyRecipeWidgetState extends State<MyRecipeWidget> {
|
||||||
|
late final int hours;
|
||||||
|
late final int minutes;
|
||||||
|
late final String timeTitle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
hours = widget.recipe.cookingTime ~/ 60;
|
||||||
|
minutes = widget.recipe.cookingTime % 60;
|
||||||
|
if (hours > 0) {
|
||||||
|
timeTitle = '$hours ${getHourForm(hours)} $minutes ${getMinuteForm(minutes)}';
|
||||||
|
} else {
|
||||||
|
timeTitle = '$minutes ${getMinuteForm(minutes)}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () async => GoRouter.of(context).pushNamed(
|
||||||
|
'aboutRecipesScreen',
|
||||||
|
extra: {
|
||||||
|
'recipe': await context.read<MyRecipesCubit>().getRecipeById(widget.recipe.slug),
|
||||||
|
'navigateToComments': false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: '$baseUrl/storage/${widget.recipe.image}',
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 8,
|
||||||
|
bottom: 8,
|
||||||
|
child: Wrap(
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
_iconButton('assets/icons/clock.svg', timeTitle),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_iconButton('assets/icons/steps.svg', '${widget.recipe.steps?.length ?? 0} ${getStepForm(widget.recipe.steps?.length ?? 0)}'),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_iconButton('assets/icons/calories.svg', '${widget.recipe.calories.toStringAsFixed(0)} ккал'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text(
|
||||||
|
widget.recipe.title!,
|
||||||
|
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.recipe.description.isNotEmpty) ...[
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Html(
|
||||||
|
data: widget.recipe.description.length > 200 ? '${widget.recipe.description.substring(0, 200)}...' : widget.recipe.description,
|
||||||
|
style: {
|
||||||
|
"*": Style(
|
||||||
|
margin: Margins.zero,
|
||||||
|
color: appTheme.black,
|
||||||
|
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
|
||||||
|
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await sl<RecipesCubit>().deleteRecipe(widget.recipe.slug);
|
||||||
|
if (!result) {
|
||||||
|
showErrorToast('Не удалось удалить рецепт');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showSuccessToast('Рецепт удален');
|
||||||
|
if (context.mounted) context.read<MyRecipesCubit>().getMyRecipes();
|
||||||
|
},
|
||||||
|
text: 'Удалить',
|
||||||
|
height: 52,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final box = context.findRenderObject() as RenderBox?;
|
||||||
|
await SharePlus.instance.share(
|
||||||
|
ShareParams(
|
||||||
|
uri: Uri.parse('$baseUrl/ru/recipe/${widget.recipe.slug}'),
|
||||||
|
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
height: 52,
|
||||||
|
width: 52,
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
),
|
||||||
|
text: s.share,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_iconButton(String iconPath, [String? text]) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: appTheme.whiteA700,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
height: 32,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
iconPath,
|
||||||
|
),
|
||||||
|
if (text != null) const SizedBox(width: 8),
|
||||||
|
if (text != null)
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
lib/features/recipe/presentation/widgets/my_recipes_tab.dart
Normal file
68
lib/features/recipe/presentation/widgets/my_recipes_tab.dart
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/animated_option_button.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class MyRecipesTab extends StatefulWidget {
|
||||||
|
const MyRecipesTab({super.key, this.additionalOnTap});
|
||||||
|
|
||||||
|
final void Function()? additionalOnTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyRecipesTab> createState() => _MyRecipesTabState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyRecipesTabState extends State<MyRecipesTab> {
|
||||||
|
final List<String> tabTitles = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
final s = S.of(context);
|
||||||
|
if (tabTitles.isEmpty) {
|
||||||
|
tabTitles.add(s.published);
|
||||||
|
tabTitles.add(s.drafted);
|
||||||
|
}
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: appTheme.whiteA700,
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
...List.generate(
|
||||||
|
2,
|
||||||
|
(index) =>
|
||||||
|
Expanded(
|
||||||
|
child: AnimatedOptionButton(
|
||||||
|
textStyle: TextStyles.textSMedium,
|
||||||
|
isSelected: false, // state.selectedStateIndex == index,
|
||||||
|
text: tabTitles[index],
|
||||||
|
onTap: () {
|
||||||
|
widget.additionalOnTap?.call();
|
||||||
|
// context.read<MyPlansCubit>().changeSelectedStateIndex(index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
lib/features/recipe/repositories/my_recipes_repository.dart
Normal file
16
lib/features/recipe/repositories/my_recipes_repository.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
||||||
|
|
||||||
|
class MyRecipesRepository {
|
||||||
|
final MyRecipesService myRecipesService;
|
||||||
|
|
||||||
|
MyRecipesRepository({required this.myRecipesService});
|
||||||
|
|
||||||
|
Future<List<MyRecipeModel>> getMyRecipes() async {
|
||||||
|
try {
|
||||||
|
return (await myRecipesService.getMyRecipes()).data;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
lib/features/recipe/services/my_recipes_service.dart
Normal file
13
lib/features/recipe/services/my_recipes_service.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart';
|
||||||
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
|
part 'my_recipes_service.g.dart';
|
||||||
|
|
||||||
|
@RestApi()
|
||||||
|
abstract class MyRecipesService {
|
||||||
|
factory MyRecipesService(Dio dio, {String baseUrl}) = _MyRecipesService;
|
||||||
|
|
||||||
|
@GET('/api/recipes/my')
|
||||||
|
Future<MyRecipePaginationModel> getMyRecipes();
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user