From 48444ddf11c8e13cddbff80269bddc470827c15b Mon Sep 17 00:00:00 2001 From: jayesh Date: Tue, 28 May 2024 16:35:33 +0530 Subject: [PATCH] login screen, common widgets --- .../images/auth_screen/svg/hide_password.svg | 7 + .../images/auth_screen/svg/show_password.svg | 4 + .../images/country_flag/svg/bahrain_flag.svg | 9 ++ .../images/country_flag/svg/kuwait_flag.svg | 9 ++ assets/images/country_flag/svg/oman_flag.svg | 9 ++ assets/images/country_flag/svg/qatar_flag.svg | 9 ++ .../country_flag/svg/saudi_arabia_flag.svg | 9 ++ .../svg/united_arab_emirates_flag.svg | 9 ++ lib/core/styles/app_color.dart | 19 ++- lib/core/styles/app_images.dart | 23 ++- lib/core/styles/app_text.dart | 16 +- .../pages/choose_country_screen.dart | 0 .../login/presentation/bloc/login_bloc.dart | 76 ++++++++++ .../login/presentation/bloc/login_event.dart | 25 +++ .../login/presentation/bloc/login_state.dart | 29 ++++ .../presentation/pages/login_layout.dart | 22 +++ .../presentation/pages/login_screen.dart | 22 ++- .../presentation/widgets/bottom_section.dart | 100 ++++++++++++ .../presentation/widgets/login_form.dart | 53 +++++++ .../presentation/widgets/top_section.dart | 39 +++++ .../presentation/pages/weclome_screen.dart | 1 - .../presentation/pages/welcome_layout.dart | 1 - .../widgets/login_signup_button.dart | 12 +- lib/main.dart | 2 +- .../password_visibility_bloc.dart | 14 ++ .../password_visibility_event.dart | 10 ++ .../password_visibility_state.dart | 10 ++ lib/shared/components/button_widget.dart | 9 +- .../components/form_label_textfield.dart | 68 +++++++++ lib/shared/components/loader.dart | 28 ++++ .../components/password_text_form_field.dart | 142 ++++++++++++++++++ .../components/text_from_field_widget.dart | 139 +++++++++++++++++ lib/shared/components/text_widget.dart | 44 +++++- lib/shared/components/toast_message.dart | 43 ++++++ pubspec.lock | 8 + pubspec.yaml | 8 + 36 files changed, 1012 insertions(+), 16 deletions(-) create mode 100644 assets/images/auth_screen/svg/hide_password.svg create mode 100644 assets/images/auth_screen/svg/show_password.svg create mode 100644 assets/images/country_flag/svg/bahrain_flag.svg create mode 100644 assets/images/country_flag/svg/kuwait_flag.svg create mode 100644 assets/images/country_flag/svg/oman_flag.svg create mode 100644 assets/images/country_flag/svg/qatar_flag.svg create mode 100644 assets/images/country_flag/svg/saudi_arabia_flag.svg create mode 100644 assets/images/country_flag/svg/united_arab_emirates_flag.svg create mode 100644 lib/features/countrySelection/presentation/pages/choose_country_screen.dart create mode 100644 lib/features/login/presentation/bloc/login_bloc.dart create mode 100644 lib/features/login/presentation/bloc/login_event.dart create mode 100644 lib/features/login/presentation/bloc/login_state.dart create mode 100644 lib/features/login/presentation/pages/login_layout.dart create mode 100644 lib/features/login/presentation/widgets/bottom_section.dart create mode 100644 lib/features/login/presentation/widgets/login_form.dart create mode 100644 lib/features/login/presentation/widgets/top_section.dart create mode 100644 lib/shared/components/bloc/password_field/password_visibility_bloc.dart create mode 100644 lib/shared/components/bloc/password_field/password_visibility_event.dart create mode 100644 lib/shared/components/bloc/password_field/password_visibility_state.dart create mode 100644 lib/shared/components/form_label_textfield.dart create mode 100644 lib/shared/components/loader.dart create mode 100644 lib/shared/components/password_text_form_field.dart create mode 100644 lib/shared/components/text_from_field_widget.dart create mode 100644 lib/shared/components/toast_message.dart diff --git a/assets/images/auth_screen/svg/hide_password.svg b/assets/images/auth_screen/svg/hide_password.svg new file mode 100644 index 0000000..0313ac2 --- /dev/null +++ b/assets/images/auth_screen/svg/hide_password.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/auth_screen/svg/show_password.svg b/assets/images/auth_screen/svg/show_password.svg new file mode 100644 index 0000000..932a449 --- /dev/null +++ b/assets/images/auth_screen/svg/show_password.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/country_flag/svg/bahrain_flag.svg b/assets/images/country_flag/svg/bahrain_flag.svg new file mode 100644 index 0000000..25d8620 --- /dev/null +++ b/assets/images/country_flag/svg/bahrain_flag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/country_flag/svg/kuwait_flag.svg b/assets/images/country_flag/svg/kuwait_flag.svg new file mode 100644 index 0000000..bbc8958 --- /dev/null +++ b/assets/images/country_flag/svg/kuwait_flag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/country_flag/svg/oman_flag.svg b/assets/images/country_flag/svg/oman_flag.svg new file mode 100644 index 0000000..1636dff --- /dev/null +++ b/assets/images/country_flag/svg/oman_flag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/country_flag/svg/qatar_flag.svg b/assets/images/country_flag/svg/qatar_flag.svg new file mode 100644 index 0000000..68b2a67 --- /dev/null +++ b/assets/images/country_flag/svg/qatar_flag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/country_flag/svg/saudi_arabia_flag.svg b/assets/images/country_flag/svg/saudi_arabia_flag.svg new file mode 100644 index 0000000..de0b22e --- /dev/null +++ b/assets/images/country_flag/svg/saudi_arabia_flag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/country_flag/svg/united_arab_emirates_flag.svg b/assets/images/country_flag/svg/united_arab_emirates_flag.svg new file mode 100644 index 0000000..56291aa --- /dev/null +++ b/assets/images/country_flag/svg/united_arab_emirates_flag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/core/styles/app_color.dart b/lib/core/styles/app_color.dart index 0de7396..b9b6a59 100644 --- a/lib/core/styles/app_color.dart +++ b/lib/core/styles/app_color.dart @@ -4,8 +4,6 @@ class AppColor { //Primary Color static const Color primaryColor = Color(0xFF002F0F); - //Secondary Color - //Welcome Color static const Color indicatorActiveColor = Color(0xFF002F0F); static const Color indicatorInactiveColor = Color(0xFFb8c1bb); @@ -13,4 +11,21 @@ class AppColor { //Common Color static const Color plainWhite = Color(0xFFFFFFFF); static const Color darkGreyColor = Color(0xFF343434); + + //Auth Color + static const Color charcoalColor = Color(0xFF272727); + static const Color smokeGrayColor = Color(0xFF787878); + static const Color textLabelColor = Color(0xFF363636); + static const Color forgotPassButtonColor = Color(0xFF888888); + + //Text Form Field Color + static const Color txtBorderColor = Color(0xFFE3E3E3); + static const Color txtBorderShadowColor = Color(0xFFE9E9E9); + static const Color hintTextColor = Color(0xFF8D8D8D); + static const Color txtErrorBorderColor = Color(0xFFF1B9B9); + static const Color txtErrorColor = Color(0xFFD21C1C); + + //Button Color + static const Color inactiveBtnColor = Color(0xFFD8D8D8); + static const Color inactiveBtnTxtColor = Color(0xFF8D8D8D); } diff --git a/lib/core/styles/app_images.dart b/lib/core/styles/app_images.dart index 9627984..f1e8ea6 100644 --- a/lib/core/styles/app_images.dart +++ b/lib/core/styles/app_images.dart @@ -10,10 +10,29 @@ class AppImages { "assets/images/welcome_screen/svg/Tanami_Capital_Logo.svg"; static const String firstWelcome = "assets/images/welcome_screen/png/First_Onboarding.png"; - static const String secondWelcome = "assets/images/welcome_screen/png/Second_Onboarding.png"; - static const String thirdWelcome = "assets/images/welcome_screen/png/Third_Onboarding.png"; + + //Auth + static const String forwardArrow = + "assets/images/auth_screen/svg/right_arrow.svg"; + static const String hidePassword = + "assets/images/auth_screen/svg/hide_password.svg"; + static const String showPassword = + "assets/images/auth_screen/svg/show_password.svg"; + + //Country Flag + static const String bahrainFlag = + "assets/images/country_flag/svg/bahrain_flag.svg"; + static const String kuwaitFlag = + "assets/images/auth_screen/svg/kuwait_flag.svg"; + static const String omanFlag = "assets/images/auth_screen/svg/oman_flag.svg"; + static const String qatarFlag = + "assets/images/country_flag/svg/qatar_flag.svg"; + static const String saudiArabiaflag = + "assets/images/auth_screen/svg/saudi_arabia_flag.svg"; + static const String unitedArabEmiratesFlag = + "assets/images/auth_screen/svg/united_arab_emirates_flag.svg"; } diff --git a/lib/core/styles/app_text.dart b/lib/core/styles/app_text.dart index 033f058..c9387f3 100644 --- a/lib/core/styles/app_text.dart +++ b/lib/core/styles/app_text.dart @@ -12,6 +12,20 @@ class AppText { static const String weclomeDescription2Text = "experienced investment experts with a long-standing track record"; static const String weclomeDescription3Text = "with only SAR 1,000"; - static const String loginText = "Login In"; + static const String loginText = "Log In"; static const String signUpText = "Sign Up"; + + //Login + static const String welcomeText = "Welcome back!"; + static const String pleaseEnterLoginDetails = + "Please enter your login details to get started"; + static const String countryOfResidence = "Country of residence"; + static const String chooseCountry = "Choose country"; + static const String phoneNumber = "Phone Number"; + static const String password = "Password"; + static const String enterPassword = "Enter password"; + static const String invalidPhoneNo = "Invalid Phone Number"; + static const String enterPhoneNo = "Enter phone number"; + static const String invalidPassword = "Invalid Password"; + static const String forgorPassword = "Forgot Password"; } diff --git a/lib/features/countrySelection/presentation/pages/choose_country_screen.dart b/lib/features/countrySelection/presentation/pages/choose_country_screen.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/login/presentation/bloc/login_bloc.dart b/lib/features/login/presentation/bloc/login_bloc.dart new file mode 100644 index 0000000..e6ade5b --- /dev/null +++ b/lib/features/login/presentation/bloc/login_bloc.dart @@ -0,0 +1,76 @@ +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'login_event.dart'; +import 'login_state.dart'; + +class LoginBloc extends Bloc { + final GlobalKey formKey = GlobalKey(); + final TextEditingController countrySelectionTextField = + TextEditingController(); + final TextEditingController phoneNumberTextField = TextEditingController(); + final TextEditingController passwordTextField = TextEditingController(); + + GlobalKey getFormKey() { + return formKey; + } + + LoginBloc() : super(LoginInitial()) { + phoneNumberTextField.addListener(_onFormFieldChanged); + passwordTextField.addListener(_onFormFieldChanged); + countrySelectionTextField.addListener(_onFormFieldChanged); + on((event, emit) async { + if (!formKey.currentState!.validate()) { + return; + } + emit(LoginLoading()); + 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(LoginSuccess()); + } else { + emit(const LoginFailure( + "Login failed. Please check your credentials.")); + } + } catch (e) { + emit(LoginFailure(e.toString())); + } + }); + } + void _onFormFieldChanged() { + add(LoginFormChanged()); + } + + bool areFieldsFilled() { + return phoneNumberTextField.text.isNotEmpty && + passwordTextField.text.isNotEmpty && + countrySelectionTextField.text.isNotEmpty; + } + + Stream mapEventToState(LoginEvent event) async* { + if (event is LoginFormChanged) { + yield LoginFieldsState(areFieldsFilled()); + } + } + + // Mock API function, replace with actual API call + Future _mockLoginApi( + String email, + String password, + String countryResidence, + ) async { + return email == "1234567891" && password == "123456"; + } + + @override + Future close() { + phoneNumberTextField.dispose(); + passwordTextField.dispose(); + return super.close(); + } +} diff --git a/lib/features/login/presentation/bloc/login_event.dart b/lib/features/login/presentation/bloc/login_event.dart new file mode 100644 index 0000000..aa0ac58 --- /dev/null +++ b/lib/features/login/presentation/bloc/login_event.dart @@ -0,0 +1,25 @@ +import 'package:equatable/equatable.dart'; + +abstract class LoginEvent extends Equatable { + const LoginEvent(); + + @override + List get props => []; +} + +class LoginSubmitted extends LoginEvent { + final String phoneNumber; + final String password; + final String countryResidence; + + const LoginSubmitted( + this.phoneNumber, + this.password, + this.countryResidence, + ); + + @override + List get props => [phoneNumber, password, countryResidence]; +} + +class LoginFormChanged extends LoginEvent {} diff --git a/lib/features/login/presentation/bloc/login_state.dart b/lib/features/login/presentation/bloc/login_state.dart new file mode 100644 index 0000000..5dcb209 --- /dev/null +++ b/lib/features/login/presentation/bloc/login_state.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; + +abstract class LoginState extends Equatable { + const LoginState(); + + @override + List get props => []; +} + +class LoginInitial extends LoginState {} + +class LoginLoading extends LoginState {} + +class LoginSuccess extends LoginState {} + +class LoginFailure extends LoginState { + final String error; + + const LoginFailure(this.error); + + @override + List get props => [error]; +} + +class LoginFieldsState extends LoginState { + final bool areFieldsFilled; + + const LoginFieldsState(this.areFieldsFilled); +} diff --git a/lib/features/login/presentation/pages/login_layout.dart b/lib/features/login/presentation/pages/login_layout.dart new file mode 100644 index 0000000..91c442f --- /dev/null +++ b/lib/features/login/presentation/pages/login_layout.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:tanami_app/features/login/presentation/widgets/login_form.dart'; + +import '../widgets/bottom_section.dart'; +import '../widgets/top_section.dart'; + +class LoginLayout extends StatelessWidget { + LoginLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + // + children: [ + const TopSection(), + LoginForm(), + const BottomSection(), + ], + )); + } +} diff --git a/lib/features/login/presentation/pages/login_screen.dart b/lib/features/login/presentation/pages/login_screen.dart index 04a03e1..f1d6b37 100644 --- a/lib/features/login/presentation/pages/login_screen.dart +++ b/lib/features/login/presentation/pages/login_screen.dart @@ -1,10 +1,30 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart'; +import '../bloc/login_bloc.dart'; +import 'login_layout.dart'; class LoginScreen extends StatelessWidget { const LoginScreen({super.key}); @override Widget build(BuildContext context) { - return Scaffold(); + 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: LoginLayout(), + ), + ); } } diff --git a/lib/features/login/presentation/widgets/bottom_section.dart b/lib/features/login/presentation/widgets/bottom_section.dart new file mode 100644 index 0000000..891d449 --- /dev/null +++ b/lib/features/login/presentation/widgets/bottom_section.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.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/login_bloc.dart'; +import '../bloc/login_event.dart'; +import '../bloc/login_state.dart'; + +class BottomSection extends StatelessWidget { + const BottomSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const Gap(12), + Align( + alignment: Alignment.topRight, + child: ButtonWidget().textBtn( + function: () { + goRouter.goNamed(RouteName.loginScreen); + }, + text: TextWidget().tex15W400(AppText.forgorPassword, + clr: AppColor.forgotPassButtonColor, + textDecoration: TextDecoration.underline)), + ), + const Gap(20), + BlocConsumer( + listener: (context, state) { + if (state is LoginLoading) { + Loader.loader(context); + } else if (state is LoginSuccess) { + goRouter.pop(); + successToastMessage(context, "login successful !"); + } else if (state is LoginFailure) { + goRouter.pop(); + errorToastMessage( + context, + state.error, + ); + } + }, + builder: (context, state) { + bool isButtonEnabled = false; + if (state is LoginFieldsState) { + isButtonEnabled = state.areFieldsFilled; + } + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + ), + width: 1.sw, + height: 56.h, + child: ButtonWidget().elevatedBtn( + txtClr: AppColor.inactiveBtnTxtColor, + function: () { + isButtonEnabled + ? context.read().add( + LoginSubmitted( + context + .read() + .phoneNumberTextField + .text, + context + .read() + .passwordTextField + .text, + ""), + ) + : null; + }, + text: AppText.loginText, + clr: isButtonEnabled + ? AppColor.primaryColor + : AppColor.inactiveBtnColor, + ), + ); + }, + ), + const Gap(5), + ButtonWidget().textBtn( + function: () { + goRouter.goNamed(RouteName.loginScreen); + }, + text: TextWidget().tex14W700(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 new file mode 100644 index 0000000..1d1f687 --- /dev/null +++ b/lib/features/login/presentation/widgets/login_form.dart @@ -0,0 +1,53 @@ +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/form_label_textfield.dart'; +import '../bloc/login_bloc.dart'; + +class LoginForm extends StatelessWidget { + LoginForm({super.key}); + + @override + Widget build(BuildContext context) { + final loginBloc = context.read(); + 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( + hintText: AppText.chooseCountry, + title: AppText.countryOfResidence, + type: "country selection", + textEditingController: loginBloc.countrySelectionTextField, + ), + const Gap(20), + FormLabelTextField( + hintText: "+0 (000) 000 00 00", + title: AppText.phoneNumber, + type: "phone number", + textEditingController: loginBloc.phoneNumberTextField, + ), + const Gap(20), + 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 new file mode 100644 index 0000000..73cf478 --- /dev/null +++ b/lib/features/login/presentation/widgets/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 TopSection extends StatelessWidget { + const TopSection({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.welcomeText, + clr: AppColor.charcoalColor, + ), + const Gap(10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 75), + child: TextWidget().tex14W500( + AppText.pleaseEnterLoginDetails, + clr: AppColor.smokeGrayColor, + ), + ), + ], + ); + } +} diff --git a/lib/features/welcome/presentation/pages/weclome_screen.dart b/lib/features/welcome/presentation/pages/weclome_screen.dart index 91286c4..b06268b 100644 --- a/lib/features/welcome/presentation/pages/weclome_screen.dart +++ b/lib/features/welcome/presentation/pages/weclome_screen.dart @@ -10,7 +10,6 @@ class WelcomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.white, body: MultiBlocProvider( // Define the providers for the OnboardingBloc and other blocs providers: [ diff --git a/lib/features/welcome/presentation/pages/welcome_layout.dart b/lib/features/welcome/presentation/pages/welcome_layout.dart index ef4178c..006837c 100644 --- a/lib/features/welcome/presentation/pages/welcome_layout.dart +++ b/lib/features/welcome/presentation/pages/welcome_layout.dart @@ -13,7 +13,6 @@ class WelcomeLayout extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.white, body: BlocListener( // Define the listener for the BlocListener listener: (context, state) { diff --git a/lib/features/welcome/presentation/widgets/login_signup_button.dart b/lib/features/welcome/presentation/widgets/login_signup_button.dart index 1f74da4..3542f8b 100644 --- a/lib/features/welcome/presentation/widgets/login_signup_button.dart +++ b/lib/features/welcome/presentation/widgets/login_signup_button.dart @@ -1,10 +1,13 @@ 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 LoginSignUpButton extends StatelessWidget { const LoginSignUpButton({super.key}); @@ -24,11 +27,18 @@ class LoginSignUpButton extends StatelessWidget { child: ButtonWidget().elevatedBtn( function: () {}, text: AppText.signUpText, + txtClr: AppColor.plainWhite, clr: AppColor.primaryColor, ), ), const Gap(16), - ButtonWidget().textBtn(function: () {}, text: AppText.loginText) + ButtonWidget().textBtn( + function: () { + goRouter.goNamed(RouteName.loginScreen); + }, + text: TextWidget().tex14W700(AppText.loginText, + clr: AppColor.darkGreyColor, + textDecoration: TextDecoration.underline)) ], ), ); diff --git a/lib/main.dart b/lib/main.dart index 3e6ceb7..a759a60 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,7 +28,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State with WidgetsBindingObserver { final NetworkConnectivity _networkConnectivity = - NetworkConnectivity(onStatusChange: (String) {}); + NetworkConnectivity(onStatusChange: (_) {}); @override void initState() { super.initState(); diff --git a/lib/shared/components/bloc/password_field/password_visibility_bloc.dart b/lib/shared/components/bloc/password_field/password_visibility_bloc.dart new file mode 100644 index 0000000..dfe0c7d --- /dev/null +++ b/lib/shared/components/bloc/password_field/password_visibility_bloc.dart @@ -0,0 +1,14 @@ +import 'package:bloc/bloc.dart'; +import 'password_visibility_event.dart'; +import 'password_visibility_state.dart'; + +class PasswordVisibilityBloc + extends Bloc { + PasswordVisibilityBloc() + : super(const PasswordVisibilityState(isPasswordVisible: false)) { + on((event, emit) { + emit( + PasswordVisibilityState(isPasswordVisible: !state.isPasswordVisible)); + }); + } +} diff --git a/lib/shared/components/bloc/password_field/password_visibility_event.dart b/lib/shared/components/bloc/password_field/password_visibility_event.dart new file mode 100644 index 0000000..32a5d4c --- /dev/null +++ b/lib/shared/components/bloc/password_field/password_visibility_event.dart @@ -0,0 +1,10 @@ +import 'package:equatable/equatable.dart'; + +abstract class PasswordVisibilityEvent extends Equatable { + const PasswordVisibilityEvent(); + + @override + List get props => []; +} + +class TogglePasswordVisibility extends PasswordVisibilityEvent {} diff --git a/lib/shared/components/bloc/password_field/password_visibility_state.dart b/lib/shared/components/bloc/password_field/password_visibility_state.dart new file mode 100644 index 0000000..26deae9 --- /dev/null +++ b/lib/shared/components/bloc/password_field/password_visibility_state.dart @@ -0,0 +1,10 @@ +import 'package:equatable/equatable.dart'; + +class PasswordVisibilityState extends Equatable { + final bool isPasswordVisible; + + const PasswordVisibilityState({required this.isPasswordVisible}); + + @override + List get props => [isPasswordVisible]; +} diff --git a/lib/shared/components/button_widget.dart b/lib/shared/components/button_widget.dart index c5f4913..c4069cd 100644 --- a/lib/shared/components/button_widget.dart +++ b/lib/shared/components/button_widget.dart @@ -5,14 +5,12 @@ import 'package:tanami_app/shared/components/text_widget.dart'; class ButtonWidget { //Text Button Widget textBtn({ - required String text, + required Widget text, required VoidCallback function, }) { return TextButton( onPressed: function, - child: TextWidget().tex14W700(text, - clr: AppColor.darkGreyColor, - textDecoration: TextDecoration.underline), + child: text, ); } @@ -20,6 +18,7 @@ class ButtonWidget { Widget elevatedBtn({ required String text, required Color clr, + Color? txtClr, required VoidCallback function, }) { return ElevatedButton( @@ -29,7 +28,7 @@ class ButtonWidget { ), child: TextWidget().tex14W700( text, - clr: AppColor.plainWhite, + clr: txtClr ?? AppColor.plainWhite, ), ); } diff --git a/lib/shared/components/form_label_textfield.dart b/lib/shared/components/form_label_textfield.dart new file mode 100644 index 0000000..892e471 --- /dev/null +++ b/lib/shared/components/form_label_textfield.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.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/password_text_form_field.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; + +import 'text_from_field_widget.dart'; + +class FormLabelTextField extends StatelessWidget { + const FormLabelTextField({ + super.key, + required this.title, + required this.type, + required this.textEditingController, + required this.hintText, + }); + final String title; + final String type; + final String hintText; + final TextEditingController textEditingController; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().tex14W500( + title, + clr: AppColor.textLabelColor, + ), + const Gap(10), + type == "password" + ? PasswordField( + controller: textEditingController, + ) + : textFormField( + validator: (value) { + if (type == "phone number") { + if (value != null && value.isEmpty) { + return AppText.enterPhoneNo; + } + return null; + } else if (type == "country selection") { + if (textEditingController.text.isEmpty) { + return AppText.chooseCountry; + } + return null; + } else { + return null; + } + }, + texttype: type == "phone number" + ? TextInputType.phone + : TextInputType.none, + textEditingController: textEditingController, + readonly: type == "country selection" ? true : false, + hintText: hintText, + suffixIcon: type == "country selection" + ? const Icon( + Icons.arrow_forward_ios_rounded, + color: Color(0xFFB4B4B4), + ) + : const SizedBox()) + ], + ); + } +} diff --git a/lib/shared/components/loader.dart b/lib/shared/components/loader.dart new file mode 100644 index 0000000..919f646 --- /dev/null +++ b/lib/shared/components/loader.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; + +class Loader { + static loader(BuildContext context) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Dialog( + elevation: 0, + backgroundColor: Colors.transparent, + child: WillPopScope( + onWillPop: () async => false, + child: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator( + color: AppColor.primaryColor, + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/shared/components/password_text_form_field.dart b/lib/shared/components/password_text_form_field.dart new file mode 100644 index 0000000..6e09187 --- /dev/null +++ b/lib/shared/components/password_text_form_field.dart @@ -0,0 +1,142 @@ +import 'package:control_style/control_style.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:tanami_app/core/styles/app_images.dart'; + +import '../../core/styles/app_color.dart'; +import '../../core/styles/app_text.dart'; +import 'bloc/password_field/password_visibility_bloc.dart'; +import 'bloc/password_field/password_visibility_event.dart'; +import 'bloc/password_field/password_visibility_state.dart'; + +class PasswordField extends StatelessWidget { + final TextEditingController controller; + + const PasswordField({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextFormField( + validator: (value) { + if (value != null && value.isEmpty) { + return AppText.enterPassword; + } + return null; + }, + controller: controller, + autovalidateMode: AutovalidateMode.onUserInteraction, + obscureText: !state.isPasswordVisible, + style: GoogleFonts.dmSans( + color: AppColor.charcoalColor, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + decoration: InputDecoration( + errorStyle: GoogleFonts.dmSans( + color: AppColor.txtErrorColor, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + fillColor: AppColor.plainWhite, + filled: true, + hintStyle: GoogleFonts.dmSans( + color: AppColor.hintTextColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + border: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + contentPadding: + const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + errorBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: + const BorderSide(color: AppColor.txtErrorBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + disabledBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + enabledBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + focusedBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + hintText: 'Password', + suffixIcon: IconButton( + icon: SvgPicture.asset( + state.isPasswordVisible + ? AppImages.showPassword + : AppImages.hidePassword, + ), + onPressed: () { + context + .read() + .add(TogglePasswordVisibility()); + }, + ), + ), + ); + }, + ); + } +} diff --git a/lib/shared/components/text_from_field_widget.dart b/lib/shared/components/text_from_field_widget.dart new file mode 100644 index 0000000..d77f243 --- /dev/null +++ b/lib/shared/components/text_from_field_widget.dart @@ -0,0 +1,139 @@ +import 'package:control_style/control_style.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; + +Widget textFormField({ + final dynamic validator, + final TextEditingController? textEditingController, + final String? hintText, + final Widget? leadingIcon, + final Color? prefixIconColor, + final String? validatorText, + final String? value, + final bool? readonly, + final bool? enabled, + final int? maxlines, + final TextInputType? texttype, + final dynamic inputFormatters, + final Function(String)? onInput, + final VoidCallback? onTap, + final TextCapitalization? textCapV, + final Widget? suffixIcon, +}) { + return TextFormField( + validator: validator, + textAlignVertical: TextAlignVertical.center, + // cursorColor: AppColor.txtBorderShadowColor, + initialValue: value, + readOnly: readonly!, + onTap: onTap, + + enabled: enabled, + enableInteractiveSelection: false, + maxLines: maxlines, + autovalidateMode: AutovalidateMode.onUserInteraction, + controller: textEditingController, + textCapitalization: textCapV ?? TextCapitalization.none, + decoration: InputDecoration( + fillColor: AppColor.plainWhite, + filled: true, + hintStyle: GoogleFonts.dmSans( + color: AppColor.hintTextColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + hintText: hintText, + prefixIconColor: prefixIconColor, + prefixIcon: leadingIcon, + errorStyle: GoogleFonts.dmSans( + color: AppColor.txtErrorColor, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + suffixIcon: suffixIcon, + border: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + contentPadding: + const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + errorBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtErrorBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + disabledBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + enabledBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + focusedBorder: DecoratedInputBorder( + child: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.0), + borderSide: const BorderSide(color: AppColor.txtBorderColor), + ), + shadow: const [ + BoxShadow( + color: AppColor.txtBorderShadowColor, + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0.50, + ) + ], + ), + ), + style: GoogleFonts.dmSans( + color: AppColor.charcoalColor, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + keyboardType: texttype, + inputFormatters: inputFormatters, + onChanged: (value) { + onInput?.call(value); + }, + ); +} diff --git a/lib/shared/components/text_widget.dart b/lib/shared/components/text_widget.dart index 0278c92..609bfa0 100644 --- a/lib/shared/components/text_widget.dart +++ b/lib/shared/components/text_widget.dart @@ -23,8 +23,25 @@ class TextWidget { color: clr ?? AppColor.plainWhite)); } + Widget tex14W500( + String text, { + Color? clr, + TextDecoration? textDecoration, + }) { + return Text(text, + textAlign: TextAlign.center, + style: GoogleFonts.dmSans( + fontSize: 14, + fontWeight: FontWeight.w500, + decoration: textDecoration ?? TextDecoration.none, + color: clr ?? AppColor.plainWhite)); + } + //Text Size 15 - Widget tex15W500(String text, {Color? clr}) { + Widget tex15W500( + String text, { + Color? clr, + }) { return Text(text, textAlign: TextAlign.center, style: GoogleFonts.dmSans( @@ -33,6 +50,22 @@ class TextWidget { color: clr ?? AppColor.plainWhite)); } + Widget tex15W400( + String text, { + Color? clr, + TextDecoration? textDecoration, + TextAlign? txtAlign, + }) { + return Text(text, + textAlign: txtAlign ?? TextAlign.center, + style: GoogleFonts.dmSans( + decoration: textDecoration ?? TextDecoration.none, + decorationColor: clr ?? AppColor.plainWhite, + fontSize: 15, + fontWeight: FontWeight.w400, + color: clr ?? AppColor.plainWhite)); + } + //Text Size 22 Widget tex22W700(String text, {Color? clr}) { return Text(text, @@ -41,4 +74,13 @@ class TextWidget { fontWeight: FontWeight.w700, color: clr ?? AppColor.plainWhite)); } + + //Text Size 20 + Widget tex20W700(String text, {Color? clr}) { + return Text(text, + style: GoogleFonts.dmSans( + fontSize: 20, + fontWeight: FontWeight.w700, + color: clr ?? AppColor.plainWhite)); + } } diff --git a/lib/shared/components/toast_message.dart b/lib/shared/components/toast_message.dart new file mode 100644 index 0000000..89a7e6c --- /dev/null +++ b/lib/shared/components/toast_message.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:toastification/toastification.dart'; + +import '../../core/styles/app_color.dart'; +import 'text_widget.dart'; + +ToastificationItem successToastMessage( + BuildContext context, + String textV, +) { + return toastification.show( + showProgressBar: false, + style: ToastificationStyle.minimal, + icon: const Icon(Icons.done), + type: ToastificationType.success, + context: context, + title: TextWidget().tex15W400( + textV, + clr: AppColor.darkGreyColor, + txtAlign: TextAlign.start, + ), + autoCloseDuration: const Duration(seconds: 5), + ); +} + +ToastificationItem errorToastMessage( + BuildContext context, + String textV, +) { + return toastification.show( + showProgressBar: false, + style: ToastificationStyle.minimal, + icon: const Icon(Icons.error), + type: ToastificationType.error, + context: context, + title: TextWidget().tex15W400( + textV, + clr: AppColor.darkGreyColor, + txtAlign: TextAlign.start, + ), + autoCloseDuration: const Duration(seconds: 5), + ); +} diff --git a/pubspec.lock b/pubspec.lock index 52e3a74..dcd9ae0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -153,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + control_style: + dependency: "direct main" + description: + name: control_style + sha256: "4b27dad0c87a7bce81319676b717c8134996450595b1e5cbb0cebdaa8131492a" + url: "https://pub.dev" + source: hosted + version: "0.1.0" convert: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0e3e8f0..6f6bfee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,6 +71,9 @@ dependencies: # Json Annotation json_serializable: ^6.8.0 + #Style + control_style: ^0.1.0 + dev_dependencies: flutter_test: sdk: flutter @@ -85,3 +88,8 @@ flutter: - assets/images/welcome_screen/ - assets/images/welcome_screen/svg/ - 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/