pull/8/head
aamir-csol 2 months ago
parent 8f1e4500cc
commit 67e580447e

@ -787,7 +787,8 @@
"iAcceptTermsConditions": "أوافق على الشروط والأحكام", "iAcceptTermsConditions": "أوافق على الشروط والأحكام",
"alreadyHaveAccount": "هل لديك حساب بالفعل؟", "alreadyHaveAccount": "هل لديك حساب بالفعل؟",
"loginNow": "تسجيل الدخول الآن", "loginNow": "تسجيل الدخول الآن",
"notice": "إشعار" "notice": "إشعار",
"oR": "أو",
"sendOTPWHATSAPP": "أرسل لي OTP عبر واتساب",
"sendOTPSMS": "أرسل لي OTP عبر الرسائل القصيرة"
} }

@ -774,7 +774,7 @@
"termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...", "termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...",
"receiveOtpToast": "Where would you like to receive OTP?", "receiveOtpToast": "Where would you like to receive OTP?",
"enterPhoneNumber": "Enter Phone Number", "enterPhoneNumber": "Enter Phone Number",
"enterEmailDesc": "Enter your email address to complete the process of creating a medical file", "enterEmailDesc": "Enter your email address to complete the process of creating a medical file",
"enterPhoneDesc": "Enter your phone number to receive OTP verification code", "enterPhoneDesc": "Enter your phone number to receive OTP verification code",
"pleaseChooseOption": "Please select from the below options to receive OTP", "pleaseChooseOption": "Please select from the below options to receive OTP",
"dontHaveAccount": "Don't have an account?", "dontHaveAccount": "Don't have an account?",
@ -783,5 +783,8 @@
"iAcceptTermsConditions": "I Accept the Terms and Conditions", "iAcceptTermsConditions": "I Accept the Terms and Conditions",
"alreadyHaveAccount": "Already have an account?", "alreadyHaveAccount": "Already have an account?",
"loginNow": "Login Now", "loginNow": "Login Now",
"notice": "Notice" "notice": "Notice",
"oR": "OR",
"sendOTPWHATSAPP": "Send me OTP on Whatsapp",
"sendOTPSMS": "Send me OTP on SMS"
} }

@ -2,15 +2,32 @@ import 'package:flutter/material.dart';
extension ContextUtils on BuildContext { extension ContextUtils on BuildContext {
double get screenHeight => MediaQuery.of(this).size.height; double get screenHeight => MediaQuery.of(this).size.height;
double get screenWidth => MediaQuery.of(this).size.width; double get screenWidth => MediaQuery.of(this).size.width;
ThemeData get theme => Theme.of(this); ThemeData get theme => Theme.of(this);
TextTheme get textTheme => theme.textTheme; TextTheme get textTheme => theme.textTheme;
// TextStyle get headline1 => textTheme.headline1!; // TextStyle get headline1 => textTheme.headline1!;
// TextStyle get headline2 => textTheme.headline2!; // TextStyle get headline2 => textTheme.headline2!;
// TextStyle get headline3 => textTheme.headline3!; // TextStyle get headline3 => textTheme.headline3!;
// TextStyle get headline4 => textTheme.headline4!; // TextStyle get headline4 => textTheme.headline4!;
// TextStyle get headline5 => textTheme.headline5!; // TextStyle get headline5 => textTheme.headline5!;
// TextStyle get headline6 => textTheme.headline6!; // TextStyle get headline6 => textTheme.headline6!;
// TextStyle get bodyText1 => textTheme.bodyText1!; // TextStyle get bodyText1 => textTheme.bodyText1!;
// TextStyle get bodyText2 => textTheme.bodyText2!; // TextStyle get bodyText2 => textTheme.bodyText2!;
}
extension ShowBottomSheet on BuildContext {
Future<T?> showBottomSheet<T>({isScrollControlled = true, isDismissible = false, required Widget child, Color? backgroundColor, enableDra = false, useSafeArea = false}) {
return showModalBottomSheet<T>(
context: this,
isScrollControlled: isScrollControlled,
isDismissible: isDismissible,
enableDrag: enableDra,
useSafeArea: useSafeArea,
backgroundColor: backgroundColor ?? Colors.transparent,
builder: (_) => child,
);
}
} }

