diff --git a/lib/services/common_auth_service.dart b/lib/services/common_auth_service.dart new file mode 100644 index 0000000..c3edc37 --- /dev/null +++ b/lib/services/common_auth_service.dart @@ -0,0 +1,56 @@ +import 'package:flutter/services.dart'; +import 'package:local_auth/local_auth.dart'; +import 'package:local_auth_android/local_auth_android.dart'; +import 'package:local_auth_ios/local_auth_ios.dart'; + +abstract class CommonAuthServices { + Future authenticate(); + + Future> getAvailBio(); +} + +class CommonAuthImp implements CommonAuthServices { + final LocalAuthentication localAuth = LocalAuthentication(); + + Future authenticate() async { + try { + final availableBiometrics = await localAuth.getAvailableBiometrics(); + if (availableBiometrics == null || availableBiometrics.isEmpty) { + return false; + } + + final didAuthenticate = await localAuth.authenticate( + localizedReason: "Scan your fingerprint or face ID to authenticate", + options: const AuthenticationOptions( + stickyAuth: false, + biometricOnly: true, + sensitiveTransaction: true, + useErrorDialogs: true, + ), + authMessages: [ + const AndroidAuthMessages( + cancelButton: 'Cancel', + biometricHint: 'Please use your fingerprint or face ID', + goToSettingsButton: 'Settings', + goToSettingsDescription: 'Please set up your Touch ID.', + ), + const IOSAuthMessages( + cancelButton: 'Cancel', + goToSettingsButton: 'Settings', + goToSettingsDescription: 'Please set up your Touch ID.', + lockOut: 'Please reenable your Touch ID', + ), + ], + ); + + return didAuthenticate; + } on PlatformException catch (e) { + print('Error during authentication: $e'); + return false; + } + } + + Future> getAvailBio() async { + return await localAuth.getAvailableBiometrics(); + } +} diff --git a/lib/view_models/user_view_model.dart b/lib/view_models/user_view_model.dart index 708bc18..09c5068 100644 --- a/lib/view_models/user_view_model.dart +++ b/lib/view_models/user_view_model.dart @@ -4,10 +4,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:http/http.dart'; +import 'package:local_auth/local_auth.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/main.dart'; import 'package:mc_common_app/models/general_models/m_response.dart'; import 'package:mc_common_app/models/user_models/basic_otp.dart'; import 'package:mc_common_app/models/user_models/change_email.dart'; @@ -23,6 +25,7 @@ import 'package:mc_common_app/models/user_models/register_user.dart'; import 'package:mc_common_app/models/user_models/user.dart'; import 'package:mc_common_app/models/user_models/verify_email.dart'; import 'package:mc_common_app/repositories/user_repo.dart'; +import 'package:mc_common_app/services/common_auth_service.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/shared_prefrence.dart'; @@ -142,7 +145,8 @@ class UserVM extends BaseVM { } } - Future performCompleteProfile(BuildContext context, { + Future performCompleteProfile( + BuildContext context, { required String password, required String confirmPassword, required String firstName, @@ -480,8 +484,7 @@ class UserVM extends BaseVM { if (basicOtp.messageStatus == 1) { showMDialog(context, child: OtpDialog( onClick: (String code) async { - if (reloadPage == null) - pop(context); + if (reloadPage == null) pop(context); Utils.showLoading(context); RegisterUserRespModel user = await userRepo.basicVerify( countryCode + phoneNum, @@ -508,4 +511,28 @@ class UserVM extends BaseVM { Utils.showToast(basicOtp.message ?? ""); } } + + // Aamir Code + List? availBio; + + Future verifiyAuth({BuildContext? context, String? userToken, AppType? apptype}) async { + bool auth = await CommonAuthImp().authenticate(); + if (auth) { + performBasicOtpLoginSelectionPage(context!, userToken: userToken!, appType: apptype!); + } + } + + Future getAvailBio() async { + availBio = await CommonAuthImp().getAvailBio(); + } + + bool isBioAvailable(BiometricType bioType) { + logger.d(availBio); + if (availBio == null) return true; + if (availBio!.contains(bioType)) { + return false; + } else { + return true; + } + } } diff --git a/lib/views/user/login_method_selection_page.dart b/lib/views/user/login_method_selection_page.dart index 175655c..47ce5aa 100644 --- a/lib/views/user/login_method_selection_page.dart +++ b/lib/views/user/login_method_selection_page.dart @@ -1,4 +1,7 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:local_auth/local_auth.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/dependencies.dart'; @@ -12,18 +15,23 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -class LoginMethodSelectionPage extends StatelessWidget { +class LoginMethodSelectionPage extends StatefulWidget { final String userToken; const LoginMethodSelectionPage(this.userToken, {Key? key}) : super(key: key); + @override + State createState() => _LoginMethodSelectionPageState(); +} + +class _LoginMethodSelectionPageState extends State { @override Widget build(BuildContext context) { - UserVM userVM = context.read(); + UserVM userVM = context.read(); AppState appState = injector.get(); print("current: ${appState.currentAppType}"); return Scaffold( - appBar: CustomAppBar( isRemoveBackButton: true, title: LocaleKeys.log_in.tr()), + appBar: CustomAppBar(isRemoveBackButton: true, title: LocaleKeys.log_in.tr()), body: Container( width: double.infinity, height: double.infinity, @@ -31,29 +39,39 @@ class LoginMethodSelectionPage extends StatelessWidget { child: SingleChildScrollView( child: Column( children: [ - LocaleKeys.loginSelection.tr().toText(fontSize: 20, letterSpacing: -1.44,), + LocaleKeys.loginSelection.tr().toText( + fontSize: 20, + letterSpacing: -1.44, + ), 30.height, - LocaleKeys.welcomeBack.tr().toText(fontSize: 20, letterSpacing: -1.44,), + LocaleKeys.welcomeBack.tr().toText( + fontSize: 20, + letterSpacing: -1.44, + ), 40.height, Row( children: [ Expanded( child: ShowImageButton( onClick: () { - userVM.performBasicOtpLoginSelectionPage(context, userToken: userToken,appType: appState.currentAppType); + userVM.verifiyAuth(context: context, userToken: widget.userToken, apptype: appState.currentAppType); + //userVM.performBasicOtpLoginSelectionPage(context, userToken: userToken,appType: appState.currentAppType); }, title: LocaleKeys.fingerPrint.tr(), icon: MyAssets.icFingerprintSvg, + isDisabled: userVM.isBioAvailable(Platform.isAndroid ? BiometricType.weak : BiometricType.fingerprint), ), ), 20.width, Expanded( child: ShowImageButton( onClick: () { - userVM.performBasicOtpLoginSelectionPage(context, userToken: userToken,appType: appState.currentAppType); + // userVM.performBasicOtpLoginSelectionPage(context, userToken: widget.userToken, appType: appState.currentAppType); + userVM.verifiyAuth(context: context, userToken: widget.userToken, apptype: appState.currentAppType); }, title: LocaleKeys.faceRecognition.tr(), icon: MyAssets.icFace, + isDisabled: userVM.isBioAvailable(Platform.isAndroid ? BiometricType.strong : BiometricType.face), ), ), ], @@ -64,7 +82,7 @@ class LoginMethodSelectionPage extends StatelessWidget { Expanded( child: ShowImageButton( onClick: () { - userVM.performBasicOtpLoginSelectionPage(context, userToken: userToken,appType: appState.currentAppType); + userVM.performBasicOtpLoginSelectionPage(context, userToken: widget.userToken, appType: appState.currentAppType); }, title: LocaleKeys.SMS.tr(), icon: MyAssets.icSmsSvg, @@ -75,7 +93,7 @@ class LoginMethodSelectionPage extends StatelessWidget { child: ShowImageButton( onClick: () { // navigateWithName(context, AppRoutes.dashboard); - userVM.performBasicOtpLoginSelectionPage(context, userToken: userToken,appType: appState.currentAppType); + userVM.performBasicOtpLoginSelectionPage(context, userToken: widget.userToken, appType: appState.currentAppType); }, title: LocaleKeys.whatsapp.tr(), icon: MyAssets.icWhatsAppSvg, @@ -89,5 +107,4 @@ class LoginMethodSelectionPage extends StatelessWidget { ), ); } - } diff --git a/lib/views/user/login_with_password_page.dart b/lib/views/user/login_with_password_page.dart index 875703d..eec65c6 100644 --- a/lib/views/user/login_with_password_page.dart +++ b/lib/views/user/login_with_password_page.dart @@ -51,6 +51,7 @@ class _LoginWithPasswordState extends State { } scheduleMicrotask(() { userVM = Provider.of(context, listen: false); + context.read().getAvailBio(); getCountryList(); }); } diff --git a/lib/widgets/button/show_image_button.dart b/lib/widgets/button/show_image_button.dart index 2519233..b0045ea 100644 --- a/lib/widgets/button/show_image_button.dart +++ b/lib/widgets/button/show_image_button.dart @@ -1,4 +1,4 @@ - +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; @@ -6,16 +6,28 @@ import 'package:mc_common_app/extensions/string_extensions.dart'; import '../../theme/colors.dart'; -class ShowImageButton extends StatelessWidget { +class ShowImageButton extends StatefulWidget { String icon, title; VoidCallback onClick; + bool isDisabled; + + ShowImageButton({required this.icon, required this.title, required this.onClick, this.isDisabled = false}); - ShowImageButton({required this.icon, required this.title, required this.onClick}); + @override + State createState() => _ShowImageButtonState(); +} +class _ShowImageButtonState extends State { @override Widget build(BuildContext context) { return InkWell( - onTap: onClick, + highlightColor: widget.isDisabled ? Colors.transparent : null, // Semi-transparent blue highlight + splashColor: widget.isDisabled ? Colors.transparent : null, + onTap: widget.isDisabled + ? () { + if (kDebugMode) print("disabled"); + } + : widget.onClick, child: Container( width: double.infinity, child: Column( @@ -24,22 +36,22 @@ class ShowImageButton extends StatelessWidget { width: double.infinity, height: 120, child: Card( - color: MyColors.darkPrimaryColor, + color: widget.isDisabled ? MyColors.primaryColor : MyColors.darkPrimaryColor, shape: RoundedRectangleBorder( side: const BorderSide(color: Colors.transparent, width: 1), borderRadius: BorderRadius.circular(8), ), child: Padding( - padding: EdgeInsets.all(26), + padding: const EdgeInsets.all(26), child: SvgPicture.asset( - icon, - color: Colors.white, + widget.icon, + color: widget.isDisabled ? MyColors.chipColor : Colors.white, ), ), ), ), 6.height, - title.toText(color: Colors.black, isBold: true, fontSize: 16), + widget.title.toText(color: widget.isDisabled ? MyColors.chipColor : Colors.black, isBold: true, fontSize: 16), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index 8bc6900..98fb51d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -581,6 +581,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -649,26 +673,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: @@ -697,10 +721,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_drawing: dependency: transitive description: @@ -1154,14 +1178,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: + vm_service: dependency: transitive description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "13.0.0" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7286ce3..055191b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,10 @@ dependencies: geocoding: ^2.1.1 +# Auth +local_auth: ^2.2.0 + + dev_dependencies: flutter_test: sdk: flutter