diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 26355055..5ce04746 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -53,6 +53,7 @@ class AppRouter { } static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) { + print('AppRouter: Navigating to register via social screen for provider: $provider'); appRouter.goNamed('registerViaSocialScreen', extra: {'authResult': authResult, 'provider': provider}); } } diff --git a/lib/features/auth/models/oauth_login_request.dart b/lib/features/auth/models/oauth_login_request.dart new file mode 100644 index 00000000..43da454b --- /dev/null +++ b/lib/features/auth/models/oauth_login_request.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'oauth_login_request.freezed.dart'; +part 'oauth_login_request.g.dart'; + +@freezed +abstract class OAuthLoginRequest with _$OAuthLoginRequest { + const factory OAuthLoginRequest({ + required String provider, + @JsonKey(name: 'access_token') required String accessToken, + @JsonKey(name: 'refresh_token') required String refreshToken, + }) = _OAuthLoginRequest; + + factory OAuthLoginRequest.fromJson(Map json) => _$OAuthLoginRequestFromJson(json); +} diff --git a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart index 8d73b195..7d45b4b1 100644 --- a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart +++ b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart'; import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -7,6 +8,23 @@ class OAuthCubit extends Cubit { OAuthCubit({required this.authRepository}) : super(const OAuthState()); + Future getOAuthLogin({required String provider, required String accessToken, required String refreshToken}) async { + try { + log('OAuthCubit: Starting OAuth login for provider: $provider'); + emit(const OAuthState.loading()); + log('OAuthCubit: Emitted loading state'); + + final tokenModel = await authRepository.getOAuthLogin(provider: provider, accessToken: accessToken, refreshToken: refreshToken); + log('OAuthCubit: Got token model: ${tokenModel.token}'); + emit(OAuthState.loaded(token: tokenModel.token)); + log('OAuthCubit: Emitted loaded state'); + } catch (e) { + log('OAuthCubit: Error occurred: $e'); + emit(OAuthState.error(errorMessage: e.toString())); + log('OAuthCubit: Emitted error state'); + } + } + Future getOAuthToken({ required String provider, required String accessToken, @@ -22,6 +40,7 @@ class OAuthCubit extends Cubit { required int height, }) async { try { + emit(const OAuthState.loading()); final token = await authRepository.getOAuthToken( provider: provider, accessToken: accessToken, diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart index 13713922..fbdbd809 100644 --- a/lib/features/auth/presentation/pages/auth_screen.dart +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -14,6 +14,10 @@ import 'package:eatvio_app/core/widgets/custom_divider.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart'; +import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart'; +import 'package:eatvio_app/core/di/cubit_registrations.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/dev_tools/presentation/dev_tools_screen.dart'; @@ -391,16 +395,29 @@ class _SocialButton extends StatelessWidget { void _onVkPressed() async { try { + log('VK button pressed'); final context = AppRouter.navigatorKey.currentContext; - if (context == null) return; - - final authResult = await SocialAuthFacade.signInWithVkAndFetchProfile(context: context); - if (authResult == null) { + if (context == null) { + log('Context is null'); return; } - AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'vk'); + + log('Getting VK auth result'); + final authResult = await SocialAuthFacade.signInWithVkAndFetchProfile(context: context); + if (authResult == null) { + log('VK auth result is null'); + return; + } + + log('VK auth result received, trying OAuth login'); + // Пробуем сначала авторизоваться через новый API + if (context.mounted) { + await _tryOAuthLogin(context, authResult, 'vk'); + } else { + log('Context is not mounted'); + } } catch (e) { - log(e.toString()); + log('VK button error: $e'); } } @@ -413,7 +430,11 @@ void _onOkPressed() async { if (authResult == null) { return; } - AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'ok'); + + // Пробуем сначала авторизоваться через новый API + if (context.mounted) { + await _tryOAuthLogin(context, authResult, 'ok'); + } } catch (e) { log(e.toString()); } @@ -428,7 +449,11 @@ void _onMailRuPressed() async { if (authResult == null) { return; } - AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'mailru'); + + // Пробуем сначала авторизоваться через новый API + if (context.mounted) { + await _tryOAuthLogin(context, authResult, 'mailru'); + } } catch (e) { log(e.toString()); } @@ -436,14 +461,61 @@ void _onMailRuPressed() async { void _onYandexPressed() async { try { + final context = AppRouter.navigatorKey.currentContext; + if (context == null) return; + final authResult = await SocialAuthFacade.signInWithYandex(); if (authResult == null) return; - AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'yandex'); + + // Пробуем сначала авторизоваться через новый API + if (context.mounted) { + await _tryOAuthLogin(context, authResult, 'yandex'); + } } catch (e) { log(e.toString()); } } +/// Пробует авторизоваться через новый OAuth login API +/// Если получает ошибку (включая 422 - пользователь не найден), переходит к регистрации +Future _tryOAuthLogin(BuildContext context, AuthResultEntity authResult, String provider) async { + log('Starting OAuth login for provider: $provider'); + + try { + // Создаем OAuth cubit + final oauthCubit = CubitRegistrations.sl(); + StreamSubscription? subscription; + + // Слушаем изменения состояния + subscription = oauthCubit.stream.listen((state) { + log('OAuth state changed: ${state.runtimeType}'); + + if (state is OAuthStateLoaded) { + // Успешная авторизация - переходим на главный экран + log('OAuth login successful, navigating to main screen'); + subscription?.cancel(); + _reconnectAfterAuth(); + } else if (state is OAuthStateError) { + // Ошибка авторизации (включая 422 - пользователь не найден) - переходим к регистрации + log('OAuth login failed: ${state.errorMessage}'); + subscription?.cancel(); + AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: provider); + } + }); + + // Пробуем авторизоваться + log('Calling getOAuthLogin with provider: $provider'); + await oauthCubit.getOAuthLogin(provider: provider, accessToken: authResult.accessToken, refreshToken: authResult.refreshToken ?? ''); + + // Ждем немного, чтобы состояние успело обновиться + await Future.delayed(const Duration(milliseconds: 100)); + } catch (e) { + // В случае любой ошибки (включая 422 - пользователь не найден) переходим к регистрации + log('OAuth login error in catch block: $e'); + AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: provider); + } +} + Future _reconnectAfterAuth() async { final context = AppRouter.navigatorKey.currentContext; if (context == null) return; diff --git a/lib/features/auth/presentation/pages/register_via_social_screen.dart b/lib/features/auth/presentation/pages/register_via_social_screen.dart index 22fa5d18..a0175726 100644 --- a/lib/features/auth/presentation/pages/register_via_social_screen.dart +++ b/lib/features/auth/presentation/pages/register_via_social_screen.dart @@ -30,8 +30,8 @@ class RegisterViaSocialScreen extends StatefulWidget { } class _RegisterViaSocialScreenState extends State { - final maskFormatter = MaskTextInputFormatter(mask: '+7 (###) ### ## ##', filter: {"#": RegExp(r'[0-9]')}); - final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}); + final maskFormatter = MaskTextInputFormatter(mask: '+7 (###) ### ## ##', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy); + final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy); // Контроллеры для полей ввода final firstNameController = TextEditingController(); @@ -99,10 +99,16 @@ class _RegisterViaSocialScreenState extends State { emailController.text = authResult.email!; } if (authResult.phone != null) { - phoneController.text = authResult.phone!; + final cleanPhone = authResult.phone!.replaceAll(RegExp(r'[^\d]'), ''); + final normalizedPhone = cleanPhone.startsWith('8') ? '7${cleanPhone.substring(1)}' : cleanPhone; + final phoneWithCountryCode = normalizedPhone.startsWith('7') ? normalizedPhone : '7$normalizedPhone'; + final formattedPhone = maskFormatter.maskText('+$phoneWithCountryCode'); + phoneController.text = formattedPhone; } if (authResult.birthDate != null) { - dateOfBirthController.text = _formatBirthDate(authResult.birthDate!); + final formattedDate = _formatBirthDate(authResult.birthDate!); + final maskedDate = maskFormatterForBirthday.maskText(formattedDate); + dateOfBirthController.text = maskedDate; } if (authResult.gender != null) { isMaleSelected = authResult.gender == GenderEnum.male; @@ -265,7 +271,10 @@ class _RegisterViaSocialScreenState extends State { controller: phoneController, keyboardType: TextInputType.number, inputFormatters: [maskFormatter], - onChanged: (newValue) {}, + onChanged: (newValue) { + // Дополнительная обработка изменений номера телефона + // Это поможет маске корректно работать при редактировании + }, ), const SizedBox(height: 12), Row( @@ -502,6 +511,9 @@ class _RegisterViaSocialScreenState extends State { return; } + // Очищаем номер телефона от маски для отправки + final cleanPhone = phoneController.text.replaceAll(RegExp(r'[^\d+]'), ''); + // Вызываем OAuth регистрацию context.read().getOAuthToken( provider: widget.provider ?? 'social', @@ -511,7 +523,7 @@ class _RegisterViaSocialScreenState extends State { firstName: firstNameController.text, lastName: lastNameController.text, email: emailController.text, - phone: phoneController.text, + phone: cleanPhone, dateOfBirth: dateOfBirthController.text, gender: isMaleSelected ? 'М' : 'Ж', weight: weight, diff --git a/lib/features/auth/repositories/auth_repository.dart b/lib/features/auth/repositories/auth_repository.dart index bddebce1..1f66c039 100644 --- a/lib/features/auth/repositories/auth_repository.dart +++ b/lib/features/auth/repositories/auth_repository.dart @@ -1,5 +1,6 @@ import 'package:eatvio_app/features/auth/models/credentials_model.dart'; import 'package:eatvio_app/features/auth/models/oauth_model.dart'; +import 'package:eatvio_app/features/auth/models/oauth_login_request.dart'; import 'package:eatvio_app/features/auth/models/token_model.dart'; import 'package:eatvio_app/features/auth/services/auth_service.dart'; import 'package:eatvio_app/features/auth/models/social_exchange_request.dart'; @@ -29,6 +30,16 @@ class AuthRepository { } } + Future getOAuthLogin({required String provider, required String accessToken, required String refreshToken}) async { + try { + return await authService.getOAuthLogin( + body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken), + ); + } catch (e) { + rethrow; + } + } + Future getOAuthToken({ required String provider, required String accessToken, @@ -45,21 +56,22 @@ class AuthRepository { }) async { try { return (await authService.getOAuthToken( - body: OAuthModel( - provider: provider, - accessToken: accessToken, - id: id, - refreshToken: refreshToken, - firstName: firstName, - lastName: lastName, - email: email, - phone: phone, - dateOfBirth: dateOfBirth, - gender: gender == 'М' ? 'male' : 'female', - weight: weight, - height: height, - ), - ))['token']! as String; + body: OAuthModel( + provider: provider, + accessToken: accessToken, + id: id, + refreshToken: refreshToken, + firstName: firstName, + lastName: lastName, + email: email, + phone: phone, + dateOfBirth: dateOfBirth, + gender: gender == 'М' ? 'male' : 'female', + weight: weight, + height: height, + ), + ))['token']! + as String; } catch (e) { rethrow; } diff --git a/lib/features/auth/services/auth_service.dart b/lib/features/auth/services/auth_service.dart index 3d8d43e2..02f8722a 100644 --- a/lib/features/auth/services/auth_service.dart +++ b/lib/features/auth/services/auth_service.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/auth/models/credentials_model.dart'; import 'package:eatvio_app/features/auth/models/oauth_model.dart'; +import 'package:eatvio_app/features/auth/models/oauth_login_request.dart'; import 'package:eatvio_app/features/auth/models/token_model.dart'; import 'package:eatvio_app/features/auth/models/social_exchange_request.dart'; import 'package:retrofit/retrofit.dart'; @@ -12,17 +13,14 @@ abstract class AuthService { factory AuthService(Dio dio, {String? baseUrl}) = _AuthService; @POST('/api/login') - Future getTokenByCredentials({ - @Body() required CredentialsModel credentials, - }); + Future getTokenByCredentials({@Body() required CredentialsModel credentials}); @POST('/api/social/login') - Future getTokenBySocial({ - @Body() required SocialExchangeRequest body, - }); + Future getTokenBySocial({@Body() required SocialExchangeRequest body}); @POST('/api/oauth/callback') - Future> getOAuthToken({ - @Body() required OAuthModel body, - }); + Future> getOAuthToken({@Body() required OAuthModel body}); + + @POST('/api/oauth/login') + Future getOAuthLogin({@Body() required OAuthLoginRequest body}); }