From fab01aa29a65255a852f1b4d3e7d7acf5c2e2eb2 Mon Sep 17 00:00:00 2001 From: jayesh Date: Wed, 17 Jul 2024 19:12:26 +0530 Subject: [PATCH] api integration --- lib/Globalconst.dart | 3 +- lib/core/routes/routes.dart | 5 +- lib/core/styles/app_text.dart | 10 ++ .../utils/constant/register_step_data.dart | 13 -- .../phone_number_hint_generator.dart | 30 ++++ .../presentation/pages/biometric_layout.dart | 10 +- .../bloc/GetCountry/get_country_bloc.dart | 1 - .../pages/choose_country_screen.dart | 4 + .../widgets/country_selection_list.dart | 10 +- .../repository/forgot_password_api.dart | 18 +++ .../bloc/restore_password_bloc.dart | 29 ++-- ...tore_password_phone_verification_bloc.dart | 39 +++--- ...ore_password_phone_verification_event.dart | 2 + ...re_password_phone_verification_screen.dart | 35 +++-- .../widgets/restore_password_form.dart | 2 + ...ord_phone_verification_bottom_section.dart | 6 +- ...tore_password_phone_verification_form.dart | 132 ++++++++++++++++-- .../login/presentation/bloc/login_bloc.dart | 39 +++--- .../login/presentation/bloc/login_state.dart | 2 + .../presentation/pages/login_screen.dart | 5 +- .../presentation/widgets/bottom_section.dart | 25 +++- .../presentation/widgets/login_form.dart | 119 ++++++++++++++-- .../presentation/bloc/otp_bloc.dart | 7 +- .../presentation/pages/otp_screen.dart | 13 +- .../widgets/resend_otp_section.dart | 10 +- .../presentation/bloc/register_bloc.dart | 33 ++--- .../presentation/bloc/register_user_bloc.dart | 24 +--- .../presentation/pages/register_screen.dart | 5 +- .../pages/register_user_details_screen.dart | 5 +- .../widgets/register_bottom_section.dart | 14 +- .../presentation/widgets/register_form.dart | 115 +++++++++------ .../widgets/register_step_count.dart | 15 ++ .../widgets/register_user_bottom_section.dart | 59 ++++---- .../widgets/register_user_form.dart | 6 +- .../securePin/Repository/PinAPIServices.dart | 5 +- .../securePin/presentation/bloc/pin_bloc.dart | 85 ++++++----- .../presentation/bloc/pin_event.dart | 17 ++- .../presentation/pages/pin_layout.dart | 4 +- .../presentation/pages/pin_screen.dart | 8 +- .../widgets/confirm_pin_keypad_section.dart | 10 +- .../widgets/pin_keypad_section.dart | 19 ++- .../presentation/widgets/pin_top_section.dart | 6 +- .../presentation/pages/splash_screen.dart | 4 + .../widgets/build_onboarding_page_widget.dart | 21 ++- lib/main.dart | 14 ++ lib/shared/api/api_endpoints.dart | 23 ++- lib/shared/api/commonAPI.dart | 0 lib/shared/api/network_api_services.dart | 56 ++++++-- .../components/device_locked_dialog.dart | 25 +++- .../components/form_label_textfield.dart | 94 ++++++++++++- .../language_change_bottom_sheet.dart | 85 +++++++++++ lib/shared/components/log_out_dialog.dart | 3 + .../components/password_text_form_field.dart | 39 +++++- lib/shared/components/permission_dialog.dart | 15 +- 54 files changed, 1022 insertions(+), 356 deletions(-) create mode 100644 lib/core/utils/phone_number_hint_generator/phone_number_hint_generator.dart create mode 100644 lib/features/forgotPassword/domain/repository/forgot_password_api.dart delete mode 100644 lib/shared/api/commonAPI.dart create mode 100644 lib/shared/components/language_change_bottom_sheet.dart diff --git a/lib/Globalconst.dart b/lib/Globalconst.dart index 10f03d4..1e452fb 100644 --- a/lib/Globalconst.dart +++ b/lib/Globalconst.dart @@ -3,5 +3,6 @@ class Globalconst { static String name = ""; static String phonenumber = ""; static String isdcode = ""; - static String createdpin=""; + static String createdpin = ""; + static String firstName = ""; } diff --git a/lib/core/routes/routes.dart b/lib/core/routes/routes.dart index 23f32f9..99d19fc 100644 --- a/lib/core/routes/routes.dart +++ b/lib/core/routes/routes.dart @@ -1,6 +1,5 @@ // router.dart -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:tanami_app/core/routes/route_name.dart'; import 'package:tanami_app/features/MainScreens/Academy/presentation/pages/academy_details_screen.dart'; @@ -33,8 +32,6 @@ import 'package:tanami_app/features/welcome/presentation/pages/weclome_screen.da import 'package:tanami_app/shared/components/no_internet.dart'; import '../../features/MainScreens/main_screen.dart'; -import '../../features/countrySelection/bloc/GetCountry/get_country_bloc.dart'; -import '../../features/countrySelection/bloc/GetCountry/get_country_event.dart'; import '../../features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart'; import '../../features/login/presentation/pages/login_screen.dart'; import '../../features/register/presentation/pages/register_user_details_screen.dart'; @@ -54,7 +51,7 @@ final goRouter = GoRouter( name: "splash", path: RouteName.splashScreen, builder: (context, state) { - context.read().add(GetCountry()); + // context.read().add(GetCountry()); return const SplashScreen(); }, routes: [ diff --git a/lib/core/styles/app_text.dart b/lib/core/styles/app_text.dart index deb0511..2f7a5aa 100644 --- a/lib/core/styles/app_text.dart +++ b/lib/core/styles/app_text.dart @@ -27,6 +27,16 @@ class AppText { static const String enterPhoneNo = "enterPhoneNo"; static const String invalidPassword = "invalidPassword"; static const String forgotPassword = "forgotPassword"; + static const String passwordLength = + "Password must be between 8 and 20 characters long."; + static const String passwordLowerCase = + "Password must contain at least one lower-case letter."; + static const String passwordUpperCase = + "Password must contain at least one upper-case letter."; + static const String passwordDigit = + "Password must contain at least one digit."; + static const String passwordSpecialCharacter = + "Password must contain at least one special character (!@#\$%&*()-+=^)."; //Register static const String getStartedToday = "getStartedToday"; diff --git a/lib/core/utils/constant/register_step_data.dart b/lib/core/utils/constant/register_step_data.dart index 3022a35..e2aace1 100644 --- a/lib/core/utils/constant/register_step_data.dart +++ b/lib/core/utils/constant/register_step_data.dart @@ -1,17 +1,4 @@ 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, diff --git a/lib/core/utils/phone_number_hint_generator/phone_number_hint_generator.dart b/lib/core/utils/phone_number_hint_generator/phone_number_hint_generator.dart new file mode 100644 index 0000000..1bd49a7 --- /dev/null +++ b/lib/core/utils/phone_number_hint_generator/phone_number_hint_generator.dart @@ -0,0 +1,30 @@ +class PhoneNumberHintGenerator { + String formatPhoneNumber(String code, int length) { + switch (code) { + case "+973": + return "000 000 00"; + case "+965": + return "0000 0000"; + case "+968": + return "000 00 000"; + case "+974": + return "000 000 00"; + case "+966": + return "000 000 000"; + case "+971": + return "00 000 0000"; + default: + return List.generate(length, (index) => '0').join(''); + } + } + + // Map of country codes to phone number lengths + final Map countryPhoneLengths = { + "+973": 8, // Bahrain + "+965": 8, // Kuwait + "+968": 8, // Oman + "+974": 8, // Qatar + "+966": 9, // Saudi Arabia + "+971": 9, // United Arab Emirates + }; +} diff --git a/lib/features/biometric/presentation/pages/biometric_layout.dart b/lib/features/biometric/presentation/pages/biometric_layout.dart index d254555..8f94d74 100644 --- a/lib/features/biometric/presentation/pages/biometric_layout.dart +++ b/lib/features/biometric/presentation/pages/biometric_layout.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import '../../../../core/routes/route_name.dart'; import '../../../../core/routes/routes.dart'; @@ -18,6 +19,7 @@ class BiometricLayout extends StatelessWidget { @override Widget build(BuildContext context) { + final SecureStorageService secureStorageService = SecureStorageService(); String biometricImage = ""; if (Platform.isIOS) { biometricImage = AppImages.biometricFace; @@ -27,15 +29,15 @@ class BiometricLayout extends StatelessWidget { return Scaffold( backgroundColor: Colors.white, body: BlocConsumer( - listener: (context, state) { + listener: (context, state) async { if (state is BiometricChecked && state.canAuthenticate) { context.read().add(AuthenticateBiometricEvent()); } else if (state is BiometricFailed) { deviceLockedDialog(context); } else if (state is BiometricAuthenticated) { - goRouter.goNamed(RouteName.pinScreen, pathParameters: { - "fromScreen": "LoginedInUser", - }); + await secureStorageService.write('isLoginedIn', "true"); + // successToastMessage(context, "Authentication Successful"); + goRouter.goNamed(RouteName.mainScreen); } }, builder: (context, state) { diff --git a/lib/features/countrySelection/bloc/GetCountry/get_country_bloc.dart b/lib/features/countrySelection/bloc/GetCountry/get_country_bloc.dart index a3bf90e..ee6d510 100644 --- a/lib/features/countrySelection/bloc/GetCountry/get_country_bloc.dart +++ b/lib/features/countrySelection/bloc/GetCountry/get_country_bloc.dart @@ -19,7 +19,6 @@ class GetCountryBlock extends Bloc { GetCountryModel countryModel = GetCountryModel.fromJson(response.data); emit(CountryLoaded(countryModel)); } - print("//"); } catch (e) { emit(CountryError("Oops Something went wrong")); } diff --git a/lib/features/countrySelection/presentation/pages/choose_country_screen.dart b/lib/features/countrySelection/presentation/pages/choose_country_screen.dart index 4898924..ee667db 100644 --- a/lib/features/countrySelection/presentation/pages/choose_country_screen.dart +++ b/lib/features/countrySelection/presentation/pages/choose_country_screen.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/styles/app_color.dart'; +import '../../bloc/GetCountry/get_country_bloc.dart'; +import '../../bloc/GetCountry/get_country_event.dart'; import 'choose_country_layout.dart'; class ChooseCountryScreen extends StatelessWidget { @@ -8,6 +11,7 @@ class ChooseCountryScreen extends StatelessWidget { @override Widget build(BuildContext context) { + context.read().add(GetCountry()); return const Scaffold( backgroundColor: AppColor.plainWhite, resizeToAvoidBottomInset: true, diff --git a/lib/features/countrySelection/presentation/widgets/country_selection_list.dart b/lib/features/countrySelection/presentation/widgets/country_selection_list.dart index 8767478..7ab1d43 100644 --- a/lib/features/countrySelection/presentation/widgets/country_selection_list.dart +++ b/lib/features/countrySelection/presentation/widgets/country_selection_list.dart @@ -38,18 +38,20 @@ class CountrySelectionList extends StatelessWidget { context.read().add(GetCountry()); }); } else { - const SnackBar(content: Text(" not fetch")); + const SnackBar(content: Text("not fetch")); } }, builder: (context, state) { - print(state); if (state is CountryLoading) { - return const Center(child: CircularProgressIndicator()); + return const Center( + child: CircularProgressIndicator( + color: AppColor.primaryColor, + )); } else if (state is CountryLoaded) { return ListView.builder( itemCount: state.countryModel.data?.length ?? 0, itemBuilder: (context, index) { var country = state.countryModel.data![index]; - print("${ApiEndpoints.base}${country.flagIcon}"); + return ListTile( title: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/features/forgotPassword/domain/repository/forgot_password_api.dart b/lib/features/forgotPassword/domain/repository/forgot_password_api.dart new file mode 100644 index 0000000..b50489f --- /dev/null +++ b/lib/features/forgotPassword/domain/repository/forgot_password_api.dart @@ -0,0 +1,18 @@ +import '../../../../Api_Helper/base_manager.dart'; +import '../../../../shared/api/api_endpoints.dart'; +import '../../../../shared/api/network_api_services.dart'; + +class ForgotPasswordApi { + ForgotPasswordApi(); + Future forgotPasswordApi(Map data) async { + String url = ApiEndpoints.forgotPasswordApi; + final response = await NetworkApiService().post(url, data); + return response; + } + + Future resetPasswordApi(Map data) async { + String url = ApiEndpoints.resetPasswordApi; + final response = await NetworkApiService().post(url, data); + return response; + } +} diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart index 49d732e..fb2d5ea 100644 --- a/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart @@ -1,6 +1,11 @@ +import 'dart:developer'; + import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:tanami_app/Globalconst.dart'; +import '../../../../Api_Helper/base_manager.dart'; +import '../../domain/repository/forgot_password_api.dart'; import 'restore_password_event.dart'; import 'restore_password_state.dart'; @@ -24,15 +29,18 @@ class RestorePasswordBloc } emit(RestorePasswordLoading()); try { - // Simulate API call - await Future.delayed(const Duration(seconds: 2)); - // Replace the next line with actual API call - final isSuccess = await _mockLoginApi(event.password); - if (isSuccess) { + log(Globalconst.token); + Map dataForm = { + "token": Globalconst.token, + "passwordHash": event.password + }; + ResponseData response = + await ForgotPasswordApi().resetPasswordApi(dataForm); + + if (response.status == ResponseStatus.SUCCESS) { emit(RestorePasswordSuccess()); } else { - emit(const RestorePasswordFailure( - "Failed. Please check your credentials.")); + emit(RestorePasswordFailure(response.message)); } } catch (e) { emit(RestorePasswordFailure(e.toString())); @@ -59,13 +67,6 @@ class RestorePasswordBloc repeatPasswordTextField.clear(); } - // Mock API function, replace with actual API call - Future _mockLoginApi( - String phoneNumber, - ) async { - return true; - } - @override Future close() { passwordTextField.dispose(); diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart index c0dbc70..f26425b 100644 --- a/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart @@ -1,12 +1,20 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:tanami_app/Globalconst.dart'; +import '../../../../Api_Helper/base_manager.dart'; +import '../../../../core/utils/secure/secure_storage_service.dart'; +import '../../domain/repository/forgot_password_api.dart'; import 'restore_password_phone_verification_event.dart'; import 'restore_password_phone_verification_state.dart'; class RestorePasswordPhoneVerificationBloc extends Bloc< RestorePasswordPhoneVerificationEvent, RestorePasswordPhoneVerificationState> { + final SecureStorageService secureStorageService; + String isdcode = ""; + + String countryId = ""; final GlobalKey formKey = GlobalKey(); final TextEditingController phoneNumberTextField = TextEditingController(); final TextEditingController countrySelectionTextField = @@ -16,26 +24,28 @@ class RestorePasswordPhoneVerificationBloc extends Bloc< return formKey; } - RestorePasswordPhoneVerificationBloc() + RestorePasswordPhoneVerificationBloc({required this.secureStorageService}) : super(RestorePasswordPhoneVerificationInitial()) { phoneNumberTextField.addListener(_onFormFieldChanged); countrySelectionTextField.addListener(_onFormFieldChanged); on(_onLoginFormChanged); on((event, emit) async { - if (!formKey.currentState!.validate()) { - return; - } emit(RestorePasswordPhoneVerificationLoading()); try { - // Simulate API call - await Future.delayed(const Duration(seconds: 2)); - // Replace the next line with actual API call - const isSuccess = true; - if (isSuccess) { + Map dataForm = { + "countryId": event.id, + "phoneNumber": event.phoneNumber + }; + ResponseData response = + await ForgotPasswordApi().forgotPasswordApi(dataForm); + + if (response.status == ResponseStatus.SUCCESS) { + var data = response.data["data"]; + await secureStorageService.write('temp_token', data["token"]); + Globalconst.token = data["token"]; emit(RestorePasswordPhoneVerificationSuccess()); } else { - emit(const RestorePasswordPhoneVerificationFailure( - "Failed. Please check your credentials.")); + emit(RestorePasswordPhoneVerificationFailure(response.message)); } } catch (e) { emit(RestorePasswordPhoneVerificationFailure(e.toString())); @@ -62,13 +72,6 @@ class RestorePasswordPhoneVerificationBloc extends Bloc< countrySelectionTextField.clear(); } - // Mock API function, replace with actual API call - Future _mockLoginApi( - String phoneNumber, - ) async { - return phoneNumber == "1234567891"; - } - @override Future close() { phoneNumberTextField.dispose(); diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_event.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_event.dart index cc3dbcc..6045c2d 100644 --- a/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_event.dart +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_event.dart @@ -11,10 +11,12 @@ class RestorePasswordPhoneVerificationSubmitted extends RestorePasswordPhoneVerificationEvent { final String phoneNumber; final String countryResidence; + final String id; const RestorePasswordPhoneVerificationSubmitted( this.phoneNumber, this.countryResidence, + this.id, ); @override diff --git a/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart b/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart index f52c7fd..ceac93f 100644 --- a/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart +++ b/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/features/forgotPassword/presentation/pages/restore_password_phone_verification_layout.dart'; import '../../../../core/styles/app_color.dart'; +import '../../../countrySelection/bloc/choose_country_bloc.dart'; import '../bloc/restore_password_phone_verification_bloc.dart'; class RestorePasswordPhoneVerificationScreen extends StatelessWidget { @@ -10,18 +12,27 @@ class RestorePasswordPhoneVerificationScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColor.plainWhite, - resizeToAvoidBottomInset: true, - body: MultiBlocProvider( - // Define the providers for the OnboardingBloc and other blocs - providers: [ - BlocProvider( - // Create an instance of the OnboardingBloc - create: (context) => RestorePasswordPhoneVerificationBloc(), - ), - ], - child: const RestorePasswordPhoneVerificationLayout(), + final SecureStorageService secureStorageService = SecureStorageService(); + final radioBloc = context.read(); + return WillPopScope( + onWillPop: () async { + radioBloc.resetSelection(); + return true; + }, + child: Scaffold( + backgroundColor: AppColor.plainWhite, + resizeToAvoidBottomInset: true, + body: MultiBlocProvider( + // Define the providers for the OnboardingBloc and other blocs + providers: [ + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => RestorePasswordPhoneVerificationBloc( + secureStorageService: secureStorageService), + ), + ], + child: const RestorePasswordPhoneVerificationLayout(), + ), ), ); } diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_form.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_form.dart index ce5c0b6..92566fe 100644 --- a/lib/features/forgotPassword/presentation/widgets/restore_password_form.dart +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_form.dart @@ -49,6 +49,8 @@ class RestorePasswordForm extends StatelessWidget { type: AppText.password.toLowerCase(), textEditingController: restorePasswordBloc.repeatPasswordTextField, + originalPasswordController: + restorePasswordBloc.passwordTextField, ), ), ], diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart index e682d2e..39e4209 100644 --- a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart @@ -26,6 +26,8 @@ class RestorePasswordPhoneVerificationBottomSection extends StatelessWidget { Widget build(BuildContext context) { var localizations = AppLocalizations.of(context); final radioBloc = context.read(); + final restorePasswordBloc = + context.read(); return Column( children: [ BlocConsumer() .phoneNumberTextField .text, - ""), + "", + restorePasswordBloc.countryId), ) : null; }, @@ -91,6 +94,7 @@ class RestorePasswordPhoneVerificationBottomSection extends StatelessWidget { const Gap(5), ButtonWidget().textBtn( function: () { + radioBloc.resetSelection(); goRouter.pop(); }, text: TextWidget().text14W700( diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart index 127f8ad..55531bf 100644 --- a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart @@ -1,11 +1,18 @@ +import 'package:cached_network_image/cached_network_image.dart'; 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:google_fonts/google_fonts.dart'; import 'package:tanami_app/core/styles/app_text.dart'; -import 'package:tanami_app/core/utils/constant/country_flag_data.dart'; +import '../../../../core/styles/app_color.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; +import '../../../../core/utils/phone_number_hint_generator/phone_number_hint_generator.dart'; +import '../../../../shared/api/api_endpoints.dart'; import '../../../../shared/components/form_label_textfield.dart'; +import '../../../countrySelection/bloc/GetCountry/get_country_bloc.dart'; +import '../../../countrySelection/bloc/GetCountry/get_country_state.dart'; import '../../../countrySelection/bloc/choose_country_bloc.dart'; import '../../../countrySelection/bloc/choose_country_state.dart'; import '../bloc/restore_password_phone_verification_bloc.dart'; @@ -15,21 +22,41 @@ class RestorePasswordPhoneVerificationForm extends StatelessWidget { @override Widget build(BuildContext context) { + String flag = ""; + final countrydata = context.read(); var localizations = AppLocalizations.of(context); final restorePasswordBloc = context.read(); // Reset fields when the screen is built restorePasswordBloc.resetFields(); + String hintText = '00 000 000'; return BlocConsumer(listener: (context, state) { int selectedCountry = -1; if (state is RadioSelectionChanged) { + final countryState = countrydata.state; selectedCountry = state.selectedIndex; - restorePasswordBloc.countrySelectionTextField.text = - countryName[selectedCountry]; - restorePasswordBloc.phoneNumberTextField.text = - "${isoCountryCode[selectedCountry]} "; + if (countryState is CountryLoaded) { + restorePasswordBloc.isdcode = + "${countryState.countryModel.data![selectedCountry].isdCode}"; + restorePasswordBloc.countrySelectionTextField.text = countryState + .countryModel.data![selectedCountry].countryName + .toString(); + restorePasswordBloc.phoneNumberTextField.text = ""; + if (PhoneNumberHintGenerator().countryPhoneLengths.containsKey( + restorePasswordBloc.isdcode, + )) { + final expectedLength = PhoneNumberHintGenerator() + .countryPhoneLengths[restorePasswordBloc.isdcode]; + hintText = PhoneNumberHintGenerator().formatPhoneNumber( + restorePasswordBloc.isdcode, expectedLength!); + } + restorePasswordBloc.countryId = + countryState.countryModel.data![selectedCountry].id!; + flag = + "${ApiEndpoints.base}${countryState.countryModel.data![selectedCountry].flagIcon}"; + } } }, builder: (context, state) { int selectedCountry = -1; @@ -51,10 +78,41 @@ class RestorePasswordPhoneVerificationForm extends StatelessWidget { FormLabelTextField( prefixWidget: selectedCountry == -1 ? null - : Image.asset( - countryFlag[selectedCountry], - width: 20, - height: 20, + : Padding( + padding: EdgeInsets.only(left: 12.w), + child: SizedBox( + height: 50.h, + width: 30.w, + child: Align( + alignment: Alignment.centerLeft, + child: SizedBox( + height: 30.h, + width: 30.w, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(100)), + child: CachedNetworkImage( + maxHeightDiskCache: 200, + maxWidthDiskCache: 200, + cacheKey: restorePasswordBloc + .countrySelectionTextField.text, + key: UniqueKey(), + imageUrl: "${ApiEndpoints.baseurl}$flag", + height: 30.h, + width: 30.w, + placeholder: (context, url) => SizedBox( + height: 30.h, + width: 30.w, + child: + const CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + const Icon(Icons.error), + fit: BoxFit.cover), + ), + ), + ), + ), ), hintText: localizations.translate(AppText.chooseCountry), title: localizations.translate(AppText.countryOfResidence), @@ -66,12 +124,58 @@ class RestorePasswordPhoneVerificationForm extends StatelessWidget { FormLabelTextField( prefixWidget: selectedCountry == -1 ? null - : Image.asset( - countryFlag[selectedCountry], - width: 20, - height: 20, + : Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(10), + SizedBox( + height: 50.h, + width: 30.w, + child: Align( + alignment: Alignment.centerLeft, + child: SizedBox( + height: 30.h, + width: 30.w, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(100)), + child: CachedNetworkImage( + maxHeightDiskCache: 200, + maxWidthDiskCache: 200, + key: UniqueKey(), + imageUrl: flag, + height: 30.h, + width: 30.w, + placeholder: (context, url) => SizedBox( + height: 30.h, + width: 30.w, + child: + const CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + const Icon(Icons.error), + fit: BoxFit.cover), + ), + ), + ), + ), + const Gap(5), + Container( + margin: const EdgeInsets.only(top: 10), + height: 30.h, + width: 40.w, + child: Text( + restorePasswordBloc.isdcode, + style: GoogleFonts.dmSans( + color: AppColor.charcoalColor, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + )), + ], ), - hintText: "+0 (000) 000 00 00", + hintText: hintText, title: localizations.translate(AppText.phoneNumber), type: "phone number", textEditingController: diff --git a/lib/features/login/presentation/bloc/login_bloc.dart b/lib/features/login/presentation/bloc/login_bloc.dart index fe34394..8277b87 100644 --- a/lib/features/login/presentation/bloc/login_bloc.dart +++ b/lib/features/login/presentation/bloc/login_bloc.dart @@ -1,37 +1,40 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:tanami_app/Api_Helper/base_manager.dart'; +import 'package:tanami_app/Globalconst.dart'; import 'package:tanami_app/features/login/Repository/LoginAPI.dart'; +import '../../../../core/utils/secure/secure_storage_service.dart'; import 'login_event.dart'; import 'login_state.dart'; class LoginBloc extends Bloc { + final SecureStorageService secureStorageService; final GlobalKey formKey = GlobalKey(); final TextEditingController countrySelectionTextField = TextEditingController(); final TextEditingController phoneNumberTextField = TextEditingController(); final TextEditingController passwordTextField = TextEditingController(); String countryId = ""; + String isdcode = ""; GlobalKey getFormKey() { return formKey; } - LoginBloc() : super(LoginInitial()) { + LoginBloc({required this.secureStorageService}) : super(LoginInitial()) { phoneNumberTextField.addListener(_onFormFieldChanged); passwordTextField.addListener(_onFormFieldChanged); countrySelectionTextField.addListener(_onFormFieldChanged); on(_onLoginFormChanged); on((event, emit) async { if (!formKey.currentState!.validate()) { + emit(LoginLoading()); emit( const LoginFailure("Login failed. Please check your credentials.")); return; } emit(LoginLoading()); try { - // Simulate API call - //await Future.delayed(const Duration(seconds: 2)); Map logindata = { "countryId": event.countryId, "phoneNumber": event.phoneNumber, @@ -39,20 +42,23 @@ class LoginBloc extends Bloc { }; ResponseData response = await LoginAPI().LoginRequest(logindata); if (response.status == ResponseStatus.SUCCESS) { + await secureStorageService.write( + 'first_name', response.data['data']['firstName']); + Globalconst.firstName = response.data['data']['firstName']; + await secureStorageService.write( + 'temp_token', response.data['data']['token']); + emit(LoginSuccess()); + } else if (response.status == ResponseStatus.PRIVATE) { + if (response.message.toString() == "Master Pin is not created") { + await secureStorageService.write( + 'temp_token', response.data['data']['user']); + emit(LoginMasterPinPending()); + } } else { emit(const LoginFailure( "Login failed. Please check your credentials.")); } - // 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())); } @@ -80,15 +86,6 @@ class LoginBloc extends Bloc { countrySelectionTextField.clear(); } - // Mock API function, replace with actual API call - Future _mockLoginApi( - String phoneNumber, - String password, - String countryResidence, - ) async { - return password == "123456"; - } - @override Future close() { phoneNumberTextField.dispose(); diff --git a/lib/features/login/presentation/bloc/login_state.dart b/lib/features/login/presentation/bloc/login_state.dart index 5429d7e..4425a51 100644 --- a/lib/features/login/presentation/bloc/login_state.dart +++ b/lib/features/login/presentation/bloc/login_state.dart @@ -13,6 +13,8 @@ class LoginLoading extends LoginState {} class LoginSuccess extends LoginState {} +class LoginMasterPinPending extends LoginState {} + class LoginFailure extends LoginState { final String error; diff --git a/lib/features/login/presentation/pages/login_screen.dart b/lib/features/login/presentation/pages/login_screen.dart index 9a89c17..3283009 100644 --- a/lib/features/login/presentation/pages/login_screen.dart +++ b/lib/features/login/presentation/pages/login_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import '../../../../core/styles/app_color.dart'; import '../../../../shared/components/exit_app_dialog.dart'; @@ -13,6 +14,7 @@ class LoginScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final SecureStorageService secureStorageService = SecureStorageService(); final radioBloc = context.read(); return WillPopScope( onWillPop: () async { @@ -34,7 +36,8 @@ class LoginScreen extends StatelessWidget { providers: [ BlocProvider( // Create an instance of the OnboardingBloc - create: (context) => LoginBloc(), + create: (context) => + LoginBloc(secureStorageService: secureStorageService), ), ], child: LoginLayout( diff --git a/lib/features/login/presentation/widgets/bottom_section.dart b/lib/features/login/presentation/widgets/bottom_section.dart index e7d36eb..7c405b9 100644 --- a/lib/features/login/presentation/widgets/bottom_section.dart +++ b/lib/features/login/presentation/widgets/bottom_section.dart @@ -3,8 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gap/gap.dart'; import 'package:tanami_app/core/utils/secure/secure_storage_service.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'; @@ -12,7 +10,9 @@ import '../../../../core/styles/app_color.dart'; import '../../../../core/styles/app_text.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; import '../../../../shared/components/button_widget.dart'; +import '../../../../shared/components/loader.dart'; import '../../../../shared/components/text_widget.dart'; +import '../../../../shared/components/toast_message.dart'; import '../../../countrySelection/bloc/choose_country_bloc.dart'; import '../bloc/login_bloc.dart'; import '../bloc/login_event.dart'; @@ -38,6 +38,9 @@ class BottomSection extends StatelessWidget { alignment: Alignment.topRight, child: ButtonWidget().textBtn( function: () { + // Reset fields when the screen is built + loginbloc.resetFields(); + radioBloc.resetSelection(); goRouter .pushNamed(RouteName.forgotPasswordPhoneVerificationScreen); }, @@ -52,7 +55,7 @@ class BottomSection extends StatelessWidget { if (state is LoginLoading) { Loader.loader(context); } else if (state is LoginSuccess) { - goRouter.goNamed('mainScreen'); + // goRouter.goNamed('mainScreen'); successToastMessage(context, "login successful !"); goRouter.pop(); radioBloc.resetSelection(); @@ -61,6 +64,11 @@ class BottomSection extends StatelessWidget { "fromScreen": fromScreen == "forgot-pin" ? "forgot-pin" : "login", }); //Push Or GO Need to Check + } else if (state is LoginMasterPinPending) { + goRouter.pop(); + goRouter.goNamed(RouteName.pinScreen, pathParameters: { + "fromScreen": "login-master-pending", + }); } else if (state is LoginFailure) { goRouter.pop(); errorToastMessage( @@ -87,11 +95,15 @@ class BottomSection extends StatelessWidget { ? AppColor.plainWhite : AppColor.inactiveBtnTxtColor, function: () { + FocusManager.instance.primaryFocus?.unfocus(); isButtonEnabled ? loginbloc.add( - LoginSubmitted(loginbloc.phoneNumberTextField.text, - loginbloc.passwordTextField.text, "",loginbloc.countryId), - ) + LoginSubmitted( + loginbloc.phoneNumberTextField.text, + loginbloc.passwordTextField.text, + "", + loginbloc.countryId), + ) : null; }, text: localizations.translate(AppText.loginText), @@ -105,6 +117,7 @@ class BottomSection extends StatelessWidget { const Gap(10), ButtonWidget().textBorderBtn( function: () { + loginbloc.resetFields(); radioBloc.resetSelection(); fromScreen == "forgot-pin" ? goRouter.pop() diff --git a/lib/features/login/presentation/widgets/login_form.dart b/lib/features/login/presentation/widgets/login_form.dart index 2ede852..96c1092 100644 --- a/lib/features/login/presentation/widgets/login_form.dart +++ b/lib/features/login/presentation/widgets/login_form.dart @@ -1,10 +1,14 @@ +import 'package:cached_network_image/cached_network_image.dart'; 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:google_fonts/google_fonts.dart'; import 'package:tanami_app/core/styles/app_text.dart'; -import 'package:tanami_app/core/utils/constant/country_flag_data.dart'; +import 'package:tanami_app/core/utils/phone_number_hint_generator/phone_number_hint_generator.dart'; import '../../../../Globalconst.dart'; +import '../../../../core/styles/app_color.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; import '../../../../shared/api/api_endpoints.dart'; import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart'; @@ -29,6 +33,8 @@ class LoginForm extends StatelessWidget { // Reset fields when the screen is built loginBloc.resetFields(); + String hintText = '00 000 000'; + return BlocConsumer(listener: (context, state) { int selectedCountry = -1; if (state is RadioSelectionChanged) { @@ -38,9 +44,19 @@ class LoginForm extends StatelessWidget { loginBloc.countrySelectionTextField.text = countryState .countryModel.data![selectedCountry].countryName .toString(); - loginBloc.phoneNumberTextField.text = + loginBloc.phoneNumberTextField.text = ""; + loginBloc.isdcode = "${countryState.countryModel.data![selectedCountry].isdCode}"; - loginBloc.countryId="${countryState.countryModel.data![selectedCountry].id}"; + if (PhoneNumberHintGenerator().countryPhoneLengths.containsKey( + loginBloc.isdcode, + )) { + final expectedLength = PhoneNumberHintGenerator() + .countryPhoneLengths[loginBloc.isdcode]; + hintText = PhoneNumberHintGenerator() + .formatPhoneNumber(loginBloc.isdcode, expectedLength!); + } + loginBloc.countryId = + "${countryState.countryModel.data![selectedCountry].id}"; flag = "${ApiEndpoints.base}${countryState.countryModel.data![selectedCountry].flagIcon}"; Globalconst.phonenumber = loginBloc.phoneNumberTextField.text; @@ -72,10 +88,41 @@ class LoginForm extends StatelessWidget { FormLabelTextField( prefixWidget: selectedCountry == -1 ? null - : Image.asset( - countryFlag[selectedCountry], - width: 20, - height: 20, + : Padding( + padding: EdgeInsets.only(left: 12.w), + child: SizedBox( + height: 50.h, + width: 30.w, + child: Align( + alignment: Alignment.centerLeft, + child: SizedBox( + height: 30.h, + width: 30.w, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(100)), + child: CachedNetworkImage( + maxHeightDiskCache: 200, + maxWidthDiskCache: 200, + cacheKey: loginBloc + .countrySelectionTextField.text, + key: UniqueKey(), + imageUrl: "${ApiEndpoints.baseurl}$flag", + height: 30.h, + width: 30.w, + placeholder: (context, url) => SizedBox( + height: 30.h, + width: 30.w, + child: + const CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + const Icon(Icons.error), + fit: BoxFit.cover), + ), + ), + ), + ), ), hintText: localizations.translate(AppText.chooseCountry), title: localizations.translate(AppText.countryOfResidence), @@ -86,12 +133,60 @@ class LoginForm extends StatelessWidget { FormLabelTextField( prefixWidget: selectedCountry == -1 ? null - : Image.asset( - countryFlag[selectedCountry], - width: 20, - height: 20, + : Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(10), + SizedBox( + height: 50.h, + width: 30.w, + child: Align( + alignment: Alignment.centerLeft, + child: SizedBox( + height: 30.h, + width: 30.w, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(100)), + child: CachedNetworkImage( + maxHeightDiskCache: 200, + maxWidthDiskCache: 200, + cacheKey: loginBloc + .countrySelectionTextField.text, + key: UniqueKey(), + imageUrl: flag, + height: 30.h, + width: 30.w, + placeholder: (context, url) => SizedBox( + height: 30.h, + width: 30.w, + child: + const CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + const Icon(Icons.error), + fit: BoxFit.cover), + ), + ), + ), + ), + const Gap(5), + Container( + margin: const EdgeInsets.only(top: 10), + height: 30.h, + width: 40.w, + child: Text( + loginBloc.isdcode, + style: GoogleFonts.dmSans( + color: AppColor.charcoalColor, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + )), + ], ), - hintText: "+0 (000) 000 00 00", + hintText: hintText, title: localizations.translate(AppText.phoneNumber), type: "phone number", textEditingController: loginBloc.phoneNumberTextField, diff --git a/lib/features/otpVerification/presentation/bloc/otp_bloc.dart b/lib/features/otpVerification/presentation/bloc/otp_bloc.dart index 01ed9a9..6ad73d7 100644 --- a/lib/features/otpVerification/presentation/bloc/otp_bloc.dart +++ b/lib/features/otpVerification/presentation/bloc/otp_bloc.dart @@ -3,15 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sms_autofill/sms_autofill.dart'; import 'package:tanami_app/Api_Helper/base_manager.dart'; -import 'package:tanami_app/Globalconst.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import '../../domain/Repository/otp_api.dart'; import 'otp_event.dart'; import 'otp_state.dart'; class OtpBloc extends Bloc { + final SecureStorageService secureStorageService; final TextEditingController otpController = TextEditingController(); - OtpBloc() : super(OtpInitial()) { + OtpBloc({required this.secureStorageService}) : super(OtpInitial()) { on(_onStartListeningForOtp); on(_onOtpCodeChanged); on(_onOtpSubmit); @@ -33,7 +34,7 @@ class OtpBloc extends Bloc { emit(OtpSubmitting()); try { Map otpdata = { - "token": Globalconst.token, + "token": await secureStorageService.read("temp_token"), "otp": otpController.text }; ResponseData response = await OTPAPI().verifyOTP(otpdata); diff --git a/lib/features/otpVerification/presentation/pages/otp_screen.dart b/lib/features/otpVerification/presentation/pages/otp_screen.dart index bb89f72..416ed16 100644 --- a/lib/features/otpVerification/presentation/pages/otp_screen.dart +++ b/lib/features/otpVerification/presentation/pages/otp_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/features/otpVerification/presentation/bloc/otp_bloc.dart'; import 'package:tanami_app/features/otpVerification/presentation/pages/otp_layout.dart'; @@ -19,14 +20,17 @@ class OtpScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final SecureStorageService secureStorageService = SecureStorageService(); return Scaffold( backgroundColor: AppColor.plainWhite, resizeToAvoidBottomInset: true, - body: MultiBlocProvider( + body: MultiBlocProvider( providers: [ BlocProvider( // Create an instance of the OnboardingBloc - create: (context) => OtpBloc()..add(StartListeningForOtp()), + create: (context) => + OtpBloc(secureStorageService: secureStorageService) + ..add(StartListeningForOtp()), ), BlocProvider( create: (context) => @@ -34,8 +38,9 @@ class OtpScreen extends StatelessWidget { ), //RegisterBloc BlocProvider( - create: (context) => - RegisterBloc(), // Start the timer here + create: (context) => RegisterBloc( + secureStorageService: + secureStorageService), // Start the timer here ), ], child: OtpLayout(fromScreen: fromScreen), diff --git a/lib/features/otpVerification/presentation/widgets/resend_otp_section.dart b/lib/features/otpVerification/presentation/widgets/resend_otp_section.dart index a3028f3..742e8bd 100644 --- a/lib/features/otpVerification/presentation/widgets/resend_otp_section.dart +++ b/lib/features/otpVerification/presentation/widgets/resend_otp_section.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tanami_app/core/styles/app_color.dart'; import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; import 'package:tanami_app/shared/components/toast_message.dart'; @@ -21,11 +22,18 @@ class ResendOtpSection extends StatelessWidget { @override Widget build(BuildContext context) { String token = ""; + getToken() async { + final SecureStorageService secureStorageService = SecureStorageService(); + token = await secureStorageService.read('temp_token') ?? ""; + } + + getToken(); var loginBloc = context.read(); loginBloc.isdcode = Globalconst.isdcode; loginBloc.phoneNumberTextField.text = Globalconst.phonenumber; loginBloc.countrySelectionTextField.text = Globalconst.name; var localizations = AppLocalizations.of(context); + final SecureStorageService secureStorageService = SecureStorageService(); return BlocBuilder( builder: (context, state) { return Padding( @@ -65,7 +73,7 @@ class ResendOtpSection extends StatelessWidget { return GestureDetector( onTap: () { loginBloc.add( - Resendotp(Globalconst.token), + Resendotp(token), ); print("///"); diff --git a/lib/features/register/presentation/bloc/register_bloc.dart b/lib/features/register/presentation/bloc/register_bloc.dart index f995cee..c3b0c7f 100644 --- a/lib/features/register/presentation/bloc/register_bloc.dart +++ b/lib/features/register/presentation/bloc/register_bloc.dart @@ -2,24 +2,26 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import '../../../../Api_Helper/base_manager.dart'; -import '../../../../Globalconst.dart'; +import '../../../../core/utils/secure/secure_storage_service.dart'; import '../../../otpVerification/domain/Repository/otp_api.dart'; import 'register_event.dart'; import 'register_state.dart'; class RegisterBloc extends Bloc { + final SecureStorageService secureStorageService; final GlobalKey formKey = GlobalKey(); final TextEditingController countrySelectionTextField = TextEditingController(); - String isdcode=""; - String countryId=""; + String isdcode = ""; + String countryId = ""; final TextEditingController phoneNumberTextField = TextEditingController(); GlobalKey getFormKey() { return formKey; } - RegisterBloc() : super(RegisterInitial()) { + RegisterBloc({required this.secureStorageService}) + : super(RegisterInitial()) { phoneNumberTextField.addListener(_onFormFieldChanged); countrySelectionTextField.addListener(_onFormFieldChanged); on(_onLoginFormChanged); @@ -29,21 +31,18 @@ class RegisterBloc extends Bloc { } emit(RegisterLoading()); try { - Map requestdata={ - "countryId":event.id, - "phoneNumber":event.phoneNumber + Map requestdata = { + "countryId": event.id, + "phoneNumber": event.phoneNumber }; ResponseData response = await OTPAPI().requestOTP(requestdata); if (response.status == ResponseStatus.SUCCESS) { - print("///////success"); var data = response.data["data"]; String token = data["token"]; - Globalconst.token = token; + await secureStorageService.write('temp_token', token); emit(RegisterSuccess(token)); //emit(OTPLoaded()); } else { - emit(const RegisterFailure( - "Register failed. Please check your credentials.")); - //emit(OTPFailed("Oops something went wrong")); + emit(RegisterFailure(response.message)); } } catch (e) { emit(RegisterFailure(e.toString())); @@ -54,7 +53,7 @@ class RegisterBloc extends Bloc { emit(RegisterLoading()); try { Map requestdata = { - "token": Globalconst.token, + "token": secureStorageService.read('temp_token'), }; ResponseData response = await OTPAPI().resendOTPRequest(requestdata); if (response.status == ResponseStatus.SUCCESS) { @@ -83,14 +82,6 @@ class RegisterBloc extends Bloc { emit(RegisterFieldsState(areFieldsFilled)); } - // Mock API function, replace with actual API call - Future _mockLoginApi( - String phoneNumber, - String countryResidence, - ) async { - return true; - } - @override Future close() { phoneNumberTextField.dispose(); diff --git a/lib/features/register/presentation/bloc/register_user_bloc.dart b/lib/features/register/presentation/bloc/register_user_bloc.dart index ad4c507..858089b 100644 --- a/lib/features/register/presentation/bloc/register_user_bloc.dart +++ b/lib/features/register/presentation/bloc/register_user_bloc.dart @@ -1,13 +1,14 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:tanami_app/Api_Helper/base_manager.dart'; -import 'package:tanami_app/Globalconst.dart'; import 'package:tanami_app/features/register/Repository/RegisterApi.dart'; +import '../../../../core/utils/secure/secure_storage_service.dart'; import 'register_user_event.dart'; import 'register_user_state.dart'; class RegisterUserBloc extends Bloc { + final SecureStorageService secureStorageService; final GlobalKey formKey = GlobalKey(); final TextEditingController firstNameTextField = TextEditingController(); final TextEditingController lastNameTextField = TextEditingController(); @@ -19,7 +20,8 @@ class RegisterUserBloc extends Bloc { return formKey; } - RegisterUserBloc() : super(RegisterUserInitial()) { + RegisterUserBloc({required this.secureStorageService}) + : super(RegisterUserInitial()) { firstNameTextField.addListener(_onFormFieldChanged); passwordTextField.addListener(_onFormFieldChanged); lastNameTextField.addListener(_onFormFieldChanged); @@ -43,12 +45,11 @@ class RegisterUserBloc extends Bloc { ResponseData response = await RegisterAPIService().RegisterRequest(registerdata); if (response.status == ResponseStatus.SUCCESS) { - var data=response.data["data"]; - Globalconst.token=data["token"].toString(); + var data = response.data["data"]; + secureStorageService.write("temp_token", data["token"].toString()); emit(RegisterUserSuccess()); } else { - emit( RegisterUserFailure( - response.message)); + emit(RegisterUserFailure(response.message)); } // Simulate API call await Future.delayed(const Duration(seconds: 2)); @@ -91,17 +92,6 @@ class RegisterUserBloc extends Bloc { emit(RegisterUserFieldsState(areFieldsFilled)); } - // Mock API function, replace with actual API call - Future _mockLoginApi( - String firstName, - String password, - String confirmPassword, - String email, - String lastName, - ) async { - return true; - } - @override Future close() { firstNameTextField.dispose(); diff --git a/lib/features/register/presentation/pages/register_screen.dart b/lib/features/register/presentation/pages/register_screen.dart index 1edc0c2..ad504a1 100644 --- a/lib/features/register/presentation/pages/register_screen.dart +++ b/lib/features/register/presentation/pages/register_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/features/register/presentation/bloc/register_bloc.dart'; import '../../../../core/styles/app_color.dart'; @@ -11,6 +12,7 @@ class RegisterScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final SecureStorageService secureStorageService = SecureStorageService(); final radioBloc = context.read(); return WillPopScope( onWillPop: () async { @@ -24,7 +26,8 @@ class RegisterScreen extends StatelessWidget { providers: [ BlocProvider( // Create an instance of the OnboardingBloc - create: (context) => RegisterBloc(), + create: (context) => + RegisterBloc(secureStorageService: secureStorageService), ), ], child: const RegisterLayout(), diff --git a/lib/features/register/presentation/pages/register_user_details_screen.dart b/lib/features/register/presentation/pages/register_user_details_screen.dart index 036f13a..a00307b 100644 --- a/lib/features/register/presentation/pages/register_user_details_screen.dart +++ b/lib/features/register/presentation/pages/register_user_details_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/features/register/presentation/pages/register_user_details_layout.dart'; import 'package:tanami_app/shared/components/bloc/checkbox/checkbox_bloc.dart'; @@ -13,6 +14,7 @@ class RegisterUserDetailsScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final SecureStorageService secureStorageService = SecureStorageService(); return Scaffold( backgroundColor: AppColor.plainWhite, resizeToAvoidBottomInset: true, @@ -20,7 +22,8 @@ class RegisterUserDetailsScreen extends StatelessWidget { providers: [ BlocProvider( // Create an instance of the OnboardingBloc - create: (context) => RegisterUserBloc(), + create: (context) => + RegisterUserBloc(secureStorageService: secureStorageService), ), BlocProvider( // Create an instance of the OnboardingBloc diff --git a/lib/features/register/presentation/widgets/register_bottom_section.dart b/lib/features/register/presentation/widgets/register_bottom_section.dart index c16f9b8..7c684ea 100644 --- a/lib/features/register/presentation/widgets/register_bottom_section.dart +++ b/lib/features/register/presentation/widgets/register_bottom_section.dart @@ -37,7 +37,7 @@ class RegisterBottomSection extends StatelessWidget { height: 12, ), const Gap(36), - BlocConsumer( + BlocConsumer( listener: (context, state) { print(loginBloc.state); if (state is RegisterLoading) { @@ -48,7 +48,6 @@ class RegisterBottomSection extends StatelessWidget { goRouter.pushNamed(RouteName.otpScreen, pathParameters: {"fromScreen": "register"}); - } else if (state is RegisterFailure) { goRouter.pop(); errorToastMessage( @@ -75,16 +74,15 @@ class RegisterBottomSection extends StatelessWidget { ? AppColor.plainWhite : AppColor.inactiveBtnTxtColor, function: () { - Globalconst.phonenumber=loginBloc.phoneNumberTextField.text; + Globalconst.phonenumber = loginBloc.phoneNumberTextField.text; isButtonEnabled ? context.read().add( RegisterSubmitted( - loginBloc.phoneNumberTextField.text, - loginBloc.countrySelectionTextField.text, - loginBloc.isdcode, - loginBloc.countryId - ), + loginBloc.phoneNumberTextField.text, + loginBloc.countrySelectionTextField.text, + loginBloc.isdcode, + loginBloc.countryId), ) : null; }, diff --git a/lib/features/register/presentation/widgets/register_form.dart b/lib/features/register/presentation/widgets/register_form.dart index 3c37992..f6410c4 100644 --- a/lib/features/register/presentation/widgets/register_form.dart +++ b/lib/features/register/presentation/widgets/register_form.dart @@ -3,10 +3,13 @@ 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:google_fonts/google_fonts.dart'; import 'package:tanami_app/core/styles/app_text.dart'; import '../../../../Globalconst.dart'; +import '../../../../core/styles/app_color.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; +import '../../../../core/utils/phone_number_hint_generator/phone_number_hint_generator.dart'; import '../../../../shared/api/api_endpoints.dart'; import '../../../../shared/components/form_label_textfield.dart'; import '../../../countrySelection/bloc/GetCountry/get_country_bloc.dart'; @@ -21,25 +24,35 @@ class RegisterForm extends StatelessWidget { @override Widget build(BuildContext context) { var localizations = AppLocalizations.of(context); - final loginBloc = context.read(); + final registerBloc = context.read(); final countrydata = context.read(); int selectedCountry = -1; String flag = ""; + String hintText = '00 000 000'; return BlocConsumer(listener: (context, state) { if (state is RadioSelectionChanged) { selectedCountry = state.selectedIndex; final countryState = countrydata.state; if (countryState is CountryLoaded) { - loginBloc.countrySelectionTextField.text = countryState + registerBloc.countrySelectionTextField.text = countryState .countryModel.data![selectedCountry].countryName .toString(); - loginBloc.phoneNumberTextField.text = + registerBloc.phoneNumberTextField.text = ""; + registerBloc.isdcode = "${countryState.countryModel.data![selectedCountry].isdCode}"; - loginBloc.isdcode="${countryState.countryModel.data![selectedCountry].isdCode}"; - loginBloc.countryId="${countryState.countryModel.data![selectedCountry].id}"; + if (PhoneNumberHintGenerator().countryPhoneLengths.containsKey( + registerBloc.isdcode, + )) { + final expectedLength = PhoneNumberHintGenerator() + .countryPhoneLengths[registerBloc.isdcode]; + hintText = PhoneNumberHintGenerator() + .formatPhoneNumber(registerBloc.isdcode, expectedLength!); + } + registerBloc.countryId = + "${countryState.countryModel.data![selectedCountry].id}"; flag = "${ApiEndpoints.base}${countryState.countryModel.data![selectedCountry].flagIcon}"; - Globalconst.phonenumber = loginBloc.phoneNumberTextField.text; + Globalconst.phonenumber = registerBloc.phoneNumberTextField.text; Globalconst.name = countryState .countryModel.data![selectedCountry].countryName @@ -55,7 +68,7 @@ class RegisterForm extends StatelessWidget { selectedCountry = -1; } return Form( - key: loginBloc.formKey, + key: registerBloc.formKey, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 14, @@ -85,10 +98,10 @@ class RegisterForm extends StatelessWidget { child: CachedNetworkImage( maxHeightDiskCache: 200, maxWidthDiskCache: 200, - cacheKey: loginBloc + cacheKey: registerBloc .countrySelectionTextField.text, key: UniqueKey(), - imageUrl: flag, + imageUrl: "${ApiEndpoints.baseurl}$flag", height: 30.h, width: 30.w, placeholder: (context, url) => SizedBox( @@ -113,7 +126,7 @@ class RegisterForm extends StatelessWidget { hintText: localizations.translate(AppText.chooseCountry), title: localizations.translate(AppText.countryOfResidence), type: "country selection", - textEditingController: loginBloc.countrySelectionTextField, + textEditingController: registerBloc.countrySelectionTextField, ), const Gap(20), FormLabelTextField( @@ -121,38 +134,58 @@ class RegisterForm extends StatelessWidget { ? null : Padding( padding: EdgeInsets.only(left: 12.w), - child: SizedBox( - height: 50.h, - width: 30.w, - child: Align( - alignment: Alignment.centerLeft, - child: SizedBox( - height: 30.h, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 50.h, width: 30.w, - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(100)), - child: CachedNetworkImage( - maxHeightDiskCache: 200, - maxWidthDiskCache: 200, - cacheKey: loginBloc - .countrySelectionTextField.text, - key: UniqueKey(), - imageUrl: flag, - height: 30.h, - width: 30.w, - placeholder: (context, url) => SizedBox( - height: 30.h, - width: 30.w, - child: - const CircularProgressIndicator(), - ), - errorWidget: (context, url, error) => - const Icon(Icons.error), - fit: BoxFit.cover), + child: Align( + alignment: Alignment.centerLeft, + child: SizedBox( + height: 30.h, + width: 30.w, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(100)), + child: CachedNetworkImage( + maxHeightDiskCache: 200, + maxWidthDiskCache: 200, + cacheKey: registerBloc + .countrySelectionTextField.text, + key: UniqueKey(), + imageUrl: flag, + height: 30.h, + width: 30.w, + placeholder: (context, url) => + SizedBox( + height: 30.h, + width: 30.w, + child: + const CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + const Icon(Icons.error), + fit: BoxFit.cover), + ), + ), ), ), - ), + const Gap(5), + Container( + margin: const EdgeInsets.only(top: 10), + height: 30.h, + width: 40.w, + child: Text( + registerBloc.isdcode, + style: GoogleFonts.dmSans( + color: AppColor.charcoalColor, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + )), + ], ), ), /* Image.asset( @@ -160,10 +193,10 @@ class RegisterForm extends StatelessWidget { width: 20, height: 20, ), */ - hintText: "+0 (000) 000 00 00", + hintText: hintText, title: localizations.translate(AppText.phoneNumber), type: "phone number", - textEditingController: loginBloc.phoneNumberTextField, + textEditingController: registerBloc.phoneNumberTextField, ), ], ), diff --git a/lib/features/register/presentation/widgets/register_step_count.dart b/lib/features/register/presentation/widgets/register_step_count.dart index 051c9bf..eea239e 100644 --- a/lib/features/register/presentation/widgets/register_step_count.dart +++ b/lib/features/register/presentation/widgets/register_step_count.dart @@ -4,11 +4,26 @@ 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'; +import '../../../../core/styles/app_text.dart'; +import '../../../../core/utils/language/localizations_delegate.dart'; + class RegisterStepCount extends StatelessWidget { const RegisterStepCount({super.key}); @override Widget build(BuildContext context) { + var localizations = AppLocalizations.of(context); + List title = [ + localizations.translate(AppText.step1), + localizations.translate(AppText.step2), + localizations.translate(AppText.step3), + ]; + + List description = [ + localizations.translate(AppText.enterYourCountryOfResidence), + localizations.translate(AppText.enterNameEmailPassword), + localizations.translate(AppText.enableBiometricAuthentication), + ]; return ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, diff --git a/lib/features/register/presentation/widgets/register_user_bottom_section.dart b/lib/features/register/presentation/widgets/register_user_bottom_section.dart index eb9e775..5015719 100644 --- a/lib/features/register/presentation/widgets/register_user_bottom_section.dart +++ b/lib/features/register/presentation/widgets/register_user_bottom_section.dart @@ -8,8 +8,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gap/gap.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:tanami_app/Globalconst.dart'; import 'package:tanami_app/core/styles/app_images.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/shared/components/checkbox_widget.dart'; import 'package:tanami_app/shared/components/loader.dart'; import 'package:tanami_app/shared/components/toast_message.dart'; @@ -31,8 +31,17 @@ class RegisterUserBottomSection extends StatelessWidget { @override Widget build(BuildContext context) { + String token = ""; + getToken() async { + final SecureStorageService secureStorageService = SecureStorageService(); + token = await secureStorageService.read("temp_token") ?? ""; + } + + getToken(); + var localizations = AppLocalizations.of(context); var registerbloc = context.read(); + return Column( children: [ const Gap(40), @@ -99,7 +108,9 @@ class RegisterUserBottomSection extends StatelessWidget { Loader.loader(context); } else if (state is RegisterUserSuccess) { goRouter.pop(); - permissionDialog(context,); + permissionDialog( + context, + ); } else if (state is RegisterUserFailure) { goRouter.pop(); errorToastMessage( @@ -130,29 +141,26 @@ class RegisterUserBottomSection extends StatelessWidget { ? AppColor.plainWhite : AppColor.inactiveBtnTxtColor, function: () { - - if(isButtonEnabled==true){ - if (registerbloc.passwordTextField.text != - registerbloc.repeatPasswordTextField.text) { - errorToastMessage( - context, - "Password and Repeat Password does not match.", - ); - }else{ - registerbloc.add( - RegisterUserSubmitted( - registerbloc.firstNameTextField.text, - registerbloc.passwordTextField.text, - registerbloc.lastNameTextField.text, - registerbloc.repeatPasswordTextField.text, - registerbloc.emailTextField.text, - Globalconst.token - ), - ); + if (isButtonEnabled == true) { + if (registerbloc.passwordTextField.text != + registerbloc.repeatPasswordTextField.text) { + errorToastMessage( + context, + "Password and Repeat Password does not match.", + ); + } else { + registerbloc.add( + RegisterUserSubmitted( + registerbloc.firstNameTextField.text, + registerbloc.passwordTextField.text, + registerbloc.lastNameTextField.text, + registerbloc.repeatPasswordTextField.text, + registerbloc.emailTextField.text, + token, + ), + ); + } } - - } - }, text: localizations.translate(AppText.nextText), clr: isButtonEnabled @@ -162,7 +170,7 @@ class RegisterUserBottomSection extends StatelessWidget { ); }, ), - const Gap(5), + const Gap(8), ButtonWidget().textBorderBtn( clr: AppColor.plainWhite, function: () { @@ -171,6 +179,7 @@ class RegisterUserBottomSection extends StatelessWidget { text: localizations.translate(AppText.backText), borderClr: AppColor.txtBorderColor, ), + const Gap(10), ], ); } diff --git a/lib/features/register/presentation/widgets/register_user_form.dart b/lib/features/register/presentation/widgets/register_user_form.dart index 98a4885..f9fc805 100644 --- a/lib/features/register/presentation/widgets/register_user_form.dart +++ b/lib/features/register/presentation/widgets/register_user_form.dart @@ -45,7 +45,7 @@ class RegisterUserForm extends StatelessWidget { FormLabelTextField( hintText: localizations.translate(AppText.enterEmail), title: localizations.translate(AppText.emailText), - type: AppText.emailText, + type: "email", textEditingController: registerUserBloc.emailTextField, ), const Gap(12), @@ -64,9 +64,11 @@ class RegisterUserForm extends StatelessWidget { child: FormLabelTextField( hintText: localizations.translate(AppText.repeatPasswordText), title: localizations.translate(AppText.repeatPasswordText), - type: AppText.password.toLowerCase(), + type: "repeat-password", textEditingController: registerUserBloc.repeatPasswordTextField, + originalPasswordController: + registerUserBloc.passwordTextField, ), ), ], diff --git a/lib/features/securePin/Repository/PinAPIServices.dart b/lib/features/securePin/Repository/PinAPIServices.dart index faae0e0..0767efb 100644 --- a/lib/features/securePin/Repository/PinAPIServices.dart +++ b/lib/features/securePin/Repository/PinAPIServices.dart @@ -9,8 +9,9 @@ class PinAPIServices { final response = await NetworkApiService().post(url, data); return response; } - Future Verifypin(Map data) async { - String url = ApiEndpoints.confirmpinapi; + + Future Verifypin(Map data) async { + String url = ApiEndpoints.verifypinapi; final response = await NetworkApiService().post(url, data); return response; } diff --git a/lib/features/securePin/presentation/bloc/pin_bloc.dart b/lib/features/securePin/presentation/bloc/pin_bloc.dart index b0c3562..367d884 100644 --- a/lib/features/securePin/presentation/bloc/pin_bloc.dart +++ b/lib/features/securePin/presentation/bloc/pin_bloc.dart @@ -1,8 +1,8 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:tanami_app/Api_Helper/base_manager.dart'; -import 'package:tanami_app/Globalconst.dart'; import 'package:tanami_app/core/styles/app_text.dart'; + import '../../../../core/utils/secure/secure_storage_service.dart'; import '../../Repository/PinAPIServices.dart'; @@ -23,43 +23,33 @@ class PinBloc extends Bloc { final newPin = state.pin + event.number; if (newPin.length <= 6) { - - - if (newPin.length == 6) { - if (event.fromscreen == "login") { - Map verifypindata = { - "token": Globalconst.token, - "masterPin": newPin - }; - ResponseData response = - await PinAPIServices().Verifypin(verifypindata); - if (response.status == ResponseStatus.SUCCESS) { - var data = response.data["user"]; - String accesstoken = data["accessToken"]; - String refreshtoken = data["refreshToken"]; - await secureStorageService.write('accesstoken', accesstoken); - await secureStorageService.write('refreshtoken', refreshtoken); - - emit(state.copyWith( - isVerified: false, - error: AppText.incorrectPinCode, - verifiedOnce: true, - )); - } else { - emit(state.copyWith( - pin: newPin, - pinComplete: newPin.length == 6, - error: 'has error', - isVerified: false, - verifiedOnce: false)); - } - } else { - emit(state.copyWith( + if ((event.fromscreen == "login" || + event.fromscreen == "LoginedInUser") && + newPin.length < 6) { + emit(state.copyWith( pin: newPin, pinComplete: newPin.length == 6, error: '', - verifiedOnce: false)); + verifiedOnce: false, + )); + } else { + if ((event.fromscreen != "login" && + event.fromscreen != "LoginedInUser")) { + emit(state.copyWith( + pin: newPin, + pinComplete: newPin.length == 6, + error: '', + verifiedOnce: false, + )); + } + } + + if (newPin.length == 6) { + if (event.fromscreen == "confirmpin") { add(VerifyPinPressed(newPin, event.fromscreen)); + } else if (event.fromscreen == "login" || + event.fromscreen == "LoginedInUser") { + add(VerifyLoginPinPressed(newPin, event.fromscreen)); } } } @@ -83,7 +73,7 @@ class PinBloc extends Bloc { on((event, emit) async { final storedPin = await secureStorageService.read('pin_code'); Map pindata = { - "token": Globalconst.token, + "token": await secureStorageService.read("temp_token"), "masterPin": event.pin }; if (storedPin == event.pin) { @@ -101,5 +91,30 @@ class PinBloc extends Bloc { )); } }); + + on((event, emit) async { + Map pindata = { + "token": await secureStorageService.read("temp_token"), + "masterPin": event.pin + }; + + ResponseData response = await PinAPIServices().Verifypin(pindata); + if (response.status == ResponseStatus.SUCCESS) { + emit(state.copyWith( + pinComplete: true, + pin: state.pin, + isVerified: true, + error: '', + verifiedOnce: false)); + } else { + emit(state.copyWith( + pinComplete: true, + pin: state.pin, + isVerified: false, + error: "Incorrect Pin Code", + verifiedOnce: true, + )); + } + }); } } diff --git a/lib/features/securePin/presentation/bloc/pin_event.dart b/lib/features/securePin/presentation/bloc/pin_event.dart index c3bb79e..1f904dd 100644 --- a/lib/features/securePin/presentation/bloc/pin_event.dart +++ b/lib/features/securePin/presentation/bloc/pin_event.dart @@ -11,10 +11,10 @@ class NumberPressed extends PinEvent { final String number; final String fromscreen; - const NumberPressed(this.number,this.fromscreen); + const NumberPressed(this.number, this.fromscreen); @override - List get props => [number]; + List get props => [number, fromscreen]; } class BackspacePressed extends PinEvent {} @@ -24,8 +24,17 @@ class SavePinPressed extends PinEvent {} class VerifyPinPressed extends PinEvent { final String pin; final String fromscreen; - const VerifyPinPressed(this.pin,this.fromscreen); + const VerifyPinPressed(this.pin, this.fromscreen); @override - List get props => [pin,fromscreen]; + List get props => [pin, fromscreen]; +} + +class VerifyLoginPinPressed extends PinEvent { + final String pin; + final String fromscreen; + const VerifyLoginPinPressed(this.pin, this.fromscreen); + + @override + List get props => [pin, fromscreen]; } diff --git a/lib/features/securePin/presentation/pages/pin_layout.dart b/lib/features/securePin/presentation/pages/pin_layout.dart index 91da8a2..a15561e 100644 --- a/lib/features/securePin/presentation/pages/pin_layout.dart +++ b/lib/features/securePin/presentation/pages/pin_layout.dart @@ -20,7 +20,9 @@ class PinLayout extends StatelessWidget { ), child: Column( children: [ - PinTopSection(fromScreen: fromScreen), + PinTopSection( + fromScreen: fromScreen, + ), PinKey( fromScreen: fromScreen, ), diff --git a/lib/features/securePin/presentation/pages/pin_screen.dart b/lib/features/securePin/presentation/pages/pin_screen.dart index d985269..580f196 100644 --- a/lib/features/securePin/presentation/pages/pin_screen.dart +++ b/lib/features/securePin/presentation/pages/pin_screen.dart @@ -20,7 +20,9 @@ class PinScreen extends StatelessWidget { final secureStorageService = SecureStorageService(); return WillPopScope( onWillPop: () async { - if (fromScreen == "LoginedInUser") { + if (fromScreen == "login" || + fromScreen == "LoginedInUser" || + fromScreen == "login-master-pending") { exitAppDialog(context); return false; } else { @@ -29,7 +31,9 @@ class PinScreen extends StatelessWidget { }, child: Scaffold( backgroundColor: AppColor.plainWhite, - appBar: fromScreen == "register" || fromScreen == "reset-pin" + appBar: fromScreen == "register" || + fromScreen == "reset-pin" || + fromScreen == "login-master-pending" ? AppBarWidget( height: 75, titleTxt: fromScreen == "reset-pin" diff --git a/lib/features/securePin/presentation/widgets/confirm_pin_keypad_section.dart b/lib/features/securePin/presentation/widgets/confirm_pin_keypad_section.dart index 698ee3e..a1d8936 100644 --- a/lib/features/securePin/presentation/widgets/confirm_pin_keypad_section.dart +++ b/lib/features/securePin/presentation/widgets/confirm_pin_keypad_section.dart @@ -4,6 +4,7 @@ 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/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; import '../bloc/pin_bloc.dart'; @@ -13,13 +14,14 @@ class ConfirmPinKey extends StatelessWidget { @override Widget build(BuildContext context) { + final SecureStorageService secureStorageService = SecureStorageService(); return Column( children: [ const Gap(20), BlocConsumer( - listener: (context, state) { + listener: (context, state) async { if (state.pinComplete && state.isVerified) { - // successToastMessage(context, "Pin verified successfully"); + await secureStorageService.write('isLoginedIn', "true"); goRouter.goNamed(RouteName.mainScreen); } }, @@ -91,7 +93,9 @@ class ConfirmPinKey extends StatelessWidget { final number = index == 10 ? '0' : '${index + 1}'; return GestureDetector( onTap: () { - context.read().add(NumberPressed(number,"confirmpin")); + context + .read() + .add(NumberPressed(number, "confirmpin")); }, child: Container( margin: const EdgeInsets.all(12), diff --git a/lib/features/securePin/presentation/widgets/pin_keypad_section.dart b/lib/features/securePin/presentation/widgets/pin_keypad_section.dart index daff1fe..eaa3524 100644 --- a/lib/features/securePin/presentation/widgets/pin_keypad_section.dart +++ b/lib/features/securePin/presentation/widgets/pin_keypad_section.dart @@ -1,17 +1,17 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/core/styles/app_text.dart'; +import 'package:tanami_app/features/countrySelection/bloc/choose_country_bloc.dart'; import 'package:tanami_app/features/securePin/presentation/widgets/forgot_pin_dialog.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; -import 'package:tanami_app/shared/components/toast_message.dart'; +import '../../../../core/routes/route_name.dart'; +import '../../../../core/routes/routes.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; +import '../../../../shared/components/toast_message.dart'; +import '../../../login/presentation/bloc/login_bloc.dart'; import '../bloc/pin_bloc.dart'; class PinKey extends StatelessWidget { @@ -23,8 +23,9 @@ class PinKey extends StatelessWidget { @override Widget build(BuildContext context) { - print(fromScreen); var localizations = AppLocalizations.of(context); + final loginBloc = context.read(); + final radioBloc = context.read(); return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -35,14 +36,12 @@ class PinKey extends StatelessWidget { state.error.isEmpty && !state.verifiedOnce) { if (fromScreen == "login" || fromScreen == "LoginedInUser") { - print("login true"); + loginBloc.resetFields(); + radioBloc.resetSelection(); successToastMessage(context, localizations.translate(AppText.pinVerifiedSucess)); goRouter.pushNamed(RouteName.mainScreen); } else if (fromScreen == "reset-pin") { - print("reset true"); - - log("Running this"); successToastMessage( context, localizations.translate(AppText.pinUpdatedSucess)); goRouter.pop(); diff --git a/lib/features/securePin/presentation/widgets/pin_top_section.dart b/lib/features/securePin/presentation/widgets/pin_top_section.dart index f122423..8d8791a 100644 --- a/lib/features/securePin/presentation/widgets/pin_top_section.dart +++ b/lib/features/securePin/presentation/widgets/pin_top_section.dart @@ -5,6 +5,7 @@ import 'package:tanami_app/core/styles/app_color.dart'; import 'package:tanami_app/core/styles/app_text.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; +import '../../../../Globalconst.dart'; import '../../../../core/styles/app_images.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; @@ -38,8 +39,9 @@ class PinTopSection extends StatelessWidget { TextWidget().text14W500( localizations.translate(AppText.welcomeBackText), clr: AppColor.smokeGrayColor), - TextWidget() - .text14W700(", Pooja", clr: AppColor.plainBlack), + TextWidget().text14W700( + ", ${Globalconst.firstName.trim()}", + clr: AppColor.plainBlack), ], ), TextWidget().text14W500( diff --git a/lib/features/splash/presentation/pages/splash_screen.dart b/lib/features/splash/presentation/pages/splash_screen.dart index 21bf83c..22b7f1e 100644 --- a/lib/features/splash/presentation/pages/splash_screen.dart +++ b/lib/features/splash/presentation/pages/splash_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/Globalconst.dart'; import 'package:tanami_app/core/routes/route_name.dart'; import 'package:tanami_app/core/routes/routes.dart'; @@ -48,6 +49,9 @@ class SplashScreen extends StatelessWidget { await secureStorageService.read('biometric') == 'on') { goRouter.goNamed(RouteName.biometricScreen); } else { + Globalconst.firstName = + await secureStorageService.read("first_name") ?? ""; + goRouter.goNamed(RouteName.pinScreen, pathParameters: { "fromScreen": "LoginedInUser", }); diff --git a/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart b/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart index ec6b98c..0925bb1 100644 --- a/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart +++ b/lib/features/welcome/presentation/widgets/build_onboarding_page_widget.dart @@ -1,14 +1,13 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/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:gap/gap.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; +import '../../../../shared/components/language_change_bottom_sheet.dart'; import 'login_signup_button.dart'; import 'welcome_indicator.dart'; @@ -23,7 +22,21 @@ Widget buildOnboardingPage( }) { return Column( children: [ - const Gap(80), + index == 0 ? const Gap(40) : const Gap(0), + index == 0 + ? Align( + alignment: Alignment.centerRight, + child: IconButton( + onPressed: () { + showLanguageBottomSheet(context); + }, + icon: const Icon( + Icons.language_outlined, + size: 30, + )), + ) + : const SizedBox(), + index == 0 ? const Gap(20) : const Gap(80), SvgPicture.asset(AppImages.welcomeLogo), Image.asset( imageAsset, diff --git a/lib/main.dart b/lib/main.dart index 5e73ba0..76f4805 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:local_auth/local_auth.dart'; +import 'package:tanami_app/features/login/presentation/bloc/login_bloc.dart'; import 'core/routes/routes.dart'; import 'core/utils/connectivity/network_connectivity.dart'; @@ -13,7 +14,9 @@ import 'features/biometric/presentation/bloc/biometric_bloc.dart'; import 'features/biometric/presentation/bloc/biometric_event.dart'; import 'features/countrySelection/bloc/GetCountry/get_country_bloc.dart'; import 'features/countrySelection/bloc/choose_country_bloc.dart'; +import 'features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart'; import 'features/otpVerification/bloc/otp_bloc.dart'; +import 'features/register/presentation/bloc/register_bloc.dart'; import 'shared/components/bloc/bottom_nav_bar/bottom_navigation_bloc.dart'; import 'shared/components/bloc/language/lng_bloc.dart'; import 'shared/components/bloc/language/lng_event.dart'; @@ -96,6 +99,13 @@ class _MyAppState extends State with WidgetsBindingObserver { BlocProvider( create: (_) => BottomNavigationBloc(), ), + BlocProvider( + create: (_) => + RegisterBloc(secureStorageService: secureStorageService), + ), + BlocProvider( + create: (_) => LoginBloc(secureStorageService: secureStorageService), + ), BlocProvider( create: (_) => BiometricBloc(LocalAuthentication())..add(CheckBiometricEvent()), @@ -109,6 +119,10 @@ class _MyAppState extends State with WidgetsBindingObserver { BlocProvider( create: (_) => OTPBloc(), ), + BlocProvider( + create: (_) => RestorePasswordPhoneVerificationBloc( + secureStorageService: secureStorageService), + ), ], child: ScreenUtilInit( builder: (BuildContext context, Widget? child) => diff --git a/lib/shared/api/api_endpoints.dart b/lib/shared/api/api_endpoints.dart index 966612e..b1a9d9c 100644 --- a/lib/shared/api/api_endpoints.dart +++ b/lib/shared/api/api_endpoints.dart @@ -3,13 +3,28 @@ class ApiEndpoints { static const baseurl = "https://tanami.betadelivery.com/api/development/v1/"; //App Base url + //Country static const getcountryurl = "${baseurl}country/getAllCountry"; + + //Register static const requestotpapi = "${baseurl}auth/public/register"; + static const registerrequestapi = "${baseurl}auth/public/email-register"; + + //OTP static const requestresendotp = "${baseurl}auth/public/resend-otp"; static const verifyotp = "${baseurl}auth/public/verify-otp"; - static const registerrequestapi = "${baseurl}auth/public/email-register"; + + //Biometric static const biometricUpdateapi = "${baseurl}auth/public/biometric-update"; - static const confirmpinapi="${baseurl}auth/public/masterPin"; - static const loginapi="${baseurl}auth/public/login"; - static const verifypinapi="${baseurl}auth/public/verify-materPin"; + + //PIN + static const confirmpinapi = "${baseurl}auth/public/masterPin"; + static const verifypinapi = "${baseurl}auth/public/verify-materPin"; + + //Login + static const loginapi = "${baseurl}auth/public/login"; + + //Forgot Password + static const forgotPasswordApi = "${baseurl}auth/public/forgot-password"; + static const resetPasswordApi = "${baseurl}auth/public/reset-password"; } diff --git a/lib/shared/api/commonAPI.dart b/lib/shared/api/commonAPI.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/shared/api/network_api_services.dart b/lib/shared/api/network_api_services.dart index 7382fc3..f12b3b8 100644 --- a/lib/shared/api/network_api_services.dart +++ b/lib/shared/api/network_api_services.dart @@ -6,17 +6,22 @@ import 'package:flutter/foundation.dart'; import '../../Api_Helper/base_manager.dart'; class NetworkApiService { - final Dio _dio = Dio(); + final Dio _dio = Dio(BaseOptions( + validateStatus: (status) { + return status != null && + status < 500; // Allow any status code less than 500 + }, + )); // Common function for GET requests Future get(String url, {Map? queryParameters}) async { - if (kDebugMode) { + if (kDebugMode) { print("api url is >>> $url"); } Response response; try { - response = await _dio.get(url); + response = await _dio.get(url); if (response.statusCode == 201 || response.statusCode == 200) { return ResponseData("success", ResponseStatus.SUCCESS, data: response.data); @@ -29,10 +34,11 @@ class NetworkApiService { data: response.data, response.statusMessage!, ResponseStatus.FAILED); - }} + } + } } catch (e) { return ResponseData( - "Something went wrong", ResponseStatus.FAILED); + "Something went wrong", ResponseStatus.FAILED); } } @@ -43,24 +49,48 @@ class NetworkApiService { print("api url is >>> $url"); } try { - var response= await _dio.post(url, data: data); - if (response.statusCode == 201 || response.statusCode == 200) { - return ResponseData("success", ResponseStatus.SUCCESS, + var response = await _dio.post( + url, + data: data, + ); + if (response.statusCode == 201 || response.statusCode == 200) { + return ResponseData("success", ResponseStatus.SUCCESS, data: response.data); - }else { + } else if (response.statusCode == 400) { + if (response.data['message'] == "Master Pin is not created") { + return ResponseData( + response.data['message'], ResponseStatus.PRIVATE, + data: response.data); + } else if (response.data['error']['message'] == + "MASTER PIN NOT MATCH") { + return ResponseData( + response.data['error']['message'], ResponseStatus.PRIVATE, + data: response.data); + } else if (response.data['error']['message'] == + "Account already exists. Please Login instead.") { + return ResponseData( + response.data['error']['message'], ResponseStatus.PRIVATE, + data: response.data); + } else { + return ResponseData( + response.data['error']['message'], ResponseStatus.PRIVATE, + data: response.data); + } + } else { try { return ResponseData( - response.data['message'].toString(), ResponseStatus.FAILED); + response.data['error']['message'].toString(), + ResponseStatus.FAILED); } catch (_) { return ResponseData( data: response.data, response.statusMessage!, ResponseStatus.FAILED); - }} + } + } } catch (e) { return ResponseData( - "Oops something went wrong", - ResponseStatus.FAILED); + "Oops something went wrong", ResponseStatus.FAILED); } } diff --git a/lib/shared/components/device_locked_dialog.dart b/lib/shared/components/device_locked_dialog.dart index f229164..63de4b5 100644 --- a/lib/shared/components/device_locked_dialog.dart +++ b/lib/shared/components/device_locked_dialog.dart @@ -3,13 +3,15 @@ import 'package:flutter/services.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/routes/routes.dart'; import 'package:tanami_app/core/styles/app_color.dart'; import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/features/biometric/presentation/bloc/biometric_bloc.dart'; +import '../../Globalconst.dart'; +import '../../core/routes/route_name.dart'; +import '../../core/routes/routes.dart'; import '../../core/utils/language/localizations_delegate.dart'; -import '../../features/biometric/presentation/bloc/biometric_event.dart'; import '../../features/biometric/presentation/bloc/biometric_state.dart'; import 'text_widget.dart'; @@ -17,6 +19,7 @@ deviceLockedDialog( context, ) { var localizations = AppLocalizations.of(context); + final SecureStorageService secureStorageService = SecureStorageService(); return showDialog( barrierDismissible: false, context: context, @@ -106,11 +109,19 @@ deviceLockedDialog( ), Gap(15.w), GestureDetector( - onTap: () { - goRouter.pop(); - context - .read() - .add(AuthenticateBiometricEvent()); + onTap: () async { + // goRouter.pop(); + // context + // .read() + // .add(AuthenticateBiometricEvent()); + Globalconst.firstName = + await secureStorageService.read("first_name") ?? + ""; + + goRouter + .goNamed(RouteName.pinScreen, pathParameters: { + "fromScreen": "LoginedInUser", + }); }, child: Container( height: 48.h, diff --git a/lib/shared/components/form_label_textfield.dart b/lib/shared/components/form_label_textfield.dart index 8009c74..3f978ea 100644 --- a/lib/shared/components/form_label_textfield.dart +++ b/lib/shared/components/form_label_textfield.dart @@ -1,14 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.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/features/login/presentation/bloc/login_bloc.dart'; import 'package:tanami_app/shared/components/password_text_form_field.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; import '../../core/routes/route_name.dart'; import '../../core/routes/routes.dart'; import '../../core/utils/language/localizations_delegate.dart'; +import '../../features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart'; +import '../../features/register/presentation/bloc/register_bloc.dart'; import 'text_from_field_widget.dart'; class FormLabelTextField extends StatelessWidget { @@ -20,6 +24,7 @@ class FormLabelTextField extends StatelessWidget { required this.hintText, this.prefixWidget, this.onChangeFun, + this.originalPasswordController, }); final String title; final String type; @@ -27,9 +32,23 @@ class FormLabelTextField extends StatelessWidget { final TextEditingController textEditingController; final Widget? prefixWidget; final Function(String)? onChangeFun; + final TextEditingController? originalPasswordController; @override Widget build(BuildContext context) { + // Map of country codes to phone number lengths + final Map countryPhoneLengths = { + "+973": 8, // Bahrain + "+965": 8, // Kuwait + "+968": 8, // Oman + "+974": 8, // Qatar + "+966": 9, // Saudi Arabia + "+971": 9, // United Arab Emirates + }; + var registerBloc = context.read(); + var loginBloc = context.read(); + var restorePasswordBloc = + context.read(); var localizations = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -40,10 +59,11 @@ class FormLabelTextField extends StatelessWidget { txtAlign: type == "description" ? TextAlign.start : TextAlign.center, ), const Gap(10), - type == "password" + (type == "password" || type == "repeat-password") ? PasswordField( controller: textEditingController, hintText: hintText, + originalPasswordController: originalPasswordController, ) : textFormField( onInput: onChangeFun, @@ -52,6 +72,47 @@ class FormLabelTextField extends StatelessWidget { if (value != null && value.isEmpty) { return localizations.translate(AppText.enterPhoneNo); } + if (registerBloc.isdcode.isNotEmpty) { + // Validate phone number length based on selected country + if (countryPhoneLengths.containsKey( + registerBloc.isdcode, + )) { + final expectedLength = + countryPhoneLengths[registerBloc.isdcode]; + if (value.length != expectedLength) { + return localizations.translate( + "Invalid Phone Number", + ); + } + } + } else if (loginBloc.isdcode.isNotEmpty) { + // Validate phone number length based on selected country + if (countryPhoneLengths.containsKey( + loginBloc.isdcode, + )) { + final expectedLength = + countryPhoneLengths[loginBloc.isdcode]; + if (value.length != expectedLength) { + return localizations.translate( + "Invalid Phone Number", + ); + } + } + } else if (restorePasswordBloc.isdcode.isNotEmpty) { + // Validate phone number length based on selected country + if (countryPhoneLengths.containsKey( + restorePasswordBloc.isdcode, + )) { + final expectedLength = + countryPhoneLengths[restorePasswordBloc.isdcode]; + if (value.length != expectedLength) { + return localizations.translate( + "Invalid Phone Number", + ); + } + } + } + return null; } else if (type == "country selection") { if (textEditingController.text.isEmpty) { @@ -64,13 +125,38 @@ class FormLabelTextField extends StatelessWidget { .translate(AppText.pleaseEnteraDescription); } return null; + } else if (type == "email") { + if (value == null || value.isEmpty) { + return 'Please enter an email address.'; + } + // Email validation + if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { + return 'Please enter a valid email address.'; + } + return null; } else { return null; } }, - inputFormatters: [ - LengthLimitingTextInputFormatter(350), - ], + inputFormatters: (type == "phone number") + ? registerBloc.isdcode.isNotEmpty + ? [ + LengthLimitingTextInputFormatter( + countryPhoneLengths[registerBloc.isdcode]), + ] + : restorePasswordBloc.isdcode.isNotEmpty + ? [ + LengthLimitingTextInputFormatter( + countryPhoneLengths[ + restorePasswordBloc.isdcode]), + ] + : [ + LengthLimitingTextInputFormatter( + countryPhoneLengths[loginBloc.isdcode]), + ] + : [ + LengthLimitingTextInputFormatter(350), + ], maxlines: type == "description" ? 6 : 1, texttype: type == "phone number" ? TextInputType.phone diff --git a/lib/shared/components/language_change_bottom_sheet.dart b/lib/shared/components/language_change_bottom_sheet.dart new file mode 100644 index 0000000..c57081f --- /dev/null +++ b/lib/shared/components/language_change_bottom_sheet.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/routes/routes.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; + +void showLanguageBottomSheet(BuildContext context) { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 1.sw, + height: 168.7.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.black87), + child: Column( + children: [ + const Gap(10), + Center( + child: TextWidget() + .text17W700("Language", clr: Colors.white60), + ), + const Gap(10), + const Divider( + color: AppColor.plainWhite, + ), + const Gap(10), + InkWell( + onTap: () { + goRouter.pop(); + }, + child: Center( + child: + TextWidget().text15W700("English", clr: Colors.white), + ), + ), + const Gap(10), + const Divider( + color: AppColor.plainWhite, + ), + const Gap(10), + InkWell( + onTap: () { + goRouter.pop(); + }, + child: Center( + child: TextWidget().text15W700("عربي", clr: Colors.white), + ), + ), + const Gap(20), + ], + ), + ), + const Gap(18), + GestureDetector( + onTap: () { + goRouter.pop(); + }, + child: Container( + width: 1.sw, + height: 56.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.black87), + child: Center( + child: + TextWidget().text14W500("Dismiss", clr: Colors.white60), + ), + ), + ), + const Gap(18), + ], + ), + ); + }, + ); +} diff --git a/lib/shared/components/log_out_dialog.dart b/lib/shared/components/log_out_dialog.dart index 2c35a7b..1ef1bc8 100644 --- a/lib/shared/components/log_out_dialog.dart +++ b/lib/shared/components/log_out_dialog.dart @@ -89,6 +89,9 @@ buildprofilelogoutdialog(context) { onTap: () async { context.read().add(TabChanged(2)); await secureStorageService.write('isLoginedIn', "false"); + await secureStorageService.write('accesstoken', ''); + await secureStorageService.write('refreshtoken', ''); + goRouter.goNamed(RouteName.loginScreen, pathParameters: { "fromScreen": "registerStep", }); diff --git a/lib/shared/components/password_text_form_field.dart b/lib/shared/components/password_text_form_field.dart index 35b5f32..029f79b 100644 --- a/lib/shared/components/password_text_form_field.dart +++ b/lib/shared/components/password_text_form_field.dart @@ -1,5 +1,6 @@ import 'package:control_style/control_style.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -15,8 +16,13 @@ import 'bloc/password_field/password_visibility_state.dart'; class PasswordField extends StatelessWidget { final TextEditingController controller; final String hintText; - const PasswordField( - {super.key, required this.controller, required this.hintText}); + final TextEditingController? originalPasswordController; + const PasswordField({ + super.key, + required this.controller, + required this.hintText, + this.originalPasswordController, + }); @override Widget build(BuildContext context) { @@ -25,9 +31,31 @@ class PasswordField extends StatelessWidget { builder: (context, state) { return TextFormField( validator: (value) { - if (value != null && value.isEmpty) { + if (value == null || value.isEmpty) { return localizations.translate(AppText.enterPassword); } + + // Password validation rules + if (value.length < 8 || value.length > 20) { + return localizations.translate(AppText.passwordLength); + } + if (!RegExp(r'[a-z]').hasMatch(value)) { + return localizations.translate(AppText.passwordLowerCase); + } + if (!RegExp(r'[A-Z]').hasMatch(value)) { + return localizations.translate(AppText.passwordUpperCase); + } + if (!RegExp(r'\d').hasMatch(value)) { + return localizations.translate(AppText.passwordDigit); + } + if (!RegExp(r'[!@#$%&*()\-+=^]').hasMatch(value)) { + return localizations.translate(AppText.passwordSpecialCharacter); + } + if (originalPasswordController != null && + value != originalPasswordController!.text) { + return localizations.translate("Passwords do not match"); + } + return null; }, controller: controller, @@ -38,7 +66,12 @@ class PasswordField extends StatelessWidget { fontSize: 14, fontWeight: FontWeight.w500, ), + inputFormatters: [ + LengthLimitingTextInputFormatter(20), + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ], decoration: InputDecoration( + errorMaxLines: 2, errorStyle: GoogleFonts.dmSans( color: AppColor.txtErrorColor, fontSize: 14, diff --git a/lib/shared/components/permission_dialog.dart b/lib/shared/components/permission_dialog.dart index 2b76cc6..f71e77d 100644 --- a/lib/shared/components/permission_dialog.dart +++ b/lib/shared/components/permission_dialog.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gap/gap.dart'; import 'package:tanami_app/Api_Helper/base_manager.dart'; -import 'package:tanami_app/Globalconst.dart'; import 'package:tanami_app/core/styles/app_color.dart'; import '../../core/routes/route_name.dart'; @@ -85,16 +84,7 @@ permissionDialog( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ GestureDetector( - onTap: () async { - Map biometricdata = { - "token": Globalconst.token, - "is_2FA_on": false - }; - - await RegisterAPIService() - .BiometricUpdate(biometricdata); - - successToastMessage(context, "successful !"); + onTap: () { goRouter.pop(); goRouter.goNamed(RouteName.pinScreen, pathParameters: { @@ -120,7 +110,8 @@ permissionDialog( GestureDetector( onTap: () async { Map biometricdata = { - "token": Globalconst.token, + "token": + await secureStorageService.read("temp_token"), "is_2FA_on": true };