forked from eatvio/eatvio_app
feat(auth): Issue-691
This commit is contained in:
15
lib/features/auth/models/oauth_login_request.dart
Normal file
15
lib/features/auth/models/oauth_login_request.dart
Normal file
@@ -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<String, dynamic> json) => _$OAuthLoginRequestFromJson(json);
|
||||
}
|
||||
@@ -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<OAuthState> {
|
||||
|
||||
OAuthCubit({required this.authRepository}) : super(const OAuthState());
|
||||
|
||||
Future<void> 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<void> getOAuthToken({
|
||||
required String provider,
|
||||
required String accessToken,
|
||||
@@ -22,6 +40,7 @@ class OAuthCubit extends Cubit<OAuthState> {
|
||||
required int height,
|
||||
}) async {
|
||||
try {
|
||||
emit(const OAuthState.loading());
|
||||
final token = await authRepository.getOAuthToken(
|
||||
provider: provider,
|
||||
accessToken: accessToken,
|
||||
|
||||
@@ -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<void> _tryOAuthLogin(BuildContext context, AuthResultEntity authResult, String provider) async {
|
||||
log('Starting OAuth login for provider: $provider');
|
||||
|
||||
try {
|
||||
// Создаем OAuth cubit
|
||||
final oauthCubit = CubitRegistrations.sl<OAuthCubit>();
|
||||
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<void> _reconnectAfterAuth() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
|
||||
@@ -30,8 +30,8 @@ class RegisterViaSocialScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
||||
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<RegisterViaSocialScreen> {
|
||||
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<RegisterViaSocialScreen> {
|
||||
controller: phoneController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [maskFormatter],
|
||||
onChanged: (newValue) {},
|
||||
onChanged: (newValue) {
|
||||
// Дополнительная обработка изменений номера телефона
|
||||
// Это поможет маске корректно работать при редактировании
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
@@ -502,6 +511,9 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Очищаем номер телефона от маски для отправки
|
||||
final cleanPhone = phoneController.text.replaceAll(RegExp(r'[^\d+]'), '');
|
||||
|
||||
// Вызываем OAuth регистрацию
|
||||
context.read<OAuthCubit>().getOAuthToken(
|
||||
provider: widget.provider ?? 'social',
|
||||
@@ -511,7 +523,7 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
||||
firstName: firstNameController.text,
|
||||
lastName: lastNameController.text,
|
||||
email: emailController.text,
|
||||
phone: phoneController.text,
|
||||
phone: cleanPhone,
|
||||
dateOfBirth: dateOfBirthController.text,
|
||||
gender: isMaleSelected ? 'М' : 'Ж',
|
||||
weight: weight,
|
||||
|
||||
@@ -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<TokenModel> 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<String> 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;
|
||||
}
|
||||
|
||||
@@ -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<TokenModel> getTokenByCredentials({
|
||||
@Body() required CredentialsModel credentials,
|
||||
});
|
||||
Future<TokenModel> getTokenByCredentials({@Body() required CredentialsModel credentials});
|
||||
|
||||
@POST('/api/social/login')
|
||||
Future<TokenModel> getTokenBySocial({
|
||||
@Body() required SocialExchangeRequest body,
|
||||
});
|
||||
Future<TokenModel> getTokenBySocial({@Body() required SocialExchangeRequest body});
|
||||
|
||||
@POST('/api/oauth/callback')
|
||||
Future<Map<String, Object?>> getOAuthToken({
|
||||
@Body() required OAuthModel body,
|
||||
});
|
||||
Future<Map<String, Object?>> getOAuthToken({@Body() required OAuthModel body});
|
||||
|
||||
@POST('/api/oauth/login')
|
||||
Future<TokenModel> getOAuthLogin({@Body() required OAuthLoginRequest body});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user