diff --git a/assets/images/auth_screen/png/stage_one.png b/assets/images/auth_screen/png/stage_one.png new file mode 100644 index 0000000..4f2d753 Binary files /dev/null and b/assets/images/auth_screen/png/stage_one.png differ diff --git a/assets/images/auth_screen/png/stage_two.png b/assets/images/auth_screen/png/stage_two.png new file mode 100644 index 0000000..e55880e Binary files /dev/null and b/assets/images/auth_screen/png/stage_two.png differ diff --git a/assets/images/auth_screen/svg/first_step.svg b/assets/images/auth_screen/svg/first_step.svg new file mode 100644 index 0000000..5c3174b --- /dev/null +++ b/assets/images/auth_screen/svg/first_step.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/auth_screen/svg/second_step.svg b/assets/images/auth_screen/svg/second_step.svg new file mode 100644 index 0000000..ca94e68 --- /dev/null +++ b/assets/images/auth_screen/svg/second_step.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/auth_screen/svg/third_step.svg b/assets/images/auth_screen/svg/third_step.svg new file mode 100644 index 0000000..7b842e4 --- /dev/null +++ b/assets/images/auth_screen/svg/third_step.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/biometric_screen/png/biomertric_face.png b/assets/images/biometric_screen/png/biomertric_face.png new file mode 100644 index 0000000..5044e8b Binary files /dev/null and b/assets/images/biometric_screen/png/biomertric_face.png differ diff --git a/assets/images/biometric_screen/png/biometric_fingerprint.png b/assets/images/biometric_screen/png/biometric_fingerprint.png new file mode 100644 index 0000000..57fafeb Binary files /dev/null and b/assets/images/biometric_screen/png/biometric_fingerprint.png differ diff --git a/assets/images/biometric_screen/svg/biometric_bg.svg b/assets/images/biometric_screen/svg/biometric_bg.svg new file mode 100644 index 0000000..0be6f5d --- /dev/null +++ b/assets/images/biometric_screen/svg/biometric_bg.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/country_flag/svg/bahrain_flag.svg b/assets/images/country_flag/svg/bahrain_flag.svg deleted file mode 100644 index 25d8620..0000000 --- a/assets/images/country_flag/svg/bahrain_flag.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/country_flag/svg/kuwait_flag.svg b/assets/images/country_flag/svg/kuwait_flag.svg deleted file mode 100644 index bbc8958..0000000 --- a/assets/images/country_flag/svg/kuwait_flag.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/country_flag/svg/oman_flag.svg b/assets/images/country_flag/svg/oman_flag.svg deleted file mode 100644 index 1636dff..0000000 --- a/assets/images/country_flag/svg/oman_flag.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/country_flag/svg/qatar_flag.svg b/assets/images/country_flag/svg/qatar_flag.svg deleted file mode 100644 index 68b2a67..0000000 --- a/assets/images/country_flag/svg/qatar_flag.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/country_flag/svg/saudi_arabia_flag.svg b/assets/images/country_flag/svg/saudi_arabia_flag.svg deleted file mode 100644 index de0b22e..0000000 --- a/assets/images/country_flag/svg/saudi_arabia_flag.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/country_flag/svg/united_arab_emirates_flag.svg b/assets/images/country_flag/svg/united_arab_emirates_flag.svg deleted file mode 100644 index 56291aa..0000000 --- a/assets/images/country_flag/svg/united_arab_emirates_flag.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/dialog/svg/exit_icon.svg b/assets/images/dialog/svg/exit_icon.svg new file mode 100644 index 0000000..a505a6b --- /dev/null +++ b/assets/images/dialog/svg/exit_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/routes/route_name.dart b/lib/core/routes/route_name.dart index 5a286bd..89a8b68 100644 --- a/lib/core/routes/route_name.dart +++ b/lib/core/routes/route_name.dart @@ -9,6 +9,10 @@ class RouteName { //Login static const String loginScreen = 'loginScreen'; + //Register + static const String registerStepScreen = 'registerStepScreen'; + static const String registerScreen = 'registerScreen'; + //No Internet static const String noInternetScreen = 'noInternet'; @@ -23,4 +27,11 @@ class RouteName { //Portfolio details static const String porfolioDetails = 'porfolioDetails'; + + //Biometric + static const String biometricScreen = 'biometricScreen'; + + //Biometric + static const String otpScreen = 'otpScreen'; + } diff --git a/lib/core/routes/routes.dart b/lib/core/routes/routes.dart index 4bb1c0a..2272075 100644 --- a/lib/core/routes/routes.dart +++ b/lib/core/routes/routes.dart @@ -3,8 +3,15 @@ import 'package:go_router/go_router.dart'; import 'package:tanami_app/core/routes/route_name.dart'; import 'package:tanami_app/features/MainScreens/MainScreen.dart'; + import 'package:tanami_app/features/MainScreens/Portfolio/presentation/pages/detailsScreen.dart'; + +import 'package:tanami_app/features/biometric/presentation/pages/biometric_screen.dart'; + import 'package:tanami_app/features/countrySelection/presentation/pages/choose_country_screen.dart'; +import 'package:tanami_app/features/otpVerification/presentation/pages/otp_screen.dart'; +import 'package:tanami_app/features/register/presentation/pages/register_screen.dart'; +import 'package:tanami_app/features/register/presentation/pages/register_step_screen.dart'; import 'package:tanami_app/features/welcome/presentation/pages/weclome_screen.dart'; import '../../features/login/presentation/pages/login_screen.dart'; @@ -20,55 +27,80 @@ final goRouter = GoRouter( //errorBuilder: (context, state) => ErrorScreen(state.error), routes: [ GoRoute( - name: "splash", - path: RouteName.splashScreen, - builder: (context, state) { - return const SplashScreen(); - }, - // redirect: (context, state) { - // if (true) { - // return "/login"; - // } - // return "/"; - // }, - routes: [ - GoRoute( - name: RouteName.loginScreen, - path: RouteName.loginScreen, - builder: (context, state) { - return const LoginScreen(); - }, - ), - GoRoute( - name: RouteName.welcomeScreen, - path: RouteName.welcomeScreen, - builder: (context, state) { - return const WelcomeScreen(); - }, - ), - GoRoute( - name: RouteName.chooseCountryScreen, - path: RouteName.chooseCountryScreen, - builder: (context, state) { - return const ChooseCountryScreen(); - }, - ), - GoRoute( - name: RouteName.mainScreen, - path: RouteName.mainScreen, - builder: (context, state) { - return const MainScreen(); - }, - ), - GoRoute( + name: "splash", + path: RouteName.splashScreen, + builder: (context, state) { + return const SplashScreen(); + }, + routes: [ + GoRoute( + name: RouteName.loginScreen, + path: "${RouteName.loginScreen}/:fromScreen", + builder: (context, state) { + return LoginScreen( + fromScreen: state.pathParameters["fromScreen"]!, + ); + }, + ), + GoRoute( + name: RouteName.registerStepScreen, + path: "${RouteName.registerStepScreen}/:fromScreentype", + builder: (context, state) { + return RegisterStepScreen( + fromScreen: state.pathParameters["fromScreentype"]!, + ); + }, + ), + GoRoute( + name: RouteName.welcomeScreen, + path: RouteName.welcomeScreen, + builder: (context, state) { + return const WelcomeScreen(); + }, + ), + GoRoute( + name: RouteName.chooseCountryScreen, + path: RouteName.chooseCountryScreen, + builder: (context, state) { + return const ChooseCountryScreen(); + }, + ), + GoRoute( + name: RouteName.mainScreen, + path: RouteName.mainScreen, + builder: (context, state) { + return const MainScreen(); + }, + ), + GoRoute( + name: RouteName.biometricScreen, + path: RouteName.biometricScreen, + builder: (context, state) { + return const BiometricScreen(); + }, + ), + GoRoute( + name: RouteName.registerScreen, + path: RouteName.registerScreen, + builder: (context, state) { + return const RegisterScreen(); + }, + ), + GoRoute( + name: RouteName.otpScreen, + path: RouteName.otpScreen, + builder: (context, state) { + return const OtpScreen(); + }, + ), + GoRoute( name: RouteName.porfolioDetails, path: RouteName.porfolioDetails, builder: (context, state) { return const DetailsScreen(); }, ), - ], - ), + ]), // GoRoute( // path: '/profile/:userId', diff --git a/lib/core/styles/app_color.dart b/lib/core/styles/app_color.dart index 7fc536b..ac91fbe 100644 --- a/lib/core/styles/app_color.dart +++ b/lib/core/styles/app_color.dart @@ -36,4 +36,9 @@ class AppColor { //Radio Color static const Color radioActiveColor = Color(0xFF0B8933); + + //Otp Color + static const Color strokeColor = Color(0xFFB4B4B4); + static const Color otpTextColor = Color(0xFF191B1E); + static const Color fillColor = Color(0xFFF6F6F6); } diff --git a/lib/core/styles/app_images.dart b/lib/core/styles/app_images.dart index 893c708..feab15b 100644 --- a/lib/core/styles/app_images.dart +++ b/lib/core/styles/app_images.dart @@ -22,6 +22,16 @@ class AppImages { "assets/images/auth_screen/svg/hide_password.svg"; static const String showPassword = "assets/images/auth_screen/svg/show_password.svg"; + static const String step1Image = + "assets/images/auth_screen/svg/first_step.svg"; + static const String step2Image = + "assets/images/auth_screen/svg/second_step.svg"; + static const String step3Image = + "assets/images/auth_screen/svg/third_step.svg"; + static const String stage1Image = + "assets/images/auth_screen/png/stage_one.png"; + static const String stage2Image = + "assets/images/auth_screen/png/stage_two.png"; //Country Flag static const String bahrainFlag = @@ -35,4 +45,15 @@ class AppImages { "assets/images/country_flag/png/saudi_arabia_flag.png"; static const String unitedArabEmiratesFlag = "assets/images/country_flag/png/uae_flag.png"; + + //Biometric Screen + static const String biometricBg = + "assets/images/biometric_screen/svg/biometric_bg.svg"; + static const String biometricFingerprint = + "assets/images/biometric_screen/png/biometric_fingerprint.png"; + static const String biometricFace = + "assets/images/biometric_screen/png/biomertric_face.png"; + + //Dialog + static const String exitAppIcon = "assets/images/dialog/svg/exit_icon.svg"; } diff --git a/lib/core/styles/app_text.dart b/lib/core/styles/app_text.dart index 84d3b92..065bb8f 100644 --- a/lib/core/styles/app_text.dart +++ b/lib/core/styles/app_text.dart @@ -29,6 +29,26 @@ class AppText { static const String invalidPassword = "Invalid Password"; static const String forgorPassword = "Forgot Password"; + //Register + static const String getStartedToday = "Get started today"; + static const String setupYourTanamiAccountToBegin = + "Setup your Tanami account to begin investing in a few simple steps"; + static const String step1 = "Step 1"; + static const String step2 = "Step 2"; + static const String step3 = "Step 3"; + static const String enterYourCountryOfResidence = + "Enter your country of residence and mobile number"; + static const String enterNameEmailPassword = + "Enter your name, email and password"; + static const String enableBiometricAuthentication = + "Enable biometric authentication and select a unique pin code for easy access"; + static const String getStarted = "Get started"; + static const String welcome = "Welcome!"; + static const String selectYourCountryOfResidence = + "Select your country of residence and enter your mobile number"; + static const String nextText = "Next"; + static const String backText = "Back"; + //Country Name static const String bahrainCountryText = "Bahrain"; static const String kuwaitCountryText = "Kuwait"; @@ -45,4 +65,17 @@ class AppText { static const String totalreturn = "Total return"; static const String disttodate = "Distributions to date"; static const String includeddocs = "Included Documents"; + + //Dialog + static const String exitText = "Exit"; + static const String cancelText = "Cancel"; + static const String areYouSureYouWantToExitText = + "Are you sure you want to Exit?"; + + //OTP + static const String checkYourMessages = "Check your messages"; + static const String referToSameOtpMessage = + "Please refer to the same OTP message shown below"; + static const String resendSms = "Resend SMS"; + } diff --git a/lib/core/utils/constant/register_step_data.dart b/lib/core/utils/constant/register_step_data.dart new file mode 100644 index 0000000..3022a35 --- /dev/null +++ b/lib/core/utils/constant/register_step_data.dart @@ -0,0 +1,20 @@ +import 'package:tanami_app/core/styles/app_images.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; + +List title = [ + AppText.step1, + AppText.step2, + AppText.step3, +]; + +List description = [ + AppText.enterYourCountryOfResidence, + AppText.enterNameEmailPassword, + AppText.enableBiometricAuthentication, +]; + +List stepImage = [ + AppImages.step1Image, + AppImages.step2Image, + AppImages.step3Image, +]; diff --git a/lib/features/biometric/presentation/pages/biometric_layout.dart b/lib/features/biometric/presentation/pages/biometric_layout.dart new file mode 100644 index 0000000..c738d53 --- /dev/null +++ b/lib/features/biometric/presentation/pages/biometric_layout.dart @@ -0,0 +1,49 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../core/styles/app_images.dart'; + +class BiometricLayout extends StatelessWidget { + const BiometricLayout({super.key}); + + @override + Widget build(BuildContext context) { + String biometricImage = ""; + if (Platform.isIOS) { + biometricImage = AppImages.biometricFace; + } else { + biometricImage = AppImages.biometricFingerprint; + } + return Scaffold( + body: SizedBox( + width: 1.sw, + height: 1.sh, + child: Stack( + children: [ + Positioned.fill( + child: SvgPicture.asset( + height: 1.sh, + width: 1.sw, + AppImages.biometricBg, + fit: BoxFit.cover, + ), + ), + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: Image.asset( + biometricImage, + width: 133, + height: 155, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/biometric/presentation/pages/biometric_screen.dart b/lib/features/biometric/presentation/pages/biometric_screen.dart new file mode 100644 index 0000000..f977b6f --- /dev/null +++ b/lib/features/biometric/presentation/pages/biometric_screen.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:tanami_app/features/biometric/presentation/pages/biometric_layout.dart'; + +class BiometricScreen extends StatelessWidget { + const BiometricScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + // resizeToAvoidBottomInset: true, + body: BiometricLayout(), + ); + } +} diff --git a/lib/features/countrySelection/presentation/bloc/choose_country_bloc.dart b/lib/features/countrySelection/presentation/bloc/choose_country_bloc.dart index 8712902..1df18ea 100644 --- a/lib/features/countrySelection/presentation/bloc/choose_country_bloc.dart +++ b/lib/features/countrySelection/presentation/bloc/choose_country_bloc.dart @@ -6,12 +6,22 @@ import 'choose_country_state.dart'; class RadioBloc extends Bloc { RadioBloc() : super(RadioInitial()) { on(_onRadioSelected); + on(_onResetRadioSelection); } void _onRadioSelected(RadioSelected event, Emitter emit) { emit(RadioSelectionChanged(event.selectedIndex)); } + void _onResetRadioSelection( + ResetRadioSelection event, Emitter emit) { + emit(RadioInitial()); + } + + void resetSelection() { + add(ResetRadioSelection()); + } + int get selectedCountry { if (state is RadioSelectionChanged) { return (state as RadioSelectionChanged).selectedIndex; diff --git a/lib/features/countrySelection/presentation/bloc/choose_country_event.dart b/lib/features/countrySelection/presentation/bloc/choose_country_event.dart index 5cf9a32..9c8d94e 100644 --- a/lib/features/countrySelection/presentation/bloc/choose_country_event.dart +++ b/lib/features/countrySelection/presentation/bloc/choose_country_event.dart @@ -10,8 +10,10 @@ abstract class RadioEvent extends Equatable { class RadioSelected extends RadioEvent { final int selectedIndex; - const RadioSelected(this.selectedIndex); + const RadioSelected([this.selectedIndex = 0]); @override List get props => [selectedIndex]; } + +class ResetRadioSelection extends RadioEvent {} diff --git a/lib/features/countrySelection/presentation/pages/choose_country_layout.dart b/lib/features/countrySelection/presentation/pages/choose_country_layout.dart index 7c4ab28..ebdc5e6 100644 --- a/lib/features/countrySelection/presentation/pages/choose_country_layout.dart +++ b/lib/features/countrySelection/presentation/pages/choose_country_layout.dart @@ -24,7 +24,6 @@ class ChooseCountryLayout extends StatelessWidget { child: ButtonWidget().elevatedBtn( txtClr: AppColor.plainWhite, function: () { - // radioBloc.add(const BackPressed(true)); goRouter.pop(); }, text: AppText.confirmSelectionText, diff --git a/lib/features/countrySelection/presentation/widgets/country_selection_list.dart b/lib/features/countrySelection/presentation/widgets/country_selection_list.dart index 0466980..5f11f8c 100644 --- a/lib/features/countrySelection/presentation/widgets/country_selection_list.dart +++ b/lib/features/countrySelection/presentation/widgets/country_selection_list.dart @@ -17,7 +17,7 @@ class CountrySelectionList extends StatelessWidget { final radioBloc = context.read(); return BlocBuilder( builder: (context, state) { - int selectedIndex = 0; + int selectedIndex = -1; if (state is RadioSelectionChanged) { selectedIndex = state.selectedIndex; } diff --git a/lib/features/login/presentation/bloc/login_bloc.dart b/lib/features/login/presentation/bloc/login_bloc.dart index 5a3a9e0..1ae720f 100644 --- a/lib/features/login/presentation/bloc/login_bloc.dart +++ b/lib/features/login/presentation/bloc/login_bloc.dart @@ -56,13 +56,20 @@ class LoginBloc extends Bloc { emit(LoginFieldsState(areFieldsFilled)); } + // Method to reset text fields + void resetFields() { + phoneNumberTextField.clear(); + passwordTextField.clear(); + countrySelectionTextField.clear(); + } + // Mock API function, replace with actual API call Future _mockLoginApi( - String email, + String phoneNumber, String password, String countryResidence, ) async { - return email == "1234567891" && password == "123456"; + return phoneNumber == "1234567891" && password == "123456"; } @override diff --git a/lib/features/login/presentation/pages/login_screen.dart b/lib/features/login/presentation/pages/login_screen.dart index 1d23678..dbf1805 100644 --- a/lib/features/login/presentation/pages/login_screen.dart +++ b/lib/features/login/presentation/pages/login_screen.dart @@ -1,29 +1,40 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart'; +import '../../../../shared/components/exit_app_dialog.dart'; +import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; import '../bloc/login_bloc.dart'; import 'login_layout.dart'; class LoginScreen extends StatelessWidget { - const LoginScreen({super.key}); + final String fromScreen; + const LoginScreen({super.key, required this.fromScreen}); @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: true, - body: MultiBlocProvider( - // Define the providers for the OnboardingBloc and other blocs - providers: [ - BlocProvider( - // Create an instance of the OnboardingBloc - create: (context) => LoginBloc(), - ), - BlocProvider( - create: (context) => PasswordVisibilityBloc(), - ), - ], - child: const LoginLayout(), + final radioBloc = context.read(); + return WillPopScope( + onWillPop: () async { + if (fromScreen == "welcome" || fromScreen == "registerStep") { + exitAppDialog(context); + return false; + } else { + radioBloc.resetSelection(); + return true; + } + }, + child: Scaffold( + resizeToAvoidBottomInset: true, + body: MultiBlocProvider( + // Define the providers for the OnboardingBloc and other blocs + providers: [ + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => LoginBloc(), + ), + ], + child: const LoginLayout(), + ), ), ); } diff --git a/lib/features/login/presentation/widgets/bottom_section.dart b/lib/features/login/presentation/widgets/bottom_section.dart index a821bb3..8fc6017 100644 --- a/lib/features/login/presentation/widgets/bottom_section.dart +++ b/lib/features/login/presentation/widgets/bottom_section.dart @@ -11,6 +11,7 @@ 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 '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; import '../bloc/login_bloc.dart'; import '../bloc/login_event.dart'; import '../bloc/login_state.dart'; @@ -20,6 +21,7 @@ class BottomSection extends StatelessWidget { @override Widget build(BuildContext context) { + final radioBloc = context.read(); return Column( children: [ const Gap(12), @@ -39,9 +41,11 @@ class BottomSection extends StatelessWidget { if (state is LoginLoading) { Loader.loader(context); } else if (state is LoginSuccess) { - goRouter.pop(); goRouter.goNamed('mainScreen'); successToastMessage(context, "login successful !"); + goRouter.pop(); + + goRouter.goNamed(RouteName.biometricScreen); } else if (state is LoginFailure) { goRouter.pop(); errorToastMessage( @@ -94,7 +98,10 @@ class BottomSection extends StatelessWidget { const Gap(5), ButtonWidget().textBtn( function: () { - goRouter.goNamed(RouteName.loginScreen); + radioBloc.resetSelection(); + goRouter.pushNamed(RouteName.registerStepScreen, pathParameters: { + "fromScreentype": "login", + }); }, text: TextWidget().tex14W700(AppText.signUpText, clr: AppColor.textLabelColor, diff --git a/lib/features/login/presentation/widgets/login_form.dart b/lib/features/login/presentation/widgets/login_form.dart index 1957354..e8f5537 100644 --- a/lib/features/login/presentation/widgets/login_form.dart +++ b/lib/features/login/presentation/widgets/login_form.dart @@ -16,6 +16,9 @@ class LoginForm extends StatelessWidget { Widget build(BuildContext context) { final loginBloc = context.read(); + // Reset fields when the screen is built + loginBloc.resetFields(); + return BlocConsumer(listener: (context, state) { int selectedCountry = -1; if (state is RadioSelectionChanged) { diff --git a/lib/features/otpVerification/presentation/bloc/otp_bloc.dart b/lib/features/otpVerification/presentation/bloc/otp_bloc.dart new file mode 100644 index 0000000..021580b --- /dev/null +++ b/lib/features/otpVerification/presentation/bloc/otp_bloc.dart @@ -0,0 +1,53 @@ +// otp_bloc.dart +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:sms_autofill/sms_autofill.dart'; +import 'otp_event.dart'; +import 'otp_state.dart'; + +class OtpBloc extends Bloc { + final TextEditingController otpController = TextEditingController(); + OtpBloc() : super(OtpInitial()) { + on(_onStartListeningForOtp); + on(_onOtpCodeChanged); + on(_onOtpSubmit); + } + + void _onStartListeningForOtp( + StartListeningForOtp event, Emitter emit) { + _startListening(); + } + + void _onOtpCodeChanged(OtpCodeChanged event, Emitter emit) { + emit(OtpCodeReceived(event.code)); + if (event.code.length == 6) { + add(OtpSubmit(event.code)); + } + } + + void _onOtpSubmit(OtpSubmit event, Emitter emit) async { + emit(OtpSubmitting()); + try { + // Add your OTP verification logic here + await Future.delayed(const Duration(seconds: 2)); + if (otpController.text == "123456") { + emit(OtpSubmissionSuccess()); + } else { + emit(const OtpSubmissionFailure("Otp Invalid !")); + } // Simulate network call + } catch (e) { + emit(OtpSubmissionFailure(e.toString())); + } + } + + void _startListening() { + SmsAutoFill().listenForCode(); // Correctly call listenForCode + } + + @override + Future close() { + otpController.dispose(); + SmsAutoFill().unregisterListener(); + return super.close(); + } +} diff --git a/lib/features/otpVerification/presentation/bloc/otp_event.dart b/lib/features/otpVerification/presentation/bloc/otp_event.dart new file mode 100644 index 0000000..4d6c5c5 --- /dev/null +++ b/lib/features/otpVerification/presentation/bloc/otp_event.dart @@ -0,0 +1,29 @@ +// otp_event.dart +import 'package:equatable/equatable.dart'; + +abstract class OtpEvent extends Equatable { + const OtpEvent(); + + @override + List get props => []; +} + +class OtpCodeChanged extends OtpEvent { + final String code; + + const OtpCodeChanged(this.code); + + @override + List get props => [code]; +} + +class OtpSubmit extends OtpEvent { + final String code; + + const OtpSubmit(this.code); + + @override + List get props => [code]; +} + +class StartListeningForOtp extends OtpEvent {} diff --git a/lib/features/otpVerification/presentation/bloc/otp_state.dart b/lib/features/otpVerification/presentation/bloc/otp_state.dart new file mode 100644 index 0000000..88f23f6 --- /dev/null +++ b/lib/features/otpVerification/presentation/bloc/otp_state.dart @@ -0,0 +1,33 @@ +// otp_state.dart +import 'package:equatable/equatable.dart'; + +abstract class OtpState extends Equatable { + const OtpState(); + + @override + List get props => []; +} + +class OtpInitial extends OtpState {} + +class OtpCodeReceived extends OtpState { + final String code; + + const OtpCodeReceived(this.code); + + @override + List get props => [code]; +} + +class OtpSubmitting extends OtpState {} + +class OtpSubmissionSuccess extends OtpState {} + +class OtpSubmissionFailure extends OtpState { + final String error; + + const OtpSubmissionFailure(this.error); + + @override + List get props => [error]; +} diff --git a/lib/features/otpVerification/presentation/pages/otp_layout.dart b/lib/features/otpVerification/presentation/pages/otp_layout.dart new file mode 100644 index 0000000..46cd8a6 --- /dev/null +++ b/lib/features/otpVerification/presentation/pages/otp_layout.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:tanami_app/features/otpVerification/presentation/widgets/otp_fill_section.dart'; + +import '../widgets/otp_top_section.dart'; + +class OtpLayout extends StatelessWidget { + const OtpLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + children: const [ + OtpTopSection(), + OtpFillSection(), + ], + )); + } +} diff --git a/lib/features/otpVerification/presentation/pages/otp_screen.dart b/lib/features/otpVerification/presentation/pages/otp_screen.dart new file mode 100644 index 0000000..fbfc730 --- /dev/null +++ b/lib/features/otpVerification/presentation/pages/otp_screen.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/features/otpVerification/presentation/bloc/otp_bloc.dart'; +import 'package:tanami_app/features/otpVerification/presentation/pages/otp_layout.dart'; + +import '../bloc/otp_event.dart'; + +class OtpScreen extends StatelessWidget { + const OtpScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + body: MultiBlocProvider( + providers: [ + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => OtpBloc()..add(StartListeningForOtp()), + ), + ], + 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 new file mode 100644 index 0000000..2bf78f3 --- /dev/null +++ b/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart @@ -0,0 +1,68 @@ +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/routes.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/shared/components/loader.dart'; +import 'package:tanami_app/shared/components/toast_message.dart'; + +import '../bloc/otp_bloc.dart'; +import '../bloc/otp_event.dart'; +import '../bloc/otp_state.dart'; + +class OtpFillSection extends StatelessWidget { + const OtpFillSection({super.key}); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is OtpSubmitting) { + Loader.loader(context); + } else if (state is OtpSubmissionSuccess) { + goRouter.pop(); + successToastMessage(context, 'OTP Verified Successfully!'); + } else if (state is OtpSubmissionFailure) { + goRouter.pop(); + errorToastMessage(context, 'OTP Verification Failed: ${state.error}'); + } + }, + builder: (context, state) { + final otpBloc = context.read(); + + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PinFieldAutoFill( + controller: otpBloc.otpController, + currentCode: otpBloc.otpController.text, + decoration: BoxLooseDecoration( + textStyle: GoogleFonts.dmSans( + fontSize: 22, + color: AppColor.otpTextColor, + fontWeight: FontWeight.w700, + ), + bgColorBuilder: const FixedColorBuilder(AppColor.fillColor), + radius: const Radius.circular(12), + strokeColorBuilder: const FixedColorBuilder( + AppColor.strokeColor, + )), + codeLength: 6, + onCodeChanged: (code) { + if (code != null) { + otpBloc.otpController.text = code; + otpBloc.add(OtpCodeChanged(code)); + } + }, + autoFocus: true, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/otpVerification/presentation/widgets/otp_top_section.dart b/lib/features/otpVerification/presentation/widgets/otp_top_section.dart new file mode 100644 index 0000000..ff3df0f --- /dev/null +++ b/lib/features/otpVerification/presentation/widgets/otp_top_section.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:gap/gap.dart'; + +import '../../../../core/styles/app_color.dart'; +import '../../../../core/styles/app_images.dart'; +import '../../../../core/styles/app_text.dart'; +import '../../../../shared/components/text_widget.dart'; + +class OtpTopSection extends StatelessWidget { + const OtpTopSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(85), + Center( + child: SvgPicture.asset( + AppImages.weclomeLogo, + ), + ), + const Gap(125), + TextWidget().tex20W700( + AppText.checkYourMessages, + clr: AppColor.charcoalColor, + ), + const Gap(25), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 35), + child: TextWidget().tex14W500( + AppText.referToSameOtpMessage, + clr: AppColor.smokeGrayColor, + ), + ), + ], + ); + } +} diff --git a/lib/features/register/presentation/bloc/register_bloc.dart b/lib/features/register/presentation/bloc/register_bloc.dart new file mode 100644 index 0000000..13d3edf --- /dev/null +++ b/lib/features/register/presentation/bloc/register_bloc.dart @@ -0,0 +1,74 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'register_event.dart'; +import 'register_state.dart'; + +class RegisterBloc extends Bloc { + final GlobalKey formKey = GlobalKey(); + final TextEditingController countrySelectionTextField = + TextEditingController(); + final TextEditingController phoneNumberTextField = TextEditingController(); + final TextEditingController passwordTextField = TextEditingController(); + + GlobalKey getFormKey() { + return formKey; + } + + RegisterBloc() : super(RegisterInitial()) { + phoneNumberTextField.addListener(_onFormFieldChanged); + passwordTextField.addListener(_onFormFieldChanged); + countrySelectionTextField.addListener(_onFormFieldChanged); + on(_onLoginFormChanged); + on((event, emit) async { + if (!formKey.currentState!.validate()) { + return; + } + emit(RegisterLoading()); + try { + // 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); + if (isSuccess) { + emit(RegisterSuccess()); + } else { + emit(const RegisterFailure( + "Register failed. Please check your credentials.")); + } + } catch (e) { + emit(RegisterFailure(e.toString())); + } + }); + } + void _onFormFieldChanged() { + add(RegisterFormChanged( + phoneNumberTextField.text, + countrySelectionTextField.text, + )); + } + + void _onLoginFormChanged( + RegisterFormChanged event, Emitter emit) { + final areFieldsFilled = + event.phoneNumber.isNotEmpty && event.country.isNotEmpty; + emit(RegisterFieldsState(areFieldsFilled)); + } + + // Mock API function, replace with actual API call + Future _mockLoginApi( + String phoneNumber, + String password, + String countryResidence, + ) async { + return true; + } + + @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 new file mode 100644 index 0000000..d7a46e1 --- /dev/null +++ b/lib/features/register/presentation/bloc/register_event.dart @@ -0,0 +1,33 @@ +import 'package:equatable/equatable.dart'; + +abstract class RegisterEvent extends Equatable { + const RegisterEvent(); + + @override + List get props => []; +} + +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]; +} + +class RegisterFormChanged extends RegisterEvent { + final String phoneNumber; + final String country; + + const RegisterFormChanged(this.phoneNumber, this.country); + + @override + List get props => [phoneNumber, country]; +} diff --git a/lib/features/register/presentation/bloc/register_state.dart b/lib/features/register/presentation/bloc/register_state.dart new file mode 100644 index 0000000..8f23bcb --- /dev/null +++ b/lib/features/register/presentation/bloc/register_state.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +abstract class RegisterState extends Equatable { + const RegisterState(); + + @override + List get props => []; +} + +class RegisterInitial extends RegisterState {} + +class RegisterLoading extends RegisterState {} + +class RegisterSuccess extends RegisterState {} + +class RegisterFailure extends RegisterState { + final String error; + + const RegisterFailure(this.error); + + @override + List get props => [error]; +} + +class RegisterFieldsState extends RegisterState { + final bool areFieldsFilled; + + const RegisterFieldsState(this.areFieldsFilled); + + @override + List get props => [areFieldsFilled]; +} diff --git a/lib/features/register/presentation/pages/register_layout.dart b/lib/features/register/presentation/pages/register_layout.dart new file mode 100644 index 0000000..c059f6c --- /dev/null +++ b/lib/features/register/presentation/pages/register_layout.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:tanami_app/features/register/presentation/widgets/register_bottom_section.dart'; +import 'package:tanami_app/features/register/presentation/widgets/register_form.dart'; +import 'package:tanami_app/features/register/presentation/widgets/register_top_section.dart'; + +class RegisterLayout extends StatelessWidget { + const RegisterLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + // + children: const [ + RegisterTopSection(), + RegisterForm(), + RegisterBottomSection(), + ], + )); + } +} diff --git a/lib/features/register/presentation/pages/register_screen.dart b/lib/features/register/presentation/pages/register_screen.dart new file mode 100644 index 0000000..6e0a7ad --- /dev/null +++ b/lib/features/register/presentation/pages/register_screen.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/features/register/presentation/bloc/register_bloc.dart'; + +import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; +import 'register_layout.dart'; + +class RegisterScreen extends StatelessWidget { + const RegisterScreen({super.key}); + + @override + Widget build(BuildContext context) { + final radioBloc = context.read(); + return WillPopScope( + onWillPop: () async { + radioBloc.resetSelection(); + return true; // Allow the pop to happen + }, + child: Scaffold( + resizeToAvoidBottomInset: true, + body: MultiBlocProvider( + providers: [ + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => RegisterBloc(), + ), + ], + child: const RegisterLayout(), + ), + ), + ); + } +} diff --git a/lib/features/register/presentation/pages/register_step_layout.dart b/lib/features/register/presentation/pages/register_step_layout.dart new file mode 100644 index 0000000..0c656ea --- /dev/null +++ b/lib/features/register/presentation/pages/register_step_layout.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/features/register/presentation/widgets/register_step_count.dart'; + +import '../widgets/register_step_bottom_section.dart'; +import '../widgets/register_step_top_section.dart'; + +class RegisterStepLayout extends StatelessWidget { + const RegisterStepLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + children: const [ + RegisterStepTopSection(), + Gap(20), + RegisterStepCount(), + RegisterStepBottomSection(), + ], + )); + } +} diff --git a/lib/features/register/presentation/pages/register_step_screen.dart b/lib/features/register/presentation/pages/register_step_screen.dart new file mode 100644 index 0000000..e818e9c --- /dev/null +++ b/lib/features/register/presentation/pages/register_step_screen.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import '../../../../shared/components/exit_app_dialog.dart'; +import 'register_step_layout.dart'; + +class RegisterStepScreen extends StatelessWidget { + final String fromScreen; + const RegisterStepScreen({ + super.key, + required this.fromScreen, + }); + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + if (fromScreen == "welcome" || fromScreen == "login") { + exitAppDialog(context); + return false; + } else { + return true; + } + }, + child: const Scaffold( + resizeToAvoidBottomInset: true, + body: RegisterStepLayout(), + ), + ); + } +} diff --git a/lib/features/register/presentation/widgets/register_bottom_section.dart b/lib/features/register/presentation/widgets/register_bottom_section.dart new file mode 100644 index 0000000..df9c3bb --- /dev/null +++ b/lib/features/register/presentation/widgets/register_bottom_section.dart @@ -0,0 +1,106 @@ +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/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 '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; +import '../bloc/register_bloc.dart'; +import '../bloc/register_event.dart'; +import '../bloc/register_state.dart'; + +class RegisterBottomSection extends StatelessWidget { + const RegisterBottomSection({super.key}); + + @override + Widget build(BuildContext context) { + final radioBloc = context.read(); + return Column( + children: [ + const Gap(90), + Image.asset( + AppImages.stage1Image, + width: 75, + height: 12, + ), + const Gap(36), + BlocConsumer( + listener: (context, state) { + if (state is RegisterLoading) { + Loader.loader(context); + } else if (state is RegisterSuccess) { + successToastMessage(context, "successful !"); + goRouter.pop(); + + goRouter.pushNamed(RouteName.otpScreen); + } else if (state is RegisterFailure) { + goRouter.pop(); + errorToastMessage( + context, + state.error, + ); + } + }, + builder: (context, state) { + bool isButtonEnabled = false; + if (state is RegisterFieldsState) { + isButtonEnabled = state.areFieldsFilled; + } else if (state is RegisterSuccess || state is RegisterFailure) { + 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( + RegisterSubmitted( + context + .read() + .phoneNumberTextField + .text, + context + .read() + .passwordTextField + .text, + ""), + ) + : null; + }, + text: AppText.nextText, + clr: isButtonEnabled + ? AppColor.primaryColor2 + : AppColor.inactiveBtnColor, + ), + ); + }, + ), + const Gap(5), + ButtonWidget().textBtn( + function: () { + radioBloc.resetSelection(); + goRouter.pop(); + }, + text: TextWidget().tex14W700(AppText.backText, + clr: AppColor.textLabelColor, + textDecoration: TextDecoration.underline)), + ], + ); + } +} diff --git a/lib/features/register/presentation/widgets/register_form.dart b/lib/features/register/presentation/widgets/register_form.dart new file mode 100644 index 0000000..e9e52ea --- /dev/null +++ b/lib/features/register/presentation/widgets/register_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 'package:tanami_app/core/utils/constant/country_flag_data.dart'; + +import '../../../../shared/components/form_label_textfield.dart'; +import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; +import '../../../countrySelection/presentation/bloc/choose_country_state.dart'; +import '../bloc/register_bloc.dart'; + +class RegisterForm extends StatelessWidget { + const RegisterForm({super.key}); + + @override + Widget build(BuildContext context) { + final loginBloc = context.read(); + int selectedCountry = -1; + return BlocConsumer(listener: (context, state) { + if (state is RadioSelectionChanged) { + selectedCountry = state.selectedIndex; + loginBloc.countrySelectionTextField.text = countryName[selectedCountry]; + } + }, builder: (context, state) { + if (state is RadioSelectionChanged) { + selectedCountry = state.selectedIndex; + } else { + selectedCountry = -1; + } + return Form( + key: loginBloc.formKey, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 14, + ), + child: Align( + alignment: Alignment.topLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(50), + FormLabelTextField( + prefixWidget: selectedCountry == -1 + ? null + : Image.asset( + countryFlag[selectedCountry], + width: 20, + height: 20, + ), + hintText: AppText.chooseCountry, + title: AppText.countryOfResidence, + type: "country selection", + textEditingController: loginBloc.countrySelectionTextField, + ), + const Gap(20), + FormLabelTextField( + prefixWidget: selectedCountry == -1 + ? null + : Image.asset( + countryFlag[selectedCountry], + width: 20, + height: 20, + ), + hintText: "+0 (000) 000 00 00", + title: AppText.phoneNumber, + type: "phone number", + textEditingController: loginBloc.phoneNumberTextField, + ), + ], + ), + ), + ), + ); + }); + } +} diff --git a/lib/features/register/presentation/widgets/register_step_bottom_section.dart b/lib/features/register/presentation/widgets/register_step_bottom_section.dart new file mode 100644 index 0000000..e1bcd60 --- /dev/null +++ b/lib/features/register/presentation/widgets/register_step_bottom_section.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.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/shared/components/button_widget.dart'; + +import '../../../../core/styles/app_text.dart'; +import '../../../../shared/components/text_widget.dart'; + +class RegisterStepBottomSection extends StatelessWidget { + const RegisterStepBottomSection({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Column( + children: [ + Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + ), + width: 1.sw, + height: 56.h, + child: ButtonWidget().elevatedBtn( + function: () { + goRouter.pushNamed( + RouteName.registerScreen, + ); + }, + text: AppText.getStarted, + txtClr: AppColor.plainWhite, + clr: AppColor.primaryColor2, + ), + ), + const Gap(16), + ButtonWidget().textBtn( + function: () { + goRouter.goNamed(RouteName.loginScreen, pathParameters: { + "fromScreen": "registerStep", + }); + }, + text: TextWidget().tex14W700( + 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 new file mode 100644 index 0000000..528596f --- /dev/null +++ b/lib/features/register/presentation/widgets/register_step_count.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/utils/constant/register_step_data.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; + +class RegisterStepCount extends StatelessWidget { + const RegisterStepCount({super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + itemCount: 3, + itemBuilder: (context, index) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 27, + vertical: 5, + ), + child: ListTile( + isThreeLine: true, + leading: SvgPicture.asset(stepImage[index]), + title: TextWidget().tex14W700( + title[index], + clr: AppColor.textLabelColor, + txtAlign: TextAlign.start, + ), + subtitle: TextWidget().tex14W500( + 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 new file mode 100644 index 0000000..7f4bb24 --- /dev/null +++ b/lib/features/register/presentation/widgets/register_step_top_section.dart @@ -0,0 +1,39 @@ +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 RegisterStepTopSection extends StatelessWidget { + const RegisterStepTopSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(85), + Center( + child: SvgPicture.asset( + AppImages.weclomeLogo, + ), + ), + const Gap(30), + TextWidget().tex20W700( + AppText.getStarted, + clr: AppColor.charcoalColor, + ), + const Gap(10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 75), + child: TextWidget().tex14W500( + 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 new file mode 100644 index 0000000..d6e71c0 --- /dev/null +++ b/lib/features/register/presentation/widgets/register_top_section.dart @@ -0,0 +1,39 @@ +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 RegisterTopSection extends StatelessWidget { + const RegisterTopSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(85), + Center( + child: SvgPicture.asset( + AppImages.weclomeLogo, + ), + ), + const Gap(60), + TextWidget().tex20W700( + AppText.welcome, + clr: AppColor.charcoalColor, + ), + const Gap(10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 75), + child: TextWidget().tex14W500( + AppText.selectYourCountryOfResidence, + clr: AppColor.smokeGrayColor, + ), + ), + ], + ); + } +} diff --git a/lib/features/welcome/presentation/widgets/login_signup_button.dart b/lib/features/welcome/presentation/widgets/login_signup_button.dart index 3542f8b..ef12d97 100644 --- a/lib/features/welcome/presentation/widgets/login_signup_button.dart +++ b/lib/features/welcome/presentation/widgets/login_signup_button.dart @@ -25,7 +25,11 @@ class LoginSignUpButton extends StatelessWidget { width: 1.sw, height: 56.h, child: ButtonWidget().elevatedBtn( - function: () {}, + function: () { + goRouter.goNamed(RouteName.registerStepScreen, pathParameters: { + "fromScreentype": "welcome", + }); + }, text: AppText.signUpText, txtClr: AppColor.plainWhite, clr: AppColor.primaryColor, @@ -34,11 +38,15 @@ class LoginSignUpButton extends StatelessWidget { const Gap(16), ButtonWidget().textBtn( function: () { - goRouter.goNamed(RouteName.loginScreen); + goRouter.goNamed(RouteName.loginScreen, pathParameters: { + "fromScreen": "welcome", + }); }, - text: TextWidget().tex14W700(AppText.loginText, - clr: AppColor.darkGreyColor, - textDecoration: TextDecoration.underline)) + text: TextWidget().tex14W700( + AppText.loginText, + clr: AppColor.darkGreyColor, + textDecoration: TextDecoration.underline, + )) ], ), ); diff --git a/lib/main.dart b/lib/main.dart index dda583a..70a04f9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ 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 @@ -53,7 +54,10 @@ class _MyAppState extends State with WidgetsBindingObserver { providers: [ BlocProvider( create: (context) => RadioBloc(), - ) + ), + BlocProvider( + create: (context) => PasswordVisibilityBloc(), + ), ], child: ScreenUtilInit( builder: (BuildContext context, Widget? child) => MaterialApp.router( diff --git a/lib/shared/components/exit_app_dialog.dart b/lib/shared/components/exit_app_dialog.dart new file mode 100644 index 0000000..2112358 --- /dev/null +++ b/lib/shared/components/exit_app_dialog.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.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'; + +exitAppDialog( + context, +) { + return showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AlertDialog( + insetPadding: const EdgeInsets.symmetric(horizontal: 16), + backgroundColor: const Color(0XFFFFFFFF), + //contentPadding: EdgeInsets.fromLTRB(96, 32, 96, 28), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + side: BorderSide(color: Color(0XFFFFFFFF)), + ), + content: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + //sizedBoxHeight(32.h), + Align( + alignment: Alignment.center, + child: CircleAvatar( + radius: 25, + backgroundColor: AppColor.inactiveBtnColor, + child: Center( + child: SvgPicture.asset( + AppImages.exitAppIcon, + width: 30, + height: 30, + fit: BoxFit.contain, + color: AppColor.primaryColor, + ), + ), + ), + ), + SizedBox( + height: 22.h, + ), + Align( + alignment: Alignment.center, + child: Text( + AppText.areYouSureYouWantToExitText, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black, + fontSize: 22.sp, + //fontWeight: FontWeight.w600, + ), + ), + ), + + Gap(21.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Container( + height: 48.h, + width: 140.w, + decoration: BoxDecoration( + border: Border.all( + color: AppColor.primaryColor, + ), + borderRadius: BorderRadius.circular(10.h), + color: AppColor.plainWhite, + ), + child: Center( + child: Text( + AppText.cancelText, + style: TextStyle( + color: AppColor.primaryColor, fontSize: 18.sp), + ), + ), + ), + ), + Gap(15.w), + InkWell( + onTap: () { + SystemNavigator.pop(); + Navigator.pop(context); + }, + child: Container( + height: 48.h, + width: 140.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.h), + color: AppColor.primaryColor), + child: Center( + child: Text( + AppText.exitText, + style: TextStyle( + color: AppColor.plainWhite, fontSize: 18.sp), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); +} diff --git a/lib/shared/components/text_widget.dart b/lib/shared/components/text_widget.dart index 609bfa0..6d79bbc 100644 --- a/lib/shared/components/text_widget.dart +++ b/lib/shared/components/text_widget.dart @@ -13,23 +13,15 @@ class TextWidget { } //Text Size 14 - Widget tex14W700(String text, {Color? clr, TextDecoration? textDecoration}) { - return Text(text, - textAlign: TextAlign.center, - style: GoogleFonts.dmSans( - fontSize: 14, - fontWeight: FontWeight.w700, - decoration: textDecoration ?? TextDecoration.none, - color: clr ?? AppColor.plainWhite)); - } Widget tex14W500( String text, { Color? clr, TextDecoration? textDecoration, + TextAlign? txtAlign, }) { return Text(text, - textAlign: TextAlign.center, + textAlign: txtAlign ?? TextAlign.center, style: GoogleFonts.dmSans( fontSize: 14, fontWeight: FontWeight.w500, @@ -37,6 +29,17 @@ class TextWidget { color: clr ?? AppColor.plainWhite)); } + Widget tex14W700(String text, + {Color? clr, TextDecoration? textDecoration, TextAlign? txtAlign}) { + return Text(text, + textAlign: txtAlign ?? TextAlign.center, + style: GoogleFonts.dmSans( + fontSize: 14, + fontWeight: FontWeight.w700, + decoration: textDecoration ?? TextDecoration.none, + color: clr ?? AppColor.plainWhite)); + } + //Text Size 15 Widget tex15W500( String text, { diff --git a/pubspec.lock b/pubspec.lock index dcd9ae0..1397c28 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -808,6 +808,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + pin_input_text_field: + dependency: transitive + description: + name: pin_input_text_field + sha256: "8d6fc670aa673a4df5976086f0e8039972a5b2bcb783c8db8dd3b9b4b072ca90" + url: "https://pub.dev" + source: hosted + version: "4.5.1" platform: dependency: transitive description: @@ -941,6 +949,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + sms_autofill: + dependency: "direct main" + description: + name: sms_autofill + sha256: "43139a175fb3c57ff103ac4b82f2aa70b6c45cf93539d7974c2556810f355363" + url: "https://pub.dev" + source: hosted + version: "2.3.1" source_gen: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fb8f726..832b797 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,9 @@ dependencies: #Style control_style: ^0.1.0 + #OTP Autofill + sms_autofill: ^2.3.1 + dev_dependencies: flutter_test: sdk: flutter @@ -90,9 +93,14 @@ flutter: - assets/images/welcome_screen/png/ - assets/images/auth_screen/ - assets/images/auth_screen/svg/ + - assets/images/auth_screen/png/ - assets/images/country_flag/ - - assets/images/country_flag/svg/ - assets/images/country_flag/png/ - assets/images/bottom_bar/active/ - assets/images/bottom_bar/inactive/ - assets/images/portfolio_screen/ + - assets/images/biometric_screen/ + - assets/images/biometric_screen/png/ + - assets/images/biometric_screen/svg/ + - assets/images/dialog/ + - assets/images/dialog/svg/