diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 1038617..9e5acbc 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -46,5 +46,7 @@ "otp": "OTP", "verification": "تَحَقّق", "resend": "إعادة إرسال", - "codeExpire": "انتهت صلاحية رمز التحقق" + "codeExpire": "انتهت صلاحية رمز التحقق", + "moreVerificationOpts" :"المزيد من خيارات التحقق" + } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 27757e5..8f13464 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -46,5 +46,6 @@ "otp": "OTP", "verification": "Verification", "resend": "Resend", - "codeExpire": "The verification code has been expired" + "codeExpire": "The verification code has been expired", + "moreVerificationOpts" :"More verification options" } \ No newline at end of file diff --git a/lib/base/base_vm.dart b/lib/base/base_vm.dart new file mode 100644 index 0000000..bb5383b --- /dev/null +++ b/lib/base/base_vm.dart @@ -0,0 +1,22 @@ + +import 'package:flutter/material.dart'; +import 'package:hmg_nurses/classes/enums.dart'; + +class BaseViewModel extends ChangeNotifier { + + ViewState _state = ViewState.idle; + bool isInternetConnection = true; + + ViewState get state => _state; + + String error = ""; + + //TODO add the user login model when we need it + + void setState(ViewState viewState) { + _state = viewState; + + notifyListeners(); + } + +} diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 646e498..9c94e49 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,4 +1,8 @@ class ApiConsts { + + static const MAX_SMALL_SCREEN = 660; + + //static String baseUrl = "http://10.200.204.20:2801/"; // Local server static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server // static String baseUrl = "https://hmgwebservices.com"; // Live server diff --git a/lib/classes/enums.dart b/lib/classes/enums.dart new file mode 100644 index 0000000..c1026c2 --- /dev/null +++ b/lib/classes/enums.dart @@ -0,0 +1,3 @@ +enum APPSTATUS { loading, unAuthenticated, authenticated, unverified } +enum AuthMethodTypes { sms, whatsApp, fingerPrint, faceID, moreOptions } +enum ViewState { idle, busy, error, busyLocal, errorLocal } diff --git a/lib/classes/size_config.dart b/lib/classes/size_config.dart new file mode 100644 index 0000000..9f0374d --- /dev/null +++ b/lib/classes/size_config.dart @@ -0,0 +1,103 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hmg_nurses/classes/consts.dart'; + +class SizeConfig { + static double _blockWidth = 0; + static double _blockHeight = 0; + + static double? realScreenWidth; + static double? realScreenHeight; + static double? screenWidth; + static double? screenHeight; + static double? textMultiplier; + static double? imageSizeMultiplier; + static double? heightMultiplier; + static bool isPortrait = true; + static double? widthMultiplier; + static bool isMobilePortrait = false; + static bool isMobile = false; + static bool isHeightShort = false; + static bool isHeightVeryShort = false; + static bool isHeightMiddle = false; + static bool isHeightLarge = false; + static bool isWidthLarge = false; + + void init(BoxConstraints constraints, Orientation orientation) { + realScreenHeight = constraints.maxHeight; + realScreenWidth = constraints.maxWidth; + if (constraints.maxWidth <= ApiConsts.MAX_SMALL_SCREEN) { + isMobile = true; + } + if (constraints.maxHeight < 600) { + isHeightVeryShort = true; + } else if (constraints.maxHeight < 800) { + isHeightShort = true; + } else if (constraints.maxHeight < 1000) { + isHeightMiddle = true; + } else { + isHeightLarge = true; + } + + if (constraints.maxWidth > 600) { + isWidthLarge = true; + } + + if (orientation == Orientation.portrait) { + isPortrait = true; + if (realScreenWidth! < 450) { + isMobilePortrait = true; + } + // textMultiplier = _blockHeight; + // imageSizeMultiplier = _blockWidth; + screenHeight = realScreenHeight; + screenWidth = realScreenWidth; + } else { + isPortrait = false; + isMobilePortrait = false; + // textMultiplier = _blockWidth; + // imageSizeMultiplier = _blockHeight; + screenHeight = realScreenWidth; + screenWidth = realScreenHeight; + } + _blockWidth = screenWidth! / 100; + _blockHeight = screenHeight! / 100; + + textMultiplier = _blockHeight; + imageSizeMultiplier = _blockWidth; + heightMultiplier = _blockHeight; + widthMultiplier = _blockWidth; + + print('realScreenWidth $realScreenWidth'); + print('realScreenHeight $realScreenHeight'); + print('textMultiplier $textMultiplier'); + print('imageSizeMultiplier $imageSizeMultiplier'); + print('heightMultiplier$heightMultiplier'); + print('widthMultiplier $widthMultiplier'); + print('isPortrait $isPortrait'); + print('isMobilePortrait $isMobilePortrait'); + } + + static getTextMultiplierBasedOnWidth({double? width}) { + // TODO handel LandScape case + if (width != null) { + return width / 100; + } + return widthMultiplier; + } + + static getWidthMultiplier({double? width}) { + // TODO handel LandScape case + if (width != null) { + return width / 100; + } + return widthMultiplier; + } + + static getHeightMultiplier({double? height}) { + // TODO handel LandScape case + if (height != null) { + return height / 100; + } + return heightMultiplier; + } +} diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 46e012a..43204fa 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -14,6 +14,7 @@ import 'package:hmg_nurses/exceptions/api_exception.dart'; import 'package:hmg_nurses/extensions/int_extensions.dart'; import 'package:hmg_nurses/extensions/string_extensions.dart'; import 'package:hmg_nurses/extensions/widget_extensions.dart'; +import 'package:hmg_nurses/main.dart'; import 'package:hmg_nurses/widgets/dialogs/confirm_dialog.dart'; import 'package:hmg_nurses/widgets/loading_dialog.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -55,11 +56,11 @@ class Utils { return await Future.delayed(Duration(milliseconds: millis)); } - static void showLoading(BuildContext context) { + static void showLoading() { WidgetsBinding.instance.addPostFrameCallback((_) { _isLoadingVisible = true; showDialog( - context: context, + context: navigatorKey.currentState!.overlay!.context, barrierColor: Colors.black.withOpacity(0.5), builder: (BuildContext context) => LoadingDialog(), ).then((value) { diff --git a/lib/config/app_provider.dart b/lib/config/app_provider.dart deleted file mode 100644 index 9ccf065..0000000 --- a/lib/config/app_provider.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class AppProvider extends StatelessWidget { - final Widget child; - - AppProvider({required this.child}); - - @override - Widget build(BuildContext context) { - return child; - return MultiProvider( - providers: [ - // ChangeNotifierProvider(create: (_) => Counter()), - ], - child: child, - ); - } -} diff --git a/lib/dependency_injection.dart b/lib/dependency_injection.dart new file mode 100644 index 0000000..40bda74 --- /dev/null +++ b/lib/dependency_injection.dart @@ -0,0 +1,18 @@ +import 'package:get_it/get_it.dart'; +import 'package:hmg_nurses/ui/login/login_vm.dart'; + +GetIt getIt = GetIt.instance; + + +void initDependencies () { + // TODO; WE WILL REGISTER ALL THE SERVICES HERE AS LAZY SINGLETON + + + + //TODO: WE WILL REGISTER ALL THE VIEW MODELS AS FACTORY HERE + + /// View Model + getIt.registerFactory(() => LoginViewModel()); + + +} \ No newline at end of file diff --git a/lib/extensions/util_extensions.dart b/lib/extensions/util_extensions.dart new file mode 100644 index 0000000..f720a1a --- /dev/null +++ b/lib/extensions/util_extensions.dart @@ -0,0 +1,33 @@ +import 'package:hmg_nurses/classes/enums.dart'; + +extension SelectedAuthMethodTypesService on AuthMethodTypes { + int getTypeIdService() { + switch (this) { + case AuthMethodTypes.sms: + return 1; + case AuthMethodTypes.whatsApp: + return 2; + case AuthMethodTypes.fingerPrint: + return 3; + case AuthMethodTypes.faceID: + return 4; + case AuthMethodTypes.moreOptions: + return 5; + } + } + + static getMethodsTypeService(int typeId) { + switch (typeId) { + case 1: + return AuthMethodTypes.sms; + case 2: + return AuthMethodTypes.whatsApp; + case 3: + return AuthMethodTypes.fingerPrint; + case 4: + return AuthMethodTypes.faceID; + case 5: + return AuthMethodTypes.moreOptions; + } + } +} diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 73e4320..7d7ca5b 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -62,7 +62,8 @@ class CodegenLoader extends AssetLoader{ "otp": "OTP", "verification": "تَحَقّق", "resend": "إعادة إرسال", - "codeExpire": "انتهت صلاحية رمز التحقق" + "codeExpire": "انتهت صلاحية رمز التحقق", + "moreVerificationOpts": "المزيد من خيارات التحقق" }; static const Map en_US = { "mohemm": "Mohemm", @@ -112,7 +113,8 @@ static const Map en_US = { "otp": "OTP", "verification": "Verification", "resend": "Resend", - "codeExpire": "The verification code has been expired" + "codeExpire": "The verification code has been expired", + "moreVerificationOpts": "More verification options" }; static const Map> mapLocales = {"ar_SA": ar_SA, "en_US": en_US}; } diff --git a/lib/main.dart b/lib/main.dart index 3bff34a..d38990e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,9 +2,11 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_nurses/classes/size_config.dart'; import 'package:hmg_nurses/config/routes.dart'; import 'package:hmg_nurses/provider/dashboard_provider_model.dart'; import 'package:hmg_nurses/theme/app_theme.dart'; +import 'package:hmg_nurses/ui/login/login_vm.dart'; import 'package:logger/logger.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; @@ -12,7 +14,6 @@ import 'package:sizer/sizer.dart'; import 'generated/codegen_loader.g.dart'; - GlobalKey navigatorKey = GlobalKey(); Logger logger = Logger( @@ -45,9 +46,8 @@ Future main() async { assetLoader: const CodegenLoader(), child: MultiProvider( providers: [ - ChangeNotifierProvider( - create: (_) => DashboardProviderModel(), - ) + ChangeNotifierProvider(create: (_) => DashboardProviderModel()), + ChangeNotifierProvider(create: (_) => LoginViewModel()) ], child: const MyApp(), ), @@ -66,24 +66,26 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return Sizer( - builder: ( - BuildContext context, - Orientation orientation, - DeviceType deviceType, - ) { - List> delegates = context.localizationDelegates; - return MaterialApp( - key: navigatorKey, - theme: AppTheme.getTheme( - EasyLocalization.of(context)?.locale.languageCode == "ar", - ), - debugShowCheckedModeBanner: false, - localizationsDelegates: delegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - initialRoute: AppRoutes.login, - routes: AppRoutes.routes, + return LayoutBuilder( + builder: (context, constraints) { + return Sizer( + builder: (BuildContext context, Orientation orientation, DeviceType deviceType) { + SizeConfig().init(constraints, orientation); + + List> delegates = context.localizationDelegates; + return MaterialApp( + key: navigatorKey, + theme: AppTheme.getTheme( + EasyLocalization.of(context)?.locale.languageCode == "ar", + ), + debugShowCheckedModeBanner: false, + localizationsDelegates: delegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + initialRoute: AppRoutes.login, + routes: AppRoutes.routes, + ); + }, ); }, ); diff --git a/lib/ui/login/login_method_page.dart b/lib/ui/login/login_method_page.dart index 0ac6632..5909550 100644 --- a/lib/ui/login/login_method_page.dart +++ b/lib/ui/login/login_method_page.dart @@ -2,15 +2,18 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:hexcolor/hexcolor.dart'; import 'package:hmg_nurses/classes/colors.dart'; -import 'package:hmg_nurses/classes/utils.dart'; -import 'package:hmg_nurses/dialogs/otp_dialog.dart'; -import 'package:hmg_nurses/extensions/int_extensions.dart'; +import 'package:hmg_nurses/classes/enums.dart'; +import 'package:hmg_nurses/classes/size_config.dart'; import 'package:hmg_nurses/extensions/string_extensions.dart'; +import 'package:hmg_nurses/extensions/util_extensions.dart'; +import 'package:hmg_nurses/extensions/widget_extensions.dart'; import 'package:hmg_nurses/generated/locale_keys.g.dart'; +import 'package:hmg_nurses/ui/login/login_vm.dart'; +import 'package:hmg_nurses/ui/login/widgets/verification_method_card.dart'; import 'package:hmg_nurses/widgets/button/default_button.dart'; -import 'package:local_auth/local_auth.dart'; +import 'package:provider/provider.dart'; import 'package:sizer/sizer.dart'; // WhatsApp 2 @@ -26,162 +29,15 @@ class LoginMethodsPage extends StatefulWidget { } class LoginMethodsPageState extends State { - final LocalAuthentication localAuth = LocalAuthentication(); - List _availableBioMetricType = []; - - int selectedFlag = 0; - bool isNeedVerifyWithFaceIDAndBiometrics = false; - - bool lastLoginCheck = false; - - @override - void initState() { - _getAvailableBiometrics(); - super.initState(); - } - - String getVerificationType(int type) { - if (type == 1) { - return LocaleKeys.sms.tr(); - } else if (type == 2) { - return LocaleKeys.whatsapp.tr(); - } else if (type == 3) { - return LocaleKeys.face.tr(); - } else if (type == 4) { - return LocaleKeys.fingerPrint.tr(); - } - return ""; - } - - Future _getAvailableBiometrics() async { - try { - _availableBioMetricType = await localAuth.getAvailableBiometrics(); - } on PlatformException catch (e) { - // AppToast.showErrorToast(message: e.message); - if (kDebugMode) { - print(e); - } - } - if (mounted) setState(() {}); - } - - Future loginWithFaceIDAndBiometrics() async { - bool authenticated = false; - try { - authenticated = await localAuth.authenticate( - localizedReason: 'Scan your fingerprint to authenticate', - ); - } on PlatformException catch (e) { - if (kDebugMode) { - print(e); - } - Utils.hideLoading(context); - Utils.showToast("Please enable your Touch or Face ID"); - } - return authenticated; - } - - Widget _loginOptionButton(String title, String icon, int flag, int? loginIndex) { - bool isDisable = ((flag == 3 && !checkBiometricIsAvailable(BiometricType.face)) || (flag == 4 && !checkBiometricIsAvailable(BiometricType.fingerprint))); - return InkWell( - onTap: isDisable - ? null - : () async { - if (flag == 0) { - setState(() { - // isMoreOption = true; - }); - } else { - if (flag == 3 || flag == 4) { - bool authenticateWithFaceAndTouchID = await loginWithFaceIDAndBiometrics(); - if (!authenticateWithFaceAndTouchID) { - return; - } else { - isNeedVerifyWithFaceIDAndBiometrics = true; - selectedFlag = flag; - setState(() { - return; - }); - } - } else { - if (isNeedVerifyWithFaceIDAndBiometrics) { - // performApiCall(_title, _icon, selectedFlag, _flag); - // TODO: CALL API AND THEN SHOW DIALOG - - OtpDialog( - type: 1, - mobileNo: 0504278212, - onSuccess: (string, TextEditingController pinPutController) {}, - onFailure: () {}, - onResendCode: () {}, - ).displayDialog(context); - } else { - // performApiCall(_title, _icon, _flag, _flag); - - // TODO: CALL API AND THEN SHOW DIALOG - OtpDialog( - type: 1, - mobileNo: 0504278212, - onSuccess: (string, TextEditingController pinPutController) {}, - onFailure: () {}, - onResendCode: () {}, - ).displayDialog(context); - } - } - } - }, - child: Container( - padding: const EdgeInsets.only(left: 20, right: 20, bottom: 15, top: 28), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: isDisable ? Colors.grey.withOpacity(0.3) : Colors.white, - border: Border.all(color: MyColors.lightGreyEFColor, width: 1), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SvgPicture.asset( - icon, - height: 38, - width: 38, - color: isDisable ? MyColors.darkTextColor.withOpacity(0.7) : null, - ), - title.toText14() - ], - ), - ), - ); - } - - Widget getButton(int flag) { - switch (flag) { - case 1: - return _loginOptionButton(LocaleKeys.verifyThroughSMS.tr(), 'assets/images/login/verify_sms.svg', flag, null); - case 2: - return _loginOptionButton(LocaleKeys.verifyThroughWhatsapp.tr(), 'assets/images/login/verify_whatsapp.svg', flag, null); - case 3: - return _loginOptionButton(LocaleKeys.verifyThroughFace.tr(), 'assets/images/login/verify_face.svg', flag, BiometricType.face.index); - case 4: - return _loginOptionButton(LocaleKeys.verifyThroughFingerprint.tr(), 'assets/images/login/verify_thumb.svg', flag, BiometricType.fingerprint.index); - default: - return const SizedBox(); - } - } - - bool checkBiometricIsAvailable(BiometricType biometricType) { - bool isAvailable = false; - for (int i = 0; i < _availableBioMetricType.length; i++) { - if (biometricType == _availableBioMetricType[i]) { - isAvailable = true; - break; - } - } - return isAvailable; - } + bool isMoreOption = false; + bool onlySMSBox = false; + late AuthMethodTypes fingerPrintBefore; + late AuthMethodTypes selectedOption; + late LoginViewModel loginViewModel; @override Widget build(BuildContext context) { + loginViewModel = context.read(); return Scaffold( appBar: AppBar( backgroundColor: Colors.transparent, @@ -191,84 +47,243 @@ class LoginMethodsPageState extends State { ), // actions: [Center(child: "Employee Digital ID".toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() {})), 21.width], ), - body: ListView( - padding: const EdgeInsets.all(21), - physics: const BouncingScrollPhysics(), - children: [ - if (lastLoginCheck) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.welcomeBack.tr().toText12(), - "Faiz Hashmi".toText24(isBold: true), - 10.height, - LocaleKeys.wouldYouLikeToLoginWithCurrentUsername.tr().toText18(), - Container( - height: 72, - margin: const EdgeInsets.only(top: 23, bottom: 23), - alignment: Alignment.center, - padding: const EdgeInsets.only(left: 17, right: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - border: Border.all( - color: const Color(0xffefefef), - width: 1, - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LocaleKeys.lastLoginDetails.tr().toText16(), - //TODO: DATE - // DateUtil.formatDateToDate(DateUtil.convertStringToDate(mobileLoginInfoListModel!.editedOn!), false).toText12(), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LocaleKeys.verificationType.tr().toText10(color: MyColors.grey57Color), - //TODO: VERIFICATION TYPE - // getVerificationType(mobileLoginInfoListModel!.loginType!).toText12(), - const Expanded(child: SizedBox()), - //TODO: DATE + body: SingleChildScrollView( + child: Center( + child: FractionallySizedBox( + widthFactor: 0.9, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 1.h), + if (loginViewModel.isFromLogin) + InkWell( + onTap: () { + loginViewModel.setUnverified(false, isFromLogin: false); + loginViewModel.setAppStatus(APPSTATUS.unAuthenticated); + }, + child: const Icon(Icons.arrow_back_ios, color: MyColors.darkTextColor)), + Column( + children: [ + SizedBox(height: 1.h), + + isMoreOption != null && isMoreOption == false + ? Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.welcomeBack.tr().toText12(), + SizedBox(height: 1.h), + "Faiz Hashmi".toText24(isBold: true), + SizedBox(height: 3.h), + LocaleKeys.wouldYouLikeToLoginWithCurrentUsername.tr().toText18(), + SizedBox(height: 3.h), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all(color: HexColor('#707070'), width: 0.1), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.lastLoginDetails.tr(), + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontFamily: 'Poppins', + fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * 4.8, + color: Color(0xFF2E303A), + fontWeight: FontWeight.w600, + letterSpacing: -0.4), + ), + Container( + width: MediaQuery.of(context).size.width * 0.55, + child: RichText( + text: TextSpan( + text: "${LocaleKeys.verificationType.tr()} ", + style: TextStyle( + color: const Color(0xFF575757), + fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * 3.5, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + letterSpacing: -0.4), + children: [ + TextSpan( + text: loginViewModel.getType(1), + style: TextStyle( + color: MyColors.darkTextColor, + fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * 3.5, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + letterSpacing: -0.48), + ) + ]), + ), + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "12 NOV, 2022", + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontFamily: 'Poppins', + fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * 4.2, + color: Color(0xFF2E303A), + fontWeight: FontWeight.bold, + letterSpacing: -0.4), + ), + + Text( + "09:56 AM", + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontFamily: 'Poppins', + fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * 3.5, + color: Color(0xFF2E303A), + fontWeight: FontWeight.w600, + letterSpacing: -0.4), + ), + // AppText( + // loginViewModel.user.editedOn != null + // ? AppDateUtils.getDayMonthYearDateFormatted(AppDateUtils.convertStringToDate(loginViewModel.user.editedOn), isMonthShort: true) + // : loginViewModel.user.createdOn != null + // ? AppDateUtils.getDayMonthYearDateFormatted(AppDateUtils.convertStringToDate(loginViewModel.user.createdOn), isMonthShort: true) + // : '--', + // textAlign: TextAlign.right, + // fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * 4.5, + // color: Color(0xFF2E303A), + // fontWeight: FontWeight.w700, + // letterSpacing: -0.48, + // ), + // AppText( + // loginViewModel.user.editedOn != null + // ? AppDateUtils.getHour(AppDateUtils.convertStringToDate(loginViewModel.user.editedOn)) + // : loginViewModel.user.createdOn != null + // ? AppDateUtils.getHour(AppDateUtils.convertStringToDate(loginViewModel.user.createdOn)) + // : '--', + // textAlign: TextAlign.right, + // fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * 3.5, + // fontWeight: FontWeight.w600, + // letterSpacing: -0.48, + // color: Color(0xFF575757), + // ) + ], + ) + ], + ), + ), + SizedBox(height: SizeConfig.heightMultiplier! * 3), + Row( + children: [ + //todo add translation + LocaleKeys.pleaseVerify.tr().toText18().paddingOnly(left: 8), + ], + ), + SizedBox( + height: SizeConfig.heightMultiplier! * 2, + ), + ], + ) + : Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ + onlySMSBox == true + ? Container( + margin: const EdgeInsets.only(bottom: 20, top: 30), + child: LocaleKeys.pleaseVerify.tr().toText16(), + ) + : LocaleKeys.pleaseVerify.tr().toText16(), + ]), + isMoreOption != null && isMoreOption == false + ? Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Expanded( + child: VerificationMethodsList( + loginViewModel: loginViewModel, + authMethodType: SelectedAuthMethodTypesService.getMethodsTypeService(1), + authenticateUser: (AuthMethodTypes authMethodType, isActive) { + loginViewModel.startSMSService(authMethodType, context: context); + }, + ), + ), + Expanded( + child: VerificationMethodsList( + loginViewModel: loginViewModel, + authMethodType: AuthMethodTypes.moreOptions, + onShowMore: () { + setState(() { + isMoreOption = true; + }); + }, + )) + ]), + ]) + : Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + onlySMSBox == false + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: VerificationMethodsList( + loginViewModel: loginViewModel, + authMethodType: AuthMethodTypes.fingerPrint, + authenticateUser: (AuthMethodTypes authMethodType, isActive) { + loginViewModel.startSMSService(authMethodType, context: context); + }, + )), + Expanded( + child: VerificationMethodsList( + loginViewModel: loginViewModel, + authMethodType: AuthMethodTypes.faceID, + authenticateUser: (AuthMethodTypes authMethodType, isActive) { + loginViewModel.startSMSService(authMethodType, context: context); + }, + )) + ], + ) + : const SizedBox(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: VerificationMethodsList( + loginViewModel: loginViewModel, + authMethodType: AuthMethodTypes.sms, + authenticateUser: (AuthMethodTypes authMethodType, isActive) { + loginViewModel.startSMSService(authMethodType, context: context); + }, + )), + Expanded( + child: VerificationMethodsList( + loginViewModel: loginViewModel, + authMethodType: AuthMethodTypes.whatsApp, + authenticateUser: (AuthMethodTypes authMethodType, isActive) { + loginViewModel.startSMSService(authMethodType, context: context); + }, + )) + ], + ), + ]), - // DateUtil.formatDateToTime(DateUtil.convertStringToDate(mobileLoginInfoListModel!.editedOn!)).toText12(), - ], - ) + // ) ], ), - ), - ], - ), - if (!lastLoginCheck) - Text( - LocaleKeys.pleaseVerifyForBio.tr(), - style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600), + ], + ), ), - if (isNeedVerifyWithFaceIDAndBiometrics) Text(LocaleKeys.pleaseVerifyForBio.tr()), - GridView( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 13, mainAxisSpacing: 9), - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 9), - shrinkWrap: true, - children: [ - if (!isNeedVerifyWithFaceIDAndBiometrics) getButton(3), - if (!isNeedVerifyWithFaceIDAndBiometrics) getButton(4), - getButton(2), - getButton(1), - ], ), - SizedBox(height: 10.h), - ], + ), ), - bottomSheet: !lastLoginCheck + bottomSheet: !isMoreOption ? null : Container( height: 10.h, diff --git a/lib/ui/login/login_vm.dart b/lib/ui/login/login_vm.dart index 1b5f238..b35b025 100644 --- a/lib/ui/login/login_vm.dart +++ b/lib/ui/login/login_vm.dart @@ -1,7 +1,141 @@ - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hmg_nurses/base/base_vm.dart'; +import 'package:hmg_nurses/classes/enums.dart'; +import 'package:hmg_nurses/classes/utils.dart'; +import 'package:hmg_nurses/dialogs/otp_dialog.dart'; +import 'package:hmg_nurses/generated/locale_keys.g.dart'; +import 'package:local_auth/local_auth.dart'; + +class LoginViewModel extends BaseViewModel { + bool isLogin = false; + bool unverified = false; + bool isFromLogin = false; + APPSTATUS appStatus = APPSTATUS.loading; + String localToken = ""; + + String nurseProfile = ""; + + final LocalAuthentication auth = LocalAuthentication(); + late List _availableBiometrics; + + LoginViewModel(); + + /// get type name based on id. + getType(type) { + switch (type) { + case 1: + return LocaleKeys.sms.tr(); + case 3: + return LocaleKeys.fingerPrint.tr(); + case 4: + return LocaleKeys.face.tr(); + case 2: + return LocaleKeys.whatsapp.tr(); + default: + return LocaleKeys.sms.tr(); + } + } + + /// ask user to add his biometric + showIOSAuthMessages() async { + try { + await auth.authenticate( + localizedReason: 'Scan your fingerprint to authenticate', + ); + } on PlatformException catch (e) { + if (kDebugMode) { + print(e); + } + } + } + + /// check specific biometric if it available or not + Future checkIfBiometricAvailable(BiometricType biometricType) async { + bool isAvailable = false; + await _getAvailableBiometrics(); + if (_availableBiometrics != null) { + for (var i = 0; i < _availableBiometrics.length; i++) { + if (biometricType == _availableBiometrics[i]) isAvailable = true; + } + } + return isAvailable; + } + + /// get all available biometric on the device for local Auth service + Future _getAvailableBiometrics() async { + try { + _availableBiometrics = await auth.getAvailableBiometrics(); + } on PlatformException catch (e) { + if (kDebugMode) { + print(e); + } + } + } + + /// determine the status of the app + APPSTATUS get status { + if (state == ViewState.busy) { + appStatus = APPSTATUS.loading; + } else { + if (nurseProfile != null) { + appStatus = APPSTATUS.authenticated; + } else if (unverified) { + appStatus = APPSTATUS.unverified; + } else if (isLogin) { + appStatus = APPSTATUS.authenticated; + } else { + appStatus = APPSTATUS.unAuthenticated; + } + } + return appStatus; + } + + setAppStatus(APPSTATUS status) { + appStatus = status; + notifyListeners(); + } + + setUnverified(bool unverified, {bool isFromLogin = false}) { + this.unverified = unverified; + this.isFromLogin = isFromLogin; + notifyListeners(); + } -class LoginVM extends ChangeNotifier { + /// logout function + logout({bool isFromLogin = false}) async { + // localToken = ""; + // String lang = await sharedPref.getString(APP_Language); + // await Utils.clearSharedPref(); + // doctorProfile = null; + // sharedPref.setString(APP_Language, lang); + // deleteUser(); + // await getDeviceInfoFromFirebase(); + // this.isFromLogin = isFromLogin; + // appStatus = APPSTATUS.unAuthenticated; + // setState(ViewState.Idle); + } + deleteUser() { + // user = null; + // unverified = false; + // isLogin = false; + } -} \ No newline at end of file + startSMSService(AuthMethodTypes type, {isSilentLogin = false, required BuildContext context}) { + OtpDialog( + type: 1, + mobileNo: 0504278212, + onSuccess: (String otpCode, TextEditingController pinPut) { + Utils.showLoading(); + //TODO: API CALL + // performDirectApiCall(_title, _icon, _flag, value); + }, + onFailure: () => Navigator.pop(context), + onResendCode: () {}) + .displayDialog(context); + } +} diff --git a/lib/ui/login/widgets/verification_method_card.dart b/lib/ui/login/widgets/verification_method_card.dart new file mode 100644 index 0000000..09a7bb5 --- /dev/null +++ b/lib/ui/login/widgets/verification_method_card.dart @@ -0,0 +1,135 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:hexcolor/hexcolor.dart'; +import 'package:hmg_nurses/classes/colors.dart'; +import 'package:hmg_nurses/classes/enums.dart'; +import 'package:hmg_nurses/classes/size_config.dart'; +import 'package:hmg_nurses/generated/locale_keys.g.dart'; +import 'package:hmg_nurses/ui/login/login_vm.dart'; +import 'package:local_auth/local_auth.dart'; + +class VerificationMethodsList extends StatefulWidget { + final AuthMethodTypes authMethodType; + final Function(AuthMethodTypes type, bool isActive)? authenticateUser; + final Function? onShowMore; + final LoginViewModel loginViewModel; + + const VerificationMethodsList({Key? key, required this.authMethodType, this.authenticateUser, this.onShowMore, required this.loginViewModel}) : super(key: key); + + @override + VerificationMethodsListState createState() => VerificationMethodsListState(); +} + +class VerificationMethodsListState extends State { + final LocalAuthentication auth = LocalAuthentication(); + + @override + Widget build(BuildContext context) { + switch (widget.authMethodType) { + case AuthMethodTypes.whatsApp: + return MethodTypeCard( + assetPath: 'assets/images/login/verify_whatsapp.svg', + onTap: () => {widget.authenticateUser!(AuthMethodTypes.whatsApp, true)}, + label: LocaleKeys.verifyThroughWhatsapp.tr(), + ); + case AuthMethodTypes.sms: + return MethodTypeCard( + assetPath: "assets/images/login/verify_sms.svg", + onTap: () => {widget.authenticateUser!(AuthMethodTypes.sms, true)}, + label: LocaleKeys.verifyThroughSMS.tr(), + ); + case AuthMethodTypes.fingerPrint: + return MethodTypeCard( + assetPath: 'assets/images/login/verify_thumb.svg', + onTap: () => {widget.authenticateUser!(AuthMethodTypes.fingerPrint, true)}, + label: LocaleKeys.verifyThroughFingerprint.tr(), + ); + case AuthMethodTypes.faceID: + return MethodTypeCard( + assetPath: 'assets/images/login/verify_face.svg', + onTap: () => {widget.authenticateUser!(AuthMethodTypes.faceID, true)}, + label: LocaleKeys.verifyThroughFace.tr(), + ); + + default: + return MethodTypeCard( + assetPath: 'assets/images/login/more_icon.png', + onTap: () { + widget.onShowMore!(); + }, + isSvg: false, + label: LocaleKeys.moreVerificationOpts.tr(), + height: 0, + ); + } + } +} + +class MethodTypeCard extends StatelessWidget { + const MethodTypeCard({ + Key? key, + required this.assetPath, + required this.onTap, + required this.label, + this.height = 20, + this.isSvg = true, + }) : super(key: key); + final String assetPath; + final GestureTapCallback onTap; + final String label; + final double height; + final bool isSvg; + + @override + Widget build(BuildContext context) { + double cardHeight = SizeConfig.heightMultiplier! * + (SizeConfig.isHeightVeryShort + ? 22 + : SizeConfig.isHeightLarge + ? 25 + : 20); + return InkWell( + onTap: onTap, + child: Container( + margin: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all(color: HexColor('#707070'), width: 0.1), + ), + height: cardHeight, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + isSvg + ? SvgPicture.asset(assetPath, width: SizeConfig.widthMultiplier! * (14), height: cardHeight * 0.20) + : Image.asset( + assetPath, + width: SizeConfig.widthMultiplier! * (12), + height: cardHeight * 0.35, + // height: , + ), + SizedBox( + height: height, + ), + Text( + label, + style: TextStyle( + fontSize: SizeConfig.getTextMultiplierBasedOnWidth() * (SizeConfig.isHeightVeryShort ? 3 : 3.7), + color: MyColors.darkTextColor, + fontWeight: FontWeight.w600, + letterSpacing: -0.48, + ), + ) + ], + ), + ), + )), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 3203415..1ab34e8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -147,6 +147,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.1.1" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.0" + hexcolor: + dependency: "direct main" + description: + name: hexcolor + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" http: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5aaa270..24ec73e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,9 @@ dependencies: shimmer: ^2.0.0 sizer: ^2.0.15 local_auth: ^2.1.2 + get_it: ^7.2.0 + hexcolor: ^2.0.4 + dev_dependencies: flutter_test: