From a45590fd7db6cff0ebf8656c51d76941d5842f32 Mon Sep 17 00:00:00 2001 From: Amanda Shafack Date: Thu, 2 Mar 2023 12:33:00 +0100 Subject: [PATCH] feat: add phone number authentication - Configure native android files - Implement the logic with flutterbloc --- android/app/build.gradle | 3 +- .../i_phone_number_repository_facade.dart | 24 + ...hone_number_authentication_repository.dart | 115 ++ .../phone_number_bloc/phone_number_bloc.dart | 184 +++ .../phone_number_bloc.freezed.dart | 1415 +++++++++++++++++ .../phone_number_bloc/phone_number_event.dart | 14 + .../phone_number_bloc/phone_number_state.dart | 14 + .../create_password_phone_signup_screen.dart | 49 + .../views/phone_number_input_signup_flow.dart | 76 + .../widget/bubbles_top_background.dart | 24 + .../widget/create_new_password.dart | 121 ++ lib/core/presentation/widget/fpb_button.dart | 4 +- .../widget/medium_sized_page_title.dart | 23 + lib/core/presentation/widget/my_button.dart | 54 + .../presentation/widget/my_textformfield.dart | 123 ++ .../widget/otp_group_text_field.dart | 51 +- lib/core/presentation/widget/otp_input.dart | 6 + .../view/email_confirmation_page.dart | 0 lib/home/view/dashboard.dart | 24 +- lib/home/view/home_container.dart | 3 +- lib/home/view/user_search_screen.dart | 42 +- lib/home/view/widgets/drag_widget.dart | 0 lib/home/view/widgets/navbar_header.dart | 75 + lib/home/view/widgets/row_header_icons.dart | 2 - lib/home/view/widgets/search_input.dart | 2 +- lib/injection.config.dart | 60 +- lib/l10n/arb/app_en.arb | 45 +- lib/l10n/arb/app_fr.arb | 44 + .../view/phone_number_confirmation.dart | 154 +- lib/profile/view/profile_page.dart | 137 +- lib/router/app_route.dart | 26 +- lib/router/app_route.gr.dart | 329 ++-- lib/sign_in/domain/domain.dart | 2 + lib/sign_in/domain/normal_fields.dart | 18 + lib/sign_in/domain/otp_field.dart | 16 + lib/sign_in/domain/phone_number.dart | 16 + lib/sign_in/view/sign_in_page.dart | 30 +- lib/sign_in/view/widgets/email_input.dart | 2 +- lib/sign_in/view/widgets/login_button.dart | 28 +- lib/sign_in/view/widgets/otp_field.dart | 49 + lib/sign_in/view/widgets/password_input.dart | 57 +- lib/sign_up/view/email_address_tab_view.dart | 47 + lib/sign_up/view/phone_number_tab_view.dart | 81 + lib/sign_up/view/sign_up_text_button.dart | 30 + lib/sign_up/view/signup_page.dart | 332 ++-- lib/sign_up/widgets/signup_tab_bar.dart | 54 + 46 files changed, 3337 insertions(+), 668 deletions(-) create mode 100644 lib/authentication_with_phone_number/domain/i_phone_number_repository_facade.dart create mode 100644 lib/authentication_with_phone_number/infrastructure/phone_number_authentication_repository.dart create mode 100644 lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart create mode 100644 lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.freezed.dart create mode 100644 lib/authentication_with_phone_number/phone_number_bloc/phone_number_event.dart create mode 100644 lib/authentication_with_phone_number/phone_number_bloc/phone_number_state.dart create mode 100644 lib/authentication_with_phone_number/views/create_password_phone_signup_screen.dart create mode 100644 lib/authentication_with_phone_number/views/phone_number_input_signup_flow.dart create mode 100644 lib/core/presentation/widget/bubbles_top_background.dart create mode 100644 lib/core/presentation/widget/create_new_password.dart create mode 100644 lib/core/presentation/widget/medium_sized_page_title.dart create mode 100644 lib/core/presentation/widget/my_button.dart create mode 100644 lib/core/presentation/widget/my_textformfield.dart create mode 100644 lib/email_confirmation/view/email_confirmation_page.dart create mode 100644 lib/home/view/widgets/drag_widget.dart create mode 100644 lib/home/view/widgets/navbar_header.dart create mode 100644 lib/sign_in/domain/normal_fields.dart create mode 100644 lib/sign_in/domain/otp_field.dart create mode 100644 lib/sign_in/domain/phone_number.dart create mode 100644 lib/sign_in/view/widgets/otp_field.dart create mode 100644 lib/sign_up/view/email_address_tab_view.dart create mode 100644 lib/sign_up/view/phone_number_tab_view.dart create mode 100644 lib/sign_up/view/sign_up_text_button.dart create mode 100644 lib/sign_up/widgets/signup_tab_bar.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 9b28fdf..ebbd407 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -119,6 +119,8 @@ dependencies { implementation platform('com.google.firebase:firebase-bom:31.2.0') implementation 'com.google.firebase:firebase-analytics-ktx' implementation 'com.android.support:multidex:2.0.1' + implementation 'com.google.firebase:firebase-auth-ktx' + implementation 'com.google.android.gms:play-services-safetynet:18.0.1' /// Facebook SDK implementation 'com.facebook.android:facebook-android-sdk:latest.release' @@ -128,7 +130,6 @@ dependencies { // TODO: Add the dependencies for Firebase products you want to use // When using the BoM, don't specify versions in Firebase dependencies - implementation 'com.google.firebase:firebase-analytics-ktx' // Add the dependencies for any other desired Firebase products // https://firebase.google.com/docs/android/setup#available-libraries diff --git a/lib/authentication_with_phone_number/domain/i_phone_number_repository_facade.dart b/lib/authentication_with_phone_number/domain/i_phone_number_repository_facade.dart new file mode 100644 index 0000000..c1f82fb --- /dev/null +++ b/lib/authentication_with_phone_number/domain/i_phone_number_repository_facade.dart @@ -0,0 +1,24 @@ +import 'package:dartz/dartz.dart'; +import 'package:firebase_auth/firebase_auth.dart' hide User; +import 'package:fpb/core/domain/user.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; + +abstract class IPhoneNumberRepositoryFacade { + + Future sendOtpCode({ + required String phoneNumber, + required void Function(PhoneAuthCredential) verificationCompleted, + required void Function(FirebaseAuthException) verificationFailed, + required void Function(String, int?) codeSent, + required void Function(String) codeAutoRetrievalTimeout, + }); + + Future> verifyAndLogin({ + required String smsCode, required String verificationId, + }); + + Future> signUpWithEmailAndPassword({required String email, required String password}); + + Future> signOut(); + +} diff --git a/lib/authentication_with_phone_number/infrastructure/phone_number_authentication_repository.dart b/lib/authentication_with_phone_number/infrastructure/phone_number_authentication_repository.dart new file mode 100644 index 0000000..f1940bc --- /dev/null +++ b/lib/authentication_with_phone_number/infrastructure/phone_number_authentication_repository.dart @@ -0,0 +1,115 @@ +import 'dart:io'; + +import 'package:dartz/dartz.dart'; +import 'package:firebase_auth/firebase_auth.dart' hide User; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:fpb/authentication_with_phone_number/domain/i_phone_number_repository_facade.dart'; +import 'package:fpb/core/domain/user.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; +import 'package:fpb/core/infrastructure/user.dto.dart'; +import 'package:injectable/injectable.dart'; + + +@LazySingleton(as: IPhoneNumberRepositoryFacade) +class PhoneNumberAuthRepository implements IPhoneNumberRepositoryFacade { + final FirebaseAuth _firebaseAuth; + PhoneNumberAuthRepository(this._firebaseAuth); + + Future sendOtpCode({ + required String phoneNumber, + required void Function(PhoneAuthCredential) verificationCompleted, + required void Function(FirebaseAuthException) verificationFailed, + required void Function(String, int?) codeSent, + required void Function(String) codeAutoRetrievalTimeout, + }) async { + await _firebaseAuth.verifyPhoneNumber( + phoneNumber: phoneNumber, + verificationCompleted: verificationCompleted, + verificationFailed: verificationFailed, + codeSent: codeSent, + codeAutoRetrievalTimeout: codeAutoRetrievalTimeout, + ); + } + + @override + Future> verifyAndLogin( + {required String smsCode, required String verificationId}) async { + try { + final AuthCredential authCredential = PhoneAuthProvider.credential( + verificationId: verificationId, smsCode: smsCode); + + final authResult = + await _firebaseAuth.signInWithCredential(authCredential); + + return right(authResult.user!.phoneNumber!); + } on PlatformException catch (e) { + debugPrint("Error occurred: code: ${e.code} , message: ${e.message}"); + if (e.code == 'ERROR_INVALID_CREDENTIAL') { + return left(const AuthFailure.invalidCredential()); + } else if (e.code == 'ERROR_USER_DISABLED') { + return left(const AuthFailure.userDisable()); + } else if (e.code == 'ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL') { + return left(const AuthFailure.accountExitWithDifferentCred()); + } else if (e.code == 'ERROR_OPERATION_NOT_ALLOWED') { + return left(const AuthFailure.operationNotAllowed()); + } else if (e.code == 'ERROR_INVALID_VERIFICATION_CODE') { + return left(const AuthFailure.invalidVerificationCode()); + } else if (e.code == 'ERROR_INVALID_ACTION_CODE') { + return left(const AuthFailure.invalidVerificationCode()); + } else { + debugPrint("Error occurred: code: ${e.code} , message: ${e.message}"); + return left(const AuthFailure.serverError()); + } + } + } + + @override + Future> signUpWithEmailAndPassword({ + required String email, + required String password, + }) async { + try { + final userCred = await _firebaseAuth.createUserWithEmailAndPassword( + email: email, + password: password, + ); + final user = UserDTO.fromFirebase(userCred.user!).toDomain(); + return right(user); + } on FirebaseAuthException catch (e) { + if (e.code == 'email-already-in-use') { + return left(const AuthFailure.emailAlreadyInUse()); + } else if (e.code == 'invalid-email') { + return left(const AuthFailure.invalidEmailAndPasswordCombination()); + } else if (e.code == 'weak-password') { + return left(const AuthFailure.weakPassword()); + } else if (e.code == 'operation-not-allowed') { + return left(const AuthFailure.operationNotAllowed()); + } else if (e.code == 'user-disabled') { + return left(const AuthFailure.userDisable()); + } + return left(const AuthFailure.unexpected()); + } catch (e) { + return left(const AuthFailure.serverError()); + } + } + + @override + Future> signOut() async { + try { + await Future.wait([ + _firebaseAuth.signOut(), + ]); + return right(unit); + } on SocketException catch (e) { + return left(AuthFailure.fromErrorMessage(e.message)); + } on PlatformException catch (e) { + return left(AuthFailure.fromErrorMessage(e.code)); + } on FirebaseAuthException catch (e) { + return left(AuthFailure.fromErrorMessage(e.code)); + } on FirebaseException catch (e) { + return left(AuthFailure.fromErrorMessage(e.code)); + } + } + +} diff --git a/lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart new file mode 100644 index 0000000..019b4a6 --- /dev/null +++ b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart @@ -0,0 +1,184 @@ +import 'package:bloc/bloc.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:formz/formz.dart'; +import 'package:fpb/authentication_with_phone_number/domain/i_phone_number_repository_facade.dart'; +import 'package:fpb/core/failures/auth_failure.dart'; +import 'package:fpb/sign_in/domain/otp_field.dart'; +import 'package:fpb/sign_in/domain/password.dart'; +import 'package:fpb/sign_in/domain/phone_number.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +part 'phone_number_event.dart'; +part 'phone_number_state.dart'; +part 'phone_number_bloc.freezed.dart'; + +@injectable +class PhoneNumberBloc extends Bloc { + PhoneNumberBloc({required this.phoneNumberRepository,}) : super(const PhoneNumberState()) { + on<_PhoneNumberChanged>(_onPhoneNumberChanged); + on<_PhoneNumberSubmitted>(_onPhoneNumberSubmitted); + + on<_OtpChanged>(_onOtpChanged); + on<_OtpSubmitted>(_onOtpSubmitted); + + on<_PasswordChanged>(_onPasswordChanged); + on<_ConfirmPasswordChanged>(_onConfirmPasswordChanged); + on<_NewPasswordSubmitted>(_onNewPasswordSubmitted); + } + + final IPhoneNumberRepositoryFacade phoneNumberRepository; + late String verificationId; + + void _onPhoneNumberChanged( + _PhoneNumberChanged event, + Emitter emit, + ) { + final phoneNumber = PhoneNumber.dirty(event.phoneNumber); + emit( + state.copyWith( + phoneNumber: phoneNumber, + ), + ); + } + + Future _onPhoneNumberSubmitted( + _PhoneNumberSubmitted event, + Emitter emit, + ) async { + if (state.phoneNumber.valid) { + emit(state.copyWith(status: FormzStatus.submissionInProgress)); + try { + await phoneNumberRepository.sendOtpCode( + phoneNumber: state.phoneNumber.value, + codeSent: (String _verificationId, int? resendToken) { + verificationId = _verificationId; + emit(state.copyWith(status: FormzStatus.submissionSuccess)); + print("SendOTP: Here's the code sent"); + }, + verificationCompleted: (PhoneAuthCredential credential) { + print("SendOTP: Verif complete"); + }, + verificationFailed: (FirebaseAuthException e) { + emit(state.copyWith( + status: FormzStatus.submissionFailure, + failure: AuthFailure.fromErrorMessage( + e.message ?? 'Oops!! something went wrong'))); + print("SendOTP: Verification failed"); + }, + codeAutoRetrievalTimeout: (String verificationId) { + print("SendOTP: time out"); + }, + ); + } catch (_) { + emit(state.copyWith( + status: FormzStatus.submissionFailure, + failure: AuthFailure.fromErrorMessage( + 'Oops!! something went wrong', + ), + )); + } + }else{ + + } + } + + + void _onOtpChanged( + _OtpChanged event, + Emitter emit, + ) { + final otpCode = OtpField.dirty(event.otpCode); + emit( + state.copyWith( + otpCode: otpCode, + status: Formz.validate([otpCode]), + ), + ); + + if(state.status.isValid && state.otpCode.value.length >= 6){ + add(PhoneNumberEvent.submitPhoneNumber()); + } + } + + Future _onOtpSubmitted( + _OtpSubmitted event, + Emitter emit, + ) async { + if (state.status.isValidated) { + emit(state.copyWith(status: FormzStatus.submissionInProgress)); + try { + final failureOrUserPhoneNumber = + await phoneNumberRepository.verifyAndLogin( + smsCode: state.otpCode.value, verificationId: verificationId, + ); + failureOrUserPhoneNumber.fold( + (failure) => + emit(state.copyWith(status: FormzStatus.submissionFailure, failure: failure)), + (userPhoneNumber){ + return emit(state.copyWith(status: FormzStatus.submissionSuccess)); + }); + } catch (_) { + emit(state.copyWith(status: FormzStatus.submissionFailure, failure: AuthFailure.fromErrorMessage('Oops!! something went wrong'),),); + } + } + } + + + void _onPasswordChanged( + _PasswordChanged event, + Emitter emit, + ) { + final password = Password.dirty(event.password); + emit( + state.copyWith( + password: password, + status: Formz.validate([password]), failure: AuthFailure.fromErrorMessage('Oops!! something went wrong'), + ), + ); + } + + void _onConfirmPasswordChanged( + _ConfirmPasswordChanged event, + Emitter emit, + ) { + final confirmPassword = Password.dirty(event.password); + FormzStatus status = Formz.validate([confirmPassword]);; + if(status == FormzStatus.valid){ + if(confirmPassword.value != state.password.value){ + status = FormzStatus.invalid; + } + } + emit( + state.copyWith( + confirmPassword: confirmPassword, + status: status , failure: AuthFailure.fromErrorMessage('Oops!! something went wrong'), + ), + ); + } + + Future _onNewPasswordSubmitted( + _NewPasswordSubmitted event, + Emitter emit, + ) async { + if (state.status.isValidated) { + emit(state.copyWith(status: FormzStatus.submissionInProgress)); + final String emailDomain = '@flutterplaza.com'; + String email = '${state.phoneNumber}$emailDomain'; + + try { + final failureOrUser = + await phoneNumberRepository.signUpWithEmailAndPassword(email: email, password: state.password.value); + failureOrUser.fold( + (failure) => + emit(state.copyWith(status: FormzStatus.submissionFailure, failure: failure)), + (user) { + return emit(state.copyWith(status: FormzStatus.submissionSuccess)); + } + ); + } catch (_) { + emit(state.copyWith(status: FormzStatus.submissionFailure, failure: AuthFailure.fromErrorMessage('Oops!! something went wrong'),),); + } + } + } +} diff --git a/lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.freezed.dart b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.freezed.dart new file mode 100644 index 0000000..cff356c --- /dev/null +++ b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.freezed.dart @@ -0,0 +1,1415 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'phone_number_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$PhoneNumberEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PhoneNumberEventCopyWith<$Res> { + factory $PhoneNumberEventCopyWith( + PhoneNumberEvent value, $Res Function(PhoneNumberEvent) then) = + _$PhoneNumberEventCopyWithImpl<$Res, PhoneNumberEvent>; +} + +/// @nodoc +class _$PhoneNumberEventCopyWithImpl<$Res, $Val extends PhoneNumberEvent> + implements $PhoneNumberEventCopyWith<$Res> { + _$PhoneNumberEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$_PhoneNumberChangedCopyWith<$Res> { + factory _$$_PhoneNumberChangedCopyWith(_$_PhoneNumberChanged value, + $Res Function(_$_PhoneNumberChanged) then) = + __$$_PhoneNumberChangedCopyWithImpl<$Res>; + @useResult + $Res call({String phoneNumber}); +} + +/// @nodoc +class __$$_PhoneNumberChangedCopyWithImpl<$Res> + extends _$PhoneNumberEventCopyWithImpl<$Res, _$_PhoneNumberChanged> + implements _$$_PhoneNumberChangedCopyWith<$Res> { + __$$_PhoneNumberChangedCopyWithImpl( + _$_PhoneNumberChanged _value, $Res Function(_$_PhoneNumberChanged) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + }) { + return _then(_$_PhoneNumberChanged( + null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_PhoneNumberChanged implements _PhoneNumberChanged { + const _$_PhoneNumberChanged(this.phoneNumber); + + @override + final String phoneNumber; + + @override + String toString() { + return 'PhoneNumberEvent.phoneNumberChanged(phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_PhoneNumberChanged && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber)); + } + + @override + int get hashCode => Object.hash(runtimeType, phoneNumber); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_PhoneNumberChangedCopyWith<_$_PhoneNumberChanged> get copyWith => + __$$_PhoneNumberChangedCopyWithImpl<_$_PhoneNumberChanged>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) { + return phoneNumberChanged(phoneNumber); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) { + return phoneNumberChanged?.call(phoneNumber); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(phoneNumber); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) { + return phoneNumberChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) { + return phoneNumberChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(this); + } + return orElse(); + } +} + +abstract class _PhoneNumberChanged implements PhoneNumberEvent { + const factory _PhoneNumberChanged(final String phoneNumber) = + _$_PhoneNumberChanged; + + String get phoneNumber; + @JsonKey(ignore: true) + _$$_PhoneNumberChangedCopyWith<_$_PhoneNumberChanged> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_PhoneNumberSubmittedCopyWith<$Res> { + factory _$$_PhoneNumberSubmittedCopyWith(_$_PhoneNumberSubmitted value, + $Res Function(_$_PhoneNumberSubmitted) then) = + __$$_PhoneNumberSubmittedCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_PhoneNumberSubmittedCopyWithImpl<$Res> + extends _$PhoneNumberEventCopyWithImpl<$Res, _$_PhoneNumberSubmitted> + implements _$$_PhoneNumberSubmittedCopyWith<$Res> { + __$$_PhoneNumberSubmittedCopyWithImpl(_$_PhoneNumberSubmitted _value, + $Res Function(_$_PhoneNumberSubmitted) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_PhoneNumberSubmitted implements _PhoneNumberSubmitted { + const _$_PhoneNumberSubmitted(); + + @override + String toString() { + return 'PhoneNumberEvent.submitPhoneNumber()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$_PhoneNumberSubmitted); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) { + return submitPhoneNumber(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) { + return submitPhoneNumber?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) { + if (submitPhoneNumber != null) { + return submitPhoneNumber(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) { + return submitPhoneNumber(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) { + return submitPhoneNumber?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) { + if (submitPhoneNumber != null) { + return submitPhoneNumber(this); + } + return orElse(); + } +} + +abstract class _PhoneNumberSubmitted implements PhoneNumberEvent { + const factory _PhoneNumberSubmitted() = _$_PhoneNumberSubmitted; +} + +/// @nodoc +abstract class _$$_OtpChangedCopyWith<$Res> { + factory _$$_OtpChangedCopyWith( + _$_OtpChanged value, $Res Function(_$_OtpChanged) then) = + __$$_OtpChangedCopyWithImpl<$Res>; + @useResult + $Res call({String otpCode}); +} + +/// @nodoc +class __$$_OtpChangedCopyWithImpl<$Res> + extends _$PhoneNumberEventCopyWithImpl<$Res, _$_OtpChanged> + implements _$$_OtpChangedCopyWith<$Res> { + __$$_OtpChangedCopyWithImpl( + _$_OtpChanged _value, $Res Function(_$_OtpChanged) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? otpCode = null, + }) { + return _then(_$_OtpChanged( + null == otpCode + ? _value.otpCode + : otpCode // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_OtpChanged implements _OtpChanged { + const _$_OtpChanged(this.otpCode); + + @override + final String otpCode; + + @override + String toString() { + return 'PhoneNumberEvent.otpChanged(otpCode: $otpCode)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_OtpChanged && + (identical(other.otpCode, otpCode) || other.otpCode == otpCode)); + } + + @override + int get hashCode => Object.hash(runtimeType, otpCode); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_OtpChangedCopyWith<_$_OtpChanged> get copyWith => + __$$_OtpChangedCopyWithImpl<_$_OtpChanged>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) { + return otpChanged(otpCode); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) { + return otpChanged?.call(otpCode); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) { + if (otpChanged != null) { + return otpChanged(otpCode); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) { + return otpChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) { + return otpChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) { + if (otpChanged != null) { + return otpChanged(this); + } + return orElse(); + } +} + +abstract class _OtpChanged implements PhoneNumberEvent { + const factory _OtpChanged(final String otpCode) = _$_OtpChanged; + + String get otpCode; + @JsonKey(ignore: true) + _$$_OtpChangedCopyWith<_$_OtpChanged> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_OtpSubmittedCopyWith<$Res> { + factory _$$_OtpSubmittedCopyWith( + _$_OtpSubmitted value, $Res Function(_$_OtpSubmitted) then) = + __$$_OtpSubmittedCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_OtpSubmittedCopyWithImpl<$Res> + extends _$PhoneNumberEventCopyWithImpl<$Res, _$_OtpSubmitted> + implements _$$_OtpSubmittedCopyWith<$Res> { + __$$_OtpSubmittedCopyWithImpl( + _$_OtpSubmitted _value, $Res Function(_$_OtpSubmitted) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_OtpSubmitted implements _OtpSubmitted { + const _$_OtpSubmitted(); + + @override + String toString() { + return 'PhoneNumberEvent.submitOtp()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$_OtpSubmitted); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) { + return submitOtp(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) { + return submitOtp?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) { + if (submitOtp != null) { + return submitOtp(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) { + return submitOtp(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) { + return submitOtp?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) { + if (submitOtp != null) { + return submitOtp(this); + } + return orElse(); + } +} + +abstract class _OtpSubmitted implements PhoneNumberEvent { + const factory _OtpSubmitted() = _$_OtpSubmitted; +} + +/// @nodoc +abstract class _$$_PasswordChangedCopyWith<$Res> { + factory _$$_PasswordChangedCopyWith( + _$_PasswordChanged value, $Res Function(_$_PasswordChanged) then) = + __$$_PasswordChangedCopyWithImpl<$Res>; + @useResult + $Res call({String password}); +} + +/// @nodoc +class __$$_PasswordChangedCopyWithImpl<$Res> + extends _$PhoneNumberEventCopyWithImpl<$Res, _$_PasswordChanged> + implements _$$_PasswordChangedCopyWith<$Res> { + __$$_PasswordChangedCopyWithImpl( + _$_PasswordChanged _value, $Res Function(_$_PasswordChanged) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? password = null, + }) { + return _then(_$_PasswordChanged( + null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_PasswordChanged implements _PasswordChanged { + const _$_PasswordChanged(this.password); + + @override + final String password; + + @override + String toString() { + return 'PhoneNumberEvent.passwordChanged(password: $password)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_PasswordChanged && + (identical(other.password, password) || + other.password == password)); + } + + @override + int get hashCode => Object.hash(runtimeType, password); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_PasswordChangedCopyWith<_$_PasswordChanged> get copyWith => + __$$_PasswordChangedCopyWithImpl<_$_PasswordChanged>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) { + return passwordChanged(password); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) { + return passwordChanged?.call(password); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) { + if (passwordChanged != null) { + return passwordChanged(password); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) { + return passwordChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) { + return passwordChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) { + if (passwordChanged != null) { + return passwordChanged(this); + } + return orElse(); + } +} + +abstract class _PasswordChanged implements PhoneNumberEvent { + const factory _PasswordChanged(final String password) = _$_PasswordChanged; + + String get password; + @JsonKey(ignore: true) + _$$_PasswordChangedCopyWith<_$_PasswordChanged> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_ConfirmPasswordChangedCopyWith<$Res> { + factory _$$_ConfirmPasswordChangedCopyWith(_$_ConfirmPasswordChanged value, + $Res Function(_$_ConfirmPasswordChanged) then) = + __$$_ConfirmPasswordChangedCopyWithImpl<$Res>; + @useResult + $Res call({String password}); +} + +/// @nodoc +class __$$_ConfirmPasswordChangedCopyWithImpl<$Res> + extends _$PhoneNumberEventCopyWithImpl<$Res, _$_ConfirmPasswordChanged> + implements _$$_ConfirmPasswordChangedCopyWith<$Res> { + __$$_ConfirmPasswordChangedCopyWithImpl(_$_ConfirmPasswordChanged _value, + $Res Function(_$_ConfirmPasswordChanged) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? password = null, + }) { + return _then(_$_ConfirmPasswordChanged( + null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_ConfirmPasswordChanged implements _ConfirmPasswordChanged { + const _$_ConfirmPasswordChanged(this.password); + + @override + final String password; + + @override + String toString() { + return 'PhoneNumberEvent.confirmPasswordChanged(password: $password)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_ConfirmPasswordChanged && + (identical(other.password, password) || + other.password == password)); + } + + @override + int get hashCode => Object.hash(runtimeType, password); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_ConfirmPasswordChangedCopyWith<_$_ConfirmPasswordChanged> get copyWith => + __$$_ConfirmPasswordChangedCopyWithImpl<_$_ConfirmPasswordChanged>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) { + return confirmPasswordChanged(password); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) { + return confirmPasswordChanged?.call(password); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) { + if (confirmPasswordChanged != null) { + return confirmPasswordChanged(password); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) { + return confirmPasswordChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) { + return confirmPasswordChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) { + if (confirmPasswordChanged != null) { + return confirmPasswordChanged(this); + } + return orElse(); + } +} + +abstract class _ConfirmPasswordChanged implements PhoneNumberEvent { + const factory _ConfirmPasswordChanged(final String password) = + _$_ConfirmPasswordChanged; + + String get password; + @JsonKey(ignore: true) + _$$_ConfirmPasswordChangedCopyWith<_$_ConfirmPasswordChanged> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_NewPasswordSubmittedCopyWith<$Res> { + factory _$$_NewPasswordSubmittedCopyWith(_$_NewPasswordSubmitted value, + $Res Function(_$_NewPasswordSubmitted) then) = + __$$_NewPasswordSubmittedCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_NewPasswordSubmittedCopyWithImpl<$Res> + extends _$PhoneNumberEventCopyWithImpl<$Res, _$_NewPasswordSubmitted> + implements _$$_NewPasswordSubmittedCopyWith<$Res> { + __$$_NewPasswordSubmittedCopyWithImpl(_$_NewPasswordSubmitted _value, + $Res Function(_$_NewPasswordSubmitted) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_NewPasswordSubmitted implements _NewPasswordSubmitted { + const _$_NewPasswordSubmitted(); + + @override + String toString() { + return 'PhoneNumberEvent.submitNewPassword()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$_NewPasswordSubmitted); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitPhoneNumber, + required TResult Function(String otpCode) otpChanged, + required TResult Function() submitOtp, + required TResult Function(String password) passwordChanged, + required TResult Function(String password) confirmPasswordChanged, + required TResult Function() submitNewPassword, + }) { + return submitNewPassword(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitPhoneNumber, + TResult? Function(String otpCode)? otpChanged, + TResult? Function()? submitOtp, + TResult? Function(String password)? passwordChanged, + TResult? Function(String password)? confirmPasswordChanged, + TResult? Function()? submitNewPassword, + }) { + return submitNewPassword?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitPhoneNumber, + TResult Function(String otpCode)? otpChanged, + TResult Function()? submitOtp, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? confirmPasswordChanged, + TResult Function()? submitNewPassword, + required TResult orElse(), + }) { + if (submitNewPassword != null) { + return submitNewPassword(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PhoneNumberSubmitted value) submitPhoneNumber, + required TResult Function(_OtpChanged value) otpChanged, + required TResult Function(_OtpSubmitted value) submitOtp, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_NewPasswordSubmitted value) submitNewPassword, + }) { + return submitNewPassword(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult? Function(_OtpChanged value)? otpChanged, + TResult? Function(_OtpSubmitted value)? submitOtp, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_NewPasswordSubmitted value)? submitNewPassword, + }) { + return submitNewPassword?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PhoneNumberSubmitted value)? submitPhoneNumber, + TResult Function(_OtpChanged value)? otpChanged, + TResult Function(_OtpSubmitted value)? submitOtp, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_NewPasswordSubmitted value)? submitNewPassword, + required TResult orElse(), + }) { + if (submitNewPassword != null) { + return submitNewPassword(this); + } + return orElse(); + } +} + +abstract class _NewPasswordSubmitted implements PhoneNumberEvent { + const factory _NewPasswordSubmitted() = _$_NewPasswordSubmitted; +} + +/// @nodoc +mixin _$PhoneNumberState { + FormzStatus get status => throw _privateConstructorUsedError; + PhoneNumber get phoneNumber => throw _privateConstructorUsedError; + OtpField get otpCode => throw _privateConstructorUsedError; + Password get password => throw _privateConstructorUsedError; + Password get confirmPassword => throw _privateConstructorUsedError; + AuthFailure? get failure => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $PhoneNumberStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PhoneNumberStateCopyWith<$Res> { + factory $PhoneNumberStateCopyWith( + PhoneNumberState value, $Res Function(PhoneNumberState) then) = + _$PhoneNumberStateCopyWithImpl<$Res, PhoneNumberState>; + @useResult + $Res call( + {FormzStatus status, + PhoneNumber phoneNumber, + OtpField otpCode, + Password password, + Password confirmPassword, + AuthFailure? failure}); + + $AuthFailureCopyWith<$Res>? get failure; +} + +/// @nodoc +class _$PhoneNumberStateCopyWithImpl<$Res, $Val extends PhoneNumberState> + implements $PhoneNumberStateCopyWith<$Res> { + _$PhoneNumberStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? phoneNumber = null, + Object? otpCode = null, + Object? password = null, + Object? confirmPassword = null, + Object? failure = freezed, + }) { + return _then(_value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as FormzStatus, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as PhoneNumber, + otpCode: null == otpCode + ? _value.otpCode + : otpCode // ignore: cast_nullable_to_non_nullable + as OtpField, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as Password, + confirmPassword: null == confirmPassword + ? _value.confirmPassword + : confirmPassword // ignore: cast_nullable_to_non_nullable + as Password, + failure: freezed == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as AuthFailure?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $AuthFailureCopyWith<$Res>? get failure { + if (_value.failure == null) { + return null; + } + + return $AuthFailureCopyWith<$Res>(_value.failure!, (value) { + return _then(_value.copyWith(failure: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$_PhoneNumberStateCopyWith<$Res> + implements $PhoneNumberStateCopyWith<$Res> { + factory _$$_PhoneNumberStateCopyWith( + _$_PhoneNumberState value, $Res Function(_$_PhoneNumberState) then) = + __$$_PhoneNumberStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {FormzStatus status, + PhoneNumber phoneNumber, + OtpField otpCode, + Password password, + Password confirmPassword, + AuthFailure? failure}); + + @override + $AuthFailureCopyWith<$Res>? get failure; +} + +/// @nodoc +class __$$_PhoneNumberStateCopyWithImpl<$Res> + extends _$PhoneNumberStateCopyWithImpl<$Res, _$_PhoneNumberState> + implements _$$_PhoneNumberStateCopyWith<$Res> { + __$$_PhoneNumberStateCopyWithImpl( + _$_PhoneNumberState _value, $Res Function(_$_PhoneNumberState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? phoneNumber = null, + Object? otpCode = null, + Object? password = null, + Object? confirmPassword = null, + Object? failure = freezed, + }) { + return _then(_$_PhoneNumberState( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as FormzStatus, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as PhoneNumber, + otpCode: null == otpCode + ? _value.otpCode + : otpCode // ignore: cast_nullable_to_non_nullable + as OtpField, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as Password, + confirmPassword: null == confirmPassword + ? _value.confirmPassword + : confirmPassword // ignore: cast_nullable_to_non_nullable + as Password, + failure: freezed == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as AuthFailure?, + )); + } +} + +/// @nodoc + +class _$_PhoneNumberState implements _PhoneNumberState { + const _$_PhoneNumberState( + {this.status = FormzStatus.pure, + this.phoneNumber = const PhoneNumber.pure(), + this.otpCode = const OtpField.pure(), + this.password = const Password.pure(), + this.confirmPassword = const Password.pure(), + this.failure}); + + @override + @JsonKey() + final FormzStatus status; + @override + @JsonKey() + final PhoneNumber phoneNumber; + @override + @JsonKey() + final OtpField otpCode; + @override + @JsonKey() + final Password password; + @override + @JsonKey() + final Password confirmPassword; + @override + final AuthFailure? failure; + + @override + String toString() { + return 'PhoneNumberState(status: $status, phoneNumber: $phoneNumber, otpCode: $otpCode, password: $password, confirmPassword: $confirmPassword, failure: $failure)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_PhoneNumberState && + (identical(other.status, status) || other.status == status) && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.otpCode, otpCode) || other.otpCode == otpCode) && + (identical(other.password, password) || + other.password == password) && + (identical(other.confirmPassword, confirmPassword) || + other.confirmPassword == confirmPassword) && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, status, phoneNumber, otpCode, + password, confirmPassword, failure); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_PhoneNumberStateCopyWith<_$_PhoneNumberState> get copyWith => + __$$_PhoneNumberStateCopyWithImpl<_$_PhoneNumberState>(this, _$identity); +} + +abstract class _PhoneNumberState implements PhoneNumberState { + const factory _PhoneNumberState( + {final FormzStatus status, + final PhoneNumber phoneNumber, + final OtpField otpCode, + final Password password, + final Password confirmPassword, + final AuthFailure? failure}) = _$_PhoneNumberState; + + @override + FormzStatus get status; + @override + PhoneNumber get phoneNumber; + @override + OtpField get otpCode; + @override + Password get password; + @override + Password get confirmPassword; + @override + AuthFailure? get failure; + @override + @JsonKey(ignore: true) + _$$_PhoneNumberStateCopyWith<_$_PhoneNumberState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/authentication_with_phone_number/phone_number_bloc/phone_number_event.dart b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_event.dart new file mode 100644 index 0000000..d2955f8 --- /dev/null +++ b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_event.dart @@ -0,0 +1,14 @@ +part of 'phone_number_bloc.dart'; + +@freezed +class PhoneNumberEvent with _$PhoneNumberEvent { + const factory PhoneNumberEvent.phoneNumberChanged(String phoneNumber) = _PhoneNumberChanged; + const factory PhoneNumberEvent.submitPhoneNumber() = _PhoneNumberSubmitted; + + const factory PhoneNumberEvent.otpChanged(String otpCode) = _OtpChanged; + const factory PhoneNumberEvent.submitOtp() = _OtpSubmitted; + + const factory PhoneNumberEvent.passwordChanged(String password) = _PasswordChanged; + const factory PhoneNumberEvent.confirmPasswordChanged(String password) = _ConfirmPasswordChanged; + const factory PhoneNumberEvent.submitNewPassword() = _NewPasswordSubmitted; +} diff --git a/lib/authentication_with_phone_number/phone_number_bloc/phone_number_state.dart b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_state.dart new file mode 100644 index 0000000..122ce01 --- /dev/null +++ b/lib/authentication_with_phone_number/phone_number_bloc/phone_number_state.dart @@ -0,0 +1,14 @@ +part of 'phone_number_bloc.dart'; + + +@freezed +class PhoneNumberState with _$PhoneNumberState { + const factory PhoneNumberState({ + @Default(FormzStatus.pure) FormzStatus status, + @Default(PhoneNumber.pure()) PhoneNumber phoneNumber, + @Default(OtpField.pure()) OtpField otpCode, + @Default(Password.pure()) Password password, + @Default(Password.pure()) Password confirmPassword, + AuthFailure? failure, + }) = _PhoneNumberState; +} diff --git a/lib/authentication_with_phone_number/views/create_password_phone_signup_screen.dart b/lib/authentication_with_phone_number/views/create_password_phone_signup_screen.dart new file mode 100644 index 0000000..2460991 --- /dev/null +++ b/lib/authentication_with_phone_number/views/create_password_phone_signup_screen.dart @@ -0,0 +1,49 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:formz/formz.dart'; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart'; +import 'package:fpb/core/presentation/widget/create_new_password.dart'; +import 'package:fpb/home/home_screen.dart'; + +class CreateNewPasswordPhoneSignupScreen extends StatelessWidget { + const CreateNewPasswordPhoneSignupScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return BlocConsumer( + listener: (context, state) { + if(state.status == FormzStatus.submissionFailure){ + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: theme.colorScheme.error, + elevation: 0, + content: Text(state.failure!.message), + ), + ); + }else if(state.status == FormzStatus.submissionSuccess){ + context.router.pushNamed(HomeScreen.routeName,); + } + }, + builder: (context, state) { + return CreateNewPassword( + onTap: () { + context.read().add(PhoneNumberEvent.submitNewPassword()); + }, + onPasswordChange: (String password) { + context.read() + .add(PhoneNumberEvent.passwordChanged(password)); + }, + onConfirmPasswordChange: (String confirmPassword) { + context.read().add( + PhoneNumberEvent.confirmPasswordChanged(confirmPassword)); + }, + passwordErrorText: state.password.invalid ? "Your password is wrong" : null, + confirmPasswordErrorText: state.confirmPassword.invalid ? "The passwords don't match" : null, + ); + }, + ); + } +} diff --git a/lib/authentication_with_phone_number/views/phone_number_input_signup_flow.dart b/lib/authentication_with_phone_number/views/phone_number_input_signup_flow.dart new file mode 100644 index 0000000..cea5598 --- /dev/null +++ b/lib/authentication_with_phone_number/views/phone_number_input_signup_flow.dart @@ -0,0 +1,76 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:fpb/l10n/l10n.dart'; +import 'package:intl_phone_field/intl_phone_field.dart'; +import 'package:intl_phone_field/phone_number.dart'; + +class PhoneNumberInputSignupFlow extends StatefulWidget { + const PhoneNumberInputSignupFlow({ + super.key, + required this.l10n, + required this.cts, + this.node, + this.phoneNumberController, + required this.onChanged, + required this.validator, + }); + + final AppLocalizations l10n; + final FocusNode? node; + final TextEditingController? phoneNumberController; + final BoxConstraints cts; + final void Function(PhoneNumber phoneNumber) onChanged; + final FutureOr Function(PhoneNumber? phoneNumber)? validator; + + @override + State createState() => _PhoneNumberInputSignupFlowState(); +} + +class _PhoneNumberInputSignupFlowState extends State { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final style = theme.textTheme; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.l10n.signInPhoneNumberFieldLabel, + style: style.titleSmall, + ), + SizedBox( + height: widget.cts.maxHeight * 0.01, + ), + IntlPhoneField( + controller: widget.phoneNumberController, + disableLengthCheck: true, + flagsButtonPadding: EdgeInsets.all( + widget.cts.maxHeight * 0.01, + ), + onChanged: widget.onChanged, + dropdownIconPosition: IconPosition.trailing, + decoration: InputDecoration( + contentPadding: EdgeInsets.all( + widget.cts.maxHeight * 0.025, + ), + labelText: '1 234 89 9000', + border: OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular( + widget.cts.maxHeight * 0.025, + ), + ), + ), + ), + validator: widget.validator, + initialCountryCode: 'US', + autovalidateMode: AutovalidateMode.onUserInteraction, + ), + SizedBox( + height: widget.cts.maxHeight * .15, + ) + ], + ); + } +} diff --git a/lib/core/presentation/widget/bubbles_top_background.dart b/lib/core/presentation/widget/bubbles_top_background.dart new file mode 100644 index 0000000..fa7a155 --- /dev/null +++ b/lib/core/presentation/widget/bubbles_top_background.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class BubblesTopBackGround extends StatelessWidget { + const BubblesTopBackGround({ + super.key, + required this.cts, + required this.svgName, + }); + final BoxConstraints cts; + final String svgName; + + @override + Widget build(BuildContext context) { + return Positioned( + top: -.035 * cts.maxHeight, + child: SvgPicture.asset( + svgName, + width: cts.maxWidth, + height: 0.4 * cts.maxHeight, + ), + ); + } +} diff --git a/lib/core/presentation/widget/create_new_password.dart b/lib/core/presentation/widget/create_new_password.dart new file mode 100644 index 0000000..6e3e88f --- /dev/null +++ b/lib/core/presentation/widget/create_new_password.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:fpb/assets/fpb_svg.dart'; +import 'package:fpb/core/presentation/extension/extensions.dart'; +import 'package:fpb/core/presentation/widget/bubbles_top_background.dart'; +import 'package:fpb/core/presentation/widget/fpb_button.dart'; +import 'package:fpb/core/presentation/widget/fpb_text_form_field.dart'; +import 'package:fpb/core/presentation/widget/medium_sized_page_title.dart'; +import 'package:fpb/core/shared/helpers/is_keyboard_visible.dart'; +import 'package:fpb/l10n/l10n.dart'; + +class CreateNewPassword extends StatefulWidget { + const CreateNewPassword({ + Key? key, + required this.onTap, + required this.passwordErrorText, + required this.confirmPasswordErrorText, + required this.onPasswordChange, + required this.onConfirmPasswordChange, + }) : super(key: key); + + final void Function() onTap; + final void Function(String password)? onPasswordChange; + final void Function(String confirmPAssword)? onConfirmPasswordChange; + final String? passwordErrorText; + final String? confirmPasswordErrorText; + + @override + State createState() => _CreateNewPasswordState(); +} + +class _CreateNewPasswordState extends State { + final FocusNode? node = FocusNode(); + final TextEditingController passwordController = TextEditingController(); + final TextEditingController confirmPasswordController = + TextEditingController(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + + return LayoutBuilder( + builder: (context, cts) { + return Scaffold( + resizeToAvoidBottomInset: false, + body: Stack( + children: [ + BubblesTopBackGround( + cts: cts, + svgName: SvgNames.authBackground, + ), + Align( + alignment: Alignment.bottomCenter, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PageTitle( + title: l10n.createNewPasswordTitle, + box: cts, + ), + SizedBox( + height: 0.025 * cts.maxHeight, + ), + Text( + l10n.createNewPasswordBodyContent, + style: TextStyle( + height: 1.5, + ), + ), + SizedBox( + height: 0.05 * cts.maxHeight, + ), + FpbTextFormField( + key: const Key('password_textField_create_new_password'), + box: cts, + label: l10n.signInPasswordFieldLabel, + hint: l10n.signInPasswordFieldHintText, + node: node, + isPassword: true, + textController: passwordController, + onChanged: widget.onPasswordChange, + errorText: widget.passwordErrorText, + ), + FpbTextFormField( + key: const Key( + 'confirm_password_textField_create_new_password'), + box: cts, + label: l10n.createNewPasswordConfirmPasswordLabel, + hint: l10n.signInPasswordFieldHintText, + node: node, + isPassword: true, + textController: confirmPasswordController, + onChanged: widget.onConfirmPasswordChange, + errorText: widget.confirmPasswordErrorText, + ), + SizedBox( + height: 0.03 * cts.maxHeight, + ), + FpbButton( + label: l10n.createNewPasswordResetButtonLabel, + onTap: widget.onTap, + ), + ], + ).card( + height: + (isKeyboardVisible(context) ? .80 : .8) * cts.maxHeight, + radiusTop: cts.maxWidth * 0.05, + color: theme.colorScheme.background, + padding: EdgeInsets.all(cts.maxHeight * 0.025), + ), + ) + ], + ).card( + radiusTop: cts.maxWidth * 0.05, + color: theme.colorScheme.background, + ), + ); + }, + ); + } +} diff --git a/lib/core/presentation/widget/fpb_button.dart b/lib/core/presentation/widget/fpb_button.dart index 5d0b417..8bee792 100644 --- a/lib/core/presentation/widget/fpb_button.dart +++ b/lib/core/presentation/widget/fpb_button.dart @@ -9,6 +9,7 @@ class FpbButton extends StatelessWidget { this.height, this.width, this.leading, + this.heading, this.spaceAround = false, this.backgroundColor, this.borderSideColor, @@ -21,6 +22,7 @@ class FpbButton extends StatelessWidget { final double? height; final double? width; final Widget? leading; + final Widget? heading; final bool spaceAround; final Color? backgroundColor; final Color? borderSideColor; @@ -43,7 +45,7 @@ class FpbButton extends StatelessWidget { ? MainAxisAlignment.spaceAround : MainAxisAlignment.spaceBetween, children: [ - leading ?? const SizedBox.shrink(), + heading ?? const SizedBox.shrink(), Text( label, style: Theme.of(context).textTheme.titleMedium?.copyWith( diff --git a/lib/core/presentation/widget/medium_sized_page_title.dart b/lib/core/presentation/widget/medium_sized_page_title.dart new file mode 100644 index 0000000..7e6a0f4 --- /dev/null +++ b/lib/core/presentation/widget/medium_sized_page_title.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class PageTitle extends StatelessWidget { + const PageTitle({ + super.key, + required this.title, + required this.box, + }); + final String title; + final BoxConstraints box; + + @override + Widget build(BuildContext context) { + return Text( + title, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontSize: box.maxWidth * 0.085, + fontWeight: FontWeight.w700, + ), + ); + } +} + diff --git a/lib/core/presentation/widget/my_button.dart b/lib/core/presentation/widget/my_button.dart new file mode 100644 index 0000000..06798fa --- /dev/null +++ b/lib/core/presentation/widget/my_button.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class FpbButton extends StatelessWidget { + const FpbButton({ + super.key, + required this.label, + required this.onTap, + this.trailing, + this.heading, + this.height, + this.width, + }); + + final String label; + final void Function()? onTap; + final Widget? trailing; + final Widget? heading; + final double? height; + final double? width; + + @override + Widget build(BuildContext context) { + final text = Text( + label, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Colors.white, + // fontWeight: FontWeight.w400, + ), + ); + final size = MediaQuery.of(context).size; + return SizedBox( + width: width ?? size.width, + height: height ?? size.height * 0.075, + child: ElevatedButton( + clipBehavior: Clip.hardEdge, + onPressed: onTap, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + heading ?? const SizedBox.shrink(), + text, + if (trailing != null) + Transform.translate( + offset: const Offset(-15, 0), + child: trailing, + ) + else + const SizedBox.shrink() + ], + ), + ), + ); + } +} diff --git a/lib/core/presentation/widget/my_textformfield.dart b/lib/core/presentation/widget/my_textformfield.dart new file mode 100644 index 0000000..672ef6a --- /dev/null +++ b/lib/core/presentation/widget/my_textformfield.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:fpb/assets/fpb_icons/fpb_icons_icons.dart'; +import 'package:fpb/core/presentation/extension/extensions.dart'; + +class FpbTextFormField extends StatefulWidget { + const FpbTextFormField({ + super.key, + required this.label, + required this.hint, + this.textController, + this.node, + this.isEmail = false, + this.isPassword = false, + this.onChanged, + this.errorText, + this.showLabelText = true, + required this.box, + }); + + final String label; + final String hint; + final bool isEmail; + final bool isPassword; + final TextEditingController? textController; + final FocusNode? node; + final void Function(String)? onChanged; + final String? errorText; + final BoxConstraints box; + final bool showLabelText; + + @override + State createState() => _FpbTextFormFieldState(); +} + +class _FpbTextFormFieldState extends State { + late bool? hidePassword; + bool? showLabel; + + @override + void initState() { + super.initState(); + hidePassword = widget.isPassword ? false : null; + showLabel = widget.showLabelText ? true : false; + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + showLabel == true + ? Text( + widget.label, + style: textTheme.titleSmall, + ) + : SizedBox(), + TextFormField( + controller: widget.textController, + focusNode: widget.node, + keyboardType: widget.isEmail + ? TextInputType.emailAddress + : widget.isPassword + ? TextInputType.visiblePassword + : TextInputType.text, + onChanged: widget.onChanged, + obscureText: hidePassword ?? false, + style: textTheme + .titleSmall, //bodyMedium //.copyWith(color: colors.onSurface), + decoration: InputDecoration( + errorText: widget.errorText, + suffixIcon: !widget.isPassword + ? null + : hidePassword! + ? Padding( + padding: + EdgeInsets.only(right: widget.box.maxWidth * 0.05), + child: IconButton( + onPressed: () { + setState(() { + hidePassword = !hidePassword!; + }); + }, + icon: const Icon(FpbIcons.eye_closed), + color: widget.node != null + ? widget.node!.hasFocus + ? theme.colorScheme.onSurface + : null + : theme.colorScheme.onSurface, + ), + ) + : Padding( + padding: + EdgeInsets.only(right: widget.box.maxWidth * 0.05), + child: IconButton( + onPressed: () { + setState(() { + hidePassword = !hidePassword!; + }); + }, + icon: const Icon( + FpbIcons.eye_open, + ), + color: widget.node != null + ? widget.node!.hasFocus + ? theme.colorScheme.onSurface + : null + : theme.colorScheme.onSurface, + ), + ), + hintText: widget.hint, + ), + ).card( + height: widget.box.maxHeight * 0.11, + padding: EdgeInsets.symmetric( + vertical: widget.box.maxHeight * 0.007, + ), + ), + ], + ); + } +} diff --git a/lib/core/presentation/widget/otp_group_text_field.dart b/lib/core/presentation/widget/otp_group_text_field.dart index 1dc49ec..08ea11e 100644 --- a/lib/core/presentation/widget/otp_group_text_field.dart +++ b/lib/core/presentation/widget/otp_group_text_field.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart'; import 'package:fpb/core/presentation/widget/otp_input.dart'; class OtpGroupTextField extends StatelessWidget { @@ -11,32 +13,35 @@ class OtpGroupTextField extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Wrap( + return BlocBuilder( + builder: (context, state) { + int otpCodeLength = state.otpCode.value.length; + return Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - OtpInput( - box: box, - ), - OtpInput( - box: box, - ), - OtpInput( - box: box, - ), - OtpInput( - box: box, - ), - OtpInput( - box: box, - ), - OtpInput( - box: box, + Wrap( + children: [ + for(int i = 0; i < 6; i++) + OtpInput( + enabled: i <= otpCodeLength, + box: box, + onChanged: (String value) { + List otpCode = state.otpCode.value.split(''); + if(i <= otpCodeLength){ + otpCode[i] = value; + }else{ + otpCode.add(value); + } + final String newOtpCode = otpCode.join(''); + context.read().add(PhoneNumberEvent.otpChanged(newOtpCode)); + print('New Otp Code: $newOtpCode'); + }, + ), + ], ), ], - ), - ], + ); + }, ); } } diff --git a/lib/core/presentation/widget/otp_input.dart b/lib/core/presentation/widget/otp_input.dart index 93ddf12..8b8d951 100644 --- a/lib/core/presentation/widget/otp_input.dart +++ b/lib/core/presentation/widget/otp_input.dart @@ -4,9 +4,13 @@ class OtpInput extends StatelessWidget { const OtpInput({ super.key, required this.box, + required this.onChanged, required this.enabled, }); final BoxConstraints box; + final void Function(String value) onChanged; + final bool enabled; + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -20,6 +24,8 @@ class OtpInput extends StatelessWidget { child: Flexible( fit: FlexFit.loose, child: TextFormField( + onChanged: onChanged, + enabled: enabled, cursorColor: colors.scrim, textAlign: TextAlign.center, decoration: InputDecoration( diff --git a/lib/email_confirmation/view/email_confirmation_page.dart b/lib/email_confirmation/view/email_confirmation_page.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/home/view/dashboard.dart b/lib/home/view/dashboard.dart index 96c0ab0..19ba19a 100644 --- a/lib/home/view/dashboard.dart +++ b/lib/home/view/dashboard.dart @@ -8,9 +8,8 @@ import 'package:fpb/core/presentation/widget/vertical_spacing_widget.dart'; import 'package:fpb/core/shared/helpers/value_injector.dart'; import 'package:fpb/home/view/widgets/card_stack_widget.dart'; import 'package:fpb/home/view/widgets/cash_balance_widget.dart'; -import 'package:fpb/home/view/widgets/custom_appbar.dart'; import 'package:fpb/home/view/widgets/display_latest_activity.dart'; -import 'package:fpb/home/view/widgets/row_header_icons.dart'; +import 'package:fpb/home/view/widgets/navbar_header.dart'; import 'package:fpb/latest_activities/view/latest_activities_screen.dart'; class DashBoard extends StatelessWidget { @@ -42,21 +41,16 @@ class DashBoard extends StatelessWidget { } return true; }, - child: SafeArea( - child: Scaffold( - appBar: CustomAppBar( - titleChildWidget: CircleAvatar( - backgroundImage: NetworkImage('${user.photo}'), - ), - actionChildWidget: [ - RowHeaderIcons( - showSearchIcon: true, - ), - ], - ), - body: SingleChildScrollView( + child: Scaffold( + body: SafeArea( + child: SingleChildScrollView( child: Column( children: [ + // NavBar + NavHeader( + box: box, + showSearchIcon: true, + ), CashBalanceWidget( box: box, ), diff --git a/lib/home/view/home_container.dart b/lib/home/view/home_container.dart index 2fa8f4b..005edc4 100644 --- a/lib/home/view/home_container.dart +++ b/lib/home/view/home_container.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fpb/home/application/home_view_bloc/home_view_bloc.dart'; -import 'package:fpb/home/view/budget_screen.dart'; import 'package:fpb/home/view/dashboard.dart'; import 'package:fpb/savings/view/savings_page.dart'; import 'package:fpb/home/view/user_search_screen.dart'; @@ -17,7 +16,7 @@ class HomeContainer extends StatelessWidget { home: (_) => DashBoard(), // -> LatestActivitiesPage(), savings: (_) => SavingsPage(), quickCash: (_) => Container(child: Center(child: Text('budget'))), - budget: (_) => BudgetScreen(), + budget: (_) => Container(child: Center(child: Text('budget'))), search: (_) => UserSearchScreen(), ); }, diff --git a/lib/home/view/user_search_screen.dart b/lib/home/view/user_search_screen.dart index 1c3d3bd..0c7fe2c 100644 --- a/lib/home/view/user_search_screen.dart +++ b/lib/home/view/user_search_screen.dart @@ -3,8 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fpb/authentication_with_firebase/application/bloc/auth_bloc.dart'; import 'package:fpb/core/domain/user.dart'; import 'package:fpb/core/shared/helpers/value_injector.dart'; -import 'package:fpb/home/view/widgets/custom_appbar.dart'; -import 'package:fpb/home/view/widgets/row_header_icons.dart'; +import 'package:fpb/home/view/widgets/navbar_header.dart'; import 'package:fpb/home/view/widgets/search_input.dart'; import 'package:fpb/home/view/widgets/user_search_list.dart'; @@ -18,29 +17,24 @@ class UserSearchScreen extends StatelessWidget { context.read()..add(AuthEvent.logoutRequest()); } return LayoutBuilder(builder: (context, box) { - return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: CustomAppBar( - titleChildWidget: CircleAvatar( - backgroundImage: NetworkImage('${user.photo}'), - ), - actionChildWidget: [ - RowHeaderIcons(), + return Scaffold( + resizeToAvoidBottomInset: false, + body: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + NavHeader( + box: box, + showSearchIcon: false, + ), + SearchInputWidget( + box: box, + ), + UserSearchList( + box: box, + ), ], - ), - body: SingleChildScrollView( - child: Column( - children: [ - SearchInputWidget( - box: box, - ), - UserSearchList( - box: box, - ), - ], - ), - ), + )), ), ); }); diff --git a/lib/home/view/widgets/drag_widget.dart b/lib/home/view/widgets/drag_widget.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/home/view/widgets/navbar_header.dart b/lib/home/view/widgets/navbar_header.dart new file mode 100644 index 0000000..396d79d --- /dev/null +++ b/lib/home/view/widgets/navbar_header.dart @@ -0,0 +1,75 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fpb/assets/fpb_icons/fpb_icons_icons.dart'; +import 'package:fpb/authentication_with_firebase/application/bloc/auth_bloc.dart'; +import 'package:fpb/core/domain/user.dart'; +import 'package:fpb/core/shared/helpers/value_injector.dart'; +import 'package:fpb/router/app_route.gr.dart'; + +class NavHeader extends StatelessWidget { + const NavHeader({ + super.key, + required this.box, + this.showSearchIcon = false, + }); + + final BoxConstraints box; + final bool showSearchIcon; + + @override + Widget build(BuildContext context) { + final user = ValueInjector.of(context)?.value ?? User.empty; + if (user == User.empty) { + context.read()..add(AuthEvent.logoutRequest()); + } + return Container( + width: box.maxWidth, + padding: EdgeInsets.symmetric( + horizontal: box.maxWidth * 0.04, + vertical: box.maxHeight * 0.025, + ), + // color: Colors.red, + child: Wrap( + alignment: WrapAlignment.spaceBetween, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + CircleAvatar( + backgroundImage: NetworkImage('${user.photo}'), + ), + Container( + child: Wrap( + spacing: box.maxWidth * 0.02, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + showSearchIcon + ? InkWell( + onTap: () { + context.pushRoute( + LatestActivitiesPage(), + ); + }, + child: Icon( + FpbIcons.search, + size: 20, + ), + ) + : SizedBox(), + SizedBox( + height: box.maxHeight * 0.05, + child: Image.asset( + 'assets/fpb-assets/scan_icon.png', + ), + ), + Icon( + FpbIcons.notification, + size: 20, + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/home/view/widgets/row_header_icons.dart b/lib/home/view/widgets/row_header_icons.dart index 53c8320..4b2390d 100644 --- a/lib/home/view/widgets/row_header_icons.dart +++ b/lib/home/view/widgets/row_header_icons.dart @@ -1,8 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:fpb/assets/fpb_icons/fpb_icons_icons.dart'; -import 'package:fpb/core/presentation/animations/slide_up_route_transition.dart'; -import 'package:fpb/qr_code_screen/view/qr_code_screen.dart'; import 'package:fpb/router/app_route.gr.dart'; class RowHeaderIcons extends StatelessWidget { diff --git a/lib/home/view/widgets/search_input.dart b/lib/home/view/widgets/search_input.dart index 9202d57..92873b0 100644 --- a/lib/home/view/widgets/search_input.dart +++ b/lib/home/view/widgets/search_input.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fpb/core/application/search_user_bloc/search_user_bloc.dart'; import 'package:fpb/core/application/search_user_bloc/search_user_state.dart'; import 'package:fpb/core/domain/user.dart'; -import 'package:fpb/core/presentation/widget/fpb_text_form_field.dart'; +import 'package:fpb/core/presentation/widget/my_textformfield.dart'; class SearchInputWidget extends StatelessWidget { const SearchInputWidget({super.key, required this.box, this.textController}); diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 702ab0c..4fb7e9c 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -13,11 +13,11 @@ import 'package:firebase_auth/firebase_auth.dart' as _i7; import 'package:firebase_core/firebase_core.dart' as _i6; import 'package:flutter_facebook_auth/flutter_facebook_auth.dart' as _i5; import 'package:fpb/authentication_mock_without_backend/application/bloc/authentication_bloc.dart' - as _i19; + as _i20; import 'package:fpb/authentication_mock_without_backend/infrastructure/authentication_mock_module_injection.dart' as _i33; import 'package:fpb/authentication_with_facebook/application/facebook_auth_bloc.dart' - as _i21; + as _i22; import 'package:fpb/authentication_with_facebook/domain/i_facebook_repository_facade.dart' as _i10; import 'package:fpb/authentication_with_facebook/infrastructure/facebook_auth_repository.dart' @@ -35,33 +35,33 @@ import 'package:fpb/authentication_with_firebase/infrastructure/firebase_auth_in import 'package:fpb/authentication_with_google/application/google_auth_bloc/google_sign_in_bloc.dart' as _i23; import 'package:fpb/authentication_with_google/domain/i_google_repository_facade.dart' - as _i14; + as _i12; import 'package:fpb/authentication_with_google/infrastructure/google_authentication_injectable_module.dart' as _i31; import 'package:fpb/authentication_with_google/infrastructure/google_authentication_repository.dart' + as _i13; +import 'package:fpb/authentication_with_phone_number/domain/i_phone_number_repository_facade.dart' + as _i14; +import 'package:fpb/authentication_with_phone_number/infrastructure/phone_number_authentication_repository.dart' as _i15; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart' + as _i17; import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart' as _i28; import 'package:fpb/core/application/internet_and_time_bloc/internet_and_time_bloc.dart' as _i30; import 'package:fpb/core/infrastructure/core_injectable_module.dart' as _i32; import 'package:fpb/core/settings/app_settings_helper.dart' as _i26; -import 'package:fpb/core/settings/cached.dart' as _i20; -import 'package:fpb/forgot_password_flow/application/bloc/forgot_password_bloc.dart' - as _i22; -import 'package:fpb/forgot_password_flow/domain/i_forgot_password_facade.dart' - as _i12; -import 'package:fpb/forgot_password_flow/infrastructure/forgot_password_repository.dart' - as _i13; +import 'package:fpb/core/settings/cached.dart' as _i21; import 'package:fpb/home/application/home_view_bloc/home_view_bloc.dart' as _i29; import 'package:get_it/get_it.dart' as _i1; import 'package:google_sign_in/google_sign_in.dart' as _i9; import 'package:injectable/injectable.dart' as _i2; import 'package:ntp/ntp.dart' as _i16; -import 'package:shared_preferences/shared_preferences.dart' as _i17; +import 'package:shared_preferences/shared_preferences.dart' as _i18; import 'package:user_repository/user_repository.dart' - as _i18; // ignore_for_file: unnecessary_lambdas + as _i19; // ignore_for_file: unnecessary_lambdas // ignore_for_file: lines_longer_than_80_chars extension GetItInjectableX on _i1.GetIt { @@ -104,43 +104,37 @@ extension GetItInjectableX on _i1.GetIt { gh<_i5.FacebookAuth>(), gh<_i7.FirebaseAuth>(), )); - gh.lazySingleton<_i12.IForgotPasswordRepositoryFacade>( - () => _i13.ForgotPasswordRepository( - gh<_i7.FirebaseAuth>(), - gh(), - )); - gh.lazySingleton<_i14.IGoogleRepositoryFacade>( - () => _i15.GoogleAuthenticationRepository( + gh.lazySingleton<_i12.IGoogleRepositoryFacade>( + () => _i13.GoogleAuthenticationRepository( gh<_i9.GoogleSignIn>(), gh<_i7.FirebaseAuth>(), )); + gh.lazySingleton<_i14.IPhoneNumberRepositoryFacade>( + () => _i15.PhoneNumberAuthRepository(gh<_i7.FirebaseAuth>())); gh.lazySingleton<_i16.NTP>(() => coreInjectableModule.ntp); - await gh.factoryAsync<_i17.SharedPreferences>( + gh.factory<_i17.PhoneNumberBloc>(() => _i17.PhoneNumberBloc( + phoneNumberRepository: gh<_i14.IPhoneNumberRepositoryFacade>())); + await gh.factoryAsync<_i18.SharedPreferences>( () => firebaseAuthInjectableModule.sharePreferences, preResolve: true, ); - gh.singleton<_i18.UserRepository>( + gh.singleton<_i19.UserRepository>( authenticationMockModuleInjection.userRepository); - gh.factory<_i19.AuthenticationBloc>(() => _i19.AuthenticationBloc( + gh.factory<_i20.AuthenticationBloc>(() => _i20.AuthenticationBloc( authenticationRepository: gh<_i3.AuthenticationRepository>(), - userRepository: gh<_i18.UserRepository>(), + userRepository: gh<_i19.UserRepository>(), )); - gh.singleton<_i20.Cached>(_i20.Cached(gh<_i17.SharedPreferences>())); - gh.factory<_i21.FacebookAuthBloc>(() => _i21.FacebookAuthBloc( + gh.singleton<_i21.Cached>(_i21.Cached(gh<_i18.SharedPreferences>())); + gh.factory<_i22.FacebookAuthBloc>(() => _i22.FacebookAuthBloc( authenticationRepository: gh<_i10.IFacebookRepositoryFacade>())); - gh.factory<_i22.ForgotPasswordBloc>(() => _i22.ForgotPasswordBloc( - gh<_i22.ForgotPasswordState>(), - forgotPasswordRepositoryFacade: - gh<_i12.IForgotPasswordRepositoryFacade>(), - )); gh.factory<_i23.GoogleSignInBloc>(() => _i23.GoogleSignInBloc( - authenticationRepository: gh<_i14.IGoogleRepositoryFacade>())); + authenticationRepository: gh<_i12.IGoogleRepositoryFacade>())); gh.lazySingleton<_i24.IAuthFacade>(() => _i25.FirebaseAuthFacade( gh<_i7.FirebaseAuth>(), - gh<_i20.Cached>(), + gh<_i21.Cached>(), )); gh.lazySingleton<_i26.AppSettingsHelper>(() => _i26.AppSettingsHelper( - gh<_i20.Cached>(), + gh<_i21.Cached>(), gh<_i4.Connectivity>(), )); gh.factory<_i27.AuthBloc>(() => _i27.AuthBloc(gh<_i24.IAuthFacade>())); diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index abc46ed..9cb37cd 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -343,6 +343,49 @@ "confirmEmailResendButton": "Resend confirmation email", "@confirmEmailResendButton": { "description": "This is the text label on the resend button on the phone number confirmation screen" + }, + "createNewPasswordTitle": "Create new password", + "@createNewPasswordTitle": { + "description": "This is the main title on the create new password screen" + }, + "createNewPasswordBodyContent": "You new password must be different from the previous \nused password.", + "@createNewPasswordBodyContent": { + "description": "This is the body content of the create new password screen" + }, + "createNewPasswordConfirmPasswordLabel": "Confirm Password", + "@createNewPasswordConfirmPasswordLabel": { + "description": "This is the confirm password label on the create new password screen" + }, + "createNewPasswordResetButtonLabel": "Reset Password", + "@createNewPasswordResetButtonLabel": { + "description": "This is the text label of the reset button on the create new password screen" + }, + "forgotPasswordTitle": "Forgot password", + "@forgotPasswordTitle": { + "description": "This is the main title text label on the forgot password screen" + }, + "forgotPasswordBodyContent": "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores !", + "@forgotPasswordBodyContent": { + "description": "This is the body content on the forgot password screen" + }, + "forgotPasswordEmailTextFieldLabel": "Email address", + "@forgotPasswordEmailTextFieldLabel": { + "description": "This is text label of the email text field on the forgot password screen" + }, + "forgotPasswordEmailTextFieldHint": "Enter your email address...", + "@forgotPasswordEmailTextFieldHint": { + "description": "This is the hint of the email text field on the forgot password screen" + }, + "forgotPasswordSendButtonLabel": "Send", + "@forgotPasswordSendButtonLabel": { + "description": "This is the text label of the send button on the forgot password screen" + }, + "newPasswordSuccessTitle": "Yay !!", + "@newPasswordSuccessTitle": { + "description": "This is the main title on the new password success screen" + }, + "newPasswordSuccessBodyContent": "Your password has been reset successfully\nnow login with your new password", + "@newPasswordSuccessBodyContent": { + "description": "This is the body content of the new password success screen" } } - \ No newline at end of file diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 304da11..14159c7 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -343,6 +343,50 @@ "confirmEmailResendButton": "Renvoyer l'e-mail", "@confirmEmailResendButton": { "description": "Il s'agit du texte de l'étiquette du bouton de renvoi sur l'écran de confirmation du numéro de téléphone." + }, + "createNewPasswordTitle": "Créer un nouveau mot de passe", + "@createNewPasswordTitle": { + "description": "This is the main title on the create new password screen" + }, + "createNewPasswordBodyContent": "Le nouveau mot de passe doit être différent du précédent \nmot de passe utilisé.", + "@createNewPasswordBodyContent": { + "description": "This is the body content of the create new password screen" + }, + "createNewPasswordConfirmPasswordLabel": "Confirm Password", + "@createNewPasswordConfirmPasswordLabel": { + "description": "This is the confirm password label on the create new password screen" + }, + "createNewPasswordResetButtonLabel": "Reset Password", + "@createNewPasswordResetButtonLabel": { + "description": "This is the text label of the reset button on the create new password screen" + }, + "forgotPasswordTitle": "Forgot password", + "@forgotPasswordTitle": { + "description": "This is the main title text label on the forgot password screen" + }, + "forgotPasswordBodyContent": "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores !", + "@forgotPasswordBodyContent": { + "description": "This is the body content on the forgot password screen" + }, + "forgotPasswordEmailTextFieldLabel": "Email address", + "@forgotPasswordEmailTextFieldLabel": { + "description": "This is text label of the email text field on the forgot password screen" + }, + "forgotPasswordEmailTextFieldHint": "Enter your email address...", + "@forgotPasswordEmailTextFieldHint": { + "description": "This is the hint of the email text field on the forgot password screen" + }, + "forgotPasswordSendButtonLabel": "Send", + "@forgotPasswordSendButtonLabel": { + "description": "This is the text label of the send button on the forgot password screen" + }, + "newPasswordSuccessTitle": "Yay !!", + "@newPasswordSuccessTitle": { + "description": "This is the main title on the new password success screen" + }, + "newPasswordSuccessBodyContent": "Your password has been reset successfully\nnow login with your new password", + "@newPasswordSuccessBodyContent": { + "description": "This is the body content of the new password success screen" } } \ No newline at end of file diff --git a/lib/phone_number_confirmation/view/phone_number_confirmation.dart b/lib/phone_number_confirmation/view/phone_number_confirmation.dart index 50309e8..4a069c9 100644 --- a/lib/phone_number_confirmation/view/phone_number_confirmation.dart +++ b/lib/phone_number_confirmation/view/phone_number_confirmation.dart @@ -1,9 +1,13 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:formz/formz.dart'; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart'; import 'package:fpb/core/presentation/widget/confirmation_screen_illustration.dart'; import 'package:fpb/core/presentation/widget/otp_group_text_field.dart'; import 'package:fpb/l10n/l10n.dart'; import 'package:fpb/core/presentation/widget/confirmation_screen_actions.dart'; -import 'package:fpb/core/presentation/widget/otp_input.dart'; +import 'package:fpb/router/app_route.gr.dart'; class PhoneNumberConfirmationScreen extends StatefulWidget { static const routeName = '/phoneNumberConfirmation'; @@ -29,71 +33,93 @@ class _PhoneNumberConfirmationScreenState return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: colors.background, - body: Stack( - children: [ - SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only( - left: box.maxHeight * .025, - right: box.maxHeight * .025), - child: Column( - children: [ - SizedBox( - height: box.maxHeight * .15, - ), - Text( - l10n.confirmPhoneNumberTitleText, - style: style.titleLarge - ?.copyWith(fontSize: box.maxHeight * .045), - textAlign: TextAlign.center, - ), - SizedBox( - height: box.maxHeight * .03, - ), - RichText( - maxLines: 3, - textAlign: TextAlign.center, - text: TextSpan(children: [ - TextSpan( - text: l10n.confirmPhoneNumberBodyContentStart, - style: style.titleMedium, - ), - TextSpan( - text: l10n.confirmPhoneNumberBodyContentMid, - style: style.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - //color: theme.colorScheme.onSurface - ), - ), - TextSpan( - text: l10n.confirmPhoneNumberBodyContentEnd, - style: style.titleMedium) - ])), - SizedBox( - height: box.maxHeight * .05, - ), - OtpGroupTextField( - box: box, - ), - SizedBox( - height: box.maxHeight * .05, - ), - ConfirmationScreenIllustration( - box: box, + body: BlocConsumer( + listener: (context, state) { + if(state.status == FormzStatus.submissionFailure){ + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: theme.colorScheme.error, + elevation: 0, + content: Text(state.failure!.message), + ), + ); + }else if(state.status == FormzStatus.submissionSuccess){ + context.router.push(CreateNewPasswordPhoneSignupRoute()); + } + }, + buildWhen: (previous, current) { + return current != previous && current.status == FormzStatus.submissionSuccess + && current.status == FormzStatus.submissionInProgress + && current.status == FormzStatus.submissionFailure; + }, + builder: (context, state) { + return Stack( + children: [ + SingleChildScrollView( + child: Padding( + padding: EdgeInsets.only( + left: box.maxHeight * .025, + right: box.maxHeight * .025), + child: Column( + children: [ + SizedBox( + height: box.maxHeight * .15, + ), + Text( + l10n.confirmPhoneNumberTitleText, + style: style.titleLarge + ?.copyWith(fontSize: box.maxHeight * .045), + textAlign: TextAlign.center, + ), + SizedBox( + height: box.maxHeight * .03, + ), + RichText( + maxLines: 3, + textAlign: TextAlign.center, + text: TextSpan(children: [ + TextSpan( + text: l10n.confirmPhoneNumberBodyContentStart, + style: style.titleMedium, + ), + TextSpan( + text: l10n.confirmPhoneNumberBodyContentMid, + style: style.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: l10n.confirmPhoneNumberBodyContentEnd, + style: style.titleMedium) + ])), + SizedBox( + height: box.maxHeight * .05, + ), + OtpGroupTextField( + box: box, + ), + SizedBox( + height: box.maxHeight * .05, + ), + ConfirmationScreenIllustration( + box: box, + ), + ], ), - ], + ), ), - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: ConfirmationScreenAction( - onTapConfirmButton: () {}, - confirmButtonLabel: l10n.confirmPhoneNumberResendOtpButton, - onTapContactUsButton: () {}, - ), - ) - ], + Align( + alignment: Alignment.bottomCenter, + child: ConfirmationScreenAction( + onTapConfirmButton: () {}, + confirmButtonLabel: l10n + .confirmPhoneNumberResendOtpButton, + onTapContactUsButton: () {}, + ), + ) + ], + ); + }, ), ); }, diff --git a/lib/profile/view/profile_page.dart b/lib/profile/view/profile_page.dart index ace6831..391474a 100644 --- a/lib/profile/view/profile_page.dart +++ b/lib/profile/view/profile_page.dart @@ -17,7 +17,7 @@ class _ProfileScreenState extends State { @override Widget build(BuildContext context) { final l10n = context.l10n; - final theme = Theme.of(context); + final theme = Theme.of(context); return Scaffold( backgroundColor: theme.colorScheme.onBackground, body: LayoutBuilder( @@ -32,39 +32,29 @@ class _ProfileScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TitleText( - box: box, - l10n: l10n), + TitleText(box: box, l10n: l10n), SizedBox( height: box.maxHeight * 0.03, ), - UserPic( - box: box), + UserPic(box: box), SizedBox( height: box.maxHeight * 0.01, ), Center( - child: UserName( - box: box), + child: UserName(box: box), ), SizedBox( height: box.maxHeight * 0.03, ), - UserProfileOptions( - box: box, - l10n: l10n), + UserProfileOptions(box: box, l10n: l10n), SizedBox( height: box.maxHeight * 0.02, ), - LogOutOption( - box:box, - l10n: l10n), + LogOutOption(box: box, l10n: l10n), SizedBox( height: box.maxHeight * 0.1, ), - AppVersion( - box: box, - l10n: l10n) + AppVersion(box: box, l10n: l10n) ], ), ), @@ -76,11 +66,7 @@ class _ProfileScreenState extends State { } class AppVersion extends StatelessWidget { - const AppVersion({ - super.key, - required this.l10n, - required this.box - }); + const AppVersion({super.key, required this.l10n, required this.box}); final AppLocalizations l10n; final BoxConstraints box; @@ -91,7 +77,7 @@ class AppVersion extends StatelessWidget { child: Column( children: [ Text( - 'Version 10.2', + 'Version 10.2', style: Theme.of(context).textTheme.titleSmall, ), SizedBox( @@ -112,11 +98,7 @@ class AppVersion extends StatelessWidget { } class LogOutOption extends StatelessWidget { - const LogOutOption({ - super.key, - required this.l10n, - required this.box - }); + const LogOutOption({super.key, required this.l10n, required this.box}); final AppLocalizations l10n; final BoxConstraints box; @@ -124,24 +106,22 @@ class LogOutOption extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(box.maxHeight * 0.025), - color: Colors.white, - ), - child: Padding( - padding: EdgeInsets.only( - left: box.maxHeight * 0.03, - top: box.maxHeight * 0.01, - bottom: box.maxHeight * 0.01, - ), - child: - ProfileInfoOptions( - text: l10n.profileLogOutText, - icon: FpbIcons.logout, - box: box, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(box.maxHeight * 0.025), + color: Colors.white, ), - ) - ); + child: Padding( + padding: EdgeInsets.only( + left: box.maxHeight * 0.03, + top: box.maxHeight * 0.01, + bottom: box.maxHeight * 0.01, + ), + child: ProfileInfoOptions( + text: l10n.profileLogOutText, + icon: FpbIcons.logout, + box: box, + ), + )); } } @@ -169,23 +149,23 @@ class UserProfileOptions extends StatelessWidget { bottom: box.maxHeight * 0.03, ), child: Column( - children: [ + children: [ ProfileInfoOptions( text: l10n.profileAccountText, icon: FpbIcons.profile, box: box, - ), + ), ProfileInfoOptions( text: l10n.profileSettingsText, icon: FpbIcons.setting, box: box, - ), + ), ProfileInfoOptions( text: l10n.profileNotificationsText, icon: Icons.notifications, box: box, - ), - ProfileInfoOptions( + ), + ProfileInfoOptions( text: l10n.profilePaymentMethodsText, icon: FpbIcons.credit_card, box: box, @@ -197,44 +177,43 @@ class UserProfileOptions extends StatelessWidget { } } -class UserPic extends StatelessWidget{ -const UserPic({ +class UserPic extends StatelessWidget { + const UserPic({ super.key, required this.box, }); -final BoxConstraints box; + final BoxConstraints box; @override Widget build(BuildContext context) { return Container( width: box.maxWidth, child: Stack( - alignment: Alignment(0.0, -1.5), - children: [ - Positioned( + alignment: Alignment(0.0, -1.5), + children: [ + Positioned( child: CircleAvatar( radius: box.maxHeight * 0.060, child: FlutterLogo(size: box.maxHeight * 0.1), ), ), - Positioned( - //top: 0, - right: box.maxWidth * .001, - child: IconButton( - onPressed: () { }, - icon: Icon( - FpbIcons.edit, - size: box.maxHeight * 0.028, + Positioned( + //top: 0, + right: box.maxWidth * .001, + child: IconButton( + onPressed: () {}, + icon: Icon( + FpbIcons.edit, + size: box.maxHeight * 0.028, ), ), - ), + ), ], ), ); } } - class UserName extends StatelessWidget { const UserName({ super.key, @@ -250,17 +229,15 @@ class UserName extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - 'John Merry', - style: style.titleMedium?.copyWith( - fontWeight: FontWeight.w600 - ), - ), - Text( - '@john.merry', - style: style.titleSmall, - ), - ], + Text( + 'John Merry', + style: style.titleMedium?.copyWith(fontWeight: FontWeight.w600), + ), + Text( + '@john.merry', + style: style.titleSmall, + ), + ], ); } } @@ -277,15 +254,13 @@ class TitleText extends StatelessWidget { @override Widget build(BuildContext context) { - // final l10n = context.l10n; + // final l10n = context.l10n; final theme = Theme.of(context); final style = theme.textTheme; return Text( l10n.profileTitle, style: style.displayLarge?.copyWith( - color: theme.colorScheme.secondary, - fontWeight: FontWeight.w600 - ), + color: theme.colorScheme.secondary, fontWeight: FontWeight.w600), ); } } diff --git a/lib/router/app_route.dart b/lib/router/app_route.dart index c96f1ca..faebf6d 100644 --- a/lib/router/app_route.dart +++ b/lib/router/app_route.dart @@ -1,7 +1,7 @@ import 'package:auto_route/annotations.dart'; import 'package:fpb/contact_us/view/contact_us_screen.dart'; import 'package:fpb/contact_us/view/contact_us_success_screen.dart'; -import 'package:fpb/email_confirmation/email_confirmation.dart'; +import 'package:fpb/authentication_with_phone_number/views/create_password_phone_signup_screen.dart'; import 'package:fpb/home/view/home_screen.dart'; import 'package:fpb/latest_activities/view/latest_activities_screen.dart'; import 'package:fpb/onboarding/view/onboarding_screens.dart'; @@ -9,7 +9,6 @@ import 'package:fpb/onboarding/view/splash_screen.dart'; import 'package:fpb/phone_number_confirmation/view/phone_number_confirmation.dart'; import 'package:fpb/profile/view/profile_page.dart'; import 'package:fpb/qr_code_screen/view/qr_code_screen.dart'; -import 'package:fpb/savings/save_money_with_bucket/save_money_with_bucket.dart'; import 'package:fpb/savings/view/savings_page.dart'; import 'package:fpb/sign_in/view/sign_in_page.dart'; import 'package:fpb/sign_up/view/signup_page.dart'; @@ -19,10 +18,14 @@ import 'package:fpb/sign_up/view/signup_page.dart'; routes: [ AutoRoute(page: SplashScreen, initial: true), AutoRoute(page: SignInScreen), - AutoRoute(page: PhoneNumberConfirmationScreen), - AutoRoute(page: EmailConfirmationScreen), - AutoRoute(page: SignUpScreen), - AutoRoute(page: SaveMoneyScreen), + AutoRoute(page: QrCodeScreen), + AutoRoute( + page: SignUpScreen, + children: [ + AutoRoute(page: PhoneNumberConfirmationScreen), + AutoRoute(page: CreateNewPasswordPhoneSignupScreen), + ], + ), AutoRoute( name: 'HomeRouter', page: HomeScreen, @@ -35,15 +38,12 @@ import 'package:fpb/sign_up/view/signup_page.dart'; path: 'profile', page: ProfileScreen, ), + AutoRoute( + path: 'latestActivities', + page: LatestActivitiesPage, + ), ], ), - AutoRoute( - path: 'latestActivities', - page: LatestActivitiesPage, - ), - AutoRoute( - page: QrCodeScreen, - ), AutoRoute(page: OnboardingScreen), AutoRoute( page: ContactUsScreen, diff --git a/lib/router/app_route.gr.dart b/lib/router/app_route.gr.dart index 7a5a657..045b951 100644 --- a/lib/router/app_route.gr.dart +++ b/lib/router/app_route.gr.dart @@ -11,173 +11,181 @@ // ignore_for_file: type=lint // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i13; -import 'package:flutter/material.dart' as _i14; - -import '../contact_us/contact_us_page.dart' as _i11; -import '../core/domain/user.dart' as _i15; -import '../email_confirmation/email_confirmation.dart' as _i4; -import '../home/view/home_screen.dart' as _i7; -import '../latest_activities/view/latest_activities_screen.dart' as _i8; -import '../onboarding/view/onboarding_screens.dart' as _i10; +import 'package:auto_route/auto_route.dart' as _i14; +import 'package:flutter/material.dart' as _i15; + +import '../authentication_with_phone_number/views/create_password_phone_signup_screen.dart' + as _i10; +import '../contact_us/view/contact_us_screen.dart' as _i7; +import '../contact_us/view/contact_us_success_screen.dart' as _i8; +import '../core/domain/user.dart' as _i16; +import '../home/view/home_screen.dart' as _i5; +import '../latest_activities/view/latest_activities_screen.dart' as _i13; +import '../onboarding/view/onboarding_screens.dart' as _i6; import '../onboarding/view/splash_screen.dart' as _i1; import '../phone_number_confirmation/view/phone_number_confirmation.dart' - as _i3; + as _i9; import '../profile/view/profile_page.dart' as _i12; -import '../qr_code_screen/view/qr_code_screen.dart' as _i9; -import '../savings/save_money_with_bucket/save_money_with_bucket.dart' as _i6; +import '../qr_code_screen/view/qr_code_screen.dart' as _i3; +import '../savings/view/savings_page.dart' as _i11; import '../sign_in/view/sign_in_page.dart' as _i2; -import '../sign_up/view/signup_page.dart' as _i5; +import '../sign_up/view/signup_page.dart' as _i4; -class AppRoute extends _i13.RootStackRouter { - AppRoute([_i14.GlobalKey<_i14.NavigatorState>? navigatorKey]) +class AppRoute extends _i14.RootStackRouter { + AppRoute([_i15.GlobalKey<_i15.NavigatorState>? navigatorKey]) : super(navigatorKey); @override - final Map pagesMap = { + final Map pagesMap = { SplashRoute.name: (routeData) { - return _i13.MaterialPageX( + return _i14.MaterialPageX( routeData: routeData, child: const _i1.SplashScreen(), ); }, SignInRoute.name: (routeData) { - return _i13.MaterialPageX( + return _i14.MaterialPageX( routeData: routeData, child: const _i2.SignInScreen(), ); }, - PhoneNumberConfirmationRoute.name: (routeData) { - return _i13.MaterialPageX( - routeData: routeData, - child: const _i3.PhoneNumberConfirmationScreen(), - ); - }, - EmailConfirmationRoute.name: (routeData) { - return _i13.MaterialPageX( + QrCodeRoute.name: (routeData) { + return _i14.MaterialPageX( routeData: routeData, - child: const _i4.EmailConfirmationScreen(), + child: const _i3.QrCodeScreen(), ); }, SignUpRoute.name: (routeData) { - return _i13.MaterialPageX( - routeData: routeData, - child: const _i5.SignUpScreen(), - ); - }, - SaveMoneyRoute.name: (routeData) { - return _i13.MaterialPageX( + return _i14.MaterialPageX( routeData: routeData, - child: const _i6.SaveMoneyScreen(), + child: const _i4.SignUpScreen(), ); }, HomeRouter.name: (routeData) { final args = routeData.argsAs(); - return _i13.MaterialPageX( + return _i14.MaterialPageX( routeData: routeData, - child: _i7.HomeScreen( + child: _i5.HomeScreen( key: args.key, user: args.user, ), ); }, - LatestActivitiesPage.name: (routeData) { - return _i13.MaterialPageX( - routeData: routeData, - child: const _i8.LatestActivitiesPage(), - ); - }, - QrCodeRoute.name: (routeData) { - return _i13.MaterialPageX( - routeData: routeData, - child: const _i9.QrCodeScreen(), - ); - }, OnboardingRoute.name: (routeData) { final args = routeData.argsAs( orElse: () => const OnboardingRouteArgs()); - return _i13.MaterialPageX( + return _i14.MaterialPageX( routeData: routeData, - child: _i10.OnboardingScreen( + child: _i6.OnboardingScreen( onGetStartedPressed: args.onGetStartedPressed, key: args.key, ), ); }, ContactUsRoute.name: (routeData) { - return _i13.MaterialPageX( + return _i14.MaterialPageX( routeData: routeData, - child: const _i11.ContactUsScreen(), + child: const _i7.ContactUsScreen(), ); }, ContactUsSuccessRoute.name: (routeData) { - return _i13.MaterialPageX( + return _i14.MaterialPageX( + routeData: routeData, + child: const _i8.ContactUsSuccessScreen(), + ); + }, + PhoneNumberConfirmationRoute.name: (routeData) { + return _i14.MaterialPageX( + routeData: routeData, + child: const _i9.PhoneNumberConfirmationScreen(), + ); + }, + CreateNewPasswordPhoneSignupRoute.name: (routeData) { + return _i14.MaterialPageX( + routeData: routeData, + child: const _i10.CreateNewPasswordPhoneSignupScreen(), + ); + }, + SavingsPage.name: (routeData) { + return _i14.MaterialPageX( routeData: routeData, - child: const _i11.ContactUsSuccessScreen(), + child: const _i11.SavingsPage(), ); }, ProfileRoute.name: (routeData) { - return _i13.MaterialPageX( + return _i14.MaterialPageX( routeData: routeData, child: const _i12.ProfileScreen(), ); }, + LatestActivitiesPage.name: (routeData) { + return _i14.MaterialPageX( + routeData: routeData, + child: const _i13.LatestActivitiesPage(), + ); + }, }; @override - List<_i13.RouteConfig> get routes => [ - _i13.RouteConfig( + List<_i14.RouteConfig> get routes => [ + _i14.RouteConfig( SplashRoute.name, path: '/', ), - _i13.RouteConfig( + _i14.RouteConfig( SignInRoute.name, path: '/sign-in-screen', ), - _i13.RouteConfig( - PhoneNumberConfirmationRoute.name, - path: '/phone-number-confirmation-screen', - ), - _i13.RouteConfig( - EmailConfirmationRoute.name, - path: '/email-confirmation-screen', + _i14.RouteConfig( + QrCodeRoute.name, + path: '/qr-code-screen', ), - _i13.RouteConfig( + _i14.RouteConfig( SignUpRoute.name, path: '/sign-up-screen', + children: [ + _i14.RouteConfig( + PhoneNumberConfirmationRoute.name, + path: 'phone-number-confirmation-screen', + parent: SignUpRoute.name, + ), + _i14.RouteConfig( + CreateNewPasswordPhoneSignupRoute.name, + path: 'create-new-password-phone-signup-screen', + parent: SignUpRoute.name, + ), + ], ), - _i13.RouteConfig( - SaveMoneyRoute.name, - path: '/save-money-screen', - ), - _i13.RouteConfig( + _i14.RouteConfig( HomeRouter.name, path: '/home-screen', children: [ - _i13.RouteConfig( + _i14.RouteConfig( + SavingsPage.name, + path: 'savings', + parent: HomeRouter.name, + ), + _i14.RouteConfig( ProfileRoute.name, path: 'profile', parent: HomeRouter.name, - ) + ), + _i14.RouteConfig( + LatestActivitiesPage.name, + path: 'latestActivities', + parent: HomeRouter.name, + ), ], ), - _i13.RouteConfig( - LatestActivitiesPage.name, - path: 'latestActivities', - ), - _i13.RouteConfig( - QrCodeRoute.name, - path: '/qr-code-screen', - ), - _i13.RouteConfig( + _i14.RouteConfig( OnboardingRoute.name, path: '/onboarding-screen', ), - _i13.RouteConfig( + _i14.RouteConfig( ContactUsRoute.name, path: '/contact-us-screen', ), - _i13.RouteConfig( + _i14.RouteConfig( ContactUsSuccessRoute.name, path: '/contact-us-success-screen', ), @@ -186,7 +194,7 @@ class AppRoute extends _i13.RootStackRouter { /// generated route for /// [_i1.SplashScreen] -class SplashRoute extends _i13.PageRouteInfo { +class SplashRoute extends _i14.PageRouteInfo { const SplashRoute() : super( SplashRoute.name, @@ -198,7 +206,7 @@ class SplashRoute extends _i13.PageRouteInfo { /// generated route for /// [_i2.SignInScreen] -class SignInRoute extends _i13.PageRouteInfo { +class SignInRoute extends _i14.PageRouteInfo { const SignInRoute() : super( SignInRoute.name, @@ -209,60 +217,37 @@ class SignInRoute extends _i13.PageRouteInfo { } /// generated route for -/// [_i3.PhoneNumberConfirmationScreen] -class PhoneNumberConfirmationRoute extends _i13.PageRouteInfo { - const PhoneNumberConfirmationRoute() - : super( - PhoneNumberConfirmationRoute.name, - path: '/phone-number-confirmation-screen', - ); - - static const String name = 'PhoneNumberConfirmationRoute'; -} - -/// generated route for -/// [_i4.EmailConfirmationScreen] -class EmailConfirmationRoute extends _i13.PageRouteInfo { - const EmailConfirmationRoute() +/// [_i3.QrCodeScreen] +class QrCodeRoute extends _i14.PageRouteInfo { + const QrCodeRoute() : super( - EmailConfirmationRoute.name, - path: '/email-confirmation-screen', + QrCodeRoute.name, + path: '/qr-code-screen', ); - static const String name = 'EmailConfirmationRoute'; + static const String name = 'QrCodeRoute'; } /// generated route for -/// [_i5.SignUpScreen] -class SignUpRoute extends _i13.PageRouteInfo { - const SignUpRoute() +/// [_i4.SignUpScreen] +class SignUpRoute extends _i14.PageRouteInfo { + const SignUpRoute({List<_i14.PageRouteInfo>? children}) : super( SignUpRoute.name, path: '/sign-up-screen', + initialChildren: children, ); static const String name = 'SignUpRoute'; } /// generated route for -/// [_i6.SaveMoneyScreen] -class SaveMoneyRoute extends _i13.PageRouteInfo { - const SaveMoneyRoute() - : super( - SaveMoneyRoute.name, - path: '/save-money-screen', - ); - - static const String name = 'SaveMoneyRoute'; -} - -/// generated route for -/// [_i7.HomeScreen] -class HomeRouter extends _i13.PageRouteInfo { +/// [_i5.HomeScreen] +class HomeRouter extends _i14.PageRouteInfo { HomeRouter({ - _i14.Key? key, - required _i15.User user, - List<_i13.PageRouteInfo>? children, + _i15.Key? key, + required _i16.User user, + List<_i14.PageRouteInfo>? children, }) : super( HomeRouter.name, path: '/home-screen', @@ -282,9 +267,9 @@ class HomeRouterArgs { required this.user, }); - final _i14.Key? key; + final _i15.Key? key; - final _i15.User user; + final _i16.User user; @override String toString() { @@ -293,35 +278,11 @@ class HomeRouterArgs { } /// generated route for -/// [_i8.LatestActivitiesPage] -class LatestActivitiesPage extends _i13.PageRouteInfo { - const LatestActivitiesPage() - : super( - LatestActivitiesPage.name, - path: 'latestActivities', - ); - - static const String name = 'LatestActivitiesPage'; -} - -/// generated route for -/// [_i9.QrCodeScreen] -class QrCodeRoute extends _i13.PageRouteInfo { - const QrCodeRoute() - : super( - QrCodeRoute.name, - path: '/qr-code-screen', - ); - - static const String name = 'QrCodeRoute'; -} - -/// generated route for -/// [_i10.OnboardingScreen] -class OnboardingRoute extends _i13.PageRouteInfo { +/// [_i6.OnboardingScreen] +class OnboardingRoute extends _i14.PageRouteInfo { OnboardingRoute({ void Function()? onGetStartedPressed, - _i14.Key? key, + _i15.Key? key, }) : super( OnboardingRoute.name, path: '/onboarding-screen', @@ -342,7 +303,7 @@ class OnboardingRouteArgs { final void Function()? onGetStartedPressed; - final _i14.Key? key; + final _i15.Key? key; @override String toString() { @@ -351,8 +312,8 @@ class OnboardingRouteArgs { } /// generated route for -/// [_i11.ContactUsScreen] -class ContactUsRoute extends _i13.PageRouteInfo { +/// [_i7.ContactUsScreen] +class ContactUsRoute extends _i14.PageRouteInfo { const ContactUsRoute() : super( ContactUsRoute.name, @@ -363,8 +324,8 @@ class ContactUsRoute extends _i13.PageRouteInfo { } /// generated route for -/// [_i11.ContactUsSuccessScreen] -class ContactUsSuccessRoute extends _i13.PageRouteInfo { +/// [_i8.ContactUsSuccessScreen] +class ContactUsSuccessRoute extends _i14.PageRouteInfo { const ContactUsSuccessRoute() : super( ContactUsSuccessRoute.name, @@ -374,9 +335,45 @@ class ContactUsSuccessRoute extends _i13.PageRouteInfo { static const String name = 'ContactUsSuccessRoute'; } +/// generated route for +/// [_i9.PhoneNumberConfirmationScreen] +class PhoneNumberConfirmationRoute extends _i14.PageRouteInfo { + const PhoneNumberConfirmationRoute() + : super( + PhoneNumberConfirmationRoute.name, + path: 'phone-number-confirmation-screen', + ); + + static const String name = 'PhoneNumberConfirmationRoute'; +} + +/// generated route for +/// [_i10.CreateNewPasswordPhoneSignupScreen] +class CreateNewPasswordPhoneSignupRoute extends _i14.PageRouteInfo { + const CreateNewPasswordPhoneSignupRoute() + : super( + CreateNewPasswordPhoneSignupRoute.name, + path: 'create-new-password-phone-signup-screen', + ); + + static const String name = 'CreateNewPasswordPhoneSignupRoute'; +} + +/// generated route for +/// [_i11.SavingsPage] +class SavingsPage extends _i14.PageRouteInfo { + const SavingsPage() + : super( + SavingsPage.name, + path: 'savings', + ); + + static const String name = 'SavingsPage'; +} + /// generated route for /// [_i12.ProfileScreen] -class ProfileRoute extends _i13.PageRouteInfo { +class ProfileRoute extends _i14.PageRouteInfo { const ProfileRoute() : super( ProfileRoute.name, @@ -385,3 +382,15 @@ class ProfileRoute extends _i13.PageRouteInfo { static const String name = 'ProfileRoute'; } + +/// generated route for +/// [_i13.LatestActivitiesPage] +class LatestActivitiesPage extends _i14.PageRouteInfo { + const LatestActivitiesPage() + : super( + LatestActivitiesPage.name, + path: 'latestActivities', + ); + + static const String name = 'LatestActivitiesPage'; +} diff --git a/lib/sign_in/domain/domain.dart b/lib/sign_in/domain/domain.dart index 31cc892..4ba5d87 100644 --- a/lib/sign_in/domain/domain.dart +++ b/lib/sign_in/domain/domain.dart @@ -1,2 +1,4 @@ export 'email.dart'; export 'password.dart'; +export 'phone_number.dart'; + diff --git a/lib/sign_in/domain/normal_fields.dart b/lib/sign_in/domain/normal_fields.dart new file mode 100644 index 0000000..f7bbfcb --- /dev/null +++ b/lib/sign_in/domain/normal_fields.dart @@ -0,0 +1,18 @@ +import 'package:formz/formz.dart'; + +enum NormalValidationError { invalid } + +class NormalString extends FormzInput { + const NormalString.pure() : super.pure(''); + const NormalString.dirty([super.value = '']) : super.dirty(); + + static final _passwordRegExp = + RegExp(r'^[A-Z][a-z]*'); + + @override + NormalValidationError? validator(String? value) { + return _passwordRegExp.hasMatch(value ?? '') + ? null + : NormalValidationError.invalid; + } +} diff --git a/lib/sign_in/domain/otp_field.dart b/lib/sign_in/domain/otp_field.dart new file mode 100644 index 0000000..88daf43 --- /dev/null +++ b/lib/sign_in/domain/otp_field.dart @@ -0,0 +1,16 @@ +import 'package:formz/formz.dart'; + +enum OtpFieldValidationError { invalid } + +class OtpField extends FormzInput { + const OtpField.pure() : super.pure(''); + const OtpField.dirty([super.value = '']) : super.dirty(); + + static final _phoneNumberRegExp = + RegExp(r'^[0-9]{1,6}$'); + + @override + OtpFieldValidationError? validator(String? value) { + return _phoneNumberRegExp.hasMatch(value ?? '') ? null : OtpFieldValidationError.invalid; + } +} diff --git a/lib/sign_in/domain/phone_number.dart b/lib/sign_in/domain/phone_number.dart new file mode 100644 index 0000000..e31b7bc --- /dev/null +++ b/lib/sign_in/domain/phone_number.dart @@ -0,0 +1,16 @@ +import 'package:formz/formz.dart'; + +enum PhoneNumberValidationError { invalid } + +class PhoneNumber extends FormzInput { + const PhoneNumber.pure() : super.pure(''); + const PhoneNumber.dirty([super.value = '']) : super.dirty(); + + static final _phoneNumberRegExp = + RegExp(r'^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*$'); + + @override + PhoneNumberValidationError? validator(String? value) { + return _phoneNumberRegExp.hasMatch(value ?? '') ? null : PhoneNumberValidationError.invalid; + } +} diff --git a/lib/sign_in/view/sign_in_page.dart b/lib/sign_in/view/sign_in_page.dart index ca8c867..e51a58f 100644 --- a/lib/sign_in/view/sign_in_page.dart +++ b/lib/sign_in/view/sign_in_page.dart @@ -1,5 +1,4 @@ import 'package:auto_route/auto_route.dart'; -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; @@ -60,16 +59,14 @@ class SignInBody extends StatefulWidget { class _SignInBodyState extends State with SingleTickerProviderStateMixin { late TabController tabController; - final user = FirebaseAuth.instance.currentUser; @override void initState() { - super.initState(); - tabController = TabController( length: 2, vsync: this, ); + super.initState(); } @override @@ -82,13 +79,15 @@ class _SignInBodyState extends State return BlocConsumer( listener: (context, state) { state.failureOrUser.fold( - (l) => ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: theme.colorScheme.error, - elevation: 0, - content: Text(l.message)), - ), - (r) {}); + (l) => ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: theme.colorScheme.error, + elevation: 0, + content: Text(l.message), + ), + ), + (r) {}, + ); }, builder: (context, state) { return LayoutBuilder( @@ -169,7 +168,7 @@ class _SignInBodyState extends State mainAxisSize: MainAxisSize.min, children: [ EmailInput(box: cts), - PasswordInput(box: cts), + PasswordInput(box: cts, tabController: tabController.index,), Text(l10n.signInForgotPasswordText), ], ), @@ -177,16 +176,15 @@ class _SignInBodyState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ - PhoneNumberInput( - l10n: l10n, cts: cts), - PasswordInput(box: cts), + PhoneNumberInput(l10n: l10n, cts: cts), + PasswordInput(box: cts, tabController: tabController.index), ], ), ], ), ), ), - const LoginButton(), + tabController.index == 0 ? LoginButton(tabController: tabController.index) : SizedBox(), Padding( padding: EdgeInsets.symmetric( vertical: cts.maxHeight * 0.012, diff --git a/lib/sign_in/view/widgets/email_input.dart b/lib/sign_in/view/widgets/email_input.dart index 9f9059e..be954a0 100644 --- a/lib/sign_in/view/widgets/email_input.dart +++ b/lib/sign_in/view/widgets/email_input.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart'; -import 'package:fpb/core/presentation/widget/fpb_text_form_field.dart'; +import 'package:fpb/core/presentation/widget/my_textformfield.dart'; import 'package:fpb/l10n/l10n.dart'; class EmailInput extends StatelessWidget { diff --git a/lib/sign_in/view/widgets/login_button.dart b/lib/sign_in/view/widgets/login_button.dart index 6827f61..e7d889d 100644 --- a/lib/sign_in/view/widgets/login_button.dart +++ b/lib/sign_in/view/widgets/login_button.dart @@ -1,15 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart'; import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart'; -import 'package:fpb/core/presentation/widget/fpb_button.dart'; +import 'package:fpb/core/presentation/widget/my_button.dart'; import 'package:fpb/l10n/l10n.dart'; class LoginButton extends StatelessWidget { - const LoginButton({ + LoginButton({ super.key, + required this.tabController, }); + final int tabController; + @override Widget build(BuildContext context) { final l10n = context.l10n; @@ -18,12 +22,20 @@ class LoginButton extends StatelessWidget { builder: (context, state) { return FormzStatus.submissionInProgress == state.status ? const FpbButton(label: 'Log in', onTap: null) - : FpbButton( - label: l10n.signInLogInButtonLabel, - onTap: () => context - .read() - .add(const EmailPasswordEvent.submitted()), - ); + : tabController == 1 + ? FpbButton( + label: 'Log in', + onTap: () { + print("Phone login pressed: $tabController"); + context.read().add( + const PhoneNumberEvent.submitPhoneNumber()); + }) + : FpbButton( + label: l10n.signInLogInButtonLabel, + onTap: () => context + .read() + .add(const EmailPasswordEvent.submitted()), + ); }, ); } diff --git a/lib/sign_in/view/widgets/otp_field.dart b/lib/sign_in/view/widgets/otp_field.dart new file mode 100644 index 0000000..f435919 --- /dev/null +++ b/lib/sign_in/view/widgets/otp_field.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class OtpWidget extends StatelessWidget { + OtpWidget( + {Key? key, required this.otpCodeController, required this.verificationId}) + : super(key: key); + final TextEditingController otpCodeController; + final String verificationId; + final GlobalKey _otpFormKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TextFormField( + keyboardType: TextInputType.number, + controller: otpCodeController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Enter OTP', + prefixIcon: Icon(Icons.message), + ), + validator: (value) { + if (value!.length != 6) { + return 'Please enter valid OTP'; + } + return null; + }, + autovalidateMode: AutovalidateMode.onUserInteraction, + ), + const SizedBox( + height: 30, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: ElevatedButton( + onPressed: () { + if (_otpFormKey.currentState!.validate()) { + + } + }, + child: const Text('Verify OTP'), + ), + ), + ], + ); + } + +} \ No newline at end of file diff --git a/lib/sign_in/view/widgets/password_input.dart b/lib/sign_in/view/widgets/password_input.dart index 9f8391c..ae87f3e 100644 --- a/lib/sign_in/view/widgets/password_input.dart +++ b/lib/sign_in/view/widgets/password_input.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart'; import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart'; -import 'package:fpb/core/presentation/widget/fpb_text_form_field.dart'; +import 'package:fpb/core/presentation/widget/my_textformfield.dart'; import 'package:fpb/l10n/l10n.dart'; class PasswordInput extends StatelessWidget { @@ -10,30 +11,52 @@ class PasswordInput extends StatelessWidget { this.node, this.textController, required this.box, + required this.tabController, }); final FocusNode? node; final TextEditingController? textController; final BoxConstraints box; + final int tabController; @override Widget build(BuildContext context) { final l10n = context.l10n; - return BlocBuilder( - buildWhen: (previous, current) => previous.password != current.password, - builder: (context, state) => FpbTextFormField( - key: const Key('Email_password_form_passwordInput_textField'), - label: l10n.signInPasswordFieldLabel, - box: box, - hint: l10n.signInPasswordFieldHintText, - isPassword: true, - node: node, - textController: textController, - onChanged: (password) => context - .read() - .add(EmailPasswordEvent.emailChanged(password)), - errorText: state.password.invalid ? 'Invalid password' : null, - ), - ); + return tabController == 0 + ? BlocBuilder( + buildWhen: (previous, current) => + previous.password != current.password, + builder: (context, state) => FpbTextFormField( + key: const Key('Email_password_form_passwordInput_textField'), + label: l10n.signInPasswordFieldLabel, + box: box, + hint: l10n.signInPasswordFieldHintText, + isPassword: true, + node: node, + textController: textController, + onChanged: (password) => context + .read() + .add(EmailPasswordEvent.emailChanged(password)), + errorText: state.password.invalid ? 'Invalid password' : null, + ), + ) + : BlocBuilder( + buildWhen: (previous, current) => + previous != current.password, + builder: (context, state) => FpbTextFormField( + label: l10n.signInPasswordFieldLabel, + box: box, + hint: l10n.signInPasswordFieldHintText, + isPassword: true, + node: node, + textController: textController, + onChanged: (password) => context + .read() + .add(PhoneNumberEvent.phoneNumberChanged( + password)), + errorText: state.password.invalid ? 'Invalid password' : null, + ), + ); } } diff --git a/lib/sign_up/view/email_address_tab_view.dart b/lib/sign_up/view/email_address_tab_view.dart new file mode 100644 index 0000000..7ac93e0 --- /dev/null +++ b/lib/sign_up/view/email_address_tab_view.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:fpb/core/presentation/widget/fpb_button.dart'; +import 'package:fpb/core/presentation/widget/my_textformfield.dart'; +import 'package:fpb/l10n/l10n.dart'; + +class EmailAddressTabView extends StatelessWidget { + const EmailAddressTabView({ + super.key, + required this.l10n, + required this.box, + }); + + final AppLocalizations l10n; + final BoxConstraints box; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + FpbTextFormField( + label: l10n.signUpFullNameTextFieldLabel, + hint: l10n.signUpFullNameTextFieldHintText, + box: box, + ), + FpbTextFormField( + label: l10n.signInEmailTextFieldLabel, + hint: l10n.signInEmailTextFieldHintText, + isEmail: true, + box: box, + ), + FpbTextFormField( + label: l10n.signInPasswordFieldLabel, + hint: l10n.signInPasswordFieldHintText, + isPassword: true, + box: box, + ), + FpbButton( + label: l10n.signInSignUpLabel, + onTap: () {}, + heading: CircularProgressIndicator(), + ), + ], + ); + } +} diff --git a/lib/sign_up/view/phone_number_tab_view.dart b/lib/sign_up/view/phone_number_tab_view.dart new file mode 100644 index 0000000..671f940 --- /dev/null +++ b/lib/sign_up/view/phone_number_tab_view.dart @@ -0,0 +1,81 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:formz/formz.dart'; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart'; +import 'package:fpb/authentication_with_phone_number/views/phone_number_input_signup_flow.dart'; +import 'package:fpb/core/presentation/widget/my_button.dart'; +import 'package:fpb/l10n/l10n.dart'; +import 'package:fpb/router/app_route.gr.dart'; +import 'package:intl_phone_field/phone_number.dart'; + +class PhoneNumberTabView extends StatelessWidget { + const PhoneNumberTabView({ + super.key, + required this.l10n, + required this.tabController, + required this.box, + }); + + final AppLocalizations l10n; + final TabController tabController; + final BoxConstraints box; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final l10n = context.l10n; + final colors = theme.colorScheme; + + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PhoneNumberInputSignupFlow( + l10n: l10n, + cts: box, + validator: (phoneNumber) { + if (state.phoneNumber.valid) { + return null; + } else { + return 'Please enter valid phone number'; + } + }, + onChanged: (PhoneNumber phoneNumber) { + context.read().add(PhoneNumberEvent.phoneNumberChanged(phoneNumber.completeNumber),); + print("Your phone number here $phoneNumber"); + }, + ), + BlocConsumer( + listener: (context, state) { + if (state.phoneNumber.invalid) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: theme.colorScheme.error, + elevation: 0, + content: Text(state.failure?.message ?? ""), + ), + ); + } else if (state.status.isSubmissionSuccess) { + context.router.push(PhoneNumberConfirmationRoute()); + } + }, + builder: (context, state) { + return FpbButton( + label: l10n.signInSignUpLabel, + onTap: () { + context.read().add(PhoneNumberEvent.submitPhoneNumber()); + }, + heading: state.status.isSubmissionInProgress + ? CircularProgressIndicator(color: colors.onPrimary,) + : null, + ); + }, + ), + ], + ); + }, + ); + } +} diff --git a/lib/sign_up/view/sign_up_text_button.dart b/lib/sign_up/view/sign_up_text_button.dart new file mode 100644 index 0000000..869bbba --- /dev/null +++ b/lib/sign_up/view/sign_up_text_button.dart @@ -0,0 +1,30 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +class AuthTextButton extends StatelessWidget { + const AuthTextButton({ + super.key, + required this.textTheme, + required this.theme, + required this.buttonLabel, + }); + + final TextTheme textTheme; + final ThemeData theme; + final String buttonLabel; + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: () { + context.router.pop(); + }, + child: Text( + buttonLabel, + style: textTheme.headlineSmall?.copyWith( + color: theme.primaryColor, + ), + ), + ); + } +} diff --git a/lib/sign_up/view/signup_page.dart b/lib/sign_up/view/signup_page.dart index 1c79d00..b8ad78d 100644 --- a/lib/sign_up/view/signup_page.dart +++ b/lib/sign_up/view/signup_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; @@ -6,12 +5,15 @@ import 'package:fpb/assets/fpb_svg.dart'; import 'package:fpb/authentication_with_facebook/application/facebook_auth_bloc.dart'; import 'package:fpb/authentication_with_google/application/google_auth_bloc/google_sign_in_bloc.dart'; import 'package:fpb/authentication_with_google/view/loading_indicator.dart'; +import 'package:fpb/authentication_with_phone_number/phone_number_bloc/phone_number_bloc.dart'; import 'package:fpb/core/application/email_password_bloc/email_password_bloc.dart'; -import 'package:fpb/core/presentation/widget/fpb_button.dart'; -import 'package:fpb/core/presentation/widget/fpb_text_form_field.dart'; import 'package:fpb/injection.dart'; import 'package:fpb/l10n/l10n.dart'; import 'package:fpb/onboarding/view/widgets/alternative_auth.dart'; +import 'package:fpb/sign_up/view/email_address_tab_view.dart'; +import 'package:fpb/sign_up/view/phone_number_tab_view.dart'; +import 'package:fpb/sign_up/view/sign_up_text_button.dart'; +import 'package:fpb/sign_up/widgets/signup_tab_bar.dart'; class SignUpScreen extends StatelessWidget { const SignUpScreen({super.key}); @@ -30,6 +32,9 @@ class SignUpScreen extends StatelessWidget { BlocProvider( create: (context) => getIt(), ), + BlocProvider( + create: (context) => getIt(), + ), ], child: SignUpBody(), ); @@ -86,223 +91,140 @@ class _SignUpBodyState extends State ), ), Align( - alignment: Alignment.bottomCenter, - child: Container( - height: box.maxHeight * .94, - padding: const EdgeInsets.all(18), - decoration: BoxDecoration( - color: theme.colorScheme.background, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), + alignment: Alignment.bottomCenter, + child: Container( + height: box.maxHeight * .94, + padding: const EdgeInsets.all(18), + decoration: BoxDecoration( + color: theme.colorScheme.background, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Join Us!', - style: textTheme.displaySmall?.copyWith( - color: theme.colorScheme.onBackground, - fontWeight: FontWeight.bold, - fontSize: box.maxWidth * 0.037, - ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Join Us!', + style: textTheme.displaySmall?.copyWith( + color: theme.colorScheme.onBackground, + fontWeight: FontWeight.bold, + fontSize: box.maxWidth * 0.037, ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - height: box.maxHeight * .85, - padding: EdgeInsets.only( - top: box.maxHeight * .025, - left: box.maxHeight * .025, - right: box.maxHeight * .025, + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + height: box.maxHeight * .85, + padding: EdgeInsets.only( + top: box.maxHeight * .025, + left: box.maxHeight * .025, + right: box.maxHeight * .025, + ), + decoration: BoxDecoration( + color: theme.colorScheme.background, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), - decoration: BoxDecoration( - color: theme.colorScheme.background, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + l10n.signUpRegisterTitle, + style: textTheme.titleLarge, ), - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - l10n.signUpRegisterTitle, - style: textTheme.titleLarge, - ), - SizedBox( - height: box.maxHeight * 0.001, - ), - Container( - margin: EdgeInsets.symmetric( - vertical: box.maxHeight * .008, - ), - height: box.maxHeight * 0.06, - decoration: BoxDecoration( - color: theme.cardColor, - borderRadius: - BorderRadius.circular(10), - ), - // color: Colors.red, - child: TabBar( - padding: EdgeInsets.all( - box.maxHeight * .008), - controller: tabController, - onTap: (_) { - setState(() { - tabController.index = _; - }); - }, - tabs: [ - Tab( - child: Text( - l10n.signInEmailLogInLabel, - maxLines: 1, - overflow: - TextOverflow.ellipsis, + SizedBox( + height: box.maxHeight * 0.001, + ), + SignUpTabBar( + box: box, + tabController: tabController, + onChanged: (int index) { + setState(() { + tabController.index = index; + }); + }), + SizedBox( + height: box.maxHeight * .025, + ), + Flexible( + child: Form( + child: SizedBox( + height: box.maxHeight * 0.5, + child: TabBarView( + physics: + const BouncingScrollPhysics(), + controller: tabController, + children: [ + EmailAddressTabView( + l10n: l10n, + box: box, ), - ), - Tab( - child: Text( - l10n.signInPhoneNumberLogInLabel, - maxLines: 1, - overflow: - TextOverflow.ellipsis, + PhoneNumberTabView( + box: box, + l10n: l10n, + tabController: + tabController, ), - ) - ], - ), - ), - SizedBox( - height: box.maxHeight * .025, - ), - Flexible( - child: Form( - child: SizedBox( - height: box.maxHeight * 0.5, - child: TabBarView( - physics: - const BouncingScrollPhysics(), - controller: tabController, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment - .end, - mainAxisSize: - MainAxisSize.min, - children: [ - FpbTextFormField( - label: l10n - .signUpFullNameTextFieldLabel, - hint: l10n - .signUpFullNameTextFieldHintText, - box: box, - ), - FpbTextFormField( - label: l10n - .signInEmailTextFieldLabel, - hint: l10n - .signInEmailTextFieldHintText, - isEmail: true, - box: box, - ), - FpbTextFormField( - label: l10n - .signInPasswordFieldLabel, - hint: l10n - .signInPasswordFieldHintText, - isPassword: true, - box: box, - ), - ], - ), - Container(), - // Column( - // children: [ - // Flexible( - // child: Container( - // color: Colors.green, - // width: double.infinity, - // ), - // ) - // ], - // ), - ], - ), + ], ), ), ), - FpbButton( - label: l10n.signInSignUpLabel, - onTap: () {}, - ), - Padding( - padding: EdgeInsets.symmetric( - vertical: box.maxHeight * 0.012, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceEvenly, - children: [ - const Expanded( - child: Divider()), - Padding( - padding: - EdgeInsets.symmetric( - horizontal: - box.maxHeight * .015, - ), - child: Text( - l10n.signUpOrSignupWithText, - ), - ), - const Expanded( - child: Divider()) - ], - ), + ), + Padding( + padding: EdgeInsets.symmetric( + vertical: box.maxHeight * 0.012, ), - AlternativeAuth(box: box), - Padding( - padding: const EdgeInsets.only( - top: 25, bottom: 8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - 'Already a member?', - style: - textTheme.headlineSmall, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + const Expanded( + child: Divider()), + Padding( + padding: EdgeInsets.symmetric( + horizontal: + box.maxHeight * .015, ), - TextButton( - onPressed: () { - context.router.pop(); - // context.router - // .push(SignInRoute()); - }, - child: Text( - 'Login', - style: textTheme - .headlineSmall - ?.copyWith( - color: - theme.primaryColor, - ), - ), + child: Text( + l10n.signUpOrSignupWithText, ), - ], - ), + ), + const Expanded(child: Divider()) + ], ), - ], - ), + ), + AlternativeAuth(box: box), + Padding( + padding: const EdgeInsets.only( + top: 25, bottom: 8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + 'Already a member?', + style: + textTheme.headlineSmall, + ), + AuthTextButton( + buttonLabel: 'Login', + textTheme: textTheme, + theme: theme,), + ], + ), + ), + ], ), ), - ]), - )) + ), + ]), + ), + ), ], ), ); diff --git a/lib/sign_up/widgets/signup_tab_bar.dart b/lib/sign_up/widgets/signup_tab_bar.dart new file mode 100644 index 0000000..ef4fe15 --- /dev/null +++ b/lib/sign_up/widgets/signup_tab_bar.dart @@ -0,0 +1,54 @@ + +import 'package:flutter/material.dart'; +import 'package:fpb/l10n/l10n.dart'; + +class SignUpTabBar extends StatelessWidget { + const SignUpTabBar({Key? key, this.box, required this.tabController, required this.onChanged,}) : super(key: key); + + final BoxConstraints? box; + final TabController tabController; + final void Function(int index) onChanged; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + + return Container( + margin: EdgeInsets.symmetric( + vertical: box!.maxHeight * .008, + ), + height: box!.maxHeight * 0.06, + decoration: BoxDecoration( + color: theme.cardColor, + borderRadius: + BorderRadius.circular(10), + ), + // color: Colors.red, + child: TabBar( + padding: EdgeInsets.all( + box!.maxHeight * .008), + controller: tabController, + onTap: onChanged, + tabs: [ + Tab( + child: Text( + l10n.signInEmailLogInLabel, + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), + ), + Tab( + child: Text( + l10n.signInPhoneNumberLogInLabel, + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), + ) + ], + ), + ); + } +}