Initial commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
|
||||
|
||||
abstract class AuthRemoteDataSource {
|
||||
Future<LoginResponse> login(LoginRequest request);
|
||||
|
||||
Future<SignUpResponse> signUp(SignUpRequest request);
|
||||
|
||||
Future<AuthVerificationResponse> verify(AuthVerificationRequest request);
|
||||
|
||||
Future<FcmAddResponse> addFcm(FcmAddRequest request);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
|
||||
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../../../../constants/constants.dart';
|
||||
import '../../../../../core/error/exceptions.dart';
|
||||
import '../../../../../core/local_source/local_source.dart';
|
||||
import '../../../../../injector_container.dart';
|
||||
import 'auth_remote_data_source.dart';
|
||||
|
||||
class AuthRemoteDataSourceImpl extends AuthRemoteDataSource {
|
||||
final Dio dio;
|
||||
|
||||
AuthRemoteDataSourceImpl(this.dio);
|
||||
|
||||
@override
|
||||
Future<LoginResponse> login(LoginRequest request) async {
|
||||
try {
|
||||
final Response response = await dio.post(
|
||||
Constants.baseUrl + Urls.login,
|
||||
options: DioConstants.options,
|
||||
data: request,
|
||||
);
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return LoginResponse.fromJson(response.data);
|
||||
}
|
||||
throw ServerException.fromJson(response.data);
|
||||
} on DioException catch (e) {
|
||||
throw ServerException.fromJson(e.response?.data);
|
||||
} on FormatException {
|
||||
throw ServerException(message: Validations.someThingWentWrong);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SignUpResponse> signUp(SignUpRequest request) async {
|
||||
try {
|
||||
final Response response = await dio.post(
|
||||
Constants.baseUrl + Urls.signUp,
|
||||
options: DioConstants.options,
|
||||
data: request,
|
||||
);
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return SignUpResponse.fromJson(response.data);
|
||||
}
|
||||
throw ServerException.fromJson(response.data);
|
||||
} on DioException catch (e) {
|
||||
throw ServerException.fromJson(e.response?.data);} on FormatException {
|
||||
throw ServerException(message: Validations.someThingWentWrong);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthVerificationResponse> verify(
|
||||
AuthVerificationRequest request,
|
||||
) async {
|
||||
try {
|
||||
final Response response = await dio.post(
|
||||
Constants.baseUrl + Urls.verify,
|
||||
options: DioConstants.options
|
||||
..headers = {
|
||||
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
|
||||
},
|
||||
data: request,
|
||||
);
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return AuthVerificationResponse.fromJson(response.data);
|
||||
}
|
||||
throw ServerException.fromJson(response.data);
|
||||
} on DioException catch (e) {
|
||||
throw ServerException.fromJson(e.response?.data);
|
||||
} on FormatException {
|
||||
throw ServerException(message: Validations.someThingWentWrong);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FcmAddResponse> addFcm(FcmAddRequest request) async {
|
||||
try {
|
||||
final Response response = await dio.put(
|
||||
Constants.baseUrl + Urls.addFcm,
|
||||
options: DioConstants.options
|
||||
..headers = {
|
||||
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
|
||||
},
|
||||
data: request,
|
||||
);
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return FcmAddResponse.fromJson(response.data);
|
||||
}
|
||||
throw ServerException.fromJson(response.data);
|
||||
} on DioException catch (e) {
|
||||
throw ServerException.fromJson(e.response?.data);
|
||||
} on FormatException {
|
||||
throw ServerException(message: Validations.someThingWentWrong);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
lib/features/auth/data/model/auth_verification_request.dart
Normal file
21
lib/features/auth/data/model/auth_verification_request.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
class AuthVerificationRequest {
|
||||
AuthVerificationRequest({this.phoneNumber, this.smsCode, this.email});
|
||||
|
||||
AuthVerificationRequest.fromJson(dynamic json) {
|
||||
phoneNumber = json['phone_number'];
|
||||
smsCode = json['smsCode'];
|
||||
email = json['email'];
|
||||
}
|
||||
|
||||
String? phoneNumber;
|
||||
String? smsCode;
|
||||
String? email;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['phone_number'] = phoneNumber;
|
||||
map['smsCode'] = smsCode;
|
||||
map['email'] = email;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
16
lib/features/auth/data/model/auth_verification_response.dart
Normal file
16
lib/features/auth/data/model/auth_verification_response.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class AuthVerificationResponse {
|
||||
AuthVerificationResponse({
|
||||
this.accessToken,});
|
||||
|
||||
AuthVerificationResponse.fromJson(dynamic json) {
|
||||
accessToken = json['access_token'];
|
||||
}
|
||||
String? accessToken;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['access_token'] = accessToken;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
16
lib/features/auth/data/model/fcm_add_request.dart
Normal file
16
lib/features/auth/data/model/fcm_add_request.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class FcmAddRequest {
|
||||
FcmAddRequest({
|
||||
this.sfmToken,});
|
||||
|
||||
FcmAddRequest.fromJson(dynamic json) {
|
||||
sfmToken = json['sfm_token'];
|
||||
}
|
||||
String? sfmToken;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['sfm_token'] = sfmToken;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
51
lib/features/auth/data/model/fcm_add_response.dart
Normal file
51
lib/features/auth/data/model/fcm_add_response.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
class FcmAddResponse {
|
||||
FcmAddResponse({
|
||||
this.message,
|
||||
this.user,});
|
||||
|
||||
FcmAddResponse.fromJson(dynamic json) {
|
||||
message = json['message'];
|
||||
user = json['user'] != null ? User.fromJson(json['user']) : null;
|
||||
}
|
||||
String? message;
|
||||
User? user;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['message'] = message;
|
||||
if (user != null) {
|
||||
map['user'] = user?.toJson();
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class User {
|
||||
User({
|
||||
this.fullname,
|
||||
this.phoneNumber,
|
||||
this.email,
|
||||
this.sfmToken,});
|
||||
|
||||
User.fromJson(dynamic json) {
|
||||
fullname = json['fullname'];
|
||||
phoneNumber = json['phone_number'];
|
||||
email = json['email'];
|
||||
sfmToken = json['sfm_token'];
|
||||
}
|
||||
String? fullname;
|
||||
String? phoneNumber;
|
||||
String? email;
|
||||
String? sfmToken;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['fullname'] = fullname;
|
||||
map['phone_number'] = phoneNumber;
|
||||
map['email'] = email;
|
||||
map['sfm_token'] = sfmToken;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
24
lib/features/auth/data/model/login_request.dart
Normal file
24
lib/features/auth/data/model/login_request.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
class LoginRequest {
|
||||
LoginRequest({
|
||||
this.email,
|
||||
this.phone,
|
||||
this.ucode,});
|
||||
|
||||
LoginRequest.fromJson(dynamic json) {
|
||||
email = json['email'];
|
||||
phone = json['phone'];
|
||||
ucode = json['ucode'];
|
||||
}
|
||||
String? email;
|
||||
String? phone;
|
||||
String? ucode;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['email'] = email;
|
||||
map['phone'] = phone;
|
||||
map['ucode'] = ucode;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
20
lib/features/auth/data/model/login_response.dart
Normal file
20
lib/features/auth/data/model/login_response.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
class LoginResponse {
|
||||
LoginResponse({
|
||||
this.token,
|
||||
this.refreshToken,});
|
||||
|
||||
LoginResponse.fromJson(dynamic json) {
|
||||
token = json['token'];
|
||||
refreshToken = json['refreshToken'];
|
||||
}
|
||||
String? token;
|
||||
String? refreshToken;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['token'] = token;
|
||||
map['refreshToken'] = refreshToken;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
25
lib/features/auth/data/model/sign_up_request.dart
Normal file
25
lib/features/auth/data/model/sign_up_request.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
class SignUpRequest {
|
||||
SignUpRequest({
|
||||
this.phoneNumber,
|
||||
this.email,
|
||||
this.fullname,});
|
||||
|
||||
SignUpRequest.fromJson(dynamic json) {
|
||||
phoneNumber = json['phone_number'];
|
||||
email = json['email'];
|
||||
fullname = json['fullname'];
|
||||
}
|
||||
String? phoneNumber;
|
||||
String? email;
|
||||
String? fullname;
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['phone_number'] = phoneNumber;
|
||||
map['email'] = email;
|
||||
map['fullname'] = fullname;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
16
lib/features/auth/data/model/sign_up_response.dart
Normal file
16
lib/features/auth/data/model/sign_up_response.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class SignUpResponse {
|
||||
SignUpResponse({
|
||||
this.token,});
|
||||
|
||||
SignUpResponse.fromJson(dynamic json) {
|
||||
token = json['token'];
|
||||
}
|
||||
String? token;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['token'] = token;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
73
lib/features/auth/data/repository/auth_repository_impl.dart
Normal file
73
lib/features/auth/data/repository/auth_repository_impl.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/error/exceptions.dart';
|
||||
import '../../domain/repository/auth_repository.dart';
|
||||
import '../data_source/remote/auth_remote_data_source.dart';
|
||||
|
||||
class AuthRepositoryImpl extends AuthRepository {
|
||||
final AuthRemoteDataSource remoteDataSource;
|
||||
|
||||
AuthRepositoryImpl(this.remoteDataSource);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LoginResponse>> login(LoginRequest request) async {
|
||||
try {
|
||||
final response = await remoteDataSource.login(request);
|
||||
return Right(response);
|
||||
} catch (e) {
|
||||
if (e is ServerException) {
|
||||
return Left(ServerFailure(message: e.message));
|
||||
}
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, SignUpResponse>> signUp(SignUpRequest request) async {
|
||||
try {
|
||||
final response = await remoteDataSource.signUp(request);
|
||||
return Right(response);
|
||||
} catch (e) {
|
||||
if (e is ServerException) {
|
||||
return Left(ServerFailure(message: e.message));
|
||||
}
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AuthVerificationResponse>> verify(
|
||||
AuthVerificationRequest request,
|
||||
) async {
|
||||
try {
|
||||
final response = await remoteDataSource.verify(request);
|
||||
return Right(response);
|
||||
} catch (e) {
|
||||
if (e is ServerException) {
|
||||
return Left(ServerFailure(message: e.message));
|
||||
}
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, FcmAddResponse>> fcmAdd(FcmAddRequest request) async {
|
||||
try {
|
||||
final response = await remoteDataSource.addFcm(request);
|
||||
return Right(response);
|
||||
} catch (e) {
|
||||
if (e is ServerException) {
|
||||
return Left(ServerFailure(message: e.message));
|
||||
}
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
23
lib/features/auth/domain/repository/auth_repository.dart
Normal file
23
lib/features/auth/domain/repository/auth_repository.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../../../../core/error/failure.dart';
|
||||
|
||||
abstract class AuthRepository {
|
||||
Future<Either<Failure, LoginResponse>> login(LoginRequest request);
|
||||
|
||||
Future<Either<Failure, SignUpResponse>> signUp(SignUpRequest request);
|
||||
|
||||
Future<Either<Failure, AuthVerificationResponse>> verify(
|
||||
AuthVerificationRequest request,
|
||||
);
|
||||
|
||||
Future<Either<Failure, FcmAddResponse>> fcmAdd(FcmAddRequest request);
|
||||
}
|
||||
18
lib/features/auth/domain/usecases/fcm_add_usecase.dart
Normal file
18
lib/features/auth/domain/usecases/fcm_add_usecase.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../repository/auth_repository.dart';
|
||||
|
||||
class FcmAddUseCase extends UseCase<FcmAddResponse, FcmAddRequest> {
|
||||
final AuthRepository repository;
|
||||
|
||||
FcmAddUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, FcmAddResponse>> call(FcmAddRequest params) async {
|
||||
return await repository.fcmAdd(params);
|
||||
}
|
||||
}
|
||||
19
lib/features/auth/domain/usecases/login_usecase.dart
Normal file
19
lib/features/auth/domain/usecases/login_usecase.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../repository/auth_repository.dart';
|
||||
|
||||
class LoginUseCase extends UseCase<LoginResponse, LoginRequest> {
|
||||
final AuthRepository repository;
|
||||
|
||||
LoginUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LoginResponse>> call(LoginRequest params) async {
|
||||
final response = await repository.login(params);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
19
lib/features/auth/domain/usecases/sign_up_usecase.dart
Normal file
19
lib/features/auth/domain/usecases/sign_up_usecase.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../repository/auth_repository.dart';
|
||||
|
||||
class SignUpUseCase extends UseCase<SignUpResponse, SignUpRequest> {
|
||||
final AuthRepository repository;
|
||||
|
||||
SignUpUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, SignUpResponse>> call(SignUpRequest params) async {
|
||||
final response = await repository.signUp(params);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
22
lib/features/auth/domain/usecases/verify_phone_usecase.dart
Normal file
22
lib/features/auth/domain/usecases/verify_phone_usecase.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../repository/auth_repository.dart';
|
||||
|
||||
class VerifyPhoneUseCase
|
||||
extends UseCase<AuthVerificationResponse, AuthVerificationRequest> {
|
||||
final AuthRepository repository;
|
||||
|
||||
VerifyPhoneUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AuthVerificationResponse>> call(
|
||||
AuthVerificationRequest params,
|
||||
) async {
|
||||
final response = await repository.verify(params);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
116
lib/features/auth/presentation/bloc/auth/auth_bloc.dart
Normal file
116
lib/features/auth/presentation/bloc/auth/auth_bloc.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:cargocalculaterapp/core/local_source/local_source.dart';
|
||||
import 'package:cargocalculaterapp/generated/l10n.dart';
|
||||
import 'package:cargocalculaterapp/router/app_routes.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../../constants/constants.dart';
|
||||
import '../../../../../injector_container.dart';
|
||||
import '../../../../../router/name_routes.dart';
|
||||
import '../../../data/model/login_request.dart';
|
||||
import '../../../domain/usecases/login_usecase.dart';
|
||||
import '../../pages/auth_confirm/argument/auth_confirm_argument.dart';
|
||||
|
||||
part 'auth_event.dart';
|
||||
|
||||
part 'auth_state.dart';
|
||||
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
AuthBloc(this.loginUseCase)
|
||||
: super(
|
||||
const AuthState(
|
||||
passwordHidden: true,
|
||||
isLoading: false,
|
||||
isButtonEnabled: false,
|
||||
),
|
||||
) {
|
||||
on<PasswordVisibilityEvent>(_showHidePassword);
|
||||
on<OnInputEnterEvent>(_onInputEnter);
|
||||
on<SubmitEvent>(_onSubmit);
|
||||
}
|
||||
|
||||
final LoginUseCase loginUseCase;
|
||||
|
||||
void _showHidePassword(
|
||||
PasswordVisibilityEvent event,
|
||||
Emitter<AuthState> emit,
|
||||
) {
|
||||
emit(state.copyWith(passwordHidden: !state.passwordHidden));
|
||||
}
|
||||
|
||||
void _onInputEnter(OnInputEnterEvent event, Emitter<AuthState> emit) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isButtonEnabled:
|
||||
(isEmail(event.login) || isPhoneNumber(event.login)) &&
|
||||
event.password.isNotEmpty,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSubmit(SubmitEvent event, Emitter<AuthState> emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final response = await loginUseCase(
|
||||
LoginRequest(
|
||||
email: isEmail(event.login) ? event.login : "",
|
||||
phone: isPhoneNumber(event.login)
|
||||
? event.login.replaceAll("+", "")
|
||||
: "",
|
||||
ucode: event.password,
|
||||
),
|
||||
);
|
||||
response.fold(
|
||||
(l) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
showErrorSnackBar();
|
||||
},
|
||||
(r) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
sl<LocalSource>().setUCode(event.password);
|
||||
Navigator.pushNamed(
|
||||
rootNavigatorKey.currentContext!,
|
||||
Routes.authConfirm,
|
||||
arguments: AuthConfirmArgument(
|
||||
fullName: "",
|
||||
mail: isEmail(event.login) ? event.login : "",
|
||||
phoneNumber: isPhoneNumber(event.login) ? event.login : "",
|
||||
fromLoginPage: true,
|
||||
password: event.password,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool isEmail(String input) {
|
||||
return RegExConst.emailRegex.hasMatch(input);
|
||||
}
|
||||
|
||||
bool isPhoneNumber(String input) {
|
||||
return RegExConst.phoneRegex.hasMatch(input);
|
||||
}
|
||||
|
||||
void showErrorSnackBar() {
|
||||
final snackBar = SnackBar(
|
||||
backgroundColor: Colors.red,
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.white),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalization.current.login_error,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 3),
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(
|
||||
rootNavigatorKey.currentContext!,
|
||||
).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
32
lib/features/auth/presentation/bloc/auth/auth_event.dart
Normal file
32
lib/features/auth/presentation/bloc/auth/auth_event.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
part of 'auth_bloc.dart';
|
||||
|
||||
sealed class AuthEvent extends Equatable {
|
||||
const AuthEvent();
|
||||
}
|
||||
|
||||
final class PasswordVisibilityEvent extends AuthEvent {
|
||||
const PasswordVisibilityEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
final class OnInputEnterEvent extends AuthEvent {
|
||||
final String login;
|
||||
final String password;
|
||||
|
||||
const OnInputEnterEvent({required this.login, required this.password});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [login, password];
|
||||
}
|
||||
|
||||
final class SubmitEvent extends AuthEvent {
|
||||
final String login;
|
||||
final String password;
|
||||
|
||||
const SubmitEvent({required this.login, required this.password});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [login, password];
|
||||
}
|
||||
28
lib/features/auth/presentation/bloc/auth/auth_state.dart
Normal file
28
lib/features/auth/presentation/bloc/auth/auth_state.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
part of 'auth_bloc.dart';
|
||||
|
||||
class AuthState extends Equatable {
|
||||
const AuthState({
|
||||
required this.passwordHidden,
|
||||
required this.isLoading,
|
||||
required this.isButtonEnabled,
|
||||
});
|
||||
|
||||
final bool passwordHidden;
|
||||
final bool isLoading;
|
||||
final bool isButtonEnabled;
|
||||
|
||||
AuthState copyWith({
|
||||
bool? passwordHidden,
|
||||
bool? isLoading,
|
||||
bool? isButtonEnabled,
|
||||
}) {
|
||||
return AuthState(
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
passwordHidden: passwordHidden ?? this.passwordHidden,
|
||||
isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [passwordHidden, isLoading, isButtonEnabled];
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../core/error/failure.dart';
|
||||
import '../../../../../core/functions/base_finctions.dart';
|
||||
import '../../../../../core/local_source/local_source.dart';
|
||||
import '../../../../../injector_container.dart';
|
||||
import '../../../../../service/notification_service.dart';
|
||||
import '../../../data/model/auth_verification_request.dart';
|
||||
import '../../../data/model/fcm_add_request.dart';
|
||||
import '../../../data/model/login_request.dart';
|
||||
import '../../../data/model/sign_up_request.dart';
|
||||
import '../../../domain/usecases/fcm_add_usecase.dart';
|
||||
import '../../../domain/usecases/login_usecase.dart';
|
||||
import '../../../domain/usecases/sign_up_usecase.dart';
|
||||
import '../../../domain/usecases/verify_phone_usecase.dart';
|
||||
import '../../pages/auth_confirm/argument/auth_confirm_argument.dart';
|
||||
|
||||
part 'auth_confirm_event.dart';
|
||||
|
||||
part 'auth_confirm_state.dart';
|
||||
|
||||
class AuthConfirmBloc extends Bloc<AuthConfirmEvent, AuthConfirmState> {
|
||||
AuthConfirmBloc(
|
||||
this.signUpUseCase,
|
||||
this.verifyPhoneUseCase,
|
||||
this.loginUseCase,
|
||||
this.fcmAddUseCase,
|
||||
) : super(const InitialState(true)) {
|
||||
on<TimerChangedEvent>(_timerChanged);
|
||||
on<SmsCodeEnterEvent>(_codeChanged);
|
||||
on<ResendCodeEvent>(_resendCode);
|
||||
on<OnSubmitEvent>(_onSubmitCode);
|
||||
}
|
||||
|
||||
final SignUpUseCase signUpUseCase;
|
||||
final VerifyPhoneUseCase verifyPhoneUseCase;
|
||||
final LoginUseCase loginUseCase;
|
||||
final FcmAddUseCase fcmAddUseCase;
|
||||
|
||||
Future<void> _timerChanged(
|
||||
TimerChangedEvent event,
|
||||
Emitter<AuthConfirmState> emit,
|
||||
) async {
|
||||
emit(InitialState(event.isVisible));
|
||||
}
|
||||
|
||||
void _codeChanged(SmsCodeEnterEvent event, Emitter<AuthConfirmState> emit) {
|
||||
if (event.code.length == 4) {
|
||||
emit(ButtonEnableState(state.isTimerVisible, event.code));
|
||||
} else {
|
||||
emit(InitialState(state.isTimerVisible));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _resendCode(
|
||||
ResendCodeEvent event,
|
||||
Emitter<AuthConfirmState> emit,
|
||||
) async {
|
||||
add(const TimerChangedEvent(isVisible: true));
|
||||
if (event.argument.fromLoginPage) {
|
||||
await loginUseCase(
|
||||
LoginRequest(
|
||||
email: event.argument.mail,
|
||||
phone: event.argument.phoneNumber?.replaceAll("+", ""),
|
||||
ucode: event.argument.password,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await signUpUseCase(
|
||||
SignUpRequest(
|
||||
fullname: event.argument.fullName,
|
||||
email: event.argument.mail,
|
||||
phoneNumber: event.argument.phoneNumber?.replaceAll("+", ""),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSubmitCode(
|
||||
OnSubmitEvent event,
|
||||
Emitter<AuthConfirmState> emit,
|
||||
) async {
|
||||
emit(LoadingState(state.isTimerVisible));
|
||||
final response = await verifyPhoneUseCase(
|
||||
AuthVerificationRequest(
|
||||
phoneNumber: event.argument?.phoneNumber?.replaceAll("+", ""),
|
||||
smsCode: event.code,
|
||||
email: event.argument?.mail ?? "",
|
||||
),
|
||||
);
|
||||
await response.fold(
|
||||
(l) {
|
||||
if (l is ServerFailure) {
|
||||
Functions.showErrorSnackBar(l.message);
|
||||
}
|
||||
emit(InitialState(state.isTimerVisible));
|
||||
},
|
||||
(r) async {
|
||||
sl<LocalSource>().setHasProfile(true);
|
||||
sl<LocalSource>().setAccessToken(r.accessToken ?? "");
|
||||
await _addFcmToken();
|
||||
emit(SuccessSate(state.isTimerVisible));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addFcmToken() async {
|
||||
final String fcmToken = await NotificationService.getFcmToken();
|
||||
await fcmAddUseCase(FcmAddRequest(sfmToken: fcmToken));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
part of 'auth_confirm_bloc.dart';
|
||||
|
||||
sealed class AuthConfirmEvent extends Equatable {
|
||||
const AuthConfirmEvent();
|
||||
}
|
||||
|
||||
final class SmsCodeEnterEvent extends AuthConfirmEvent {
|
||||
final String code;
|
||||
|
||||
const SmsCodeEnterEvent(this.code);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [code];
|
||||
}
|
||||
|
||||
final class TimerChangedEvent extends AuthConfirmEvent {
|
||||
final bool isVisible;
|
||||
|
||||
const TimerChangedEvent({required this.isVisible});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [isVisible];
|
||||
}
|
||||
|
||||
final class OnSubmitEvent extends AuthConfirmEvent {
|
||||
final String code;
|
||||
final AuthConfirmArgument? argument;
|
||||
|
||||
const OnSubmitEvent({required this.code, required this.argument});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [code, argument];
|
||||
}
|
||||
|
||||
final class ResendCodeEvent extends AuthConfirmEvent {
|
||||
final AuthConfirmArgument argument;
|
||||
|
||||
const ResendCodeEvent({required this.argument});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [argument];
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
part of 'auth_confirm_bloc.dart';
|
||||
|
||||
sealed class AuthConfirmState extends Equatable {
|
||||
const AuthConfirmState(this.isTimerVisible);
|
||||
|
||||
final bool isTimerVisible;
|
||||
}
|
||||
|
||||
class InitialState extends AuthConfirmState {
|
||||
const InitialState(super.isTimerVisible);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [super.isTimerVisible];
|
||||
}
|
||||
|
||||
class LoadingState extends AuthConfirmState {
|
||||
const LoadingState(super.isTimerVisible);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [super.isTimerVisible];
|
||||
}
|
||||
|
||||
class SuccessSate extends AuthConfirmState {
|
||||
const SuccessSate(super.isTimerVisible);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [super.isTimerVisible];
|
||||
}
|
||||
|
||||
class ErrorState extends AuthConfirmState {
|
||||
const ErrorState(super.isTimerVisible);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [super.isTimerVisible];
|
||||
}
|
||||
|
||||
class ButtonEnableState extends AuthConfirmState {
|
||||
const ButtonEnableState(super.isTimerVisible, this.code);
|
||||
|
||||
final String code;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [super.isTimerVisible, code];
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import 'package:cargocalculaterapp/core/local_source/local_source.dart';
|
||||
import 'package:cargocalculaterapp/generated/l10n.dart';
|
||||
import 'package:cargocalculaterapp/router/app_routes.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../../constants/constants.dart';
|
||||
import '../../../../../core/error/failure.dart';
|
||||
import '../../../../../core/functions/base_finctions.dart';
|
||||
import '../../../../../injector_container.dart';
|
||||
import '../../../../../router/name_routes.dart';
|
||||
import '../../../data/model/sign_up_request.dart';
|
||||
import '../../../domain/usecases/sign_up_usecase.dart';
|
||||
import '../../pages/auth_confirm/argument/auth_confirm_argument.dart';
|
||||
|
||||
part 'sign_up_event.dart';
|
||||
|
||||
part 'sign_up_state.dart';
|
||||
|
||||
class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
|
||||
SignUpBloc(this.signUpUseCase)
|
||||
: super(
|
||||
const SignUpState(
|
||||
isLoading: false,
|
||||
isButtonEnabled: false,
|
||||
passwordHidden: true,
|
||||
),
|
||||
) {
|
||||
on<PasswordVisibilityEvent>(_passwordVisibility);
|
||||
on<OnInputEnterEvent>(_onDataEnter);
|
||||
on<SubmitEvent>(_signUp);
|
||||
}
|
||||
|
||||
final SignUpUseCase signUpUseCase;
|
||||
|
||||
void _passwordVisibility(
|
||||
PasswordVisibilityEvent event,
|
||||
Emitter<SignUpState> emit,
|
||||
) {
|
||||
emit(state.copyWith(isButtonEnabled: !state.isButtonEnabled));
|
||||
}
|
||||
|
||||
void _onDataEnter(OnInputEnterEvent event, Emitter<SignUpState> emit) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isButtonEnabled:
|
||||
(isEmail(event.login) || isPhoneNumber(event.login)) &&
|
||||
event.fullName.isNotEmpty,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool isEmail(String input) {
|
||||
return RegExConst.emailRegex.hasMatch(input);
|
||||
}
|
||||
|
||||
bool isPhoneNumber(String input) {
|
||||
return RegExConst.phoneRegex.hasMatch(input);
|
||||
}
|
||||
|
||||
Future<void> _signUp(SubmitEvent event, Emitter<SignUpState> emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final response = await signUpUseCase(
|
||||
SignUpRequest(
|
||||
fullname: event.fullName,
|
||||
email: isEmail(event.login) ? event.login : "",
|
||||
phoneNumber: isPhoneNumber(event.login)
|
||||
? event.login.replaceAll("+", "")
|
||||
: "",
|
||||
),
|
||||
);
|
||||
response.fold(
|
||||
(l) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
if (l is ServerFailure) {
|
||||
Functions.showErrorSnackBar(AppLocalization.current.phone_registered);
|
||||
}
|
||||
},
|
||||
(r) {
|
||||
sl<LocalSource>().setAccessToken(r.token ?? "");
|
||||
emit(state.copyWith(isLoading: false));
|
||||
Navigator.pushNamed(
|
||||
rootNavigatorKey.currentContext!,
|
||||
Routes.authConfirm,
|
||||
arguments: AuthConfirmArgument(
|
||||
fullName: event.fullName,
|
||||
mail: isEmail(event.login) ? event.login : "",
|
||||
phoneNumber: isPhoneNumber(event.login) ? event.login : "",
|
||||
fromLoginPage: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
part of 'sign_up_bloc.dart';
|
||||
|
||||
sealed class SignUpEvent extends Equatable {
|
||||
const SignUpEvent();
|
||||
}
|
||||
|
||||
final class PasswordVisibilityEvent extends SignUpEvent {
|
||||
const PasswordVisibilityEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
final class OnInputEnterEvent extends SignUpEvent {
|
||||
final String login;
|
||||
final String fullName;
|
||||
|
||||
const OnInputEnterEvent({required this.login, required this.fullName});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [login, fullName];
|
||||
}
|
||||
|
||||
final class SubmitEvent extends SignUpEvent {
|
||||
final String login;
|
||||
final String fullName;
|
||||
|
||||
const SubmitEvent({required this.login, required this.fullName});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [login, fullName];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
part of 'sign_up_bloc.dart';
|
||||
|
||||
class SignUpState extends Equatable {
|
||||
const SignUpState({
|
||||
required this.passwordHidden,
|
||||
required this.isLoading,
|
||||
required this.isButtonEnabled,
|
||||
});
|
||||
|
||||
final bool passwordHidden;
|
||||
final bool isLoading;
|
||||
final bool isButtonEnabled;
|
||||
|
||||
SignUpState copyWith({
|
||||
bool? passwordHidden,
|
||||
bool? isLoading,
|
||||
bool? isButtonEnabled,
|
||||
}) {
|
||||
return SignUpState(
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
passwordHidden: passwordHidden ?? this.passwordHidden,
|
||||
isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [passwordHidden, isLoading, isButtonEnabled];
|
||||
}
|
||||
185
lib/features/auth/presentation/pages/auth/auth_page.dart
Normal file
185
lib/features/auth/presentation/pages/auth/auth_page.dart
Normal file
@@ -0,0 +1,185 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
|
||||
import 'package:cargocalculaterapp/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import '../../../../../core/widgets/loading/custom_loading.dart';
|
||||
import '../../../../../core/widgets/text_filds/custom_text_field_name.dart';
|
||||
import '../../../../../router/name_routes.dart';
|
||||
import '../../bloc/auth/auth_bloc.dart';
|
||||
import '../mixin/auth_mixin.dart';
|
||||
|
||||
class AuthPage extends StatefulWidget {
|
||||
const AuthPage({super.key});
|
||||
|
||||
@override
|
||||
State<AuthPage> createState() => _AuthPageState();
|
||||
}
|
||||
|
||||
class _AuthPageState extends State<AuthPage> with AuthMixin {
|
||||
@override
|
||||
void initState() {
|
||||
initControllers();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: ListView(
|
||||
padding: AppUtils.kPaddingHor16,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 60),
|
||||
child: SvgPicture.asset("assets/svg/ic_logo_auth.svg"),
|
||||
),
|
||||
AppUtils.kBoxHeight48,
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: AppUtils.kBorderRadius12,
|
||||
color: context.color.iconBackground,
|
||||
),
|
||||
padding: AppUtils.kPaddingAll6,
|
||||
child: SvgPicture.asset("assets/svg/ic_user.svg"),
|
||||
),
|
||||
AppUtils.kBoxWidth12,
|
||||
Text(
|
||||
AppLocalization.current.auth_login,
|
||||
style: context.text.authTitle,
|
||||
),
|
||||
],
|
||||
),
|
||||
AppUtils.kBoxHeight32,
|
||||
CustomTextFieldName(
|
||||
hint: AppLocalization.current.enter_phone_or_mail,
|
||||
inputType: TextInputType.text,
|
||||
name: AppLocalization.current.phone,
|
||||
controller: loginController,
|
||||
onchange: (value) {
|
||||
context.read<AuthBloc>().add(
|
||||
OnInputEnterEvent(
|
||||
login: value,
|
||||
password: passwordController.text,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
CustomTextFieldName(
|
||||
hint: "********",
|
||||
prefixWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"CT-",
|
||||
style: context.text.profileCategory.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
inputType: TextInputType.number,
|
||||
name: AppLocalization.current.password,
|
||||
obscureText: state.passwordHidden,
|
||||
maxLines: 1,
|
||||
controller: passwordController,
|
||||
suffix: IconButton(
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(
|
||||
const PasswordVisibilityEvent(),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
state.passwordHidden
|
||||
? Icons.visibility_off_rounded
|
||||
: Icons.visibility_rounded,
|
||||
),
|
||||
),
|
||||
onchange: (value) {
|
||||
context.read<AuthBloc>().add(
|
||||
OnInputEnterEvent(
|
||||
login: loginController.text,
|
||||
password: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AppUtils.kBoxHeight48,
|
||||
Padding(
|
||||
padding: AppUtils.kPaddingHor34,
|
||||
child: ElevatedButton(
|
||||
onPressed: state.isButtonEnabled && !state.isLoading
|
||||
? () {
|
||||
context.read<AuthBloc>().add(
|
||||
SubmitEvent(
|
||||
login: loginController.text,
|
||||
password: "CT-${passwordController.text}",
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: state.isLoading
|
||||
? const CustomLoadingWidget()
|
||||
: Text(AppLocalization.current.auth),
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
Text(
|
||||
AppLocalization.current.no_account,
|
||||
style: context.text.authDesc,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
Padding(
|
||||
padding: AppUtils.kPaddingHor34,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, Routes.signUp);
|
||||
},
|
||||
borderRadius: AppUtils.kBorderRadius24,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.scaffoldBackgroundColor,
|
||||
borderRadius: AppUtils.kBorderRadius24,
|
||||
border: Border.all(color: context.color.primaryColor),
|
||||
),
|
||||
height: 56,
|
||||
padding: AppUtils.kPaddingHor16,
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalization.current.sign_up,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: context.color.textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeControllers();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
class AuthConfirmArgument {
|
||||
final String? fullName;
|
||||
final String? mail;
|
||||
final String? phoneNumber;
|
||||
final bool fromLoginPage;
|
||||
final String? password;
|
||||
|
||||
AuthConfirmArgument({
|
||||
this.fullName,
|
||||
required this.mail,
|
||||
required this.phoneNumber,
|
||||
required this.fromLoginPage,
|
||||
this.password,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
|
||||
import 'package:cargocalculaterapp/core/widgets/loading/custom_loading.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/presentation/pages/auth_confirm/timer_widget.dart';
|
||||
import 'package:cargocalculaterapp/generated/l10n.dart';
|
||||
import 'package:cargocalculaterapp/router/name_routes.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:pin_code_fields/pin_code_fields.dart';
|
||||
import '../../../../../core/theme/app_text_styles.dart';
|
||||
import '../../../../../core/theme/colors/app_colors.dart';
|
||||
import '../../bloc/auth_confirm/auth_confirm_bloc.dart';
|
||||
import '../mixin/auth_confirm_mixin.dart';
|
||||
import 'argument/auth_confirm_argument.dart';
|
||||
|
||||
class AuthConfirmPage extends StatefulWidget {
|
||||
const AuthConfirmPage({super.key, required this.argument});
|
||||
|
||||
final AuthConfirmArgument? argument;
|
||||
|
||||
@override
|
||||
State<AuthConfirmPage> createState() => _AuthConfirmPageState();
|
||||
}
|
||||
|
||||
class _AuthConfirmPageState extends State<AuthConfirmPage>
|
||||
with AuthConfirmMixin {
|
||||
@override
|
||||
void initState() {
|
||||
initControllers();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<AuthConfirmBloc, AuthConfirmState>(
|
||||
listener: (context, state) {
|
||||
if (state is SuccessSate) {
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
Routes.main,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Padding(
|
||||
padding: AppUtils.kPaddingAll16,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: AppUtils.kBorderRadius16,
|
||||
color: context.color.iconBackground,
|
||||
),
|
||||
padding: AppUtils.kPaddingAll16,
|
||||
child: SvgPicture.asset(
|
||||
"assets/svg/ic_mail.svg",
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
(widget.argument?.mail?.isNotEmpty ?? false)
|
||||
? AppLocalization.current.confirm_email
|
||||
: AppLocalization.current.confirm_phone_text,
|
||||
style: context.text.authTitle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
(widget.argument?.mail?.isNotEmpty ?? false)
|
||||
? AppLocalization.current.enter_code_mail(
|
||||
widget.argument?.mail ?? "",
|
||||
)
|
||||
: AppLocalization.current.enter_code_phone(
|
||||
widget.argument?.phoneNumber ?? "",
|
||||
),
|
||||
style: context.text.authDesc,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight48,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 296,
|
||||
child: PinCodeTextField(
|
||||
controller: confirmController,
|
||||
appContext: context,
|
||||
autoDisposeControllers: false,
|
||||
length: 4,
|
||||
animationType: AnimationType.scale,
|
||||
enabled: true,
|
||||
enablePinAutofill: true,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
onChanged: (code) {
|
||||
context.read<AuthConfirmBloc>().add(
|
||||
SmsCodeEnterEvent(code),
|
||||
);
|
||||
},
|
||||
autoFocus: true,
|
||||
keyboardType: const TextInputType.numberWithOptions(),
|
||||
showCursor: true,
|
||||
textStyle: context.text.bigTitle,
|
||||
cursorColor: context.color.accentColor,
|
||||
cursorHeight: 20,
|
||||
enableActiveFill: true,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
pinTheme: PinTheme(
|
||||
activeColor:
|
||||
state is ErrorState
|
||||
? ThemeColors.timerRed
|
||||
: state is SuccessSate
|
||||
? ThemeColors.successInputSMS
|
||||
: context.color.borderColor,
|
||||
selectedFillColor:
|
||||
context.color.scaffoldBackgroundColor,
|
||||
activeFillColor:
|
||||
context.color.scaffoldBackgroundColor,
|
||||
shape: PinCodeFieldShape.box,
|
||||
inactiveFillColor:
|
||||
context.color.scaffoldBackgroundColor,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
borderWidth: 1,
|
||||
inactiveColor:
|
||||
state is ErrorState
|
||||
? ThemeColors.timerRed
|
||||
: state is SuccessSate
|
||||
? ThemeColors.successInputSMS
|
||||
: context.color.borderColor,
|
||||
fieldWidth: 56,
|
||||
fieldHeight: 64,
|
||||
selectedColor:
|
||||
state is ErrorState
|
||||
? ThemeColors.timerRed
|
||||
: state is SuccessSate
|
||||
? ThemeColors.successInputSMS
|
||||
: context.color.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state is ErrorState) AppUtils.kBoxHeight16,
|
||||
if (state is ErrorState)
|
||||
Center(
|
||||
child: Text(
|
||||
AppLocalization.current.incorrect_code,
|
||||
style: AppTextStyles.timerBlue,
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight24,
|
||||
if (state.isTimerVisible)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalization.current.send_sms,
|
||||
style: AppTextStyles.timerBlue,
|
||||
),
|
||||
const TimerWidget(),
|
||||
],
|
||||
),
|
||||
if (!state.isTimerVisible)
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.argument != null) {
|
||||
context.read<AuthConfirmBloc>().add(
|
||||
ResendCodeEvent(argument: widget.argument!),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
AppLocalization.current.send_sms,
|
||||
style: AppTextStyles.timerBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight48,
|
||||
Padding(
|
||||
padding: AppUtils.kPaddingHor34,
|
||||
child: ElevatedButton(
|
||||
onPressed:
|
||||
(confirmController.text.length == 4 &&
|
||||
state is! LoadingState)
|
||||
? () {
|
||||
context.read<AuthConfirmBloc>().add(
|
||||
OnSubmitEvent(
|
||||
code: confirmController.text,
|
||||
argument: widget.argument,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child:
|
||||
state is LoadingState
|
||||
? const CustomLoadingWidget()
|
||||
: Text(AppLocalization.current.confirm),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeController();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'dart:async';
|
||||
import 'package:cargocalculaterapp/core/theme/app_text_styles.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../bloc/auth_confirm/auth_confirm_bloc.dart';
|
||||
|
||||
class TimerWidget extends StatefulWidget {
|
||||
const TimerWidget({super.key});
|
||||
|
||||
@override
|
||||
State<TimerWidget> createState() => _TimerWidgetState();
|
||||
}
|
||||
|
||||
class _TimerWidgetState extends State<TimerWidget> {
|
||||
Timer? timer;
|
||||
int time = 60;
|
||||
String timeText = "60";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (timer?.isActive ?? false) {
|
||||
timer?.cancel();
|
||||
}
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) async {
|
||||
time--;
|
||||
if (time == 0) {
|
||||
timer.cancel();
|
||||
context.read<AuthConfirmBloc>().add(
|
||||
const TimerChangedEvent(isVisible: false),
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
timeText = time.toString().padLeft(2, "0");
|
||||
});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(" 00:$timeText", style: AppTextStyles.timerStyle);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
mixin AuthConfirmMixin {
|
||||
late TextEditingController confirmController;
|
||||
|
||||
void initControllers() {
|
||||
confirmController = TextEditingController();
|
||||
}
|
||||
|
||||
void disposeController() {
|
||||
confirmController.dispose();
|
||||
}
|
||||
}
|
||||
16
lib/features/auth/presentation/pages/mixin/auth_mixin.dart
Normal file
16
lib/features/auth/presentation/pages/mixin/auth_mixin.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
mixin AuthMixin {
|
||||
late TextEditingController loginController;
|
||||
late TextEditingController passwordController;
|
||||
|
||||
void initControllers() {
|
||||
loginController = TextEditingController();
|
||||
passwordController = TextEditingController();
|
||||
}
|
||||
|
||||
void disposeControllers() {
|
||||
loginController.dispose();
|
||||
passwordController.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
mixin SignUpMixin {
|
||||
late TextEditingController loginController;
|
||||
late TextEditingController fullNameController;
|
||||
|
||||
void initControllers() {
|
||||
loginController = TextEditingController();
|
||||
fullNameController = TextEditingController();
|
||||
}
|
||||
|
||||
void disposeControllers() {
|
||||
loginController.dispose();
|
||||
fullNameController.dispose();
|
||||
}
|
||||
}
|
||||
158
lib/features/auth/presentation/pages/sign_up/sign_up_page.dart
Normal file
158
lib/features/auth/presentation/pages/sign_up/sign_up_page.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:cargocalculaterapp/features/auth/presentation/pages/mixin/sign_up_mixin.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import '../../../../../core/utils/app_utils.dart';
|
||||
import '../../../../../core/widgets/loading/custom_loading.dart';
|
||||
import '../../../../../core/widgets/text_filds/custom_text_field_name.dart';
|
||||
import '../../../../../generated/l10n.dart';
|
||||
import '../../bloc/sign_up/sign_up_bloc.dart';
|
||||
|
||||
class SignUpPage extends StatefulWidget {
|
||||
const SignUpPage({super.key});
|
||||
|
||||
@override
|
||||
State<SignUpPage> createState() => _SignUpPageState();
|
||||
}
|
||||
|
||||
class _SignUpPageState extends State<SignUpPage> with SignUpMixin {
|
||||
@override
|
||||
void initState() {
|
||||
initControllers();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SignUpBloc, SignUpState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: ListView(
|
||||
padding: AppUtils.kPaddingAll16,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: AppUtils.kBorderRadius16,
|
||||
color: context.color.iconBackground,
|
||||
),
|
||||
padding: AppUtils.kPaddingAll16,
|
||||
child: SvgPicture.asset(
|
||||
"assets/svg/ic_add_user.svg",
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
Text(
|
||||
AppLocalization.current.sign_up,
|
||||
style: context.text.authTitle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
AppUtils.kBoxHeight24,
|
||||
CustomTextFieldName(
|
||||
hint: AppLocalization.current.full_name_hint,
|
||||
inputType: TextInputType.name,
|
||||
name: AppLocalization.current.full_name,
|
||||
controller: fullNameController,
|
||||
onchange: (value) {
|
||||
context.read<SignUpBloc>().add(
|
||||
OnInputEnterEvent(
|
||||
login: loginController.text,
|
||||
fullName: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
CustomTextFieldName(
|
||||
hint: AppLocalization.current.enter_phone_or_mail,
|
||||
inputType: TextInputType.text,
|
||||
name: AppLocalization.current.phone,
|
||||
controller: loginController,
|
||||
onchange: (value) {
|
||||
context.read<SignUpBloc>().add(
|
||||
OnInputEnterEvent(
|
||||
login: value,
|
||||
fullName: fullNameController.text,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AppUtils.kBoxHeight48,
|
||||
Padding(
|
||||
padding: AppUtils.kPaddingHor34,
|
||||
child: ElevatedButton(
|
||||
onPressed:
|
||||
state.isButtonEnabled && !state.isLoading
|
||||
? () {
|
||||
context.read<SignUpBloc>().add(
|
||||
SubmitEvent(
|
||||
login: loginController.text,
|
||||
fullName: fullNameController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child:
|
||||
state.isLoading
|
||||
? const CustomLoadingWidget()
|
||||
: Text(AppLocalization.current.sign_up),
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
Text(
|
||||
AppLocalization.current.has_account,
|
||||
style: context.text.authDesc,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
Padding(
|
||||
padding: AppUtils.kPaddingHor34,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
borderRadius: AppUtils.kBorderRadius24,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.scaffoldBackgroundColor,
|
||||
borderRadius: AppUtils.kBorderRadius24,
|
||||
border: Border.all(color: context.color.primaryColor),
|
||||
),
|
||||
height: 56,
|
||||
padding: AppUtils.kPaddingHor16,
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalization.current.auth,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: context.color.textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeControllers();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user