diff --git a/assets/images/png/female_img.png b/assets/images/png/female_img.png new file mode 100644 index 0000000..3307034 Binary files /dev/null and b/assets/images/png/female_img.png differ diff --git a/assets/images/png/lock-icon.png b/assets/images/png/lock-icon.png new file mode 100644 index 0000000..3964588 Binary files /dev/null and b/assets/images/png/lock-icon.png differ diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index ede5e64..933afa4 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -806,5 +806,10 @@ "loginBy": "تسجيل الدخول بواسطة", "loginByOTP": "تسجيل الدخول بواسطة OTP", "guest": "زائر", - "switchAccount": "تبديل الحساب" + "switchAccount": "تبديل الحساب", + "lastLoginBy":"آخر تسجيل دخول بواسطة", + "allSet": "جاهز! الآن يمكنك تسجيل الدخول باستخدام Face ID / Biometric أو البصمة", + "enableQuickLogin":"تمكين تسجيل الدخول السريع", + "enableMsg":"تمكين تسجيل الدخول السريع سيسمح بالتحقق من خلال Face ID / Biometric الخاص بجهازك الحالي", + "notNow": "ليس الآن" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 8ccd2c4..0b29886 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -802,5 +802,10 @@ "loginBy": "Login By", "loginByOTP": "Login By OTP", "guest": "Guest", - "switchAccount": "Switch Account" + "switchAccount": "Switch Account", + "lastloginBy": "Last login by", + "allSet": "All Set! Now you can login with Face ID or Biometric", + "enableQuickLogin": "Enable Quick Login", + "enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric", + "notNow": "Not Now" } \ No newline at end of file diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index ac3cfe6..e4ddcfe 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -75,6 +75,8 @@ class AppAssets { static const String insurance = '$svgBasePath/insurance.svg'; static const String requests = '$svgBasePath/requests.svg'; static const String more = '$svgBasePath/more.svg'; + static const String bell = '$svgBasePath/bell.svg'; + //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; @@ -88,12 +90,13 @@ class AppAssets { static const String hmg_logo = '$pngBasePath/hmg_logo.png'; static const String livecare_service = '$pngBasePath/livecare_service.png'; static const String male_img = '$pngBasePath/male_img.png'; - + static const String femaleImg = '$pngBasePath/female_img.png'; static const String apple_pay = '$pngBasePath/Apple_Pay.png'; static const String mada = '$pngBasePath/Mada.png'; static const String Mastercard = '$pngBasePath/Mastercard.png'; static const String tamara_en = '$pngBasePath/tamara_en.png'; static const String visa = '$pngBasePath/visa.png'; + static const String lockIcon = '$pngBasePath/lock-icon.png'; } class AppAnimations { diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index cf8f8e5..53304b0 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -33,6 +33,7 @@ class AppState { if (isFamily) { _authenticatedChildUser = authenticatedUser; } else { + setIsAuthenticated = true; _authenticatedRootUser = authenticatedUser; } } @@ -75,7 +76,7 @@ class AppState { set setSessionId(v) => sessionId = v; - bool isAuthenticated = true; + bool isAuthenticated = false; set setIsAuthenticated(v) => isAuthenticated = v; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a900742..afb2e83 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -21,8 +21,10 @@ import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/firebase_service.dart'; +import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -49,6 +51,8 @@ class AppDependencies { )); getIt.registerLazySingleton(() => NavigationService()); + getIt.registerLazySingleton(() => LocalAuthentication()); + getIt.registerLazySingleton(() => GAnalytics()); getIt.registerLazySingleton(() => AppState(navigationService: getIt())); getIt.registerLazySingleton(() => LocationUtils( @@ -76,6 +80,7 @@ class AppDependencies { getIt.registerLazySingleton(() => RadiologyRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => LocalAuthService(loggerService: getIt(), localAuth: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -116,6 +121,7 @@ class AppDependencies { dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), + localAuthService: getIt() ), ); diff --git a/lib/core/enums.dart b/lib/core/enums.dart index ab5cac1..63d63d7 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -32,7 +32,7 @@ enum MaritalStatusTypeEnum { single, married, divorced, widowed } enum ChipTypeEnum { success, error, alert, info, warning } -enum OTPTypeEnum { sms, whatsapp } +enum OTPTypeEnum { sms, whatsapp, faceIDFingerprint } enum LoginTypeEnum { sms, whatsapp, face, fingerprint } @@ -89,6 +89,8 @@ extension OTPTypeEnumExtension on OTPTypeEnum { return 1; case OTPTypeEnum.whatsapp: return 2; + case OTPTypeEnum.faceIDFingerprint: + return 0; } } diff --git a/lib/extensions/context_extensions.dart b/lib/extensions/context_extensions.dart index 4e9da0d..27d4cf4 100644 --- a/lib/extensions/context_extensions.dart +++ b/lib/extensions/context_extensions.dart @@ -5,6 +5,11 @@ extension ContextUtils on BuildContext { double get screenWidth => MediaQuery.of(this).size.width; + EdgeInsets get padding => MediaQuery.of(this).padding; + + bool get safeInsets => padding.top > 0 || padding.bottom > 0 || padding.left > 0 || padding.right > 0; + + ThemeData get theme => Theme.of(this); TextTheme get textTheme => theme.textTheme; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 4ce1400..52c83e7 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -14,21 +14,21 @@ import 'package:hmg_patient_app_new/features/authentication/models/request_model import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/saved_login_screen.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; -import 'models/resp_models/authenticated_user_resp_model.dart'; - class AuthenticationViewModel extends ChangeNotifier { final AuthenticationRepo _authenticationRepo; final AppState _appState; final ErrorHandlerService _errorHandlerService; final DialogService _dialogService; final NavigationService _navigationService; - + final LocalAuthService _localAuthService; AuthenticationViewModel({ required AppState appState, required AuthenticationRepo authenticationRepo, @@ -36,11 +36,13 @@ class AuthenticationViewModel extends ChangeNotifier { required DialogService dialogService, required NavigationService navigationService, required CacheService cacheService, + required LocalAuthService localAuthService, }) : _navigationService = navigationService, _dialogService = dialogService, _errorHandlerService = errorHandlerService, _appState = appState, - _authenticationRepo = authenticationRepo; + _authenticationRepo = authenticationRepo, + _localAuthService = localAuthService; final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(); CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; @@ -75,7 +77,7 @@ class AuthenticationViewModel extends ChangeNotifier { try { LoadingUtils.showFullScreenLoader(); //TODO: We will remove this delay - await Future.delayed(Duration(seconds: 3)); + // await Future.delayed(Duration(seconds: 3)); var data = _appState.getSelectDeviceByImeiRespModelElement; log("Cached IMEI data: ${data?.toJson()}"); if (data != null) { @@ -168,8 +170,8 @@ class AuthenticationViewModel extends ChangeNotifier { if (savedData != null) { // TODO: Navigate to SavedLogin when available - _navigationService.pushPage(page: LoginScreen()); - // navigationService.pushPage(page: SavedLogin(savedData)); + //_navigationService.pushPage(page: LoginScreen()); + _navigationService.pushPage(page: SavedLogin()); } } catch (e) { log("Error handling existing IMEI data: $e"); @@ -189,8 +191,8 @@ class AuthenticationViewModel extends ChangeNotifier { // TODO: Navigate to SavedLogin when available // SelectDeviceByImeiRespModelElement savedData = // SelectDeviceByImeiRespModelElement.fromJson(respData); - // navigationService.pushPage(page: SavedLogin(savedData)); - _navigationService.pushPage(page: LoginScreen()); + _navigationService.pushPage(page: SavedLogin()); + // _navigationService.pushPage(page: LoginScreen()); } else { LoadingUtils.hideFullScreenLoader(); _navigationService.pushPage(page: LoginScreen()); @@ -365,6 +367,9 @@ class AuthenticationViewModel extends ChangeNotifier { // GifLoaderDialogUtils.hideDialog(context); onWrongActivationCode(activation.errorEndUserMessage); return; + } else if(activation.messageStatus ==2){ + onWrongActivationCode(activation.errorEndUserMessage); + return; } else if (registerd_data?.isRegister == true) { _navigationService.popUntilNamed(AppRoutes.registerNewScreen); // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); @@ -372,6 +377,7 @@ class AuthenticationViewModel extends ChangeNotifier { } else { if (activation.list != null && activation.list!.isNotEmpty) { _appState.setAuthenticatedUser(activation.list!.first); + } _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppLoginTokenID = activation.authenticationTokenId; @@ -381,7 +387,9 @@ class AuthenticationViewModel extends ChangeNotifier { if (isUserAgreedBefore) { navigateToHomeScreen(); } else { - getUserAgreementContent(request: request); + navigateToHomeScreen(); + //Agreement page not designed yet so we are navigating to home screen directly + // getUserAgreementContent(request: request); } // TODO: setPreferences and stuff // sharedPref.remove(FAMILY_FILE); @@ -457,7 +465,34 @@ class AuthenticationViewModel extends ChangeNotifier { } Future onWrongActivationCode({String? message}) async { - // TODO: HANDLE THIS VIA BOTTOM SHEET + await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {}); + } + + loginWithFingerPrintFace(int selectedOption) async { + _localAuthService.authenticate().then((value) { + if (value) { + // we have to handle this if verification true; + checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: 0000, onWrongActivationCode: (String? message) { }); + // authenticated = true; + notifyListeners(); + // navigateToHomeScreen(); + } else { + //authenticated = false; + notifyListeners(); + } + }); + this.selectedOption = selectedOption; + notifyListeners(); + + } + + checkLastLoginStatus(Function() onSuccess) async { + if(_appState.getSelectDeviceByImeiRespModelElement !=null && (_appState.getSelectDeviceByImeiRespModelElement!.logInType ==1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType ==4)){ + Future.delayed(Duration(seconds: 1),(){ + onSuccess(); + }); + + } } @override diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 7831616..dc71850 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -805,5 +805,9 @@ abstract class LocaleKeys { static const checkAvailability = 'checkAvailability'; static const readInstructions = 'readInstructions'; static const searchLabReport = 'searchLabReport'; - + static const lastloginBy = 'lastloginBy'; + static const allSet ='allSet'; + static const enableQuickLogin = 'enableQuickLogin'; + static const enableMsg = 'enableMsg'; + static const notNow = 'notNow'; } diff --git a/lib/main.dart b/lib/main.dart index 53825c8..6c5848d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -99,6 +99,7 @@ void main() async { errorHandlerService: getIt(), navigationService: getIt(), cacheService: getIt(), + localAuthService: getIt(), ), ), ], child: MyApp()), diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart new file mode 100644 index 0000000..9cf0431 --- /dev/null +++ b/lib/presentation/authentication/quick_login.dart @@ -0,0 +1,136 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/core/app_assets.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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class QuickLogin extends StatefulWidget { + final VoidCallback onPressed; + final bool isDone; + + const QuickLogin({super.key, required this.onPressed, this.isDone = false}); + + @override + _QuickLogin createState() => _QuickLogin(); +} + +class _QuickLogin extends State { + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + widget.isDone + ? Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () { + Navigator.pop(context, true); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle)), + ], + ), + Utils.showLottie(context: context, assetPath: AppAnimations.checkmark, width: 120, height: 120, repeat: true), + LocaleKeys.allSet.tr().toText16(textAlign: + TextAlign.center, weight: FontWeight.w500) + // Text( + // ' TranslationBase.of(context).allSet', + // textAlign: TextAlign.center, + // style: context.dynamicTextStyle( + // fontSize: 16, + // fontWeight: FontWeight.w500, + // color: Colors.black, + // ), + // ), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + AppAssets.lockIcon, + height: 101, + ), + const SizedBox(height: 10), + LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), + // Text( + // ' TranslationBase.of(context).enableQuickLogin', + // style: context.dynamicTextStyle( + // fontSize: 26, + // fontWeight: FontWeight.bold, + // color: Colors.black, + // ), + // ), + const SizedBox(height: 5), + LocaleKeys.enableQuickLogin.tr().toText16(color: AppColors.quickLoginColor), + // Description + // Text( + // 'TranslationBase.of(context).enableMsg', + // style: context.dynamicTextStyle( + // fontSize: 16, + // color: Color(0xFF666666), + // height: 1.5, + // ), + //), + const SizedBox(height: 24), + // Buttons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: CustomButton( + text:LocaleKeys.enableQuickLogin.tr(), + onPressed: () { + widget.onPressed(); + }, + backgroundColor: Color(0xffED1C2B), + borderColor: Color(0xffED1C2B), + textColor: Colors.white, + icon:AppAssets.apple_finder, + )), + ], + ), + SizedBox( + height: 16, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.notNow.tr(), + onPressed: () { + Navigator.pop(context, true); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Colors.red, + // icon: "assets/images/svg/apple-finder.svg", + )), + ], + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index da04c40..fe2c760 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -2,37 +2,51 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.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/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.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/bottomsheet/generic_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; + class SavedLogin extends StatefulWidget { - // final SelectDeviceIMEIRES savedLoginData; - // const SavedLogin(this.savedLoginData, {Key? key}) : super(key: key); - const SavedLogin(); + + const SavedLogin({Key? key}) : super(key: key); @override _SavedLogin createState() => _SavedLogin(); } class _SavedLogin extends State { - LoginTypeEnum loginType = LoginTypeEnum.sms; + LoginTypeEnum loginType = LoginTypeEnum.sms; + late AuthenticationViewModel authVm; + late AppState appState; @override void initState() { + authVm = context.read(); + appState = getIt.get(); + authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!; + authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!; + super.initState(); } @override Widget build(BuildContext context) { + + return Scaffold( appBar: CustomAppBar( onBackPressed: () {}, @@ -48,7 +62,7 @@ class _SavedLogin extends State { // Welcome back text LocaleKeys.welcomeBack.tr().toText16(color: AppColors.inputLabelTextColor), SizedBox(height: 16.h), - ("widget.savedLoginData.name!.toLowerCase().capitalizeFirstofEach").toText26(isBold: true, height: 26 / 36, color: AppColors.textColor), + appState.getSelectDeviceByImeiRespModelElement!.name!.toCamelCase.toText26(isBold: true, height: 26 / 36, color: AppColors.textColor), SizedBox(height: 24.h), Container( padding: EdgeInsets.all(16.h), @@ -63,22 +77,25 @@ class _SavedLogin extends State { child: Column( children: [ // Last login info - ('LocaleKeys.lastloginBy.tr()' + ' {getType(widget.savedLoginData.logInType!, context)}').toText14(isBold: true, color: AppColors.greyTextColor), - ('widget.savedLoginData.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(widget.savedLoginData.createdOn!), "d MMMM, y at HH:mm") : --') + + ("${LocaleKeys.lastloginBy.tr() } ${LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!.displayName}" + ).toText14(isBold: true, color: AppColors.greyTextColor), + (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y at HH:mm") : '--') .toText16(isBold: true, color: AppColors.textColor), - Container(margin: EdgeInsets.all(16.h), child: Utils.buildSvgWithAssets(icon: getTypeIcons(loginType.toInt), iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), + Container(margin: EdgeInsets.all(16.h), child: Utils.buildSvgWithAssets(icon: getTypeIcons(loginType.toInt), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), // Face ID login button - Container( + SizedBox( height: 45, child: CustomButton( text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - // loginWithFingerPrintFace(loginType.toInt, widget.savedLoginData.iMEI!); - } else { - int? val = loginType.toInt; - //checkUserAuthentication(val); + authVm.loginWithFingerPrintFace(loginType.toInt); + } + else { + // int? val = loginType.toInt; + authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp); } }, backgroundColor: Color(0xffED1C2B), @@ -104,7 +121,7 @@ class _SavedLogin extends State { ), const SizedBox(height: 24), // OTP login button - loginType != null && loginType.toInt != 1 + loginType.toInt != 1 ? Column( children: [ loginType.toInt != 1 @@ -140,7 +157,7 @@ class _SavedLogin extends State { Navigator.of(context).pop(); loginType = LoginTypeEnum.sms; int? val = loginType.toInt; - // checkUserAuthentication(val); + authVm.checkUserAuthentication(otpTypeEnum:OTPTypeEnum.sms ); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, @@ -163,7 +180,7 @@ class _SavedLogin extends State { Navigator.of(context).pop(); loginType = LoginTypeEnum.whatsapp; int? val = loginType.toInt; - // checkUserAuthentication(val); + authVm.checkUserAuthentication(otpTypeEnum:OTPTypeEnum.whatsapp ); }, backgroundColor: AppColors.transparent, borderColor: AppColors.textColor, @@ -195,11 +212,12 @@ class _SavedLogin extends State { text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - // loginWithFingerPrintFace(loginType.toInt, "iMEI"); + authVm.loginWithFingerPrintFace(loginType.toInt); } else { loginType = LoginTypeEnum.whatsapp; int? val = loginType.toInt; - // checkUserAuthentication(val); + authVm.checkUserAuthentication(otpTypeEnum:OTPTypeEnum.whatsapp ); + } }, backgroundColor: AppColors.whiteColor, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index fdeb407..47a7846 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -11,14 +11,17 @@ 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/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; +import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart' show CustomTabBar; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -31,11 +34,25 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { + late final AuthenticationViewModel authVM; + + @override + void initState() { + authVM = context.read(); + if(mounted) { + authVM.checkLastLoginStatus(() { + + showQuickLogin(context, false); + }); + } + super.initState(); + } + @override Widget build(BuildContext context) { AppState appState = getIt.get(); NavigationService navigationService = getIt.get(); - final AuthenticationViewModel authenticationViewModel = context.read(); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: SingleChildScrollView( @@ -46,32 +63,54 @@ class _LandingPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authenticationViewModel.onLoginPressed(); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50, - ), - Utils.buildSvgWithAssets( - icon: AppAssets.contact_icon, - width: 24, - height: 24, - ).onPress(() { - Navigator.of(context).push( - FadePage( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); - }), + appState.isAuthenticated + ? WelcomeWidget( + onTap: () {}, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + ) + : CustomButton( + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50, + ), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 20, width: 20).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }).paddingSymmetrical(8.h, 0), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 20, width: 20).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }).paddingSymmetrical(8.h, 0), + Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 20, width: 20).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }).paddingSymmetrical(8.h, 0), + ], + ) ], ).paddingSymmetrical(24.h, 0.h), ), @@ -275,4 +314,21 @@ class _LandingPageState extends State { ), ); } + + void showQuickLogin(BuildContext context, bool isDone) { + showCommonBottomSheet( + context, + title: "", + child: QuickLogin( + isDone: isDone, + onPressed: () { + // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); + // loginWithFingerPrintFace(3, 1, user, deviceToken); + }, + ), + height: 400, + isFullScreen: false, + callBackFunc: (str) {}, + ); + } } diff --git a/lib/presentation/home/widgets/welcome_widget.dart b/lib/presentation/home/widgets/welcome_widget.dart new file mode 100644 index 0000000..d761a7f --- /dev/null +++ b/lib/presentation/home/widgets/welcome_widget.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.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/theme/colors.dart'; + +class WelcomeWidget extends StatelessWidget { + final String name; + final String imageUrl; + final VoidCallback? onTap; + + const WelcomeWidget({ + super.key, + required this.name, + required this.imageUrl, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(30), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Profile image + + Image.asset(imageUrl, width: 40, height: 40), + + const SizedBox(width: 10), + + // Text column + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + "Welcome".toText14(color: AppColors.greyTextColor), + + + Row( + children: [ + + name.toText16(isBold: true), + + const SizedBox(width: 4), + const Icon(Icons.keyboard_arrow_down, size: 20, color: Colors.black), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index 131c454..451eeee 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -64,7 +64,7 @@ class _InsuranceHomePageState extends State { insuranceVM.setIsInsuranceHistoryLoading(true); insuranceVM.getPatientInsuranceCardHistory(); showCommonBottomSheet(context, - child: InsuranceHistory(), callBackFunc: () {}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false); + child: InsuranceHistory(), callBackFunc: (str){}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false); }, backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), borderColor: AppColors.primaryRedColor.withOpacity(0.0), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 1edef84..4906493 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -120,7 +120,7 @@ class PatientInsuranceCard extends StatelessWidget { insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); insuranceViewModel.getPatientInsuranceDetailsForUpdate(); showCommonBottomSheet(context, - child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", height: ResponsiveExtension.screenHeight * 0.42, isCloseButtonVisible: false, isFullScreen: false); + child: PatientInsuranceCardUpdateCard(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.42, isCloseButtonVisible: false, isFullScreen: false); }, backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), borderColor: AppColors.bgGreenColor.withOpacity(0.0), diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 28b77e1..9ba939b 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; abstract class DialogService { @@ -70,7 +71,9 @@ class _ErrorBottomSheet extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), ), - child: const Text("OK"), + child: const Text("OK", style: TextStyle(color: Colors.white)).onPress((){ + Navigator.of(context).pop(); + }), ), ], ), diff --git a/lib/services/localauth_service.dart b/lib/services/localauth_service.dart new file mode 100644 index 0000000..a01d1c1 --- /dev/null +++ b/lib/services/localauth_service.dart @@ -0,0 +1,40 @@ +import 'package:local_auth/local_auth.dart'; + +import 'logger_service.dart'; + +class LocalAuthService { + final LocalAuthentication localAuth; + final LoggerService loggerService; + LocalAuthService({required this.localAuth, required this.loggerService}); + + Future authenticate() async { + try { + bool isAuthenticated = await localAuth.authenticate( + localizedReason: 'Please authenticate to proceed', + options: const AuthenticationOptions( + biometricOnly: true, + stickyAuth: true, + ), + ); + return isAuthenticated; + } catch (e) { + return false; + } + } + + Future canCheckBiometrics() async { + try { + return await localAuth.canCheckBiometrics; + } catch (e) { + return false; + } + } + + Future> getAvailableBiometrics() async { + try { + return await localAuth.getAvailableBiometrics(); + } catch (e) { + return []; + } + } +} \ No newline at end of file diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 5cb9ced..c8eaa87 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -58,4 +58,6 @@ static const Color warningLightColor = Color(0xFFFFCC00); static const Color greyLightColor = Color(0xFFEFEFF0); static const Color bottomNAVBorder = Color(0xFFEEEEEE); + +static const Color quickLoginColor = Color(0xFF666666); }