@ -786,5 +786,8 @@ abstract class LocaleKeys {
static const alreadyHaveAccount = 'alreadyHaveAccount'; static const alreadyHaveAccount = 'alreadyHaveAccount';
static const loginNow = 'loginNow'; static const loginNow = 'loginNow';
static const notice = 'notice'; static const notice = 'notice';
static const oR = 'oR';
static const sendOTPWHATSAPP = 'sendOTPWHATSAPP';
static const sendOTPSMS = 'sendOTPSMS';
} }

@ -4,12 +4,14 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/context_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart';
@ -77,7 +79,22 @@ class _LoginScreen extends State<LoginScreen> {
text: LocaleKeys.login.tr(), text: LocaleKeys.login.tr(),
icon: AppAssets.login1, icon: AppAssets.login1,
iconColor: Colors.white, iconColor: Colors.white,
onPressed: () {}, onPressed: () {
showLoginModel(context: context);
// if (nationIdController.text.isNotEmpty) {
// } else {
// showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterNationalIdOrFileNo,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// }
},
), ),
SizedBox(height: 10.h), // Adjusted to sizer unit (approx 14px) SizedBox(height: 10.h), // Adjusted to sizer unit (approx 14px)
Center( Center(
@ -119,4 +136,61 @@ class _LoginScreen extends State<LoginScreen> {
), ),
); );
} }
void showLoginModel({required BuildContext context, TextEditingController? textController}) {
context.showBottomSheet(
isScrollControlled: true,
isDismissible: false,
useSafeArea: true,
backgroundColor: Colors.transparent,
child: StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: SingleChildScrollView(
child: GenericBottomSheet(
countryCode: "966",
initialPhoneNumber: "",
textController: TextEditingController(),
isEnableCountryDropdown: true,
onCountryChange: (value) {},
onChange: (String? value) {},
buttons: [
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () {},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: AppColors.whiteColor,
icon: AppAssets.message,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.h),
child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor),
),
],
),
Padding(
padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () {},
backgroundColor: Colors.white,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor,
icon: AppAssets.whatsapp,
),
),
],
),
),
);
}));
}
} }

@ -12,10 +12,12 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton;
import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/otp/otp.dart';
class RegisterNew extends StatefulWidget { class RegisterNew extends StatefulWidget {
@override @override
@ -39,173 +41,275 @@ class _RegisterNew extends State<RegisterNew> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>(); AppState appState = getIt.get<AppState>();
return Scaffold( return Scaffold(
backgroundColor: AppColors.bgScaffoldColor, backgroundColor: AppColors.bgScaffoldColor,
appBar: CustomAppBar( appBar: CustomAppBar(
onBackPressed: () { onBackPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
onLanguageChanged: (String value) { onLanguageChanged: (String value) {
// context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US')); // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US'));
}, },
), ),
body: GestureDetector( body: GestureDetector(
onTap: () { onTap: () {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
child: ScrollConfiguration( child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(overscroll: false, physics: const ClampingScrollPhysics()), behavior: ScrollConfiguration.of(context).copyWith(overscroll: false, physics: const ClampingScrollPhysics()),
child: NotificationListener<OverscrollIndicatorNotification>( child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (notification) { onNotification: (notification) {
notification.disallowIndicator(); notification.disallowIndicator();
return true; return true;
}, },
child: SingleChildScrollView( child: SingleChildScrollView(
physics: ClampingScrollPhysics(), physics: ClampingScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 24.h), padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.h, height: 200.h, fit: BoxFit.cover, repeat: true), Utils.showLottie(context: context,
SizedBox(height: 16.h), assetPath: 'assets/animations/lottie/register.json',
LocaleKeys.prepareToElevate.tr().toText32(isBold: true), width: 200.h,
SizedBox(height: 24.h), height: 200.h,
Directionality( fit: BoxFit.cover,
textDirection: Directionality.of(context), repeat: true),
child: Container( SizedBox(height: 16.h),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), LocaleKeys.prepareToElevate.tr().toText32(isBold: true),
padding: EdgeInsets.symmetric(horizontal: 16.h), SizedBox(height: 24.h),
child: Column( Directionality(
children: [ textDirection: Directionality.of(context),
CustomCountryDropdown( child: Container(
countryList: Country.values, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)),
onCountryChange: (Country? value) {}, padding: EdgeInsets.symmetric(horizontal: 16.h),
isRtl: Directionality.of(context) == TextDirection.LTR, child: Column(
).withVerticalPadding(8.h), children: [
CustomCountryDropdown(
// DropdownWidget( countryList: Country.values,
// labelText: LocaleKeys.country.tr(), onCountryChange: (Country? value) {},
// hintText: LocaleKeys.ksa.tr(), isRtl: Directionality.of(context) == TextDirection.LTR,
// isEnable: true, ).withVerticalPadding(8.h),
// selectedValue: appState.getLanguageID(context) == "1" ? "selectedCountry.nameArabic" : "selectedCountry.displayName", Divider(height: 1.h),
// dropdownItems: Country.values.map((e) => appState.getLanguageID(context) == " 1" ? e.displayName : e.displayName).toList(), TextInputWidget(
// onChange: (val) { labelText: LocaleKeys.nationalIdNumber.tr(),
// if (val != null) {} hintText: "xxxxxxxxx",
// }, controller: TextEditingController(),
// isBorderAllowed: false, isEnable: true,
// hasSelectionCustomIcon: true, prefix: null,
// isAllowRadius: false, isAllowRadius: true,
// padding: EdgeInsets.symmetric(vertical: 8.h), isBorderAllowed: false,
// selectionCustomIcon: AppAssets.arrow_down, isAllowLeadingIcon: true,
// ).withVerticalPadding(8), autoFocus: true,
Divider(height: 1.h), padding: EdgeInsets.symmetric(vertical: 8.h),
TextInputWidget( leadingIcon: AppAssets.student_card,
labelText: LocaleKeys.nationalIdNumber.tr(), onChange: (value) {
hintText: "xxxxxxxxx", print(value);
controller: TextEditingController(), }).withVerticalPadding(8),
isEnable: true, Divider(height: 1),
prefix: null, TextInputWidget(
isAllowRadius: true, labelText: LocaleKeys.dob.tr(),
isBorderAllowed: false, hintText: "11 July, 1994",
isAllowLeadingIcon: true, controller: TextEditingController(),
isEnable: true,
autoFocus: true, prefix: null,
padding: EdgeInsets.symmetric(vertical: 8.h), isAllowRadius: true,
leadingIcon: AppAssets.student_card, isBorderAllowed: false,
onChange: (value) { isAllowLeadingIcon: true,
print(value); padding: EdgeInsets.symmetric(vertical: 8.h),
}).withVerticalPadding(8), leadingIcon: AppAssets.birthday_cake,
Divider(height: 1), onChange: (value) {},
TextInputWidget( ).withVerticalPadding(8),
labelText: LocaleKeys.dob.tr(), ],
hintText: "11 July, 1994", ),
controller: TextEditingController(), ),
isEnable: true, ),
prefix: null, SizedBox(height: 25.h),
isAllowRadius: true, GestureDetector(
isBorderAllowed: false, onTap: () {},
isAllowLeadingIcon: true, child: Row(
padding: EdgeInsets.symmetric(vertical: 8.h), children: [
leadingIcon: AppAssets.birthday_cake, AnimatedContainer(
onChange: (value) {}, duration: const Duration(milliseconds: 200),
).withVerticalPadding(8), height: 24.h,
], width: 24.h,
decoration: BoxDecoration(
color: isTermsAccepted ? const Color(0xFFE92227) : Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isTermsAccepted ? const Color(0xFFE92227) : Colors.grey,
width: 2.h,
),
),
child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null,
),
SizedBox(width: 12.h),
Expanded(
child: Text(
LocaleKeys.iAcceptTermsConditions.tr(),
style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
),
),
],
),
),
SizedBox(height: 25.h),
CustomButton(
text: "Register",
icon: AppAssets.note_edit,
onPressed: () {
showRegisterModel(context: context);
},
),
SizedBox(height: 14),
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: context.dynamicTextStyle(
color: Colors.black,
fontSize: 16.fSize,
height: 26 / 16,
fontWeight: FontWeight.w500,
),
children: <TextSpan>[
TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(), style: context.dynamicTextStyle()),
TextSpan(text: " "),
TextSpan(
text: LocaleKeys.loginNow.tr(),
style: context.dynamicTextStyle(
color: AppColors.primaryRedColor,
fontSize: 16.fSize,
height: 26 / 16,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.of(context).pop();
},
),
],
),
),
),
SizedBox(height: 30),
],
),
), ),
), ),
), ),
SizedBox(height: 25.h), ));
GestureDetector( }
onTap: () {},
child: Row( void showRegisterModel({required BuildContext context, TextEditingController? textController}) {
children: [ showModalBottomSheet(
AnimatedContainer( context: context,
duration: const Duration(milliseconds: 200), isScrollControlled: true,
height: 24.h, isDismissible: false,
width: 24.h, useSafeArea: true,
decoration: BoxDecoration( backgroundColor: Colors.transparent,
color: isTermsAccepted ? const Color(0xFFE92227) : Colors.transparent, builder: (bottomSheetContext) =>
borderRadius: BorderRadius.circular(6), Padding(
border: Border.all( padding: EdgeInsets.only(bottom: MediaQuery
color: isTermsAccepted ? const Color(0xFFE92227) : Colors.grey, .of(bottomSheetContext)
width: 2.h, .viewInsets
.bottom),
child: SingleChildScrollView(
child: GenericBottomSheet(
countryCode: "966",
initialPhoneNumber: "",
textController: TextEditingController(),
onChange: (String? value) {},
buttons: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567',)));
// if (mobileNo.isEmpty) {
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterMobile,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// } else if (!Utils.validateMobileNumber(mobileNo)) {
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterValidMobile,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// } else {
// registerUser(1);
// }
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: AppColors.whiteColor,
icon: AppAssets.message,
), ),
), ),
child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, Row(
), crossAxisAlignment: CrossAxisAlignment.center,
SizedBox(width: 12.h), mainAxisAlignment: MainAxisAlignment.center,
Expanded( children: [
child: Text( Padding(
LocaleKeys.iAcceptTermsConditions.tr(), padding: EdgeInsets.symmetric(horizontal: 8.h),
style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor),
),
],
), ),
), Padding(
], padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
), child: CustomButton(
), text: LocaleKeys.sendOTPWHATSAPP.tr(),
SizedBox(height: 25.h), onPressed: () {
CustomButton( // if (mobileNo.isEmpty) {
text: "Register", // context.showBottomSheet(
icon: AppAssets.note_edit, // child: ExceptionBottomSheet(
onPressed: () {}, // message: TranslationBase.of(context).pleaseEnterMobile,
), // showCancel: false,
SizedBox(height: 14), // onOkPressed: () {
Center( // Navigator.of(context).pop();
child: RichText( // },
textAlign: TextAlign.center, // ),
text: TextSpan( // );
style: context.dynamicTextStyle( // } else if (!Utils.validateMobileNumber(mobileNo)) {
color: Colors.black, // context.showBottomSheet(
fontSize: 16.fSize, // child: ExceptionBottomSheet(
height: 26 / 16, // message: TranslationBase.of(context).pleaseEnterValidMobile,
fontWeight: FontWeight.w500, // showCancel: false,
), // onOkPressed: () {
children: <TextSpan>[ // Navigator.of(context).pop();
TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(), style: context.dynamicTextStyle()), // },
TextSpan(text: " "), // ),
TextSpan( // );
text: LocaleKeys.loginNow.tr(), // } else {
style: context.dynamicTextStyle( // registerUser(4);
color: AppColors.primaryRedColor, // }
fontSize: 16.fSize, // int? val = Utils.onOtpBtnPressed(OTPType.whatsapp, mobileNo, context);
height: 26 / 16, // registerUser(val);
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.of(context).pop();
}, },
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor,
icon: AppAssets.whatsapp,
),
), ),
], ],
), ),
), ),
), ),
SizedBox(height: 30),
],
),
),
),)
,
)
); );
} }
} }

@ -29,6 +29,7 @@ class AppColors {
static const Color bgGreenColor = Color(0xFF18C273); static const Color bgGreenColor = Color(0xFF18C273);
static const Color textColor = Color(0xFF2E3039); static const Color textColor = Color(0xFF2E3039);
static const Color borderOnlyColor = Color(0xFF2E3039); static const Color borderOnlyColor = Color(0xFF2E3039);
static const Color blackBgColor = Color(0xFF2E3039);
static const blackColor = textColor; static const blackColor = textColor;
static const inputLabelTextColor = Color(0xff898A8D); static const inputLabelTextColor = Color(0xff898A8D);

@ -11,11 +11,13 @@ import '../../generated/locale_keys.g.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final VoidCallback onBackPressed; final VoidCallback onBackPressed;
final ValueChanged<String> onLanguageChanged; final ValueChanged<String> onLanguageChanged;
bool hideLogoAndLang;
const CustomAppBar({ CustomAppBar({
Key? key, Key? key,
required this.onBackPressed, required this.onBackPressed,
required this.onLanguageChanged, required this.onLanguageChanged,
this.hideLogoAndLang = false,
}) : super(key: key); }) : super(key: key);
@override @override
@ -28,7 +30,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
leading: null, leading: null,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: Padding( title: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.h), padding: EdgeInsets.symmetric(horizontal: 10.h),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
@ -43,25 +45,26 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
), ),
// Logo // Logo
Utils.buildSvgWithAssets( if (!hideLogoAndLang)
icon: AppAssets.habiblogo, Utils.buildSvgWithAssets(
), icon: AppAssets.habiblogo,
),
// Language Selector if (!hideLogoAndLang)
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: LanguageSelector( child: LanguageSelector(
currentLanguage: context.locale.languageCode, currentLanguage: context.locale.languageCode,
showOnlyIcon: false, showOnlyIcon: false,
onLanguageChanged: onLanguageChanged, onLanguageChanged: onLanguageChanged,
languages: [ languages: [
{'code': 'ar', 'name': LocaleKeys.arabic.tr()}, {'code': 'ar', 'name': LocaleKeys.arabic.tr()},
{'code': 'en', 'name': LocaleKeys.english.tr()} {'code': 'en', 'name': LocaleKeys.english.tr()}
], ],
),
), ),
), ),
),
], ],
), ),
), ),

@ -3,9 +3,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart';
class GenericBottomSheet extends StatefulWidget { class GenericBottomSheet extends StatefulWidget {
@ -18,20 +21,21 @@ class GenericBottomSheet extends StatefulWidget {
final bool isEnableCountryDropdown; final bool isEnableCountryDropdown;
final bool isFromSavedLogin; final bool isFromSavedLogin;
Function(String?)? onChange; Function(String?)? onChange;
// FocusNode myFocusNode; // FocusNode myFocusNode;
GenericBottomSheet( GenericBottomSheet({
{this.countryCode = "", this.countryCode = "",
this.initialPhoneNumber = "", this.initialPhoneNumber = "",
required this.buttons, required this.buttons,
this.textController, this.textController,
this.isForEmail = false, this.isForEmail = false,
this.onCountryChange, this.onCountryChange,
this.isEnableCountryDropdown = false, this.isEnableCountryDropdown = false,
this.isFromSavedLogin = false, this.isFromSavedLogin = false,
this.onChange, this.onChange,
// required this.myFocusNode // required this.myFocusNode
}); });
@override @override
_GenericBottomSheetState createState() => _GenericBottomSheetState(); _GenericBottomSheetState createState() => _GenericBottomSheetState();
@ -64,11 +68,8 @@ class _GenericBottomSheetState extends State<GenericBottomSheet> {
child: Directionality( child: Directionality(
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
child: Container( child: Container(
padding: const EdgeInsets.all(24), padding: EdgeInsets.all(24.h),
decoration: BoxDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bgScaffoldColor, borderRadius: 16),
color: Color(0xFFF8F8FA),
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -86,18 +87,17 @@ class _GenericBottomSheetState extends State<GenericBottomSheet> {
? LocaleKeys.enterEmail.tr().toText24() ? LocaleKeys.enterEmail.tr().toText24()
: LocaleKeys.enterPhoneNumber.tr().toText24()), : LocaleKeys.enterPhoneNumber.tr().toText24()),
InkWell( InkWell(
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 10), padding: EdgeInsets.only(top: 10.h),
child: Utils.buildSvgWithAssets( child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle),
icon: AppAssets.cross_circle, ),
), ),
)),
], ],
), ),
const SizedBox(height: 8), SizedBox(height: 8.h),
// Subtitle // Subtitle
widget.isFromSavedLogin widget.isFromSavedLogin
? LocaleKeys.pleaseChooseOption.tr().toText16() ? LocaleKeys.pleaseChooseOption.tr().toText16()
@ -113,7 +113,7 @@ class _GenericBottomSheetState extends State<GenericBottomSheet> {
labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber, labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber,
hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx",
controller: widget.textController!, controller: widget.textController!,
padding: EdgeInsets.only(top: 8, bottom: 8, left: 8, right: 8), padding: EdgeInsets.all(8.h),
keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number,
onChange: (value) { onChange: (value) {
widget.textController!.text = value!; widget.textController!.text = value!;
@ -133,7 +133,7 @@ class _GenericBottomSheetState extends State<GenericBottomSheet> {
: SizedBox(), : SizedBox(),
], ],
SizedBox(height: 24), SizedBox(height: 24.h),
...widget.buttons, ...widget.buttons,
], ],
), ),

