feat(auth): implement OAuth verification request and confirmation flow with error handling and UI updates

This commit is contained in:
mso7nueh
2025-12-17 15:41:38 +03:00
parent 91bd8f9d68
commit 2ab3bc1cf0
49 changed files with 601 additions and 39 deletions

View File

@@ -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 = 87;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -539,7 +539,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 87;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -578,7 +578,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 87;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (

View File

@@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>87</string>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -0,0 +1,27 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'oauth_verification_confirm_request.freezed.dart';
part 'oauth_verification_confirm_request.g.dart';
@freezed
abstract class OAuthVerificationConfirmRequest with _$OAuthVerificationConfirmRequest {
const factory OAuthVerificationConfirmRequest({
@JsonKey(name: 'access_token') required String accessToken,
@JsonKey(name: 'refresh_token') required String refreshToken,
@JsonKey(name: 'client_id') required String clientId,
required String provider,
required String id,
String? email,
String? phone,
required String code,
@JsonKey(name: 'verification_type') required String verificationType,
@JsonKey(name: 'first_name') required String firstName,
@JsonKey(name: 'last_name') required String lastName,
@JsonKey(name: 'date_of_birth') required String dateOfBirth,
required String gender,
required int weight,
required int height,
}) = _OAuthVerificationConfirmRequest;
factory OAuthVerificationConfirmRequest.fromJson(Map<String, dynamic> json) => _$OAuthVerificationConfirmRequestFromJson(json);
}

View File

@@ -0,0 +1,18 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'oauth_verification_confirm_response.freezed.dart';
part 'oauth_verification_confirm_response.g.dart';
@freezed
abstract class OAuthVerificationConfirmResponse with _$OAuthVerificationConfirmResponse {
const factory OAuthVerificationConfirmResponse({
required bool success,
required String message,
required String token,
required Map<String, dynamic> user,
@JsonKey(name: 'is_new_user') required bool isNewUser,
}) = _OAuthVerificationConfirmResponse;
factory OAuthVerificationConfirmResponse.fromJson(Map<String, dynamic> json) => _$OAuthVerificationConfirmResponseFromJson(json);
}

View File

@@ -0,0 +1,12 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'oauth_verification_request.freezed.dart';
part 'oauth_verification_request.g.dart';
@freezed
abstract class OAuthVerificationRequest with _$OAuthVerificationRequest {
const factory OAuthVerificationRequest({String? email, String? phone}) = _OAuthVerificationRequest;
factory OAuthVerificationRequest.fromJson(Map<String, dynamic> json) => _$OAuthVerificationRequestFromJson(json);
}

View File

@@ -0,0 +1,12 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'oauth_verification_request_response.freezed.dart';
part 'oauth_verification_request_response.g.dart';
@freezed
abstract class OAuthVerificationRequestResponse with _$OAuthVerificationRequestResponse {
const factory OAuthVerificationRequestResponse({required String email, required String status}) = _OAuthVerificationRequestResponse;
factory OAuthVerificationRequestResponse.fromJson(Map<String, dynamic> json) => _$OAuthVerificationRequestResponseFromJson(json);
}

View File

@@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
import 'package:eatvio_app/core/mixins/registration_date_cache_mixin.dart';
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
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';
@@ -28,6 +29,19 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
log('OAuthCubit: Got token model: ${tokenModel.token}');
TokenStorage.saveTokenInStorage(tokenModel.token);
emit(OAuthState.loaded(token: tokenModel.token));
} on DioException catch (e) {
log('OAuthCubit: Error occurred: $e');
// Проверяем, является ли ошибка ошибкой валидации email/phone
final errors = e.response?.data['errors'] as Map<String, dynamic>?;
final emailErrors = errors?['email'] as List<dynamic>?;
final phoneErrors = errors?['phone'] as List<dynamic>?;
if ((emailErrors != null && emailErrors.isNotEmpty) || (phoneErrors != null && phoneErrors.isNotEmpty)) {
// Это ошибка валидации - нужно показать экран подтверждения
emit(const OAuthState.error(errorMessage: 'validation_error'));
} else {
emit(OAuthState.error(errorMessage: e.toString()));
}
} catch (e) {
log('OAuthCubit: Error occurred: $e');
emit(OAuthState.error(errorMessage: e.toString()));
@@ -77,16 +91,22 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
final dateOfBirth = errors?['date_of_birth'] as List<dynamic>?;
final errorsList = <RegistrationValidationErrors>[];
if (emailErrors != null && emailErrors.isNotEmpty) {
if (emailErrors.contains('Некорректный формат email')) {
errorsList.add(RegistrationValidationErrors.incorrectEmailFormat);
}
if (emailErrors.contains('Такой Эл. почта уже существует.')) {
errorsList.add(RegistrationValidationErrors.emailExist);
for (final error in emailErrors) {
final errorStr = error.toString();
if (errorStr.contains('Некорректный формат email')) {
errorsList.add(RegistrationValidationErrors.incorrectEmailFormat);
}
if (errorStr.contains('почта уже существует') || errorStr.contains('Эл. почта уже существует')) {
errorsList.add(RegistrationValidationErrors.emailExist);
}
}
}
if (phoneErrors != null && phoneErrors.isNotEmpty) {
if (phoneErrors.contains('Такой phone уже существует.')) {
errorsList.add(RegistrationValidationErrors.phoneExist);
for (final error in phoneErrors) {
final errorStr = error.toString();
if (errorStr.contains('phone уже существует') || errorStr.contains('телефон уже существует')) {
errorsList.add(RegistrationValidationErrors.phoneExist);
}
}
}
if (dateOfBirth != null && dateOfBirth.isNotEmpty) {
@@ -101,6 +121,69 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
return null;
}
Future<OAuthVerificationRequestResponse?> requestOAuthVerification({String? email, String? phone}) async {
try {
emit(const OAuthState.loading());
log('OAuthCubit: Requesting verification code for email: $email, phone: $phone');
final response = await authRepository.requestOAuthVerification(email: email, phone: phone);
log('OAuthCubit: Verification code requested successfully, masked email: ${response.email}');
// Сбрасываем состояние после успешного запроса кода
emit(const OAuthState());
return response;
} catch (e) {
log('OAuthCubit: Error requesting verification code: $e');
emit(OAuthState.error(errorMessage: e.toString()));
return null;
}
}
Future<void> confirmOAuthVerification({
required String provider,
required String accessToken,
required String refreshToken,
required String clientId,
required String id,
String? email,
String? phone,
required String code,
required String verificationType,
required String firstName,
required String lastName,
required String dateOfBirth,
required String gender,
required int weight,
required int height,
}) async {
try {
emit(const OAuthState.loading());
log('OAuthCubit: Confirming OAuth verification - provider: $provider, verificationType: $verificationType, code: $code');
final response = await authRepository.confirmOAuthVerification(
provider: provider,
accessToken: accessToken,
refreshToken: refreshToken,
clientId: clientId,
id: id,
email: email,
phone: phone,
code: code,
verificationType: verificationType,
firstName: firstName,
lastName: lastName,
dateOfBirth: dateOfBirth,
gender: gender,
weight: weight,
height: height,
);
log('OAuthCubit: OAuth verification confirmed successfully, token received');
TokenStorage.saveTokenInStorage(response.token);
emit(OAuthState.loaded(token: response.token));
} catch (e) {
log('OAuthCubit: Error confirming OAuth verification: $e');
emit(OAuthState.error(errorMessage: e.toString()));
rethrow; // Пробрасываем ошибку дальше, чтобы UI мог её обработать
}
}
}
enum RegistrationValidationErrors { incorrectEmailFormat, emailExist, phoneExist, incorrectDateOfBirthFormat }

View File

@@ -1,3 +1,4 @@
import 'dart:developer';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -49,6 +50,8 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
bool weightError = false;
bool heightError = false;
bool incorrectEmailFormatError = false;
bool showVerificationButton = false;
String? verificationType; // 'email' or 'phone'
// Состояние для выбора пола
bool isMaleSelected = true;
@@ -221,7 +224,28 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
hasError: emailExistError || incorrectEmailFormatError,
onChanged: (newValue) {},
),
if (emailExistError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Такая почта уже существует.')],
if (emailExistError) ...[
const SizedBox(height: 4),
const ValidationErrorWidget(error: 'Такая почта уже существует.'),
if (showVerificationButton && verificationType == 'email') ...[
const SizedBox(height: 12),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => _handleVerificationRequest(context, 'email'),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
height: 52,
decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
child: const Center(
child: Text(
'Да, это мой аккаунт, подтвердить',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white),
),
),
),
),
],
],
if (incorrectEmailFormatError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Некорректный формат email')],
const SizedBox(height: 12),
const Text('Номер телефона'),
@@ -235,7 +259,28 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
hasError: phoneExistError,
onChanged: (newValue) {},
),
if (phoneExistError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Такой номер телефона уже существует.')],
if (phoneExistError) ...[
const SizedBox(height: 4),
const ValidationErrorWidget(error: 'Такой номер телефона уже существует.'),
if (showVerificationButton && verificationType == 'phone') ...[
const SizedBox(height: 12),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => _handleVerificationRequest(context, 'phone'),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
height: 52,
decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
child: const Center(
child: Text(
'Да, это мой аккаунт, подтвердить',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white),
),
),
),
),
],
],
const SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -501,6 +546,10 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
clientId: clientID,
);
if (errors != null) {
bool hasEmailExist = false;
bool hasPhoneExist = false;
// Сначала определяем, какие ошибки присутствуют
for (final error in errors) {
switch (error) {
case RegistrationValidationErrors.incorrectEmailFormat:
@@ -509,14 +558,10 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
});
break;
case RegistrationValidationErrors.emailExist:
setState(() {
emailExistError = true;
});
hasEmailExist = true;
break;
case RegistrationValidationErrors.phoneExist:
setState(() {
phoneExistError = true;
});
hasPhoneExist = true;
break;
case RegistrationValidationErrors.incorrectDateOfBirthFormat:
setState(() {
@@ -525,8 +570,236 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
break;
}
}
// Устанавливаем verificationType согласно правилам:
// - Если оба (email и phone) существуют -> 'email'
// - Если только email -> 'email'
// - Если только phone -> 'phone'
if (hasEmailExist || hasPhoneExist) {
setState(() {
emailExistError = hasEmailExist;
phoneExistError = hasPhoneExist;
showVerificationButton = true;
if (hasEmailExist && hasPhoneExist) {
verificationType = 'email'; // Если оба существуют, используем email
} else if (hasEmailExist) {
verificationType = 'email'; // Только email
} else {
verificationType = 'phone'; // Только phone
}
});
}
// Если есть ошибки, возвращаемся и не показываем toast об успехе
return;
}
// Регистрация успешна только если нет ошибок
showSuccessToast('Регистрация успешна');
}
Future<void> _handleVerificationRequest(BuildContext context, String type) async {
try {
// Используем service locator для доступа к OAuthCubit
final oauthCubit = CubitRegistrations.sl<OAuthCubit>();
// Всегда передаем и email и phone
final String email = emailController.text;
final String phone = phoneController.text.replaceAll(RegExp(r'[^\d+]'), '');
final response = await oauthCubit.requestOAuthVerification(email: email, phone: phone);
if (response != null && context.mounted) {
// Показываем диалог для ввода кода
final displayText = response.email;
_showVerificationCodeDialog(context, type, displayText);
} else {
if (context.mounted) {
showErrorToast('Не удалось отправить код верификации');
}
}
} catch (e) {
if (context.mounted) {
showErrorToast('Ошибка: ${e.toString()}');
}
}
}
void _showVerificationCodeDialog(BuildContext context, String verificationType, String displayText) {
final List<FocusNode> focusNodes = List.generate(4, (_) => FocusNode());
final List<TextEditingController> controllers = List.generate(4, (_) => TextEditingController());
showDialog(
context: context,
barrierDismissible: false,
builder: (dialogContext) => AlertDialog(
title: const Text('Подтверждение аккаунта'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Код отправлен на $displayText'),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(4, (index) {
return SizedBox(
width: 50,
child: TextField(
controller: controllers[index],
focusNode: focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
decoration: const InputDecoration(counterText: '', border: OutlineInputBorder()),
onChanged: (value) {
if (value.isNotEmpty && index < 3) {
focusNodes[index + 1].requestFocus();
}
if (value.isEmpty && index > 0) {
focusNodes[index - 1].requestFocus();
}
// Проверяем, заполнен ли весь код
final code = controllers.map((c) => c.text).join();
if (code.length == 4) {
// Небольшая задержка перед отправкой, чтобы пользователь увидел последний символ
Future.delayed(const Duration(milliseconds: 100), () async {
if (dialogContext.mounted && context.mounted) {
await _handleVerificationConfirm(context, dialogContext, verificationType, code, controllers, focusNodes);
}
});
}
},
),
);
}),
),
],
),
),
actions: [
TextButton(
onPressed: () {
for (var controller in controllers) {
controller.dispose();
}
for (var node in focusNodes) {
node.dispose();
}
Navigator.of(dialogContext).pop();
},
child: const Text('Отмена'),
),
],
),
);
}
Future<void> _handleVerificationConfirm(
BuildContext widgetContext,
BuildContext dialogContext,
String verificationType,
String code,
List<TextEditingController> controllers,
List<FocusNode> focusNodes,
) async {
try {
// Используем service locator для доступа к OAuthCubit, так как контекст из диалога может не иметь доступа к провайдеру
final oauthCubit = CubitRegistrations.sl<OAuthCubit>();
final authResult = widget.authResult;
if (authResult == null) {
showErrorToast('Ошибка: данные авторизации не найдены');
return;
}
// Всегда передаем и email и phone (полный набор данных от провайдера)
// Используем данные из формы, если они есть, иначе из authResult
final String email = emailController.text.isNotEmpty ? emailController.text : (authResult.email ?? '');
final String phone = phoneController.text.isNotEmpty ? phoneController.text.replaceAll(RegExp(r'[^\d+]'), '') : (authResult.phone ?? '');
// Проверяем, что данные есть
if (email.isEmpty && phone.isEmpty) {
log('OAuth Verification Confirm: Both email and phone are empty');
showErrorToast('Ошибка: не указан email или телефон');
return;
}
// Проверяем, что все необходимые данные от провайдера есть
if (authResult.accessToken.isEmpty || authResult.clientId.isEmpty || authResult.userId.isEmpty) {
log('OAuth Verification Confirm: Missing required OAuth data');
showErrorToast('Ошибка: неполные данные авторизации');
return;
}
// Проверяем, что код не пустой
if (code.isEmpty || code.length != 4) {
showErrorToast('Введите полный код');
return;
}
// Получаем данные профиля из формы
final String firstName = firstNameController.text.isNotEmpty ? firstNameController.text : (authResult.firstName ?? '');
final String lastName = lastNameController.text.isNotEmpty ? lastNameController.text : (authResult.lastName ?? '');
final String dateOfBirth = dateOfBirthController.text.isNotEmpty ? dateOfBirthController.text : (authResult.birthDate ?? '');
final String gender = isMaleSelected ? 'male' : 'female';
final int weight = weightController.text.isNotEmpty ? (int.tryParse(weightController.text) ?? 0) : 0;
final int height = heightController.text.isNotEmpty ? (int.tryParse(heightController.text) ?? 0) : 0;
// Проверяем, что все обязательные поля заполнены
if (firstName.isEmpty || lastName.isEmpty || dateOfBirth.isEmpty || weight == 0 || height == 0) {
log('OAuth Verification Confirm: Missing required profile data');
showErrorToast('Ошибка: заполните все обязательные поля');
return;
}
// Логируем перед отправкой запроса
log('OAuth Verification Confirm: provider=${widget.provider}, verificationType=$verificationType, code=$code, email=$email, phone=$phone');
// Отправляем запрос на подтверждение с полным набором данных от провайдера
await oauthCubit.confirmOAuthVerification(
provider: widget.provider ?? 'social',
accessToken: authResult.accessToken,
refreshToken: authResult.refreshToken ?? '',
clientId: authResult.clientId,
id: authResult.userId,
email: email,
phone: phone,
code: code,
verificationType: verificationType,
firstName: firstName,
lastName: lastName,
dateOfBirth: dateOfBirth,
gender: gender,
weight: weight,
height: height,
);
// Закрываем диалог
for (var controller in controllers) {
controller.dispose();
}
for (var node in focusNodes) {
node.dispose();
}
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop();
}
// Перезагружаем веб-сокет и переходим на главный экран
if (widgetContext.mounted) {
_reconnectWebSocket(widgetContext);
}
} catch (e, stackTrace) {
log('OAuth Verification Confirm Error: $e', error: e, stackTrace: stackTrace);
if (widgetContext.mounted) {
showErrorToast('Неверный код. Попробуйте снова.');
}
// Очищаем поля
for (var controller in controllers) {
controller.clear();
}
if (focusNodes.isNotEmpty) {
focusNodes[0].requestFocus();
}
}
}
}

