diff --git a/assets/images/auth_screen/png/stage_two.png b/assets/images/auth_screen/png/stage_two.png index e55880e..354c494 100644 Binary files a/assets/images/auth_screen/png/stage_two.png and b/assets/images/auth_screen/png/stage_two.png differ diff --git a/lib/core/routes/route_name.dart b/lib/core/routes/route_name.dart index 89a8b68..0a58eb5 100644 --- a/lib/core/routes/route_name.dart +++ b/lib/core/routes/route_name.dart @@ -12,6 +12,7 @@ class RouteName { //Register static const String registerStepScreen = 'registerStepScreen'; static const String registerScreen = 'registerScreen'; + static const String registerUserDetailsScreen = 'registerUserDetailsScreen'; //No Internet static const String noInternetScreen = 'noInternet'; @@ -33,5 +34,4 @@ class RouteName { //Biometric static const String otpScreen = 'otpScreen'; - } diff --git a/lib/core/routes/routes.dart b/lib/core/routes/routes.dart index 2272075..5841606 100644 --- a/lib/core/routes/routes.dart +++ b/lib/core/routes/routes.dart @@ -15,6 +15,7 @@ import 'package:tanami_app/features/register/presentation/pages/register_step_sc import 'package:tanami_app/features/welcome/presentation/pages/weclome_screen.dart'; import '../../features/login/presentation/pages/login_screen.dart'; +import '../../features/register/presentation/pages/register_user_details_screen.dart'; import '../../features/splash/presentation/pages/splash_screen.dart'; /* CREATED BY - JAYESH JAIN @@ -94,12 +95,19 @@ final goRouter = GoRouter( }, ), GoRoute( - name: RouteName.porfolioDetails, - path: RouteName.porfolioDetails, - builder: (context, state) { - return const DetailsScreen(); - }, - ), + name: RouteName.porfolioDetails, + path: RouteName.porfolioDetails, + builder: (context, state) { + return const DetailsScreen(); + }, + ), + GoRoute( + name: RouteName.registerUserDetailsScreen, + path: RouteName.registerUserDetailsScreen, + builder: (context, state) { + return const RegisterUserDetailsScreen(); + }, + ), ]), // GoRoute( diff --git a/lib/core/styles/app_color.dart b/lib/core/styles/app_color.dart index ac91fbe..22e7280 100644 --- a/lib/core/styles/app_color.dart +++ b/lib/core/styles/app_color.dart @@ -41,4 +41,7 @@ class AppColor { static const Color strokeColor = Color(0xFFB4B4B4); static const Color otpTextColor = Color(0xFF191B1E); static const Color fillColor = Color(0xFFF6F6F6); + + //CheckBox Color + static const Color checkBoxActiveColor = Color(0xFF09622E); } diff --git a/lib/core/styles/app_text.dart b/lib/core/styles/app_text.dart index 1b154bc..efc5536 100644 --- a/lib/core/styles/app_text.dart +++ b/lib/core/styles/app_text.dart @@ -40,6 +40,8 @@ class AppText { "Enter your country of residence and mobile number"; static const String enterNameEmailPassword = "Enter your name, email and password"; + static const String enterNameEmailUniquePassword = + "Enter your name and email and choose a unique password"; static const String enableBiometricAuthentication = "Enable biometric authentication and select a unique pin code for easy access"; static const String getStarted = "Get started"; @@ -48,6 +50,17 @@ class AppText { "Select your country of residence and enter your mobile number"; static const String nextText = "Next"; static const String backText = "Back"; + static const String firstNameText = "First Name"; + static const String lastNameText = "Last Name"; + static const String emailText = "Email"; + static const String repeatPasswordText = "Repeat Password"; + static const String min8CharactersSpecialCharctersText = + "Min 8 characters, special characters required"; + static const String enterFirstName = "Enter First Name"; + static const String enterLastName = "Enter Last Name"; + static const String enterEmail = "Enter Email"; + static const String cantBeEmptyText = "Can't Be Empty"; + static const String passwordMismatch = "Password Mismatch"; //Country Name static const String bahrainCountryText = "Bahrain"; @@ -76,5 +89,6 @@ class AppText { static const String referToSameOtpMessage = "Please refer to the same OTP message shown below"; static const String resendSms = "Resend SMS"; - + static const String otpVerifiedSucessfully = "OTP Verified Successfully!"; + static const String otpVerifiedFailed = "OTP Verification Failed:"; } diff --git a/lib/features/countrySelection/presentation/widgets/country_selection_list.dart b/lib/features/countrySelection/presentation/widgets/country_selection_list.dart index 5f11f8c..4c31feb 100644 --- a/lib/features/countrySelection/presentation/widgets/country_selection_list.dart +++ b/lib/features/countrySelection/presentation/widgets/country_selection_list.dart @@ -34,7 +34,7 @@ class CountrySelectionList extends StatelessWidget { height: 24, ), const Gap(10), - TextWidget().tex14W500(countryName[index], + TextWidget().text14W500(countryName[index], clr: AppColor.charcoalColor), ], ), diff --git a/lib/features/login/presentation/widgets/bottom_section.dart b/lib/features/login/presentation/widgets/bottom_section.dart index 8fc6017..d3f3afb 100644 --- a/lib/features/login/presentation/widgets/bottom_section.dart +++ b/lib/features/login/presentation/widgets/bottom_section.dart @@ -31,7 +31,7 @@ class BottomSection extends StatelessWidget { function: () { goRouter.goNamed(RouteName.loginScreen); }, - text: TextWidget().tex15W400(AppText.forgorPassword, + text: TextWidget().text15W400(AppText.forgorPassword, clr: AppColor.forgotPassButtonColor, textDecoration: TextDecoration.underline)), ), @@ -103,7 +103,7 @@ class BottomSection extends StatelessWidget { "fromScreentype": "login", }); }, - text: TextWidget().tex14W700(AppText.signUpText, + text: TextWidget().text14W700(AppText.signUpText, clr: AppColor.textLabelColor, textDecoration: TextDecoration.underline)), ], diff --git a/lib/features/login/presentation/widgets/login_form.dart b/lib/features/login/presentation/widgets/login_form.dart index e8f5537..d933d3a 100644 --- a/lib/features/login/presentation/widgets/login_form.dart +++ b/lib/features/login/presentation/widgets/login_form.dart @@ -4,6 +4,7 @@ import 'package:gap/gap.dart'; import 'package:tanami_app/core/styles/app_text.dart'; import 'package:tanami_app/core/utils/constant/country_flag_data.dart'; +import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart'; import '../../../../shared/components/form_label_textfield.dart'; import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; import '../../../countrySelection/presentation/bloc/choose_country_state.dart'; @@ -70,11 +71,14 @@ class LoginForm extends StatelessWidget { textEditingController: loginBloc.phoneNumberTextField, ), const Gap(20), - FormLabelTextField( - hintText: AppText.enterPassword, - title: AppText.password, - type: "password", - textEditingController: loginBloc.passwordTextField, + BlocProvider( + create: (_) => PasswordVisibilityBloc(), + child: FormLabelTextField( + hintText: AppText.enterPassword, + title: AppText.password, + type: "password", + textEditingController: loginBloc.passwordTextField, + ), ), ], ), diff --git a/lib/features/login/presentation/widgets/top_section.dart b/lib/features/login/presentation/widgets/top_section.dart index 73cf478..e447561 100644 --- a/lib/features/login/presentation/widgets/top_section.dart +++ b/lib/features/login/presentation/widgets/top_section.dart @@ -21,14 +21,14 @@ class TopSection extends StatelessWidget { ), ), const Gap(60), - TextWidget().tex20W700( + TextWidget().text20W700( AppText.welcomeText, clr: AppColor.charcoalColor, ), const Gap(10), Padding( padding: const EdgeInsets.symmetric(horizontal: 75), - child: TextWidget().tex14W500( + child: TextWidget().text14W500( AppText.pleaseEnterLoginDetails, clr: AppColor.smokeGrayColor, ), diff --git a/lib/features/otpVerification/presentation/bloc/timer/timer_bloc.dart b/lib/features/otpVerification/presentation/bloc/timer/timer_bloc.dart new file mode 100644 index 0000000..acbe5a3 --- /dev/null +++ b/lib/features/otpVerification/presentation/bloc/timer/timer_bloc.dart @@ -0,0 +1,46 @@ +// timer_bloc.dart +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'timer_event.dart'; +import 'timer_state.dart'; + +class TimerBloc extends Bloc { + Timer? _timer; + + TimerBloc() : super(const TimerInitial()) { + on(_onStartTimer); + on(_onTick); + } + + void _onStartTimer(StartTimer event, Emitter emit) { + const int duration = 300; // 5 minutes in seconds + emit(const TimerRunInProgress(duration)); + _startTicker(duration); + } + + void _onTick(Tick event, Emitter emit) { + if (event.duration > 0) { + emit(TimerRunInProgress(event.duration)); + } else { + emit(const TimerRunComplete()); + } + } + + void _startTicker(int duration) { + _timer?.cancel(); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (duration - timer.tick > 0) { + add(Tick(duration - timer.tick)); + } else { + timer.cancel(); + add(const Tick(0)); + } + }); + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/features/otpVerification/presentation/bloc/timer/timer_event.dart b/lib/features/otpVerification/presentation/bloc/timer/timer_event.dart new file mode 100644 index 0000000..302880e --- /dev/null +++ b/lib/features/otpVerification/presentation/bloc/timer/timer_event.dart @@ -0,0 +1,20 @@ +// timer_event.dart +import 'package:equatable/equatable.dart'; + +abstract class TimerEvent extends Equatable { + const TimerEvent(); + + @override + List get props => []; +} + +class StartTimer extends TimerEvent {} + +class Tick extends TimerEvent { + final int duration; + + const Tick(this.duration); + + @override + List get props => [duration]; +} diff --git a/lib/features/otpVerification/presentation/bloc/timer/timer_state.dart b/lib/features/otpVerification/presentation/bloc/timer/timer_state.dart new file mode 100644 index 0000000..5410e76 --- /dev/null +++ b/lib/features/otpVerification/presentation/bloc/timer/timer_state.dart @@ -0,0 +1,28 @@ +// timer_state.dart +import 'package:equatable/equatable.dart'; + +class TimerState extends Equatable { + final int duration; + const TimerState(this.duration); + + String get formattedDuration { + final minutes = (duration ~/ 60).toString().padLeft(2, '0'); + final seconds = (duration % 60).toString().padLeft(2, '0'); + return '$minutes:$seconds'; + } + + @override + List get props => [duration]; +} + +class TimerInitial extends TimerState { + const TimerInitial() : super(300); // Initial state with 5 minutes +} + +class TimerRunInProgress extends TimerState { + const TimerRunInProgress(int duration) : super(duration); +} + +class TimerRunComplete extends TimerState { + const TimerRunComplete() : super(0); +} diff --git a/lib/features/otpVerification/presentation/pages/otp_layout.dart b/lib/features/otpVerification/presentation/pages/otp_layout.dart index 46cd8a6..658a4e3 100644 --- a/lib/features/otpVerification/presentation/pages/otp_layout.dart +++ b/lib/features/otpVerification/presentation/pages/otp_layout.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:tanami_app/features/otpVerification/presentation/widgets/otp_fill_section.dart'; import '../widgets/otp_top_section.dart'; +import '../widgets/resend_otp_section.dart'; class OtpLayout extends StatelessWidget { const OtpLayout({super.key}); @@ -13,6 +14,7 @@ class OtpLayout extends StatelessWidget { children: const [ OtpTopSection(), OtpFillSection(), + ResendOtpSection(), ], )); } diff --git a/lib/features/otpVerification/presentation/pages/otp_screen.dart b/lib/features/otpVerification/presentation/pages/otp_screen.dart index fbfc730..7a9e994 100644 --- a/lib/features/otpVerification/presentation/pages/otp_screen.dart +++ b/lib/features/otpVerification/presentation/pages/otp_screen.dart @@ -4,6 +4,8 @@ import 'package:tanami_app/features/otpVerification/presentation/bloc/otp_bloc.d import 'package:tanami_app/features/otpVerification/presentation/pages/otp_layout.dart'; import '../bloc/otp_event.dart'; +import '../bloc/timer/timer_bloc.dart'; +import '../bloc/timer/timer_event.dart'; class OtpScreen extends StatelessWidget { const OtpScreen({super.key}); @@ -18,6 +20,10 @@ class OtpScreen extends StatelessWidget { // Create an instance of the OnboardingBloc create: (context) => OtpBloc()..add(StartListeningForOtp()), ), + BlocProvider( + create: (context) => + TimerBloc()..add(StartTimer()), // Start the timer here + ), ], child: const OtpLayout(), ), diff --git a/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart b/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart index 2bf78f3..417e1f9 100644 --- a/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart +++ b/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:sms_autofill/sms_autofill.dart'; +import 'package:tanami_app/core/routes/route_name.dart'; import 'package:tanami_app/core/routes/routes.dart'; import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; import 'package:tanami_app/shared/components/loader.dart'; import 'package:tanami_app/shared/components/toast_message.dart'; @@ -22,10 +24,15 @@ class OtpFillSection extends StatelessWidget { Loader.loader(context); } else if (state is OtpSubmissionSuccess) { goRouter.pop(); - successToastMessage(context, 'OTP Verified Successfully!'); + successToastMessage( + context, + AppText.otpVerifiedSucessfully, + ); + goRouter.pushNamed(RouteName.registerUserDetailsScreen); } else if (state is OtpSubmissionFailure) { goRouter.pop(); - errorToastMessage(context, 'OTP Verification Failed: ${state.error}'); + errorToastMessage( + context, '${AppText.otpVerifiedFailed} ${state.error}'); } }, builder: (context, state) { diff --git a/lib/features/otpVerification/presentation/widgets/otp_top_section.dart b/lib/features/otpVerification/presentation/widgets/otp_top_section.dart index ff3df0f..c1d85f9 100644 --- a/lib/features/otpVerification/presentation/widgets/otp_top_section.dart +++ b/lib/features/otpVerification/presentation/widgets/otp_top_section.dart @@ -22,14 +22,14 @@ class OtpTopSection extends StatelessWidget { ), ), const Gap(125), - TextWidget().tex20W700( + TextWidget().text20W700( AppText.checkYourMessages, clr: AppColor.charcoalColor, ), const Gap(25), Padding( padding: const EdgeInsets.symmetric(horizontal: 35), - child: TextWidget().tex14W500( + child: TextWidget().text14W500( AppText.referToSameOtpMessage, clr: AppColor.smokeGrayColor, ), diff --git a/lib/features/otpVerification/presentation/widgets/resend_otp_section.dart b/lib/features/otpVerification/presentation/widgets/resend_otp_section.dart new file mode 100644 index 0000000..20204a0 --- /dev/null +++ b/lib/features/otpVerification/presentation/widgets/resend_otp_section.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; +import 'package:tanami_app/shared/components/toast_message.dart'; + +import '../bloc/timer/timer_bloc.dart'; +import '../bloc/timer/timer_state.dart'; + +class ResendOtpSection extends StatelessWidget { + const ResendOtpSection({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextWidget().text14W500( + state.formattedDuration, + clr: AppColor.plainBlack, + ), + InkWell( + onTap: () { + successToastMessage(context, "OTP Resend Sucessfully !"); + }, + child: TextWidget().text14W500( + AppText.resendSms, + clr: AppColor.plainBlack, + textDecoration: TextDecoration.underline, + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/register/presentation/bloc/register_bloc.dart b/lib/features/register/presentation/bloc/register_bloc.dart index 13d3edf..7811fe6 100644 --- a/lib/features/register/presentation/bloc/register_bloc.dart +++ b/lib/features/register/presentation/bloc/register_bloc.dart @@ -8,7 +8,6 @@ class RegisterBloc extends Bloc { final TextEditingController countrySelectionTextField = TextEditingController(); final TextEditingController phoneNumberTextField = TextEditingController(); - final TextEditingController passwordTextField = TextEditingController(); GlobalKey getFormKey() { return formKey; @@ -16,7 +15,7 @@ class RegisterBloc extends Bloc { RegisterBloc() : super(RegisterInitial()) { phoneNumberTextField.addListener(_onFormFieldChanged); - passwordTextField.addListener(_onFormFieldChanged); + countrySelectionTextField.addListener(_onFormFieldChanged); on(_onLoginFormChanged); on((event, emit) async { @@ -28,8 +27,8 @@ class RegisterBloc extends Bloc { // Simulate API call await Future.delayed(const Duration(seconds: 2)); // Replace the next line with actual API call - final isSuccess = await _mockLoginApi( - event.phoneNumber, event.password, event.countryResidence); + final isSuccess = + await _mockLoginApi(event.phoneNumber, event.countryResidence); if (isSuccess) { emit(RegisterSuccess()); } else { @@ -58,7 +57,6 @@ class RegisterBloc extends Bloc { // Mock API function, replace with actual API call Future _mockLoginApi( String phoneNumber, - String password, String countryResidence, ) async { return true; @@ -67,7 +65,7 @@ class RegisterBloc extends Bloc { @override Future close() { phoneNumberTextField.dispose(); - passwordTextField.dispose(); + countrySelectionTextField.dispose(); return super.close(); } diff --git a/lib/features/register/presentation/bloc/register_event.dart b/lib/features/register/presentation/bloc/register_event.dart index d7a46e1..2481914 100644 --- a/lib/features/register/presentation/bloc/register_event.dart +++ b/lib/features/register/presentation/bloc/register_event.dart @@ -9,17 +9,16 @@ abstract class RegisterEvent extends Equatable { class RegisterSubmitted extends RegisterEvent { final String phoneNumber; - final String password; + final String countryResidence; const RegisterSubmitted( this.phoneNumber, - this.password, this.countryResidence, ); @override - List get props => [phoneNumber, password, countryResidence]; + List get props => [phoneNumber, countryResidence]; } class RegisterFormChanged extends RegisterEvent { diff --git a/lib/features/register/presentation/bloc/register_user_bloc.dart b/lib/features/register/presentation/bloc/register_user_bloc.dart new file mode 100644 index 0000000..937a3a2 --- /dev/null +++ b/lib/features/register/presentation/bloc/register_user_bloc.dart @@ -0,0 +1,94 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +import 'register_user_event.dart'; +import 'register_user_state.dart'; + +class RegisterUserBloc extends Bloc { + final GlobalKey formKey = GlobalKey(); + final TextEditingController firstNameTextField = TextEditingController(); + final TextEditingController lastNameTextField = TextEditingController(); + final TextEditingController emailTextField = TextEditingController(); + final TextEditingController passwordTextField = TextEditingController(); + final TextEditingController repeatPasswordTextField = TextEditingController(); + + GlobalKey getFormKey() { + return formKey; + } + + RegisterUserBloc() : super(RegisterUserInitial()) { + firstNameTextField.addListener(_onFormFieldChanged); + passwordTextField.addListener(_onFormFieldChanged); + lastNameTextField.addListener(_onFormFieldChanged); + emailTextField.addListener(_onFormFieldChanged); + repeatPasswordTextField.addListener(_onFormFieldChanged); + + on(_onLoginFormChanged); + on((event, emit) async { + if (!formKey.currentState!.validate()) { + return; + } + emit(RegisterUserLoading()); + try { + // Simulate API call + await Future.delayed(const Duration(seconds: 2)); + // Replace the next line with actual API call + final isSuccess = await _mockLoginApi( + event.firstName, + event.password, + event.lastName, + event.confirmPassword, + event.email, + ); + if (isSuccess) { + emit(RegisterUserSuccess()); + } else { + emit(const RegisterUserFailure( + "Register failed. Please check your credentials.")); + } + } catch (e) { + emit(RegisterUserFailure(e.toString())); + } + }); + } + void _onFormFieldChanged() { + add(RegisterFormChanged( + firstNameTextField.text, + lastNameTextField.text, + emailTextField.text, + passwordTextField.text, + repeatPasswordTextField.text, + )); + } + + void _onLoginFormChanged( + RegisterFormChanged event, Emitter emit) { + final areFieldsFilled = event.firstName.isNotEmpty && + event.lastName.isNotEmpty && + event.email.isNotEmpty && + event.password.isNotEmpty && + event.confirmPassword.isNotEmpty; + emit(RegisterUserFieldsState(areFieldsFilled)); + } + + // Mock API function, replace with actual API call + Future _mockLoginApi( + String firstName, + String password, + String confirmPassword, + String email, + String lastName, + ) async { + return true; + } + + @override + Future close() { + firstNameTextField.dispose(); + passwordTextField.dispose(); + lastNameTextField.dispose(); + emailTextField.dispose(); + repeatPasswordTextField.dispose(); + return super.close(); + } +} diff --git a/lib/features/register/presentation/bloc/register_user_event.dart b/lib/features/register/presentation/bloc/register_user_event.dart new file mode 100644 index 0000000..b48da92 --- /dev/null +++ b/lib/features/register/presentation/bloc/register_user_event.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; + +abstract class RegisterUserEvent extends Equatable { + const RegisterUserEvent(); + + @override + List get props => []; +} + +class RegisterUserSubmitted extends RegisterUserEvent { + final String firstName; + final String lastName; + final String email; + final String password; + final String confirmPassword; + + const RegisterUserSubmitted( + this.firstName, + this.password, + this.lastName, + this.confirmPassword, + this.email, + ); + + @override + List get props => + [firstName, lastName, email, confirmPassword, password]; +} + +class RegisterFormChanged extends RegisterUserEvent { + final String firstName; + final String lastName; + final String email; + final String password; + final String confirmPassword; + + const RegisterFormChanged( + this.firstName, + this.password, + this.lastName, + this.confirmPassword, + this.email, + ); + + @override + List get props => + [firstName, lastName, email, confirmPassword, password]; +} diff --git a/lib/features/register/presentation/bloc/register_user_state.dart b/lib/features/register/presentation/bloc/register_user_state.dart new file mode 100644 index 0000000..6364781 --- /dev/null +++ b/lib/features/register/presentation/bloc/register_user_state.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +abstract class RegisterUserState extends Equatable { + const RegisterUserState(); + + @override + List get props => []; +} + +class RegisterUserInitial extends RegisterUserState {} + +class RegisterUserLoading extends RegisterUserState {} + +class RegisterUserSuccess extends RegisterUserState {} + +class RegisterUserFailure extends RegisterUserState { + final String error; + + const RegisterUserFailure(this.error); + + @override + List get props => [error]; +} + +class RegisterUserFieldsState extends RegisterUserState { + final bool areFieldsFilled; + + const RegisterUserFieldsState(this.areFieldsFilled); + + @override + List get props => [areFieldsFilled]; +} diff --git a/lib/features/register/presentation/pages/register_user_details_layout.dart b/lib/features/register/presentation/pages/register_user_details_layout.dart new file mode 100644 index 0000000..2a49b8f --- /dev/null +++ b/lib/features/register/presentation/pages/register_user_details_layout.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:tanami_app/features/register/presentation/widgets/register_user_bottom_section.dart'; + +import '../../../../shared/components/appbar_widget.dart'; +import '../widgets/register_user_form.dart'; +import '../widgets/register_user_top_section.dart'; + +class RegisterUserDetailsLayout extends StatelessWidget { + const RegisterUserDetailsLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const AppBarWidget( + height: 75, + titleTxt: "", + ), + body: ListView( + children: const [ + RegisterUserTopSection(), + RegisterUserForm(), + RegisterUserBottomSection(), + ], + )); + } +} diff --git a/lib/features/register/presentation/pages/register_user_details_screen.dart b/lib/features/register/presentation/pages/register_user_details_screen.dart new file mode 100644 index 0000000..93ae371 --- /dev/null +++ b/lib/features/register/presentation/pages/register_user_details_screen.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/features/register/presentation/pages/register_user_details_layout.dart'; +import 'package:tanami_app/shared/components/bloc/checkbox/checkbox_bloc.dart'; + +import '../bloc/register_user_bloc.dart'; + +class RegisterUserDetailsScreen extends StatelessWidget { + const RegisterUserDetailsScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + body: MultiBlocProvider( + providers: [ + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => RegisterUserBloc(), + ), + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => CheckboxBloc(), + ), + ], + child: const RegisterUserDetailsLayout(), + ), + ); + } +} diff --git a/lib/features/register/presentation/widgets/register_bottom_section.dart b/lib/features/register/presentation/widgets/register_bottom_section.dart index df9c3bb..a3b6b51 100644 --- a/lib/features/register/presentation/widgets/register_bottom_section.dart +++ b/lib/features/register/presentation/widgets/register_bottom_section.dart @@ -75,10 +75,6 @@ class RegisterBottomSection extends StatelessWidget { .read() .phoneNumberTextField .text, - context - .read() - .passwordTextField - .text, ""), ) : null; @@ -97,7 +93,7 @@ class RegisterBottomSection extends StatelessWidget { radioBloc.resetSelection(); goRouter.pop(); }, - text: TextWidget().tex14W700(AppText.backText, + text: TextWidget().text14W700(AppText.backText, clr: AppColor.textLabelColor, textDecoration: TextDecoration.underline)), ], diff --git a/lib/features/register/presentation/widgets/register_step_bottom_section.dart b/lib/features/register/presentation/widgets/register_step_bottom_section.dart index e1bcd60..d76b878 100644 --- a/lib/features/register/presentation/widgets/register_step_bottom_section.dart +++ b/lib/features/register/presentation/widgets/register_step_bottom_section.dart @@ -42,7 +42,7 @@ class RegisterStepBottomSection extends StatelessWidget { "fromScreen": "registerStep", }); }, - text: TextWidget().tex14W700( + text: TextWidget().text14W700( AppText.loginText, clr: AppColor.darkGreyColor, textDecoration: TextDecoration.underline, diff --git a/lib/features/register/presentation/widgets/register_step_count.dart b/lib/features/register/presentation/widgets/register_step_count.dart index 528596f..d4a2595 100644 --- a/lib/features/register/presentation/widgets/register_step_count.dart +++ b/lib/features/register/presentation/widgets/register_step_count.dart @@ -21,12 +21,12 @@ class RegisterStepCount extends StatelessWidget { child: ListTile( isThreeLine: true, leading: SvgPicture.asset(stepImage[index]), - title: TextWidget().tex14W700( + title: TextWidget().text14W700( title[index], clr: AppColor.textLabelColor, txtAlign: TextAlign.start, ), - subtitle: TextWidget().tex14W500( + subtitle: TextWidget().text14W500( description[index], clr: AppColor.textLabelColor, txtAlign: TextAlign.start, diff --git a/lib/features/register/presentation/widgets/register_step_top_section.dart b/lib/features/register/presentation/widgets/register_step_top_section.dart index 7f4bb24..fee10b5 100644 --- a/lib/features/register/presentation/widgets/register_step_top_section.dart +++ b/lib/features/register/presentation/widgets/register_step_top_section.dart @@ -21,14 +21,14 @@ class RegisterStepTopSection extends StatelessWidget { ), ), const Gap(30), - TextWidget().tex20W700( + TextWidget().text20W700( AppText.getStarted, clr: AppColor.charcoalColor, ), const Gap(10), Padding( padding: const EdgeInsets.symmetric(horizontal: 75), - child: TextWidget().tex14W500( + child: TextWidget().text14W500( AppText.setupYourTanamiAccountToBegin, clr: AppColor.smokeGrayColor, ), diff --git a/lib/features/register/presentation/widgets/register_top_section.dart b/lib/features/register/presentation/widgets/register_top_section.dart index d6e71c0..3917f5f 100644 --- a/lib/features/register/presentation/widgets/register_top_section.dart +++ b/lib/features/register/presentation/widgets/register_top_section.dart @@ -21,14 +21,14 @@ class RegisterTopSection extends StatelessWidget { ), ), const Gap(60), - TextWidget().tex20W700( + TextWidget().text20W700( AppText.welcome, clr: AppColor.charcoalColor, ), const Gap(10), Padding( padding: const EdgeInsets.symmetric(horizontal: 75), - child: TextWidget().tex14W500( + child: TextWidget().text14W500( AppText.selectYourCountryOfResidence, clr: AppColor.smokeGrayColor, ), diff --git a/lib/features/register/presentation/widgets/register_user_bottom_section.dart b/lib/features/register/presentation/widgets/register_user_bottom_section.dart new file mode 100644 index 0000000..64f8beb --- /dev/null +++ b/lib/features/register/presentation/widgets/register_user_bottom_section.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_images.dart'; +import 'package:tanami_app/shared/components/checkbox_widget.dart'; +import 'package:tanami_app/shared/components/loader.dart'; +import 'package:tanami_app/shared/components/toast_message.dart'; + +import '../../../../core/routes/route_name.dart'; +import '../../../../core/routes/routes.dart'; +import '../../../../core/styles/app_color.dart'; +import '../../../../core/styles/app_text.dart'; +import '../../../../shared/components/button_widget.dart'; +import '../../../../shared/components/text_widget.dart'; +import '../bloc/register_user_bloc.dart'; +import '../bloc/register_user_event.dart'; +import '../bloc/register_user_state.dart'; + +class RegisterUserBottomSection extends StatelessWidget { + const RegisterUserBottomSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const Gap(40), + Row( + children: [ + const CheckBoxWidget(), + Container( + width: 0.8.sw, + child: Row( + children: [ + const Text("I agree to the "), + InkWell( + onTap: () { + // Handle Terms & Conditions tap + }, + child: const Text( + "Terms & Conditions", + style: TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + ), + ), + ), + const Text(" and the "), + InkWell( + onTap: () { + // Handle Privacy Policy tap + }, + child: const Text( + "Privacy Policy", + style: TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + ), + ), + ), + ], + ), + ) + ], + ), + const Gap(10), + Image.asset( + AppImages.stage2Image, + width: 75, + height: 12, + ), + const Gap(36), + BlocConsumer( + listener: (context, state) { + if (state is RegisterUserLoading) { + Loader.loader(context); + } else if (state is RegisterUserSuccess) { + successToastMessage(context, "successful !"); + goRouter.pop(); + + goRouter.pushNamed(RouteName.otpScreen); + } else if (state is RegisterUserFailure) { + goRouter.pop(); + errorToastMessage( + context, + state.error, + ); + } + }, + builder: (context, state) { + bool isButtonEnabled = false; + if (state is RegisterUserFieldsState) { + isButtonEnabled = state.areFieldsFilled; + } else if (state is RegisterUserSuccess || + state is RegisterUserFailure) { + isButtonEnabled = true; + } + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + ), + width: 1.sw, + height: 56.h, + child: ButtonWidget().elevatedBtn( + txtClr: isButtonEnabled + ? AppColor.plainWhite + : AppColor.inactiveBtnTxtColor, + function: () { + isButtonEnabled + ? context.read().add( + RegisterUserSubmitted( + context + .read() + .firstNameTextField + .text, + context + .read() + .passwordTextField + .text, + context + .read() + .lastNameTextField + .text, + context + .read() + .repeatPasswordTextField + .text, + context + .read() + .emailTextField + .text, + ), + ) + : null; + }, + text: AppText.nextText, + clr: isButtonEnabled + ? AppColor.primaryColor2 + : AppColor.inactiveBtnColor, + ), + ); + }, + ), + const Gap(5), + ButtonWidget().textBtn( + function: () { + goRouter.pop(); + }, + text: TextWidget().text14W700(AppText.backText, + clr: AppColor.textLabelColor, + textDecoration: TextDecoration.underline)), + ], + ); + } +} diff --git a/lib/features/register/presentation/widgets/register_user_form.dart b/lib/features/register/presentation/widgets/register_user_form.dart new file mode 100644 index 0000000..d4e077c --- /dev/null +++ b/lib/features/register/presentation/widgets/register_user_form.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; + +import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart'; +import '../../../../shared/components/form_label_textfield.dart'; +import '../bloc/register_user_bloc.dart'; + +class RegisterUserForm extends StatelessWidget { + const RegisterUserForm({super.key}); + + @override + Widget build(BuildContext context) { + final registerUserBloc = context.read(); + + return Form( + key: registerUserBloc.formKey, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 14, + ), + child: Align( + alignment: Alignment.topLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(50), + FormLabelTextField( + hintText: AppText.enterFirstName, + title: AppText.firstNameText, + type: AppText.firstNameText, + textEditingController: registerUserBloc.firstNameTextField, + ), + const Gap(12), + FormLabelTextField( + hintText: AppText.enterLastName, + title: AppText.lastNameText, + type: AppText.lastNameText, + textEditingController: registerUserBloc.lastNameTextField, + ), + const Gap(12), + FormLabelTextField( + hintText: AppText.enterEmail, + title: AppText.emailText, + type: AppText.emailText, + textEditingController: registerUserBloc.emailTextField, + ), + const Gap(12), + BlocProvider( + create: (_) => PasswordVisibilityBloc(), + child: FormLabelTextField( + hintText: AppText.enterPassword, + title: AppText.password, + type: AppText.password.toLowerCase(), + textEditingController: registerUserBloc.passwordTextField, + ), + ), + const Gap(12), + BlocProvider( + create: (_) => PasswordVisibilityBloc(), + child: FormLabelTextField( + hintText: AppText.repeatPasswordText, + title: AppText.repeatPasswordText, + type: AppText.password.toLowerCase(), + textEditingController: + registerUserBloc.repeatPasswordTextField, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/register/presentation/widgets/register_user_top_section.dart b/lib/features/register/presentation/widgets/register_user_top_section.dart new file mode 100644 index 0000000..71b8f33 --- /dev/null +++ b/lib/features/register/presentation/widgets/register_user_top_section.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/styles/app_images.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; + +class RegisterUserTopSection extends StatelessWidget { + const RegisterUserTopSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(22), + Center( + child: SvgPicture.asset( + AppImages.weclomeLogo, + ), + ), + const Gap(25), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 75), + child: TextWidget().text14W500( + AppText.enterNameEmailUniquePassword, + clr: AppColor.smokeGrayColor, + ), + ), + ], + ); + } +} diff --git a/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart b/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart index af4c921..6f96393 100644 --- a/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart +++ b/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart @@ -32,7 +32,7 @@ Widget buildOnboardingPage( fit: BoxFit.cover, ), const Gap(15), - TextWidget().tex22W700( + TextWidget().text22W700( title, clr: AppColor.primaryColor, ), @@ -41,7 +41,7 @@ Widget buildOnboardingPage( padding: const EdgeInsets.symmetric( horizontal: 50, ), - child: TextWidget().tex15W500( + child: TextWidget().text15W500( description, clr: AppColor.darkGreyColor, ), diff --git a/lib/features/welcome/presentation/widgets/login_signup_button.dart b/lib/features/welcome/presentation/widgets/login_signup_button.dart index ef12d97..6887eb8 100644 --- a/lib/features/welcome/presentation/widgets/login_signup_button.dart +++ b/lib/features/welcome/presentation/widgets/login_signup_button.dart @@ -42,7 +42,7 @@ class LoginSignUpButton extends StatelessWidget { "fromScreen": "welcome", }); }, - text: TextWidget().tex14W700( + text: TextWidget().text14W700( AppText.loginText, clr: AppColor.darkGreyColor, textDecoration: TextDecoration.underline, diff --git a/lib/main.dart b/lib/main.dart index 70a04f9..4d4555d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'core/routes/routes.dart'; import 'core/utils/connectivity/network_connectivity.dart'; import 'features/countrySelection/presentation/bloc/choose_country_bloc.dart'; -import 'shared/components/bloc/password_field/password_visibility_bloc.dart'; /* CREATED BY - JAYESH JAIN DATE - 24-05-2024 @@ -55,9 +54,9 @@ class _MyAppState extends State with WidgetsBindingObserver { BlocProvider( create: (context) => RadioBloc(), ), - BlocProvider( - create: (context) => PasswordVisibilityBloc(), - ), + // BlocProvider( + // create: (context) => PasswordVisibilityBloc(), + // ), ], child: ScreenUtilInit( builder: (BuildContext context, Widget? child) => MaterialApp.router( diff --git a/lib/shared/components/appbar_widget.dart b/lib/shared/components/appbar_widget.dart index bee8ea5..445b197 100644 --- a/lib/shared/components/appbar_widget.dart +++ b/lib/shared/components/appbar_widget.dart @@ -36,7 +36,7 @@ class AppBarWidget extends StatelessWidget implements PreferredSizeWidget { scrolledUnderElevation: 0.0, elevation: 0, centerTitle: true, - title: TextWidget().tex20W700(titleTxt, clr: AppColor.charcoalColor), + title: TextWidget().text20W700(titleTxt, clr: AppColor.charcoalColor), leading: Padding( padding: EdgeInsets.only( left: 16.w, diff --git a/lib/shared/components/bloc/checkbox/checkbox_bloc.dart b/lib/shared/components/bloc/checkbox/checkbox_bloc.dart new file mode 100644 index 0000000..90e68d4 --- /dev/null +++ b/lib/shared/components/bloc/checkbox/checkbox_bloc.dart @@ -0,0 +1,18 @@ +import 'package:bloc/bloc.dart'; + +import 'checkbox_event.dart'; +import 'checkbox_state.dart'; + +class CheckboxBloc extends Bloc { + CheckboxBloc() : super(CheckboxUnchecked()) { + on(_onToggleCheckbox); + } + + void _onToggleCheckbox(ToggleCheckbox event, Emitter emit) { + if (state is CheckboxUnchecked) { + emit(CheckboxChecked()); + } else { + emit(CheckboxUnchecked()); + } + } +} diff --git a/lib/shared/components/bloc/checkbox/checkbox_event.dart b/lib/shared/components/bloc/checkbox/checkbox_event.dart new file mode 100644 index 0000000..346cbbc --- /dev/null +++ b/lib/shared/components/bloc/checkbox/checkbox_event.dart @@ -0,0 +1,10 @@ +import 'package:equatable/equatable.dart'; + +abstract class CheckboxEvent extends Equatable { + const CheckboxEvent(); + + @override + List get props => []; +} + +class ToggleCheckbox extends CheckboxEvent {} diff --git a/lib/shared/components/bloc/checkbox/checkbox_state.dart b/lib/shared/components/bloc/checkbox/checkbox_state.dart new file mode 100644 index 0000000..a40b36b --- /dev/null +++ b/lib/shared/components/bloc/checkbox/checkbox_state.dart @@ -0,0 +1,12 @@ +import 'package:equatable/equatable.dart'; + +abstract class CheckBoxState extends Equatable { + const CheckBoxState(); + + @override + List get props => []; +} + +class CheckboxUnchecked extends CheckBoxState {} + +class CheckboxChecked extends CheckBoxState {} diff --git a/lib/shared/components/button_widget.dart b/lib/shared/components/button_widget.dart index c4069cd..29ee744 100644 --- a/lib/shared/components/button_widget.dart +++ b/lib/shared/components/button_widget.dart @@ -26,7 +26,7 @@ class ButtonWidget { style: ElevatedButton.styleFrom( backgroundColor: clr, ), - child: TextWidget().tex14W700( + child: TextWidget().text14W700( text, clr: txtClr ?? AppColor.plainWhite, ), diff --git a/lib/shared/components/checkbox_widget.dart b/lib/shared/components/checkbox_widget.dart new file mode 100644 index 0000000..81b853d --- /dev/null +++ b/lib/shared/components/checkbox_widget.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; + +import 'bloc/checkbox/checkbox_bloc.dart'; +import 'bloc/checkbox/checkbox_event.dart'; +import 'bloc/checkbox/checkbox_state.dart'; + +class CheckBoxWidget extends StatelessWidget { + const CheckBoxWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Checkbox( + activeColor: AppColor.checkBoxActiveColor, + checkColor: Colors.white, + side: MaterialStateBorderSide.resolveWith( + (states) { + if (states.contains(MaterialState.selected)) { + return const BorderSide( + color: AppColor.checkBoxActiveColor, width: 2); + } + return const BorderSide( + color: AppColor.strokeColor, + width: 2, + ); + }, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), // Custom radius + ), + value: state is CheckboxChecked, + onChanged: (value) { + context.read().add(ToggleCheckbox()); + }, + ); + }, + ); + } +} diff --git a/lib/shared/components/form_label_textfield.dart b/lib/shared/components/form_label_textfield.dart index 9beadd5..0dd53b7 100644 --- a/lib/shared/components/form_label_textfield.dart +++ b/lib/shared/components/form_label_textfield.dart @@ -29,7 +29,7 @@ class FormLabelTextField extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextWidget().tex14W500( + TextWidget().text14W500( title, clr: AppColor.textLabelColor, ), @@ -56,7 +56,7 @@ class FormLabelTextField extends StatelessWidget { }, texttype: type == "phone number" ? TextInputType.phone - : TextInputType.none, + : TextInputType.name, textEditingController: textEditingController, readonly: type == "country selection" ? true : false, hintText: hintText, diff --git a/lib/shared/components/text_widget.dart b/lib/shared/components/text_widget.dart index 6d79bbc..b54fd6f 100644 --- a/lib/shared/components/text_widget.dart +++ b/lib/shared/components/text_widget.dart @@ -14,7 +14,7 @@ class TextWidget { //Text Size 14 - Widget tex14W500( + Widget text14W500( String text, { Color? clr, TextDecoration? textDecoration, @@ -29,7 +29,7 @@ class TextWidget { color: clr ?? AppColor.plainWhite)); } - Widget tex14W700(String text, + Widget text14W700(String text, {Color? clr, TextDecoration? textDecoration, TextAlign? txtAlign}) { return Text(text, textAlign: txtAlign ?? TextAlign.center, @@ -41,7 +41,7 @@ class TextWidget { } //Text Size 15 - Widget tex15W500( + Widget text15W500( String text, { Color? clr, }) { @@ -53,7 +53,7 @@ class TextWidget { color: clr ?? AppColor.plainWhite)); } - Widget tex15W400( + Widget text15W400( String text, { Color? clr, TextDecoration? textDecoration, @@ -70,7 +70,7 @@ class TextWidget { } //Text Size 22 - Widget tex22W700(String text, {Color? clr}) { + Widget text22W700(String text, {Color? clr}) { return Text(text, style: GoogleFonts.dmSans( fontSize: 22, @@ -79,7 +79,7 @@ class TextWidget { } //Text Size 20 - Widget tex20W700(String text, {Color? clr}) { + Widget text20W700(String text, {Color? clr}) { return Text(text, style: GoogleFonts.dmSans( fontSize: 20, diff --git a/lib/shared/components/toast_message.dart b/lib/shared/components/toast_message.dart index 89a7e6c..3986f34 100644 --- a/lib/shared/components/toast_message.dart +++ b/lib/shared/components/toast_message.dart @@ -14,7 +14,7 @@ ToastificationItem successToastMessage( icon: const Icon(Icons.done), type: ToastificationType.success, context: context, - title: TextWidget().tex15W400( + title: TextWidget().text15W400( textV, clr: AppColor.darkGreyColor, txtAlign: TextAlign.start, @@ -33,7 +33,7 @@ ToastificationItem errorToastMessage( icon: const Icon(Icons.error), type: ToastificationType.error, context: context, - title: TextWidget().tex15W400( + title: TextWidget().text15W400( textV, clr: AppColor.darkGreyColor, txtAlign: TextAlign.start,