@ -43,50 +43,40 @@ class CustomButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: isDisabled ? null : onPressed, onTap: isDisabled ? null : onPressed,
child: Container( child: Container(
height: height, height: height,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: isDisabled ? Colors.transparent : backgroundColor, color: isDisabled ? Colors.transparent : backgroundColor,
borderRadius: borderRadius, borderRadius: borderRadius,
side: BorderSide( side: BorderSide(
width: borderWidth, width: borderWidth,
color: isDisabled ? borderColor.withOpacity(0.5) : borderColor, color: isDisabled ? borderColor.withOpacity(0.5) : borderColor,
)), )),
child: Padding( child: Padding(
padding: padding, padding: padding,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (icon != null) if (icon != null)
Padding( Padding(
padding: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled), child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled),
),
Text(
text,
style: context.dynamicTextStyle(
fontSize: fontSize.fSize,
color: isDisabled ? textColor.withOpacity(0.5) : textColor,
letterSpacing: -0.4,
fontWeight: fontWeight,
),
), ),
], Text(
), text,
style: context.dynamicTextStyle(
fontSize: fontSize.fSize,
color: isDisabled ? textColor.withOpacity(0.5) : textColor,
letterSpacing: -0.4,
fontWeight: fontWeight,
),
),
],
), ),
) ),
),
// .toSmoothContainer( );
// smoothness: 1,
// side: BorderSide(width: borderWidth, color: backgroundColor),
// borderRadius: BorderRadius.circular(borderRadius * 1.2),
// foregroundDecoration: BoxDecoration(
// color: isDisabled ? backgroundColor.withOpacity(0.5) : Colors.transparent,
// borderRadius: BorderRadius.circular(borderRadius),
// ),
// ),
);
} }
} }