View File

@@ -1,6 +1,11 @@
import 'dart:developer';
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/oauth_verification_confirm_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_response.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
@@ -19,7 +24,6 @@ class AuthRepository {
}
}
Future<TokenModel> signInViaSocialMedia({
required String provider,
required String accessToken,
@@ -73,4 +77,60 @@ class AuthRepository {
rethrow;
}
}
Future<OAuthVerificationRequestResponse> requestOAuthVerification({String? email, String? phone}) async {
try {
return await authService.requestOAuthVerification(
body: OAuthVerificationRequest(email: email, phone: phone),
);
} catch (e) {
rethrow;
}
}
Future<OAuthVerificationConfirmResponse> confirmOAuthVerification({
required String provider,
required String accessToken,
required String refreshToken,
required String clientId,
required String id,
String? email,
String? phone,
required String code,
required String verificationType,
required String firstName,
required String lastName,
required String dateOfBirth,
required String gender,
required int weight,
required int height,
}) async {
try {
final request = OAuthVerificationConfirmRequest(
provider: provider,
accessToken: accessToken,
refreshToken: refreshToken,
clientId: clientId,
id: id,
email: email,
phone: phone,
code: code,
verificationType: verificationType,
firstName: firstName,
lastName: lastName,
dateOfBirth: dateOfBirth,
gender: gender,
weight: weight,
height: height,
);
// Логируем запрос перед отправкой
log(
'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone',
);
return await authService.confirmOAuthVerification(body: request);
} catch (e) {
log('AuthRepository: Error in confirmOAuthVerification: $e');
rethrow;
}
}
}

