diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
index 2d390f02..3abc813c 100644
--- a/.gitea/workflows/build.yml
+++ b/.gitea/workflows/build.yml
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
on: [ pull_request ]
env:
- flutter_version: "3.29.1"
+ flutter_version: "3.32.0"
java_version: "21.x"
flutter_channel: "stable"
diff --git a/.gitea/workflows/docker-run-tests.yml b/.gitea/workflows/docker-run-tests.yml
index cf14247c..85d14070 100644
--- a/.gitea/workflows/docker-run-tests.yml
+++ b/.gitea/workflows/docker-run-tests.yml
@@ -3,7 +3,7 @@ name: Eatvio App Android Test on custom docker
on: [ pull_request ]
env:
- flutter_version: "3.29.1"
+ flutter_version: "3.32.0"
java_version: "21.x"
flutter_channel: "stable"
my_local_cache_dir: "/opt/hostedtoolcache/"
diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml
index 80825645..70ff32b8 100644
--- a/.gitea/workflows/test.yml
+++ b/.gitea/workflows/test.yml
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
on: [ pull_request ]
env:
- flutter_version: "3.29.3"
+ flutter_version: "3.32.0"
java_version: "21.x"
flutter_channel: "stable"
my_local_cache_dir: "/opt/hostedtoolcache/"
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
index f2f60fc9..f21ab9cc 100644
--- a/android/build/reports/problems/problems-report.html
+++ b/android/build/reports/problems/problems-report.html
@@ -650,7 +650,7 @@ code + .copy-button {
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index efdcc4ac..f587a472 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip
diff --git a/android/settings.gradle b/android/settings.gradle
index 849ba91f..833c476b 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -20,7 +20,7 @@ plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.7.3" apply false
// 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
id "org.jetbrains.kotlin.android" version "2.0.21" apply false
}
diff --git a/assets/lottie_animations/loading.json b/assets/lottie_animations/loading.json
new file mode 100644
index 00000000..a0ac541f
--- /dev/null
+++ b/assets/lottie_animations/loading.json
@@ -0,0 +1 @@
+{"nm":"Main Scene","ddd":0,"h":500,"w":500,"meta":{"g":"@lottiefiles/creator 1.46.0"},"layers":[{"ty":0,"nm":"Nested Scene 1","sr":1,"st":0,"op":31,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"w":500,"h":500,"refId":"precomp_newScene_abc3f55b-16ae-4191-8057-2b89484cea7e","ind":1},{"ty":4,"nm":"Слой_x0020_11","sr":1,"st":0,"op":31,"ip":0,"hd":false,"ln":"Слой_x0020_1","ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[2116.5,2116.5]},"s":{"a":0,"k":[10.7995,10.7995]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[249.892,323.7606]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","bm":0,"ln":"_2456927096976","hd":false,"nm":"_2456927096976","it":[{"ty":"gr","bm":0,"hd":false,"nm":"Group 2","it":[{"ty":"sh","bm":0,"hd":false,"nm":"Path 11","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[194,83],[145,154],[-70,66],[-67,-70],[-150,-64],[-164,0],[-150,65],[-112,119],[-71,-67],[67,-70],[194,-84],[211,0]],"o":[[-212,0],[-194,-84],[-67,-70],[71,-67],[112,119],[150,65],[163,0],[150,-64],[67,-70],[70,66],[-145,154],[-194,83],[0,0]],"v":[[2118,1902],[1504,1776],[990,1415],[997,1167],[1246,1174],[1643,1452],[2118,1550],[2592,1452],[2989,1174],[3238,1167],[3245,1415],[2731,1776],[2118,1902]]}}},{"ty":"fl","bm":0,"hd":false,"nm":"Fill","c":{"a":0,"k":[1,1,1]},"r":2,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[2117.50004629844,1510.5843947960868]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[2117.50004629844,1510.5843947960868]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"gr","bm":0,"hd":true,"nm":"Group 3","it":[{"ty":"sh","bm":0,"hd":false,"nm":"Path 12","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,-882],[881,0],[0,881],[-882,0]],"o":[[881,0],[0,881],[-882,0],[0,-882],[0,0]],"v":[[2118,1],[3713,1270],[2118,2866],[522,1270],[2118,1]]}}},{"ty":"fl","bm":0,"hd":false,"nm":"Fill","c":{"a":0,"k":[0.2627,0.651,0.949]},"r":2,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[2117.5,1433.5000000000002]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[2117.5,1433.5000000000002]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tr","a":{"a":0,"k":[2116.5000000000005,2116.5]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[2116.5000000000005,2116.5]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":2},{"ty":4,"nm":"Слой_x0020_1","sr":1,"st":0,"op":31,"ip":0,"hd":false,"ln":"Слой_x0020_1","ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[2117.5,1433.4999999999998]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[8.78118,8.78118],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[12,12],"t":15},{"s":[8.7812,8.7812],"t":30}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[249.99999499999998,250.00001499999996]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":0},{"s":[360],"t":30}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","bm":0,"ln":"_2456927096976","hd":false,"nm":"_2456927096976","it":[{"ty":"gr","bm":0,"hd":true,"nm":"Group 2","it":[{"ty":"sh","bm":0,"hd":false,"nm":"Path 11","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[194,83],[145,154],[-70,66],[-67,-70],[-150,-64],[-164,0],[-150,65],[-112,119],[-71,-67],[67,-70],[194,-84],[211,0]],"o":[[-212,0],[-194,-84],[-67,-70],[71,-67],[112,119],[150,65],[163,0],[150,-64],[67,-70],[70,66],[-145,154],[-194,83],[0,0]],"v":[[2118,1902],[1504,1776],[990,1415],[997,1167],[1246,1174],[1643,1452],[2118,1550],[2592,1452],[2989,1174],[3238,1167],[3245,1415],[2731,1776],[2118,1902]]}}},{"ty":"fl","bm":0,"hd":false,"nm":"Fill","c":{"a":0,"k":[1,1,1]},"r":2,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[2117.50004629844,1510.5843947960868]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[2117.50004629844,1510.5843947960868]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"gr","bm":0,"hd":false,"nm":"Group 3","it":[{"ty":"sh","bm":0,"hd":false,"nm":"Path 12","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,-882],[881,0],[0,881],[-882,0]],"o":[[881,0],[0,881],[-882,0],[0,-882],[0,0]],"v":[[2118,1],[3713,1270],[2118,2866],[522,1270],[2118,1]]}}},{"ty":"fl","bm":0,"hd":false,"nm":"Fill","c":{"a":0,"k":[0.2627,0.651,0.949]},"r":2,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[2117.5,1433.5000000000002]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[2117.5,1433.5000000000002]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tr","a":{"a":0,"k":[2117.5,1433.5000000000002]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[2117.5,1433.5000000000002]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":3}],"v":"5.7.0","fr":30,"op":30,"ip":0,"assets":[{"nm":"Nested Scene 1","id":"precomp_newScene_abc3f55b-16ae-4191-8057-2b89484cea7e","layers":[]}]}
\ No newline at end of file
diff --git a/assets/rive_animations/loading.riv b/assets/rive_animations/loading.riv
deleted file mode 100644
index d328f26d..00000000
Binary files a/assets/rive_animations/loading.riv and /dev/null differ
diff --git a/devtools_options.yaml b/devtools_options.yaml
index 5c27c3e3..70935407 100644
--- a/devtools_options.yaml
+++ b/devtools_options.yaml
@@ -1,2 +1,3 @@
extensions:
- - provider: true
\ No newline at end of file
+ - provider: true
+ - shared_preferences: true
\ No newline at end of file
diff --git a/ios/Flutter/ephemeral/flutter_lldb_helper.py b/ios/Flutter/ephemeral/flutter_lldb_helper.py
new file mode 100644
index 00000000..a88caf99
--- /dev/null
+++ b/ios/Flutter/ephemeral/flutter_lldb_helper.py
@@ -0,0 +1,32 @@
+#
+# Generated file, do not edit.
+#
+
+import lldb
+
+def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
+ """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
+ base = frame.register["x0"].GetValueAsAddress()
+ page_len = frame.register["x1"].GetValueAsUnsigned()
+
+ # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
+ # first page to see if handled it correctly. This makes diagnosing
+ # misconfiguration (e.g. missing breakpoint) easier.
+ data = bytearray(page_len)
+ data[0:8] = b'IHELPED!'
+
+ error = lldb.SBError()
+ frame.GetThread().GetProcess().WriteMemory(base, data, error)
+ if not error.Success():
+ print(f'Failed to write into {base}[+{page_len}]', error)
+ return
+
+def __lldb_init_module(debugger: lldb.SBDebugger, _):
+ target = debugger.GetDummyTarget()
+ # Caveat: must use BreakpointCreateByRegEx here and not
+ # BreakpointCreateByName. For some reasons callback function does not
+ # get carried over from dummy target for the later.
+ bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
+ bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
+ bp.SetAutoContinue(True)
+ print("-- LLDB integration loaded --")
diff --git a/ios/Flutter/ephemeral/flutter_lldbinit b/ios/Flutter/ephemeral/flutter_lldbinit
new file mode 100644
index 00000000..e3ba6fbe
--- /dev/null
+++ b/ios/Flutter/ephemeral/flutter_lldbinit
@@ -0,0 +1,5 @@
+#
+# Generated file, do not edit.
+#
+
+command script import --relative-to-command-file flutter_lldb_helper.py
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 2566b21b..31db8575 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -388,7 +388,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 16;
+ CURRENT_PROJECT_VERSION = 18;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -538,7 +538,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 16;
+ CURRENT_PROJECT_VERSION = 18;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -576,7 +576,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 16;
+ CURRENT_PROJECT_VERSION = 18;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 24f859fd..83164ead 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 16
+ 18
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart
index dbcea1bc..172d840d 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.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/profile/presentation/pages/profile_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/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/verify_email_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/presentation/pages/change_email_screen.dart';
@@ -76,8 +78,11 @@ class AppRouter {
name: 'authScreen',
builder: (context, state) => const AuthScreen(),
routes: [
- // GoRoute(path: 'webView-registration-screen', name: 'webViewRegistrationScreen', builder: (context, state) => const WebViewRegistrationScreen()),
- GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
+ GoRoute(
+ path: 'registration-screen',
+ name: 'registrationScreen',
+ builder: (context, state) => const RegistrationScreen(),
+ ),
],
),
ShellRoute(
@@ -201,7 +206,11 @@ class AppRouter {
name: 'aboutRecipesScreen',
builder: (context, state) {
final data = state.extra as Map;
- 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(
@@ -355,6 +364,16 @@ class AppRouter {
name: 'verifyScreen',
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(),
+ ),
],
);
}
diff --git a/lib/core/dio/phone_verify_error_interceptor.dart b/lib/core/dio/phone_verify_error_interceptor.dart
index 5d764cff..ab6188bf 100644
--- a/lib/core/dio/phone_verify_error_interceptor.dart
+++ b/lib/core/dio/phone_verify_error_interceptor.dart
@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_router.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:go_router/go_router.dart';
@@ -13,6 +14,8 @@ class PhoneVerifyErrorInterceptor extends Interceptor {
if (data is Map && data['type'] == 'unverificated_phone') {
final String phone = await sl().getMyPhone();
final String passKey = await sl().getPassKey(phone);
+ await showErrorDialog(AppRouter.navigatorKey.currentContext!,
+ title: 'Привяжите номер телефона', description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона');
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
return handler.reject(
DioException(
diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart
index fe28d2ec..80a98f3e 100644
--- a/lib/core/locator_service.dart
+++ b/lib/core/locator_service.dart
@@ -81,9 +81,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/services/recipe_service.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/my_recipes_repository.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/verify_email_cubit/verify_email_cubit.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/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
@@ -138,6 +142,8 @@ Future init() async {
sl.registerFactory(() => ConsultationsCubit(consultationsRepository: sl()));
sl.registerFactory(() => ConsultantCommentsCubit(diaryRepository: sl()));
sl.registerFactory(() => CancelPlanCubit(cancelPlanRepository: sl()));
+ sl.registerFactory(() => VerifyEmailCubit(profileRepository: sl()));
+ sl.registerFactory(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
// Repository
sl.registerLazySingleton(() => ChatRepository(chatService: sl()));
@@ -165,6 +171,7 @@ Future init() async {
sl.registerLazySingleton(() => CancelPlanRepository(cancelService: sl()));
sl.registerLazySingleton(() => CreatePlanRepository(createPlanService: sl()));
sl.registerLazySingleton(() => NotificationsRepository(notificationsService: sl()));
+ sl.registerFactory(() => MyRecipesRepository(myRecipesService: sl()));
// Services
sl.registerLazySingleton(() => ChatService(sl()));
@@ -193,6 +200,7 @@ Future init() async {
sl.registerLazySingleton(() => CancelPlanService(sl()));
sl.registerLazySingleton(() => CreatePlanService(sl()));
sl.registerLazySingleton(() => NotificationsService(sl()));
+ sl.registerLazySingleton(() => MyRecipesService(sl()));
// External
sl.registerLazySingleton(
diff --git a/lib/core/widgets/custom_popup.dart b/lib/core/widgets/custom_popup.dart
index 67d87da5..47081135 100644
--- a/lib/core/widgets/custom_popup.dart
+++ b/lib/core/widgets/custom_popup.dart
@@ -6,14 +6,12 @@ import 'package:eatvio_app/core/utils/toasts.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/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/feed_blog_post_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/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/generated/l10n.dart';
import 'package:flutter/material.dart';
@@ -100,37 +98,12 @@ class CustomPopup extends StatelessWidget {
),
);
});
- addItem('assets/icons/edit_paint.svg', 'Редактировать', () async {
- if (complainedContentType == ComplainedContent.posts) {
- GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedBlogPostModel);
- } else if (complainedContentType == ComplainedContent.recipe) {
- GoRouter.of(context).pushNamed(RecipeCreateScreen.name, extra: recipe);
- } else if (complainedContentType == ComplainedContent.diet) {
- await showErrorDialog(context,
- title: 'Предупреждение', description: 'Диета будет перенесена в архив. Вы создаете новую диету на основе существующей.');
- await sl().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().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().unpublish(slug!);
- if (!result) {
- showErrorDialog(context, title: S.current.could_not_delete_blog_post);
- return;
- }
- showSuccessToast('План перенесен в архив, обновите ленту');
+ if (complainedContentType == ComplainedContent.posts || complainedContentType == ComplainedContent.recipe) {
+ addItem('assets/icons/edit_paint.svg', 'Редактировать', () async {
+ if (complainedContentType == ComplainedContent.posts) {
+ GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedBlogPostModel);
+ } else if (complainedContentType == ComplainedContent.recipe) {
+ GoRouter.of(context).pushNamed(RecipeCreateScreen.name, extra: recipe);
}
});
}
@@ -145,21 +118,21 @@ class CustomPopup extends StatelessWidget {
} else if (complainedContentType == ComplainedContent.recipe) {
final result = await sl().deleteRecipe(slug!);
if (!result) {
- showErrorDialog(context, title: S.current.could_not_delete_blog_post);
+ showErrorDialog(context, title: 'Не удалось удалить рецепт');
return;
}
showSuccessToast('Рецепт удален, обновите ленту');
} else if (complainedContentType == ComplainedContent.diet) {
final result = await sl().deleteDiet(id!);
if (!result) {
- showErrorDialog(context, title: S.current.could_not_delete_blog_post);
+ showErrorDialog(context, title: 'Не удалось удалить диету');
return;
}
showSuccessToast('Диета удалена, обновите ленту');
} else if (complainedContentType == ComplainedContent.plan) {
final result = await sl().deletePlan(slug!);
if (!result) {
- showErrorDialog(context, title: S.current.could_not_delete_blog_post);
+ showErrorDialog(context, title: 'Не удалось удалить план');
return;
}
showSuccessToast('План удален, обновите ленту');
diff --git a/lib/core/widgets/loader.dart b/lib/core/widgets/loader.dart
index dd4727dc..ae69e10f 100644
--- a/lib/core/widgets/loader.dart
+++ b/lib/core/widgets/loader.dart
@@ -1,17 +1,19 @@
import 'package:flutter/material.dart';
-import 'package:rive/rive.dart';
+import 'package:lottie/lottie.dart';
class Loader extends StatelessWidget {
const Loader({super.key});
@override
Widget build(BuildContext context) {
- return const Center(
+ return Center(
child: SizedBox(
- height: 32,
- width: 32,
- child: RiveAnimation.asset(
- 'assets/rive_animations/loading.riv',
+ height: 42,
+ width: 42,
+ child: Lottie.asset(
+ 'assets/lottie_animations/loading.json',
+ width: 42,
+ height: 42,
),
),
);
diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart
index fe3e7bda..ad85dcc7 100644
--- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart
+++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart
@@ -186,7 +186,7 @@ class _BlogPostScreenState extends State {
CircleAvatar(
radius: 16,
foregroundImage: NetworkImage(
- widget.blogPost.author.authorProfileIcon,
+ baseUrl + widget.blogPost.author.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart
index 4655d87d..ae45a6ca 100644
--- a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart
+++ b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart
@@ -12,7 +12,11 @@ class ProfileAvatarCubit extends Cubit {
getMyProfile() async {
emit(const ProfileAvatarState.loading());
try {
- emit(ProfileAvatarState.loaded(profile: await profileRepository.getMyProfile()));
+ emit(
+ ProfileAvatarState.loaded(
+ profile: await profileRepository.getMyProfile(),
+ ),
+ );
} catch (e) {
emit(ProfileAvatarState.error(errorMessage: e.toString()));
}
diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
index 52b9b838..9584c009 100644
--- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
+++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
@@ -1,4 +1,5 @@
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/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
@@ -81,7 +82,7 @@ class _BottomNavigationBarWidgetState extends State {
BottomNavigationBarItem(
label: '',
icon: CircleAvatar(
- foregroundImage: NetworkImage(widget.profileModel.profilePhotoUrl),
+ foregroundImage: NetworkImage(baseUrl + widget.profileModel.profilePhotoUrl),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: 16,
),
diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart
index b20646b9..537aa9e9 100644
--- a/lib/features/chat/presentation/pages/chat_screen.dart
+++ b/lib/features/chat/presentation/pages/chat_screen.dart
@@ -2,6 +2,7 @@ import 'dart:io';
import 'dart:math';
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/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
@@ -141,7 +142,7 @@ class _ChatScreenState extends State {
if (state.chatRoom.users.receiver.profilePhotoUrl != null)
CircleAvatar(
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'),
),
if (state.chatRoom.users.receiver.profilePhotoUrl == null)
diff --git a/lib/features/chat/presentation/widgets/chat_entry_widget.dart b/lib/features/chat/presentation/widgets/chat_entry_widget.dart
index 1338c60b..a6d5b3bb 100644
--- a/lib/features/chat/presentation/widgets/chat_entry_widget.dart
+++ b/lib/features/chat/presentation/widgets/chat_entry_widget.dart
@@ -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/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
@@ -41,7 +42,7 @@ class _ChatEntryWidgetState extends State {
CircleAvatar(
radius: 24,
foregroundImage: NetworkImage(
- widget.chatRoom.users.toChatUser.profilePhotoUrl,
+ baseUrl + widget.chatRoom.users.toChatUser.profilePhotoUrl,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
backgroundColor: Colors.white,
diff --git a/lib/features/consultant/become_consultant/services/consultant_service.dart b/lib/features/consultant/become_consultant/services/consultant_service.dart
index cc284993..fbf7541d 100644
--- a/lib/features/consultant/become_consultant/services/consultant_service.dart
+++ b/lib/features/consultant/become_consultant/services/consultant_service.dart
@@ -20,7 +20,7 @@ abstract class ConsultantService {
@Part(name: 'photos[]', contentType: 'multipart/form-data') List? photos,
});
- @GET('/api/consultant_request/{slug}/status')
+ @GET('/api/profile/consultant/{slug}/status')
Future getConsultantRequestStatus({
@Path() required String slug,
});
diff --git a/lib/features/diets/presentation/widgets/author_card.dart b/lib/features/diets/presentation/widgets/author_card.dart
index c1113f93..6fff0b93 100644
--- a/lib/features/diets/presentation/widgets/author_card.dart
+++ b/lib/features/diets/presentation/widgets/author_card.dart
@@ -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/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
@@ -36,7 +37,7 @@ class AuthorCard extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
- image: NetworkImage(author.authorProfileIcon),
+ image: NetworkImage(baseUrl + author.authorProfileIcon),
fit: BoxFit.cover,
),
),
diff --git a/lib/features/feed/models/recipes_model.dart b/lib/features/feed/models/recipes_model.dart
index 262b9ecc..dcb4d2ea 100644
--- a/lib/features/feed/models/recipes_model.dart
+++ b/lib/features/feed/models/recipes_model.dart
@@ -16,8 +16,8 @@ abstract class Recipe with _$Recipe {
@JsonKey(name: 'description_md') required String description,
required List? steps,
@JsonKey(name: 'video_url') required String? videoUrl,
- @JsonKey(name: 'created_at') required DateTime createdAt,
- @JsonKey(name: 'updated_at') required DateTime updatedAt,
+ @JsonKey(name: 'created_at') DateTime? createdAt,
+ @JsonKey(name: 'updated_at') DateTime? updatedAt,
required String? title,
required String? type,
required String slug,
diff --git a/lib/features/feed/presentation/widgets/blog_post_widget.dart b/lib/features/feed/presentation/widgets/blog_post_widget.dart
index c5f6ff5f..542fe274 100644
--- a/lib/features/feed/presentation/widgets/blog_post_widget.dart
+++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart
@@ -78,7 +78,7 @@ class _BlogPostWidgetState extends State {
radius: 20,
backgroundColor: Colors.black12,
foregroundImage: NetworkImage(
- state.feedBlogPostList[widget.index].author.authorProfileIcon,
+ baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
@@ -245,7 +245,9 @@ class _BlogPostWidgetState extends State {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
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,
),
);
diff --git a/lib/features/feed/presentation/widgets/comments_widget.dart b/lib/features/feed/presentation/widgets/comments_widget.dart
index b12b691e..16f52f90 100644
--- a/lib/features/feed/presentation/widgets/comments_widget.dart
+++ b/lib/features/feed/presentation/widgets/comments_widget.dart
@@ -1,3 +1,4 @@
+import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.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';
@@ -77,7 +78,7 @@ class _CommentsWidgetState extends State {
);
},
child: CircleAvatar(
- foregroundImage: NetworkImage(comment.authorProfileIcon),
+ foregroundImage: NetworkImage(baseUrl + comment.authorProfileIcon),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: radius,
),
diff --git a/lib/features/feed/presentation/widgets/consultant_widget.dart b/lib/features/feed/presentation/widgets/consultant_widget.dart
index 5246d348..6e8562f3 100644
--- a/lib/features/feed/presentation/widgets/consultant_widget.dart
+++ b/lib/features/feed/presentation/widgets/consultant_widget.dart
@@ -66,7 +66,7 @@ class ConsultantWidget extends StatelessWidget {
),
const SizedBox(width: 8),
CircleAvatar(
- foregroundImage: NetworkImage(consultant.photo),
+ foregroundImage: NetworkImage(baseUrl + consultant.photo),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
const SizedBox(width: 8),
diff --git a/lib/features/feed/presentation/widgets/plan_widget.dart b/lib/features/feed/presentation/widgets/plan_widget.dart
index 849549e8..e47ca1db 100644
--- a/lib/features/feed/presentation/widgets/plan_widget.dart
+++ b/lib/features/feed/presentation/widgets/plan_widget.dart
@@ -185,7 +185,7 @@ class _PlanWidgetState extends State {
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
- image: NetworkImage(widget.plan.author.authorProfileIcon),
+ image: NetworkImage(baseUrl + widget.plan.author.authorProfileIcon),
fit: BoxFit.cover,
),
),
diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart
index 255c8510..6ccb0973 100644
--- a/lib/features/menu/presentation/pages/menu_screen.dart
+++ b/lib/features/menu/presentation/pages/menu_screen.dart
@@ -8,6 +8,7 @@ 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_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
+import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -28,7 +29,7 @@ class MenuScreen extends StatelessWidget {
_tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
if (profileModel.roles.contains('consultant')) _divider(context),
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'))
_tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
diff --git a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart
index 7eb7f15c..587e05ca 100644
--- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart
+++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart
@@ -26,6 +26,7 @@ import 'package:share_plus/share_plus.dart';
class MyDietWidget extends StatefulWidget {
final Diet diet;
+
const MyDietWidget({
super.key,
required this.diet,
diff --git a/lib/features/my_plans/services/my_plans_service.dart b/lib/features/my_plans/services/my_plans_service.dart
index 9c5cbf0c..bc24aec8 100644
--- a/lib/features/my_plans/services/my_plans_service.dart
+++ b/lib/features/my_plans/services/my_plans_service.dart
@@ -8,7 +8,7 @@ part 'my_plans_service.g.dart';
abstract class MyPlansService {
factory MyPlansService(Dio dio, {String baseUrl}) = _MyPlansService;
- @GET('/api/plans/index')
+ @GET('/api/profile/my_plans')
Future getPlans({
@Query('page') required int page,
@Query('filters[name][\$startsWith]') String? query,
diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart
index 79c41c54..64c7dda7 100644
--- a/lib/features/profile/presentation/pages/profile_screen.dart
+++ b/lib/features/profile/presentation/pages/profile_screen.dart
@@ -1044,7 +1044,7 @@ class _ProfileScreenState extends State {
tag: state.profile.profilePhotoUrl,
child: CircleAvatar(
radius: 32,
- foregroundImage: NetworkImage(state.profile.profilePhotoUrl),
+ foregroundImage: NetworkImage(baseUrl + state.profile.profilePhotoUrl),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
),
diff --git a/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart b/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart
index 43ade18c..10afdd8c 100644
--- a/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart
+++ b/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart
@@ -66,7 +66,7 @@ class _ProfileBlogPostWidgetState extends State {
radius: 20,
backgroundColor: Colors.black12,
foregroundImage: NetworkImage(
- state.blogPosts[widget.index].author.authorProfileIcon,
+ baseUrl + state.blogPosts[widget.index].author.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
diff --git a/lib/features/profile/presentation/widgets/sub_widget.dart b/lib/features/profile/presentation/widgets/sub_widget.dart
index c8342077..aa17433d 100644
--- a/lib/features/profile/presentation/widgets/sub_widget.dart
+++ b/lib/features/profile/presentation/widgets/sub_widget.dart
@@ -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/profile/models/sub_model.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/sub_action_button_widget.dart';
@@ -35,7 +36,7 @@ class _SubWidgetState extends State {
child: Row(
children: [
CircleAvatar(
- foregroundImage: NetworkImage(widget.subModel.profilePhotoUrl),
+ foregroundImage: NetworkImage(baseUrl + widget.subModel.profilePhotoUrl),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: 28,
),
diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart
index 46b3bcf6..75879d15 100644
--- a/lib/features/profile/repositories/profile_repository.dart
+++ b/lib/features/profile/repositories/profile_repository.dart
@@ -183,7 +183,8 @@ class ProfileRepository {
}
Future 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 getMyPhone() async {
@@ -194,7 +195,12 @@ class ProfileRepository {
return await profileService.sendEmailCode();
}
- Future tryVerifyEmailByCode(String code) async {
- return await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}");
+ Future tryVerifyEmailByCode(String code) async {
+ try {
+ await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}");
+ return true;
+ } catch (e) {
+ return false;
+ }
}
}
diff --git a/lib/features/recipe/models/my_recipe_model.dart b/lib/features/recipe/models/my_recipe_model.dart
new file mode 100644
index 00000000..f7068938
--- /dev/null
+++ b/lib/features/recipe/models/my_recipe_model.dart
@@ -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 data,
+ }) = _MyRecipePaginationModel;
+
+ factory MyRecipePaginationModel.fromJson(Map 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? 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 json) => _$MyRecipeModelFromJson(json);
+}
diff --git a/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart
new file mode 100644
index 00000000..df454594
--- /dev/null
+++ b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart
@@ -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 {
+ 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 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 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 archivedRecipes = myRecipes.where((recipe) => recipe.state == archived).toList();
+ emit(MyRecipesState.loaded(recipes: archivedRecipes, selectedState: archived));
+ } catch (e) {
+ emit(const MyRecipesState.error());
+ }
+ }
+
+ Future getRecipeById(String slug) async {
+ return await aboutRecipeRepository.getRecipe(slug);
+ }
+}
diff --git a/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart
new file mode 100644
index 00000000..39a6fe2c
--- /dev/null
+++ b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart
@@ -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 recipes,
+ required String selectedState,
+ }) = MyRecipesStateLoaded;
+
+ const factory MyRecipesState.error() = MyRecipesStateError;
+}
diff --git a/lib/features/recipe/presentation/about_recipe_screen.dart b/lib/features/recipe/presentation/pages/about_recipe_screen.dart
similarity index 100%
rename from lib/features/recipe/presentation/about_recipe_screen.dart
rename to lib/features/recipe/presentation/pages/about_recipe_screen.dart
diff --git a/lib/features/recipe/presentation/pages/my_recipes_screen.dart b/lib/features/recipe/presentation/pages/my_recipes_screen.dart
new file mode 100644
index 00000000..c3e29db5
--- /dev/null
+++ b/lib/features/recipe/presentation/pages/my_recipes_screen.dart
@@ -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(
+ create: (BuildContext context) => sl()..getMyRecipes(),
+ child: BlocBuilder(
+ 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();
+ },
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart
new file mode 100644
index 00000000..1deb983f
--- /dev/null
+++ b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart
@@ -0,0 +1,242 @@
+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/create_recipe/presentation/pages/recipe_create_screen.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 createState() => _MyRecipeWidgetState();
+}
+
+class _MyRecipeWidgetState extends State {
+ 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().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 {
+ GoRouter.of(context).pushNamed(
+ RecipeCreateScreen.name,
+ extra: await context.read().getRecipeById(widget.recipe.slug),
+ );
+ },
+ text: s.edit,
+ height: 52,
+ buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
+ buttonStyle: ButtonStyle(
+ shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
+ backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
+ side: const WidgetStatePropertyAll(BorderSide.none),
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ Expanded(
+ child: CustomOutlinedButton(
+ onPressed: () async {
+ final result = await sl().deleteRecipe(widget.recipe.slug);
+ if (!result) {
+ showErrorToast('Не удалось удалить рецепт');
+ return;
+ }
+ showSuccessToast('Рецепт удален');
+ if (context.mounted) context.read().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,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/features/recipe/presentation/widgets/my_recipes_tab.dart b/lib/features/recipe/presentation/widgets/my_recipes_tab.dart
new file mode 100644
index 00000000..f6de8669
--- /dev/null
+++ b/lib/features/recipe/presentation/widgets/my_recipes_tab.dart
@@ -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 createState() => _MyRecipesTabState();
+}
+
+class _MyRecipesTabState extends State {
+ final List 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().changeSelectedStateIndex(index);
+ },
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/features/recipe/repositories/my_recipes_repository.dart b/lib/features/recipe/repositories/my_recipes_repository.dart
new file mode 100644
index 00000000..73dc105c
--- /dev/null
+++ b/lib/features/recipe/repositories/my_recipes_repository.dart
@@ -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> getMyRecipes() async {
+ try {
+ return (await myRecipesService.getMyRecipes()).data;
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/features/recipe/services/my_recipes_service.dart b/lib/features/recipe/services/my_recipes_service.dart
new file mode 100644
index 00000000..7500edcb
--- /dev/null
+++ b/lib/features/recipe/services/my_recipes_service.dart
@@ -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 getMyRecipes();
+}
diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart
index 7b371f22..5956f579 100644
--- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart
+++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart
@@ -59,6 +59,7 @@ class RegistrationCubit extends Cubit {
);
await TokenStorage.saveTokenInStorage(token);
AppRouter.navigatorKey.currentContext?.go('/feed-screen');
+ AppRouter.navigatorKey.currentContext?.goNamed('verifyEmailScreen');
} catch (e) {
log(e.toString());
}
diff --git a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart
new file mode 100644
index 00000000..864951ae
--- /dev/null
+++ b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart
@@ -0,0 +1,35 @@
+import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
+import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class VerifyEmailCubit extends Cubit {
+ final ProfileRepository profileRepository;
+
+ VerifyEmailCubit({
+ required this.profileRepository,
+ }) : super(const VerifyEmailState.loading());
+
+ sendEmailCode() async {
+ try {
+ final profile = await profileRepository.getMyProfile();
+ await profileRepository.sendEmailCode();
+ emit(VerifyEmailState.loaded(email: profile.email));
+ } catch (e) {
+ emit(const VerifyEmailState.error(errorMessage: 'Ошибка отправки кода.\nПовторите попытку позже'));
+ }
+ }
+
+ tryVerify(String code) async {
+ try {
+ emit(const VerifyEmailState.verifyLoading());
+ final result = await profileRepository.tryVerifyEmailByCode(code);
+ if (result == true) {
+ emit(const VerifyEmailState.verifySuccess());
+ } else {
+ emit(const VerifyEmailState.verifyError());
+ }
+ } catch (e) {
+ emit(const VerifyEmailState.verifyError());
+ }
+ }
+}
diff --git a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart
new file mode 100644
index 00000000..718b2cce
--- /dev/null
+++ b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart
@@ -0,0 +1,24 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'verify_email_state.freezed.dart';
+
+@freezed
+abstract class VerifyEmailState with _$VerifyEmailState {
+ const factory VerifyEmailState.loading() = VerifyEmailStateLoading;
+
+ const factory VerifyEmailState.loaded({
+ required String email,
+ }) = VerifyEmailStateLoaded;
+
+ const factory VerifyEmailState.error({
+ required String errorMessage,
+ }) = VerifyEmailStateError;
+
+ const factory VerifyEmailState() = _VerifyEmailState;
+
+ const factory VerifyEmailState.verifyLoading() = VerifyEmailStateVerifyLoading;
+
+ const factory VerifyEmailState.verifySuccess() = VerifyEmailStateVerifySuccess;
+
+ const factory VerifyEmailState.verifyError() = VerifyEmailStateVerifyError;
+}
diff --git a/lib/features/registration/presentation/pages/verify_email_screen.dart b/lib/features/registration/presentation/pages/verify_email_screen.dart
new file mode 100644
index 00000000..75f38712
--- /dev/null
+++ b/lib/features/registration/presentation/pages/verify_email_screen.dart
@@ -0,0 +1,117 @@
+import 'package:eatvio_app/core/locator_service.dart';
+import 'package:eatvio_app/core/utils/toasts.dart';
+import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
+import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+
+class VerifyEmailScreen extends StatefulWidget {
+ const VerifyEmailScreen({super.key});
+
+ @override
+ State createState() => _VerifyEmailScreenState();
+}
+
+class _VerifyEmailScreenState extends State {
+ final List _focusNodes = List.generate(4, (_) => FocusNode());
+ final List _controllers = List.generate(4, (_) => TextEditingController());
+ String email = '';
+
+ @override
+ void dispose() {
+ for (var controller in _controllers) {
+ controller.dispose();
+ }
+ for (var node in _focusNodes) {
+ node.dispose();
+ }
+ super.dispose();
+ }
+
+ void _checkAndSubmitCode(BuildContext context) {
+ final code = _controllers.map((c) => c.text).join();
+ if (code.length == 4) {
+ context.read().tryVerify(code);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocProvider(
+ create: (_) => sl()..sendEmailCode(),
+ child: BlocConsumer(
+ listener: (context, state) {
+ if (state is VerifyEmailStateLoaded) {
+ setState(() {
+ email = state.email;
+ });
+ } else if (state is VerifyEmailStateVerifySuccess) {
+ showSuccessToast('Почта успешно подтверждена');
+ GoRouter.of(context).pop();
+ } else if (state is VerifyEmailStateVerifyError) {
+ showErrorToast("Неверный код. Попробуйте снова.");
+ for (var controller in _controllers) {
+ controller.clear();
+ }
+ FocusScope.of(context).requestFocus(_focusNodes[0]);
+ }
+ },
+ builder: (context, state) {
+ return Scaffold(
+ appBar: AppBar(title: const Text("Подтвердите почту")),
+ body: Padding(
+ padding: const EdgeInsets.all(24.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text("Код отправлен на почту\n$email", textAlign: TextAlign.center),
+ const SizedBox(height: 20),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: List.generate(4, (index) => _buildCodeField(context, index)),
+ ),
+ const SizedBox(height: 20),
+ const Text("Запросить код повторно", style: TextStyle(color: Colors.grey)),
+ TextButton(
+ onPressed: () {
+ context.read().sendEmailCode();
+ },
+ child: const Text("Не приходит код?"),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ Widget _buildCodeField(BuildContext context, int index) {
+ return SizedBox(
+ width: 50,
+ child: TextField(
+ controller: _controllers[index],
+ focusNode: _focusNodes[index],
+ keyboardType: TextInputType.number,
+ textAlign: TextAlign.center,
+ maxLength: 1,
+ style: const TextStyle(fontSize: 24),
+ decoration: InputDecoration(
+ counterText: "",
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
+ ),
+ onChanged: (value) {
+ if (value.isNotEmpty && index < 3) {
+ FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
+ }
+ if (value.isEmpty && index > 0) {
+ FocusScope.of(context).requestFocus(_focusNodes[index - 1]);
+ }
+ _checkAndSubmitCode(context);
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/features/registration/presentation/pages/verify_screen.dart b/lib/features/registration/presentation/pages/verify_screen.dart
index 69fa009c..7f8aede5 100644
--- a/lib/features/registration/presentation/pages/verify_screen.dart
+++ b/lib/features/registration/presentation/pages/verify_screen.dart
@@ -1,5 +1,8 @@
import 'package:eatvio_app/core/const.dart';
+import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:lottie/lottie.dart';
import 'package:webview_flutter/webview_flutter.dart';
class VerifyScreen extends StatefulWidget {
@@ -22,6 +25,80 @@ class _VerifyScreenState extends State {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
+ ..setNavigationDelegate(
+ NavigationDelegate(
+ onNavigationRequest: (NavigationRequest request) async {
+ if (request.url.contains('user_detailz') && !request.url.contains('change-phone-verify') || request.url.contains('email/verify')) {
+ GoRouter.of(context).pop();
+ 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(
+ fontSize: 20,
+ height: 1,
+ 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();
+ },
+ 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,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ return NavigationDecision.prevent;
+ }
+ return NavigationDecision.navigate;
+ },
+ ),
+ )
..loadRequest(Uri.parse('$baseUrl/login_with_pass_key?id=${widget.passKey}'));
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 5400a274..9821d31a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -47,7 +47,6 @@ dependencies:
file_picker: ^10.1.9
audioplayers: ^6.4.0
intl: ^0.20.2
- rive: ^0.13.20
mask_text_input_formatter: ^2.9.0
lottie: ^3.3.1
flutter_portal: ^1.1.4
@@ -71,9 +70,6 @@ dependencies:
version: ^3.0.2
package_info_plus: ^8.3.0
-dependency_overrides:
- intl: ^0.19.0
-
dev_dependencies:
build_runner: ^2.4.15
freezed: ^3.0.6
@@ -83,6 +79,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
+
flutter_lints: ^5.0.0
flutter:
@@ -93,7 +90,6 @@ flutter:
- assets/audio/
- assets/icons/
- assets/splash/
- - assets/rive_animations/
- assets/lottie_animations/
flutter_intl: