Merge remote-tracking branch 'upstream/main'

This commit is contained in:
misha
2025-12-25 16:36:23 +03:00
41 changed files with 3705 additions and 3478 deletions

View File

@@ -1,29 +1,33 @@
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);
}
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

@@ -1,23 +1,27 @@
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);
}
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

@@ -1,17 +1,21 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart';
import 'package:retrofit/retrofit.dart';
part 'barcode_service.g.dart';
@RestApi()
abstract class BarcodeService {
factory BarcodeService(Dio dio, {String baseUrl}) = _BarcodeService;
@GET('/api/gsone/barcode/{barcode}')
Future<BarcodeProductModel> getProductByBarcode({@Path('barcode') required String barcode});
@GET('/api/gsone/barcode/status/{task_id}')
Future<BarcodeTaskStatusModel> getTaskStatus({@Path('task_id') required String taskId});
}
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart';
import 'package:retrofit/retrofit.dart';
part 'barcode_service.g.dart';
@RestApi()
abstract class BarcodeService {
factory BarcodeService(Dio dio, {String baseUrl}) = _BarcodeService;
@GET('/api/gsone/barcode/{barcode}')
Future<BarcodeProductModel> getProductByBarcode({@Path('barcode') required String barcode});
@GET('/api/gsone/barcode/status/{task_id}')
Future<BarcodeTaskStatusModel> getTaskStatus({@Path('task_id') required String taskId});
}

View File

@@ -1,176 +1,178 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/presentation/widgets/sex_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
class FirstStepContent extends StatefulWidget {
const FirstStepContent({super.key});
@override
State<FirstStepContent> createState() => _FirstStepContentState();
}
class _FirstStepContentState extends State<FirstStepContent> {
final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy);
final firstNameController = TextEditingController();
final lastNameController = TextEditingController();
final emailController = TextEditingController();
final phoneController = TextEditingController();
final dateController = TextEditingController();
@override
void initState() {
super.initState();
firstNameController.text = context.read<RegistrationCubitV2>().state.firstName;
lastNameController.text = context.read<RegistrationCubitV2>().state.lastName;
emailController.text = context.read<RegistrationCubitV2>().state.email;
phoneController.text = context.read<RegistrationCubitV2>().state.phone;
dateController.text = context.read<RegistrationCubitV2>().state.birthday?.format('dd.MM.yyyy') ?? '';
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocBuilder<RegistrationCubitV2, RegistrationStateV2>(
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.first_name, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
const SizedBox(height: 4),
CustomTextFormField(
hintText: s.your_name,
hasError: !state.isFirstNameValid,
controller: firstNameController,
onChanged: (val) {
context.read<RegistrationCubitV2>().setFirstName(val);
},
),
if (!state.isFirstNameValid) ...[const SizedBox(height: 4), ValidationErrorWidget(error: s.first_name_validation_error)],
],
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.last_name, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
const SizedBox(height: 4),
CustomTextFormField(
hintText: s.your_last_name,
hasError: !state.isLastNameValid,
controller: lastNameController,
onChanged: (val) {
context.read<RegistrationCubitV2>().setLastName(val);
},
),
if (!state.isLastNameValid) ...[const SizedBox(height: 4), ValidationErrorWidget(error: s.last_name_error)],
],
),
),
],
),
const SizedBox(height: 12),
Text(s.email, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
CustomTextFormField(
hintText: s.type_email,
hasError: !state.isEmailValid,
controller: emailController,
onChanged: (val) {
context.read<RegistrationCubitV2>().setEmail(val);
},
),
if (!state.isEmailValid) ...[
const SizedBox(height: 4),
ValidationErrorWidget(error: state.emailDisplayError.isEmpty ? s.email_error : state.emailDisplayError),
],
const SizedBox(height: 12),
Text(s.phone, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
Column(
children: [
CustomTextFormField(
hintText: '+7 (999) 999 99 99',
controller: phoneController,
hasError: !state.isPhoneValid,
onChanged: (val) {
context.read<RegistrationCubitV2>().setPhone(val);
},
),
if (!state.isPhoneValid) ...[
const SizedBox(height: 4),
ValidationErrorWidget(error: state.phoneDisplayError.isEmpty ? s.phone_error : state.phoneDisplayError),
],
],
),
const SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.date_of_birth, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
const SizedBox(height: 4),
CustomTextFormField(
textInputAction: TextInputAction.done,
hintText: '01.01.2000',
keyboardType: TextInputType.number,
inputFormatters: [maskFormatterForBirthday],
hasError: !state.isBirthdayValid,
controller: dateController,
onChanged: (val) {
final dateArray = val.split('.');
if (dateArray.length != 3) return;
final date = DateTime(int.parse(dateArray[2]), int.parse(dateArray[1]), int.parse(dateArray[0]));
context.read<RegistrationCubitV2>().setBirthday(date);
},
),
if (!state.isBirthdayValid) ...[
const SizedBox(height: 4),
ValidationErrorWidget(error: state.dateOfBirthDisplayError.isEmpty ? s.date_of_birth_error : state.dateOfBirthDisplayError),
],
],
),
),
const SizedBox(width: 12),
const Expanded(child: SexWidget()),
],
),
const Spacer(),
CustomOutlinedButton(
onPressed: () {
context.read<RegistrationCubitV2>().nextStep();
},
text: s.next,
height: 52,
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
),
const SizedBox(height: 20),
],
);
},
);
}
}
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/presentation/widgets/sex_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
class FirstStepContent extends StatefulWidget {
const FirstStepContent({super.key});
@override
State<FirstStepContent> createState() => _FirstStepContentState();
}
class _FirstStepContentState extends State<FirstStepContent> {
final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy);
final firstNameController = TextEditingController();
final lastNameController = TextEditingController();
final emailController = TextEditingController();
final phoneController = TextEditingController();
final dateController = TextEditingController();
@override
void initState() {
super.initState();
firstNameController.text = context.read<RegistrationCubitV2>().state.firstName;
lastNameController.text = context.read<RegistrationCubitV2>().state.lastName;
emailController.text = context.read<RegistrationCubitV2>().state.email;
phoneController.text = context.read<RegistrationCubitV2>().state.phone;
dateController.text = context.read<RegistrationCubitV2>().state.birthday?.format('dd.MM.yyyy') ?? '';
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocBuilder<RegistrationCubitV2, RegistrationStateV2>(
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.first_name, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
const SizedBox(height: 4),
CustomTextFormField(
hintText: s.your_name,
hasError: !state.isFirstNameValid,
controller: firstNameController,
onChanged: (val) {
context.read<RegistrationCubitV2>().setFirstName(val);
},
),
if (!state.isFirstNameValid) ...[const SizedBox(height: 4), ValidationErrorWidget(error: s.first_name_validation_error)],
],
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.last_name, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
const SizedBox(height: 4),
CustomTextFormField(
hintText: s.your_last_name,
hasError: !state.isLastNameValid,
controller: lastNameController,
onChanged: (val) {
context.read<RegistrationCubitV2>().setLastName(val);
},
),
if (!state.isLastNameValid) ...[const SizedBox(height: 4), ValidationErrorWidget(error: s.last_name_error)],
],
),
),
],
),
const SizedBox(height: 12),
Text(s.email, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
CustomTextFormField(
hintText: s.type_email,
hasError: !state.isEmailValid,
keyboardType: TextInputType.emailAddress,
controller: emailController,
onChanged: (val) {
context.read<RegistrationCubitV2>().setEmail(val);
},
),
if (!state.isEmailValid) ...[
const SizedBox(height: 4),
ValidationErrorWidget(error: state.emailDisplayError.isEmpty ? s.email_error : state.emailDisplayError),
],
const SizedBox(height: 12),
Text(s.phone, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
Column(
children: [
CustomTextFormField(
hintText: '+7 (999) 999 99 99',
controller: phoneController,
hasError: !state.isPhoneValid,
keyboardType: TextInputType.phone,
onChanged: (val) {
context.read<RegistrationCubitV2>().setPhone(val);
},
),
if (!state.isPhoneValid) ...[
const SizedBox(height: 4),
ValidationErrorWidget(error: state.phoneDisplayError.isEmpty ? s.phone_error : state.phoneDisplayError),
],
],
),
const SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.date_of_birth, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
const SizedBox(height: 4),
CustomTextFormField(
textInputAction: TextInputAction.done,
hintText: '01.01.2000',
keyboardType: TextInputType.number,
inputFormatters: [maskFormatterForBirthday],
hasError: !state.isBirthdayValid,
controller: dateController,
onChanged: (val) {
final dateArray = val.split('.');
if (dateArray.length != 3) return;
final date = DateTime(int.parse(dateArray[2]), int.parse(dateArray[1]), int.parse(dateArray[0]));
context.read<RegistrationCubitV2>().setBirthday(date);
},
),
if (!state.isBirthdayValid) ...[
const SizedBox(height: 4),
ValidationErrorWidget(error: state.dateOfBirthDisplayError.isEmpty ? s.date_of_birth_error : state.dateOfBirthDisplayError),
],
],
),
),
const SizedBox(width: 12),
const Expanded(child: SexWidget()),
],
),
const Spacer(),
CustomOutlinedButton(
onPressed: () {
context.read<RegistrationCubitV2>().nextStep();
},
text: s.next,
height: 52,
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
),
const SizedBox(height: 20),
],
);
},
);
}
}

View File

@@ -33,8 +33,12 @@ class _SecondStepContentState extends State<SecondStepContent> {
@override
void initState() {
super.initState();
heightController.text = context.read<RegistrationCubitV2>().state.height.toString();
weightController.text = context.read<RegistrationCubitV2>().state.weight.toString();
heightController.text = context.read<RegistrationCubitV2>().state.height.toString() == '0'
? ''
: context.read<RegistrationCubitV2>().state.height.toString();
weightController.text = context.read<RegistrationCubitV2>().state.weight.toString() == '0'
? ''
: context.read<RegistrationCubitV2>().state.weight.toString();
}
@override
@@ -61,6 +65,7 @@ class _SecondStepContentState extends State<SecondStepContent> {
hintText: s.type_height,
hasError: !state.isHeightValid,
controller: heightController,
keyboardType: TextInputType.number,
textInputType: const TextInputType.numberWithOptions(),
onChanged: (val) {
final height = int.tryParse(val);
@@ -84,6 +89,7 @@ class _SecondStepContentState extends State<SecondStepContent> {
hintText: s.type_weight,
hasError: !state.isWeightValid,
controller: weightController,
keyboardType: TextInputType.number,
textInputType: const TextInputType.numberWithOptions(),
onChanged: (val) {
final weight = int.tryParse(val);