View File

@@ -2,6 +2,10 @@ 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/oauth_verification_confirm_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_response.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:retrofit/retrofit.dart';
@@ -19,4 +23,10 @@ abstract class AuthService {
@POST('/api/oauth/login')
Future<TokenModel> signInViaSocialMedia({@Body() required OAuthLoginRequest body});
@POST('/api/oauth/verification/request')
Future<OAuthVerificationRequestResponse> requestOAuthVerification({@Body() required OAuthVerificationRequest body});
@POST('/api/oauth/verification/confirm')
Future<OAuthVerificationConfirmResponse> confirmOAuthVerification({@Body() required OAuthVerificationConfirmRequest body});
}

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.3.7+87
version: 1.3.8+88
publish_to: 'none'
environment:

View File

@@ -138,3 +138,5 @@ void main() {

View File

@@ -73,3 +73,5 @@ void main() {

View File

@@ -87,3 +87,5 @@ void main() {

View File

@@ -36,3 +36,5 @@ void main() {

View File

@@ -32,3 +32,5 @@ void main() {

View File

@@ -29,3 +29,5 @@ void main() {

View File

@@ -33,3 +33,5 @@ void main() {

View File

@@ -112,3 +112,5 @@ void main() {

View File

@@ -36,3 +36,5 @@ void main() {

View File

@@ -30,3 +30,5 @@ void main() {

View File

@@ -66,3 +66,5 @@ void main() {

View File

@@ -118,3 +118,5 @@ void main() {

View File

@@ -55,3 +55,5 @@ void main() {

View File

@@ -137,3 +137,5 @@ void main() {

View File

@@ -98,3 +98,5 @@ void main() {

View File

@@ -166,3 +166,5 @@ void main() {

View File

@@ -59,3 +59,5 @@ void main() {

View File

@@ -44,3 +44,5 @@ void main() {

View File

@@ -98,3 +98,5 @@ void main() {

View File

@@ -78,3 +78,5 @@ void main() {

View File

@@ -67,3 +67,5 @@ void main() {

View File

@@ -9,6 +9,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../fixtures/doubles/create_diet_doubles.dart';
import '../../fixtures/doubles/profile_doubles.dart';
void main() {
group('Diet Creation Basic Widget Tests', () {

View File

@@ -73,3 +73,5 @@ void main() {

View File

@@ -60,3 +60,5 @@ void main() {

View File

@@ -92,3 +92,5 @@ void main() {

View File

@@ -110,3 +110,5 @@ void main() {

View File

@@ -140,3 +140,5 @@ void main() {

View File

@@ -52,3 +52,5 @@ void main() {

View File

@@ -53,3 +53,5 @@ void main() {

View File

@@ -62,3 +62,5 @@ void main() {
}

View File

@@ -1,6 +1,10 @@
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
import 'package:eatvio_app/features/auth/models/oauth_login_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_model.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_response.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_request.dart';
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
@@ -62,5 +66,15 @@ class _NoopAuthService implements AuthService {
Future<Map<String, Object?>> signUpViaSocialMedia({required OAuthModel body}) {
throw UnimplementedError('Not implemented in fixture.');
}
@override
Future<OAuthVerificationRequestResponse> requestOAuthVerification({required OAuthVerificationRequest body}) {
throw UnimplementedError('Not implemented in fixture.');
}
@override
Future<OAuthVerificationConfirmResponse> confirmOAuthVerification({required OAuthVerificationConfirmRequest body}) {
throw UnimplementedError('Not implemented in fixture.');
}
}

View File

@@ -7,9 +7,6 @@ import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/profile/services/profile_service.dart';
import 'package:retrofit/dio.dart' show HttpResponse;
import '../../fixtures/fixture_reader.dart';
@@ -133,16 +130,6 @@ class FixtureCreateDietRepository extends CreateDietRepository {
}
}
class FixtureProfileRepository extends ProfileRepository {
FixtureProfileRepository() : super(profileService: _NoopProfileService());
@override
Future<ProfileModel> getMyProfile() async {
final profileFixture = FixtureReader.readAsMap('profile/my_profile_response.json');
return ProfileModel.fromJson(profileFixture);
}
}
class FixtureGlossaryRepository extends GlossaryRepository {
FixtureGlossaryRepository() : super(glossaryService: _NoopGlossaryService());
@@ -190,11 +177,6 @@ class _NoopCreateDietService implements CreateDietService {
}
}
class _NoopProfileService implements ProfileService {
@override
noSuchMethod(Invocation invocation) => throw UnimplementedError('ProfileService call ${invocation.memberName}');
}
class _NoopGlossaryService implements GlossaryService {
@override
noSuchMethod(Invocation invocation) => throw UnimplementedError('GlossaryService call ${invocation.memberName}');