@ -0,0 +1,227 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
class OTPVerificationPage extends StatefulWidget {
final String phoneNumber;
const OTPVerificationPage({Key? key, required this.phoneNumber}) : super(key: key);
@override
State<OTPVerificationPage> createState() => _OTPVerificationPageState();
}
class _OTPVerificationPageState extends State<OTPVerificationPage> {
final int _otpLength = 4;
late final List<TextEditingController> _controllers;
late final List<FocusNode> _focusNodes;
Timer? _resendTimer;
int _resendTime = 60;
bool _isOtpComplete = false;
@override
void initState() {
super.initState();
_controllers = List.generate(_otpLength, (_) => TextEditingController());
_focusNodes = List.generate(_otpLength, (_) => FocusNode());
_startResendTimer();
// Focus the first field once the screen is built
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_focusNodes.isNotEmpty) {
FocusScope.of(context).requestFocus(_focusNodes[0]);
}
});
}
@override
void dispose() {
for (final c in _controllers) c.dispose();
for (final f in _focusNodes) f.dispose();
_resendTimer?.cancel();
super.dispose();
}
void _startResendTimer() {
_resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_resendTime > 0) {
setState(() => _resendTime--);
} else {
timer.cancel();
}
});
}
void _onOtpChanged(int index, String value) {
if (value.length == 1 && index < _otpLength - 1) {
_focusNodes[index + 1].requestFocus();
} else if (value.isEmpty && index > 0) {
_focusNodes[index - 1].requestFocus();
}
_checkOtpCompletion();
}
void _checkOtpCompletion() {
final isComplete = _controllers.every((c) => c.text.isNotEmpty);
if (isComplete != _isOtpComplete) {
setState(() => _isOtpComplete = isComplete);
if (isComplete) {
_verifyOtp();
}
}
}
void _resendOtp() {
if (_resendTime == 0) {
setState(() => _resendTime = 60);
_startResendTimer();
autoFillOtp("1234");
// call resend API here
}
}
String _getMaskedPhoneNumber() {
final phone = widget.phoneNumber;
return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
hideLogoAndLang: true,
onBackPressed: () {
Navigator.of(context).pop();
},
onLanguageChanged: (lang) {},
),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 40.h),
Text(
'OTP Verification',
style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold),
),
SizedBox(height: 16.h),
Text(
'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification',
style: TextStyle(fontSize: 16.fSize, color: Colors.grey),
),
SizedBox(height: 40.h),
// OTP Input Fields
SizedBox(
height: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(_otpLength, (index) {
return ValueListenableBuilder<TextEditingValue>(
valueListenable: _controllers[index],
builder: (context, value, _) {
final hasText = value.text.isNotEmpty;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
width: 70.h,
margin: EdgeInsets.symmetric(horizontal: 4.h),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: _isOtpComplete ? AppColors.successColor : (hasText ? AppColors.blackBgColor : AppColors.whiteColor), borderRadius: 16),
child: Center(
child: TextField(
controller: _controllers[index],
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
style: TextStyle(
fontSize: 40.fSize,
fontWeight: FontWeight.bold,
color: AppColors.whiteColor,
),
decoration: InputDecoration(
counterText: '',
filled: true,
fillColor: Colors.transparent,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(18),
borderSide: BorderSide.none,
),
),
onChanged: (v) => _onOtpChanged(index, v),
),
),
);
},
);
}),
),
),
const SizedBox(height: 32),
// Resend OTP
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Didn't receive it? "),
if (_resendTime > 0)
Text(
'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ',
style: const TextStyle(color: Colors.grey),
)
else
GestureDetector(
onTap: _resendOtp,
child: const Text(
'Resend',
style: TextStyle(
color: AppColors.primaryRedColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
),
);
}
void _verifyOtp() {
final otp = _controllers.map((c) => c.text).join();
debugPrint('Verifying OTP: $otp');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Verifying OTP: $otp')),
);
}
/// Auto fill OTP into text fields
void autoFillOtp(String otp) {
if (otp.length != _otpLength) return;
for (int i = 0; i < _otpLength; i++) {
_controllers[i].text = otp[i];
}
// Move focus to the last field
_focusNodes[_otpLength - 1].requestFocus();
// Trigger completion check and color update
_checkOtpCompletion();
}
}
Loading…
Cancel
Save