Merge pull request #107 from WDI-Ideas/jayeshjain25

Jayeshjain25
This commit is contained in:
Jayesh jain
2024-07-17 19:14:53 +05:30
committed by GitHub
54 changed files with 1022 additions and 356 deletions

View File

@@ -3,5 +3,6 @@ class Globalconst {
static String name = "";
static String phonenumber = "";
static String isdcode = "";
static String createdpin="";
static String createdpin = "";
static String firstName = "";
}

View File

@@ -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<GetCountryBlock>().add(GetCountry());
// context.read<GetCountryBlock>().add(GetCountry());
return const SplashScreen();
},
routes: [

View File

@@ -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";

View File

@@ -1,17 +1,4 @@
import 'package:tanami_app/core/styles/app_images.dart';
import 'package:tanami_app/core/styles/app_text.dart';
List<String> title = [
AppText.step1,
AppText.step2,
AppText.step3,
];
List<String> description = [
AppText.enterYourCountryOfResidence,
AppText.enterNameEmailPassword,
AppText.enableBiometricAuthentication,
];
List<String> stepImage = [
AppImages.step1Image,

View File

@@ -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<String, int> countryPhoneLengths = {
"+973": 8, // Bahrain
"+965": 8, // Kuwait
"+968": 8, // Oman
"+974": 8, // Qatar
"+966": 9, // Saudi Arabia
"+971": 9, // United Arab Emirates
};
}

View File

@@ -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<BiometricBloc, BiometricState>(
listener: (context, state) {
listener: (context, state) async {
if (state is BiometricChecked && state.canAuthenticate) {
context.read<BiometricBloc>().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) {

View File

@@ -19,7 +19,6 @@ class GetCountryBlock extends Bloc<GetCountryEvent, GetCountryState> {
GetCountryModel countryModel = GetCountryModel.fromJson(response.data);
emit(CountryLoaded(countryModel));
}
print("//");
} catch (e) {
emit(CountryError("Oops Something went wrong"));
}

View File

@@ -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<GetCountryBlock>().add(GetCountry());
return const Scaffold(
backgroundColor: AppColor.plainWhite,
resizeToAvoidBottomInset: true,

View File

@@ -38,18 +38,20 @@ class CountrySelectionList extends StatelessWidget {
context.read<GetCountryBlock>().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,

View File

@@ -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<ResponseData> forgotPasswordApi(Map<String, dynamic> data) async {
String url = ApiEndpoints.forgotPasswordApi;
final response = await NetworkApiService().post(url, data);
return response;
}
Future<ResponseData> resetPasswordApi(Map<String, dynamic> data) async {
String url = ApiEndpoints.resetPasswordApi;
final response = await NetworkApiService().post(url, data);
return response;
}
}

View File

@@ -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<String, dynamic> 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<bool> _mockLoginApi(
String phoneNumber,
) async {
return true;
}
@override
Future<void> close() {
passwordTextField.dispose();

View File

@@ -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<FormState> formKey = GlobalKey<FormState>();
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<RestorePasswordPhoneVerificationFormChanged>(_onLoginFormChanged);
on<RestorePasswordPhoneVerificationSubmitted>((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<String, dynamic> 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<bool> _mockLoginApi(
String phoneNumber,
) async {
return phoneNumber == "1234567891";
}
@override
Future<void> close() {
phoneNumberTextField.dispose();

View File

@@ -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

View File

@@ -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<RadioBloc>();
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(),
),
),
);
}

View File

@@ -49,6 +49,8 @@ class RestorePasswordForm extends StatelessWidget {
type: AppText.password.toLowerCase(),
textEditingController:
restorePasswordBloc.repeatPasswordTextField,
originalPasswordController:
restorePasswordBloc.passwordTextField,
),
),
],

View File

@@ -26,6 +26,8 @@ class RestorePasswordPhoneVerificationBottomSection extends StatelessWidget {
Widget build(BuildContext context) {
var localizations = AppLocalizations.of(context);
final radioBloc = context.read<RadioBloc>();
final restorePasswordBloc =
context.read<RestorePasswordPhoneVerificationBloc>();
return Column(
children: [
BlocConsumer<RestorePasswordPhoneVerificationBloc,
@@ -76,7 +78,8 @@ class RestorePasswordPhoneVerificationBottomSection extends StatelessWidget {
RestorePasswordPhoneVerificationBloc>()
.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(

View File

@@ -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<GetCountryBlock>();
var localizations = AppLocalizations.of(context);
final restorePasswordBloc =
context.read<RestorePasswordPhoneVerificationBloc>();
// Reset fields when the screen is built
restorePasswordBloc.resetFields();
String hintText = '00 000 000';
return BlocConsumer<RadioBloc, RadioState>(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:

View File

@@ -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<LoginEvent, LoginState> {
final SecureStorageService secureStorageService;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController countrySelectionTextField =
TextEditingController();
final TextEditingController phoneNumberTextField = TextEditingController();
final TextEditingController passwordTextField = TextEditingController();
String countryId = "";
String isdcode = "";
GlobalKey<FormState> getFormKey() {
return formKey;
}
LoginBloc() : super(LoginInitial()) {
LoginBloc({required this.secureStorageService}) : super(LoginInitial()) {
phoneNumberTextField.addListener(_onFormFieldChanged);
passwordTextField.addListener(_onFormFieldChanged);
countrySelectionTextField.addListener(_onFormFieldChanged);
on<LoginFormChanged>(_onLoginFormChanged);
on<LoginSubmitted>((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<String, dynamic> logindata = {
"countryId": event.countryId,
"phoneNumber": event.phoneNumber,
@@ -39,20 +42,23 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
};
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<LoginEvent, LoginState> {
countrySelectionTextField.clear();
}
// Mock API function, replace with actual API call
Future<bool> _mockLoginApi(
String phoneNumber,
String password,
String countryResidence,
) async {
return password == "123456";
}
@override
Future<void> close() {
phoneNumberTextField.dispose();

View File

@@ -13,6 +13,8 @@ class LoginLoading extends LoginState {}
class LoginSuccess extends LoginState {}
class LoginMasterPinPending extends LoginState {}
class LoginFailure extends LoginState {
final String error;

View File

@@ -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<RadioBloc>();
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(

View File

@@ -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()

View File

@@ -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<RadioBloc, RadioState>(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,

View File

@@ -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<OtpEvent, OtpState> {
final SecureStorageService secureStorageService;
final TextEditingController otpController = TextEditingController();
OtpBloc() : super(OtpInitial()) {
OtpBloc({required this.secureStorageService}) : super(OtpInitial()) {
on<StartListeningForOtp>(_onStartListeningForOtp);
on<OtpCodeChanged>(_onOtpCodeChanged);
on<OtpSubmit>(_onOtpSubmit);
@@ -33,7 +34,7 @@ class OtpBloc extends Bloc<OtpEvent, OtpState> {
emit(OtpSubmitting());
try {
Map<String, dynamic> otpdata = {
"token": Globalconst.token,
"token": await secureStorageService.read("temp_token"),
"otp": otpController.text
};
ResponseData response = await OTPAPI().verifyOTP(otpdata);

View File

@@ -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),

View File

@@ -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<RegisterBloc>();
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<TimerBloc, TimerState>(
builder: (context, state) {
return Padding(
@@ -65,7 +73,7 @@ class ResendOtpSection extends StatelessWidget {
return GestureDetector(
onTap: () {
loginBloc.add(
Resendotp(Globalconst.token),
Resendotp(token),
);
print("///");

View File

@@ -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<RegisterEvent, RegisterState> {
final SecureStorageService secureStorageService;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController countrySelectionTextField =
TextEditingController();
String isdcode="";
String countryId="";
String isdcode = "";
String countryId = "";
final TextEditingController phoneNumberTextField = TextEditingController();
GlobalKey<FormState> getFormKey() {
return formKey;
}
RegisterBloc() : super(RegisterInitial()) {
RegisterBloc({required this.secureStorageService})
: super(RegisterInitial()) {
phoneNumberTextField.addListener(_onFormFieldChanged);
countrySelectionTextField.addListener(_onFormFieldChanged);
on<RegisterFormChanged>(_onLoginFormChanged);
@@ -29,21 +31,18 @@ class RegisterBloc extends Bloc<RegisterEvent, RegisterState> {
}
emit(RegisterLoading());
try {
Map<String,dynamic> requestdata={
"countryId":event.id,
"phoneNumber":event.phoneNumber
Map<String, dynamic> 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<RegisterEvent, RegisterState> {
emit(RegisterLoading());
try {
Map<String, dynamic> 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<RegisterEvent, RegisterState> {
emit(RegisterFieldsState(areFieldsFilled));
}
// Mock API function, replace with actual API call
Future<bool> _mockLoginApi(
String phoneNumber,
String countryResidence,
) async {
return true;
}
@override
Future<void> close() {
phoneNumberTextField.dispose();

View File

@@ -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<RegisterUserEvent, RegisterUserState> {
final SecureStorageService secureStorageService;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController firstNameTextField = TextEditingController();
final TextEditingController lastNameTextField = TextEditingController();
@@ -19,7 +20,8 @@ class RegisterUserBloc extends Bloc<RegisterUserEvent, RegisterUserState> {
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<RegisterUserEvent, RegisterUserState> {
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<RegisterUserEvent, RegisterUserState> {
emit(RegisterUserFieldsState(areFieldsFilled));
}
// Mock API function, replace with actual API call
Future<bool> _mockLoginApi(
String firstName,
String password,
String confirmPassword,
String email,
String lastName,
) async {
return true;
}
@override
Future<void> close() {
firstNameTextField.dispose();

View File

@@ -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<RadioBloc>();
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(),

View File

@@ -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

View File

@@ -37,7 +37,7 @@ class RegisterBottomSection extends StatelessWidget {
height: 12,
),
const Gap(36),
BlocConsumer<RegisterBloc, RegisterState>(
BlocConsumer<RegisterBloc, RegisterState>(
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<RegisterBloc>().add(
RegisterSubmitted(
loginBloc.phoneNumberTextField.text,
loginBloc.countrySelectionTextField.text,
loginBloc.isdcode,
loginBloc.countryId
),
loginBloc.phoneNumberTextField.text,
loginBloc.countrySelectionTextField.text,
loginBloc.isdcode,
loginBloc.countryId),
)
: null;
},

View File

@@ -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<RegisterBloc>();
final registerBloc = context.read<RegisterBloc>();
final countrydata = context.read<GetCountryBlock>();
int selectedCountry = -1;
String flag = "";
String hintText = '00 000 000';
return BlocConsumer<RadioBloc, RadioState>(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,
),
],
),

View File

@@ -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<String> title = [
localizations.translate(AppText.step1),
localizations.translate(AppText.step2),
localizations.translate(AppText.step3),
];
List<String> description = [
localizations.translate(AppText.enterYourCountryOfResidence),
localizations.translate(AppText.enterNameEmailPassword),
localizations.translate(AppText.enableBiometricAuthentication),
];
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,

View File

@@ -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<RegisterUserBloc>();
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),
],
);
}

View File

@@ -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,
),
),
],

View File

@@ -9,8 +9,9 @@ class PinAPIServices {
final response = await NetworkApiService().post(url, data);
return response;
}
Future<ResponseData> Verifypin(Map<String, dynamic> data) async {
String url = ApiEndpoints.confirmpinapi;
Future<ResponseData> Verifypin(Map<String, dynamic> data) async {
String url = ApiEndpoints.verifypinapi;
final response = await NetworkApiService().post(url, data);
return response;
}

View File

@@ -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<PinEvent, PinState> {
final newPin = state.pin + event.number;
if (newPin.length <= 6) {
if (newPin.length == 6) {
if (event.fromscreen == "login") {
Map<String, dynamic> 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<PinEvent, PinState> {
on<VerifyPinPressed>((event, emit) async {
final storedPin = await secureStorageService.read('pin_code');
Map<String, dynamic> 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<PinEvent, PinState> {
));
}
});
on<VerifyLoginPinPressed>((event, emit) async {
Map<String, dynamic> 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,
));
}
});
}
}

View File

@@ -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<Object> get props => [number];
List<Object> 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<Object> get props => [pin,fromscreen];
List<Object> get props => [pin, fromscreen];
}
class VerifyLoginPinPressed extends PinEvent {
final String pin;
final String fromscreen;
const VerifyLoginPinPressed(this.pin, this.fromscreen);
@override
List<Object> get props => [pin, fromscreen];
}

View File

@@ -20,7 +20,9 @@ class PinLayout extends StatelessWidget {
),
child: Column(
children: [
PinTopSection(fromScreen: fromScreen),
PinTopSection(
fromScreen: fromScreen,
),
PinKey(
fromScreen: fromScreen,
),

View File

@@ -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"

View File

@@ -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<PinBloc, PinState>(
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<PinBloc>().add(NumberPressed(number,"confirmpin"));
context
.read<PinBloc>()
.add(NumberPressed(number, "confirmpin"));
},
child: Container(
margin: const EdgeInsets.all(12),

View File

@@ -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<LoginBloc>();
final radioBloc = context.read<RadioBloc>();
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();

View File

@@ -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(

View File

@@ -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",
});

View File

@@ -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,

View File

@@ -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<MyApp> 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<MyApp> with WidgetsBindingObserver {
BlocProvider(
create: (_) => OTPBloc(),
),
BlocProvider(
create: (_) => RestorePasswordPhoneVerificationBloc(
secureStorageService: secureStorageService),
),
],
child: ScreenUtilInit(
builder: (BuildContext context, Widget? child) =>

View File

@@ -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";
}

View File

@@ -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<ResponseData> get(String url,
{Map<String, dynamic>? 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<dynamic>("success", ResponseStatus.SUCCESS,
data: response.data);
@@ -29,10 +34,11 @@ class NetworkApiService {
data: response.data,
response.statusMessage!,
ResponseStatus.FAILED);
}}
}
}
} catch (e) {
return ResponseData<dynamic>(
"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<dynamic>("success", ResponseStatus.SUCCESS,
var response = await _dio.post(
url,
data: data,
);
if (response.statusCode == 201 || response.statusCode == 200) {
return ResponseData<dynamic>("success", ResponseStatus.SUCCESS,
data: response.data);
}else {
} else if (response.statusCode == 400) {
if (response.data['message'] == "Master Pin is not created") {
return ResponseData<dynamic>(
response.data['message'], ResponseStatus.PRIVATE,
data: response.data);
} else if (response.data['error']['message'] ==
"MASTER PIN NOT MATCH") {
return ResponseData<dynamic>(
response.data['error']['message'], ResponseStatus.PRIVATE,
data: response.data);
} else if (response.data['error']['message'] ==
"Account already exists. Please Login instead.") {
return ResponseData<dynamic>(
response.data['error']['message'], ResponseStatus.PRIVATE,
data: response.data);
} else {
return ResponseData<dynamic>(
response.data['error']['message'], ResponseStatus.PRIVATE,
data: response.data);
}
} else {
try {
return ResponseData<dynamic>(
response.data['message'].toString(), ResponseStatus.FAILED);
response.data['error']['message'].toString(),
ResponseStatus.FAILED);
} catch (_) {
return ResponseData<dynamic>(
data: response.data,
response.statusMessage!,
ResponseStatus.FAILED);
}}
}
}
} catch (e) {
return ResponseData<dynamic>(
"Oops something went wrong",
ResponseStatus.FAILED);
"Oops something went wrong", ResponseStatus.FAILED);
}
}

View File

@@ -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<BiometricBloc>()
.add(AuthenticateBiometricEvent());
onTap: () async {
// goRouter.pop();
// context
// .read<BiometricBloc>()
// .add(AuthenticateBiometricEvent());
Globalconst.firstName =
await secureStorageService.read("first_name") ??
"";
goRouter
.goNamed(RouteName.pinScreen, pathParameters: {
"fromScreen": "LoginedInUser",
});
},
child: Container(
height: 48.h,

View File

@@ -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<String, int> 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<RegisterBloc>();
var loginBloc = context.read<LoginBloc>();
var restorePasswordBloc =
context.read<RestorePasswordPhoneVerificationBloc>();
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

View File

@@ -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),
],
),
);
},
);
}

View File

@@ -89,6 +89,9 @@ buildprofilelogoutdialog(context) {
onTap: () async {
context.read<BottomNavigationBloc>().add(TabChanged(2));
await secureStorageService.write('isLoginedIn', "false");
await secureStorageService.write('accesstoken', '');
await secureStorageService.write('refreshtoken', '');
goRouter.goNamed(RouteName.loginScreen, pathParameters: {
"fromScreen": "registerStep",
});

View File

@@ -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,

View File

@@ -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<String, dynamic> 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<String, dynamic> biometricdata = {
"token": Globalconst.token,
"token":
await secureStorageService.read("temp_token"),
"is_2FA_on": true
};